当前位置: 首页 > news >正文

MCU开发学习记录19* - CAN学习与实践(HAL库) - 定时传输、触发传输和请求传输(轮询与中断实现) -STM32CubeMX

名词解释:

CAN:Controller Area Network
ISO:​International Organization for Standardization

OSI:Open Systems Interconnection
SOF:​Start Of Frame
EOF:​End Of Frame
​​

统一文章结构(数字后加*):

        第一部分: 阐述外设工作原理;第二部分:芯片参考手册对应外设的学习;第三部分:使用STM32CubeMX进行外设初始化;第四部分:添加应用代码;第五部分:附上本篇文章的工程代码的下载地址。

        本文将介绍CAN的相关概念以及STM32CubeMX生成CAN的配置函数,并实践两个例程:3个MCU之间的CAN通信以及通过定时传输、触发传输和请求传输实现两个MCU之间CAN通信(轮询与中断实现)。

一、什么是CAN?

1.1 ​CAN 简介(MSB->LSB(与SPI、IIC一样,UART为LSB先行)、串行异步半双工

1.1.1 CAN 介绍(Controller Area Network

CAN协议是串行、异步、半双工,适用于多节点、优先级驱动的网络通信。通过ISO 11898(高速)和ISO 11519-2(低速)成为国际标准。
        · 低速CAN(ISO11519)通信速率10~125Kbps,总线长度可达1000米
        · 高速CAN(ISO11898)通信速率125Kbps~1Mbps,总线长度≤40米

CAN协议物理结构特点:

        · 双绞线结构(CAN_High和CAN_Low),抗干扰能力强。
        · 线性总线拓扑​:所有节点并联在总线上,无主从之分(多主控制)。
        · 终端电阻​:两端需接120Ω电阻,消除信号反射。

CAN 控制器根据两根线上的电位差来判断总线电平。总线电平分为显性电平和隐性电平,二者必居其一。发送方通过使总线电平发生变化,将消息发送给接收方。线与机制​:显性电平优先,实现无冲突仲裁。

1.1.2 CAN 应用

        CAN 的高性能和可靠性已被认同,并被广泛地应用于工业自动化、船舶、医疗设备、工业设备等方面。

        

1.1.3 CAN 总线架构

高速CAN:闭环网络,CAN-H和CAN-L两端添加120Ω的终端电阻
低速CAN:开环网络,CAN_H和CAN_L其中一端添加2.2kΩ的终端电阻

参考正点原子ppr内容:

1.1.4 CAN 的特点

1. 多主控制与动态仲裁
        · 在总线空闲时,所有的单元都可开始发送消息(多主控制)。
        · 当多个节点同时发送时,采用CSMA/CA(载波侦听多路访问/冲突避免)​机制,通过ID逐位比较实现优先级仲裁(动态仲裁)。

2. 消息格式与优先级标识
        
在CAN协议中,所有的消息都以固定的格式发送。。两个以上的单元同时开始发送消息时,根据标识符(Identifier 以下称为 ID)决定优先级。ID 并不是表示发送的目的地址,而是表示访问总线的消息的优先级。两个以上的单元同时开始发送消息时,对各消息 ID 的每个位进行逐个仲裁比较。仲裁获胜(被判定为优先级最高)的单元可继续发送消息,仲裁失利的单元则立刻停止发送而进行接收工作。

3. 系统扩展的灵活性
        
新增节点时,无需修改现有节点的硬件或软件,仅需分配唯一ID即可接入总线。例如,在汽车中添加新传感器时,直接连接即可,无需重新配置网络。

4. 通信速率可调
        
同一网络内所有节点必须设定相同速率(如125 kbps、1 Mbps),否则会引发错误。高速率(1 Mbps)适用于短距离、低延迟场景(如发动机控制),而低速率(125 kbps)支持更长总线(如车身控制,总线长度可达数百米)。

5. 远程数据请求
        遥控帧(Remote Frame)​
​:接收节点可通过发送遥控帧请求特定ID的数据帧。遥控帧不包含数据段,但包含数据长度码(DLC),通知发送节点需返回的数据量。

6. 错误检测功能·错误通知功能·错误恢复功能
        所有的单元都可以检测错误(错误检测功能)。
        
检测出错误的单元会立即同时通知其他所有单元(错误通知功能)。
        
正在发送消息的单元一旦检测出错误,会强制结束当前的发送。强制结束发送的单元会不断反复地重新发送此消息直到成功发送为止(错误恢复功能)。

7. 故障节点隔离
        CAN可以判断出错误的类型是总线上暂时的数据错误(如外部噪声等)还是持续的数据错误(如单元内部故障、驱动器故障、断线等)。由此功能,当总线上发生持续数据错误时,可将引起此故障的单元从总线上隔离出去。

8. 节点容量与拓扑限制
        ​理论无限,实际受限​:总线可连接节点数受电气特性和信号延迟限制。例如:
                · 高速网络(1 Mbps)通常支持约30个节点,总线长度≤40米。
                · 低速网络(125 kbps)可支持更多节点(如100个)和更长距离(可达1公里)。

1.2 ​CAN 错误

1.2.1 ​CAN 错误状态的种类(3种)

单元始终处于 3 种状态之一。

1. 主动错误状态
        · 定义:主动错误状态是系统能够正常参与总线通信的状态。
        · 特征:在此状态下,单元具备完整的通信能力,可以主动检测错误并采取行动。
        · 错误处理:当单元检测到错误时,会输出主动错误标志,通知其他单元发生了错误。
        · 作用:这是系统运行的正常状态,适用于错误较少、通信稳定的情况。

2. 被动错误状态
        · 定义:被动错误状态是一种容易引起错误的状态。另外,处于被动错误状态的单元在发送结束后不能马上再次开始发送。在开始下次发送前,在间隔帧期间内必须插入“延迟传送”(8 个位的隐性位)。
        · 特征:单元仍能参与总线通信,但采取了更为保守的策略,避免干扰其他单元的正常通信。
        · 错误处理:在接收数据时,单元不能主动发送错误通知;若检测到错误,则输出被动错误标志
        · 作用:这种状态适用于错误累积较多但尚未严重到停止通信的情况,旨在减少对系统的整体影响。

3. 总线关闭态
        · 定义:总线关闭态是单元完全无法参与总线通信的状态。
        · 特征:单元的发送和接收功能都被禁止,彻底与总线隔离。
        · 错误处理:由于通信已被阻断,此状态下不再涉及错误标志的输出。
        · 作用:适用于错误极其严重、继续通信可能导致更大问题的极端情况。

1.2.2 ​CAN 错误计数值

CAN三种错误状态的转换又两个参数给管理:
        · 发送错误计数值 (TEC):记录单元在发送数据时发生的错误次数。
        · 接收错误计数值 (REC):记录单元在接收数据时发生的错误次数。

        一次数据的接收和发送可能同时满足多个条件。错误计数器在错误标志的第一个位出现的时间点上开始计数。

        

1.2.3 ​CAN 错误状态与错误计数值的关系

状态依靠发送错误计数和接收错误计数来管理,根据计数值决定进入何种状态。

        

        

1.3 CAN 协议基本概念

        CAN协议涵盖了 ISO 规定的 OSI 基本参照模型中的传输层、数据链路层及物理层。

1.3.1 OSI七层模型

        

1.3.2 CAN的传输层、数据链路层及物理层

        LLC : Logical Link Control (逻辑链路控制)
        MAC : Medium Access Control (媒介访问控制)

        数据链路层分为MAC子层和 LLC 子层,MAC 子层是 CAN协议的核心部分。数据链路层的功能是将物理层 收到的信号组织成有意义的消息,并提供传送错误控制等传输控制的流程。具体地说,就是消息的帧化、仲裁、 应答、错误的检测或报告。数据链路层的功能通常在CAN控制器的硬件中执行。

        在物理层定义了信号实际的发送方式、位时序、位的编码方式及同步的步骤。但具体地说,信号电平、通信 速度、采样点、驱动器和总线的电气特性、连接器的形态等均未定义*1。这些必须由用户根据系统需求自行确定。

        

二、CAN的物理层

2.1 CAN物理接口与电平标准

2.1.1 CAN物理接口

        · CAN_High(逻辑0:3.5V、逻辑1:2.5V)
        · CAN_Low(逻辑0:1.5V、逻辑1:2.5V)

2.1.2 CAN电平标准

        · CAN使用差分信号进行数据传输,根据CAN_H和CAN_L上的的电位差来判断总线电平。              · 总线电平分为显性电平(逻辑0)和隐性电平(逻辑1),二者必居其一。
        · 显性电平具有优先权。发送方通过使总线电平发生变化,将消息发送给接收方。
        · CAN_High - CAN_Low = 0 | -1.5 时候为隐性电平的,逻辑信号表现为"逻辑1",即高电平。
        · CAN_High - CAN_Low = 2 | 3 时候为显性电平的,逻辑信号表现为"逻辑0",即低电平。

        

        

2.2 CAN 标准协议及标准规格

2.2.1 ISO制定的CAN协议标准

CAN协议经 ISO标准化后有 ISO11898 标准和 ISO11519-2 标准两种。

        · ISO11898 是通信速度为 125kbps-1Mbps 的CAN高速通信标准。 目前,ISO11898 追加新规约后,成为 ISO11898-1 新标准。
        · ISO11519 是通信速度为 125kbps 以下的CAN低速通信标准。 ISO11519-2 是 ISO11519-1 追加新规约后的版本。 

ISO11898 标准和 ISO11519-2 只对数据链路层、物理层进行定义

        · 传输层​:ISO 11898/11519-2未对传输层标准化。CAN协议本身通过错误检测与自动重传实现传输可靠性,但具体重传策略由设备厂商自定义。
        · 数据链路层:(CAN协议核心定义部分​)
                · ISO 11898与ISO 11519-2对数据链路层定义完全相同,包括:
                · 帧格式(数据帧、遥控帧等)
                · 非破坏性仲裁机制
                · 错误检测与处理逻辑
        · 物理层:​(ISO标准差异化部分​)
                · ISO 11898(高速CAN):定义1Mbps速率、双绞线电平特性、终端电阻等。
                · ISO 11519-2(低速CAN):定义125kbps速率、容错电平特性,适用于车身控制。

2.2.2 ISO11898 和 ISO11519-2 的不同点

        

        

2.2.3 CAN的标准规格

        

2.3 CAN 工作模式与总线仲裁

2.3.0 CAN 两种传输方式

        广播式和请求式。

2.3.1 CAN 四种工作模式

1. 正常模式(总线的正常节点)
        可向总线发送或接收数据。
        

2. 环回模式(可统计总线的流量)
        只向总线发送1不能发送0,可从总线接收数据。
        

3. 静默模式(自检)
        发送的数据直接到输入(总线可监测数据),不能从总线接收数据。
        

4. 环回静默模式(自检(不影响总线))
        发送的数据直接到输入(总线不可监测到数据),不能从总线接收数据。
        

2.3.1 CAN 总线仲裁

典型CAN的基本原理见图所示,从图中可以看出,总线逻辑状态与驱动器输入和接收器输出逻辑是相反的。正常情况下,逻辑高电平为1,逻辑低电平为0,但是CAN总线却是逻辑高电平为0,称为显性,逻辑低电平为1,称为隐性。所以很多收发器的驱动器输入端都会内置上拉电阻,在没有任何输入时,CAN总线就会表现为隐性(逻辑低电平)。

在总线空闲时,最先开始发送报文的节点获得发送权。

如果多个节点同时访问总线,CAN使用非破坏式、逐位仲裁的方式决定哪个节点使用总线:各发送节点从仲裁域(标识符和RTR域)的第1位开始进行仲裁,连续输出显性电平(0)最多的节点可以继续发送。因此标识符数值越低的CAN报文,优先级越高。标识符数值为0的CAN报文,具有最高优先级,因为它输出的显性电平最多。

        

三、 CAN 协议层

3.1 CAN 协议的帧类型与结构(5种)

3.1.1 CAN 协议帧种类

通信是通过以下 5 种类型的帧进行的。
​​​​​​​        数据帧;遥控帧;错误帧;过载帧;帧间隔

        数据帧和遥控帧有标准格式(CAN2.0A)和扩展格式(CAN2.0B)两种格式。标准格式有11个位的标识符(Identifier: 以下称 ID), 扩展格式有29个位的 ID,各种帧的用途如下。

        

3.1.2 CAN 协议帧 - 数据帧

数据帧由 7 个段构成。

        

        

1. 帧起始(标准、扩展格式相同):
        
表示数据帧开始的段。1 个位的显性位。

2. 仲裁段(标准、扩展格式有所不同):
        
表示该帧优先级的段。ID如下:
        
标准格式的 ID 有 11 个位。从 ID28 到 ID18 被依次发送。禁止高 7 位都为隐性。(禁止设定:ID=1111111XXXX);
        扩展格式的 ID 有 29 个位。基本 ID 从 ID28 到 ID18,扩展 ID 由 ID17 到 ID0 表示。基本 ID 和标准格式的 ID 相同。禁止高 7 位都为隐性。(禁止设定:基本 ID=1111111XXXX)

        RTR 位 (Remote Transmission Request Bit),译作远程传输请求位,它是用于区分数据帧和遥控帧的,当它为显性电平时表示数据帧,隐性电平时表示遥控帧。
        SRR 位 (Substitute Remote Request Bit),只存在于扩展格式,它用于替代标准格式中的 RTR位。由于扩展帧中的 SRR 位为隐性位,RTR 在数据帧为显性位,所以在两个 ID 相同的标准格式报文与扩展格式报文中,标准格式的优先级较高。

3. 控制段:
        
控制段由 6 个位构成,表示数据段的字节数。
        IDE 位 (Identifier ExtensionBit),译作标识符扩展位,它是用于区分标准格式与扩展格式,当它为显性电平时表示标准格式,隐性电平时表示扩展格式
        r0、r1(保留位) 保留位必须全部以显性电平发送。但接收方可以接收显性、隐性及其任意组合的电平。
        DLC位(数据长度码) 数据长度码与数据的字节数的对应关系如表所示。 数据的字节数必须为 0~8 字节。但接收方对DLC = 9~15 的情况并不视为错误。

        

4. 数据段:
        
数据的内容,数据段可包含 0~8 个字节的数据。从MSB(最高位)开始输出。

5. CRC段:检查帧的传输错误的段。
        ​​​​​​​CRC段是检查帧传输错误的帧。由 15 个位的 CRC 顺序*1和 1 个位的 CRC界定符(用于分隔的位)构成。CRC 部分的计算一般由 CAN 控制器硬件完成,出错时的处理则由软件控制最大重发数。在 CRC 校验码之后,有一个 DEL界定符,它为隐性位,主要作用是把 CRC 校验码与后面的 ACK段间隔起来。

6. ACK段:表示确认正常接收的段。

        发送单元的 ACK段 发送单元在 ACK段发送 2 个位的隐性位。
        接收单元的 ACK段 接收到正确消息的单元在ACK槽(ACK Slot)发送显性位,通知发送单元正常接收结束。这称作“发送 ACK”或者“返回 ACK”。

7. 帧结束:表示数据帧结束的段。
        帧结束是表示该该帧的结束的段。由 7 个位的隐性位构成。

3.1.3 CAN 协议帧 - 遥控帧

接收单元向发送单元请求发送数据所用的帧。遥控帧由 6 个段组成。遥控帧没有数据帧的数据段。

        

1. 帧起始:表示帧开始的段。
2. 仲裁段:表示该帧优先级的段。可请求具有相同 ID 的数据帧。
3. 控制段:表示数据的字节数及保留位的段。
4. CRC段:检查帧的传输错误的段。
5. ACK段:表示确认正常接收的段。
6. 帧结束:表示遥控帧结束的段。

3.1.4 CAN 协议帧 - 数据帧与遥控帧补充

1. 数据帧和遥控帧的不同
        · 
遥控帧的 RTR位为隐性位,没有数据段。
        · 没有数据段的数据帧和遥控帧可通过 RTR位区别开来。

2. 遥控帧没有数据段,数据长度码该如何表示?
        遥控帧的数据长度码以所请求数据帧的数据长度码表示。

3. 没有数据段的数据帧有何用途?
        可用于各单元的定期连接确认/应答、或仲裁段本身带有实质性信息的情况下。

3.1.5 CAN 协议帧 - 错误帧

        用于在接收和发送消息时检测出错误通知错误的帧。错误帧由错误标志和错误界定符构成。

1. 错误标志
        错误标志包括主动错误标志和被动错误标志两种。
        主动错误标志:6 个位的显性位。(处于主动错误状态的单元检测出错误时输出的错误标志。)
        被动错误标志:6 个位的隐性位。(处于被动错误状态的单元检测出错误时输出的错误标志。)

2. 错误界定符
        错误界定符由 8 个位的隐性位构成。

        错误标志之后还有0~6个错误标志重叠部分:处于主动错误状态的节点检测到错误时会发送主动错误标志,6个连续显性位会违反位填充规则和位场的固定形式,进而造成其它节点也检测到错误并发送错误标志。所有节点所发送的显性序列叠加组成错误标志重叠部分,错误标志重叠部分的长度在6-12个显性位之间。

        

3.1.6 CAN 协议帧 - 过载帧

过载帧是用于接收单元通知其尚未完成接收准备的帧。过载帧由过载标志和过载界定符构成。

过载帧是接收节点向总线上其它节点报告自身接收能力达到极限的帧,可以理解为:接收节点Node_A接收报文的能力达到极限了,于是Node_A就会发出过载帧来告诉总线上的其它节点(包括发送节点),我接收节点Node_A已经没有能力处理你们发来的报文了。过载帧由过载标志和过载界定符构成。

· 过载标志
  6 个位的显性位。
  过载标志的构成与主动错误标志的构成相同。
·  过载界定符
  8 个位的隐性位。
  过载界定符的构成与错误界定符的构成相同。

        

3.1.7 CAN 协议帧 - 帧间隔

帧间隔是用于分隔数据帧和遥控帧的帧。数据帧和遥控帧可通过插入帧间隔将本帧与前面的任何帧(数据帧、遥控帧、错误帧、过载帧)分开。

过载帧和错误帧前不能插入帧间隔。

1. 间隔:
        3 个位的隐性位。

2. 总线空闲:
        
隐性电平,无长度限制(0 亦可)。 本状态下,可视为总线空闲,要发送的单元可开始访问总线。

3. 延迟传送(发送暂时停止):
         8 个位的隐性位。 只在处于被动错误状态的单元刚发送一个消息后的帧间隔中包含的段。

        

3.2 CAN 协议 - 优先级

3.2.1 优先级1 - 先占先得

在总线空闲态,最先开始发送消息的单元获得发送权。

1. 若当前已经有设备正在操作总线发送数据帧/遥控帧,则其他任何设备不能再同时发送数据帧/遥控帧(可以发送错误帧/过载帧破坏当前数据)

2. 任何设备检测到连续11个隐性电平,即认为总线空闲,只有在总线空闲时,设备才能发送数据帧/遥控帧

3. 一旦有设备正在发送数据帧/遥控帧,总线就会变为活跃状态,必然不会出现连续11个隐性电平,其他设备自然也不会破坏当前发送

4. 若总线活跃状态其他设备有发送需求,则需要等待总线变为空闲,才能执行发送需求

3.2.1 优先级1 - 非破坏性仲裁

多个单元同时开始发送时,各发送单元从仲裁段的第一位开始进行仲裁。连续输出显性电平最多的单元可继续发送。

若多个设备的发送需求同时到来或因等待而同时到来,则CAN总线协议会根据ID号(仲裁段)进行非破坏性仲裁,ID号小的(优先级高)取到总线控制权,ID号大的(优先级低)仲裁失利后将转入接收状态,等待下一次总线空闲时再尝试

发送实现非破坏性仲裁需要两个要求:
        线与特性:总线上任何一个设备发送显性电平0时,总线就会呈现显性电平0状态,只有当所有设备都发送隐性电平1时,总线才呈现隐性电平1状态,即:0&X&X=0, 1&1&1=1
        回读机制:每个设备发出一个数据位后,都会读回总线当前的电平状态,以确认自己发出的电平是否被真实地发送出去了,根据线与特性,发出0读回必然是0,发出1读回不一定是1

1. 数据帧和遥控帧的优先级

        具有相同 ID 的数据帧和遥控帧在总线上竞争时,仲裁段的最后一位(RTR)为显性位的数据帧具有优先权,可继续发送。

        

2. 标准格式和扩展格式的优先级

        标准格式 ID 与具有相同 ID 的遥控帧或者扩展格式的数据帧在总线上竞争时,标准格式的 RTR位为显性位 的具有优先权,可继续发送。

        

3.3 CAN 位填充与错误检测

3.3.1 CAN 位填充

        位填充是为防止突发错误而设定的功能。当同样的电平持续 5 位时则添加一个位的反型数据。

1. 发送单元的工作
        在发送数据帧和遥控帧时,SOF~CRC段间的数据,相同电平如果持续 5 位,在下一个位(第 6 个位)则要插入 1 位与前 5 位反型的电平。

2. 接收单元的工作
        在接收数据帧和遥控帧时,SOF~CRC段间的数据,相同电平如果持续 5 位,需要删除下一个位(第 6 个位)再接收。如果这个第 6 个位的电平与前 5 位相同,将被视为错误并发送错误帧。

        

        

3.3.2 CAN 错误检测(五种)- 破坏当前数据帧的数据

        位错误(发送时的回读机制)、填充错误、CRC错误、格式错误、ACK错误

1. 位错误
        · 位错误由向总线上输出数据帧、遥控帧、错误帧、过载帧的单元和输出 ACK 的单元、输出错误的单元来 检测。
​​​​​​​        · 在仲裁段输出隐性电平,但检测出显性电平时,将被视为仲裁失利,而不是位错误。
​​​​​​​        · 在仲裁段作为填充位输出隐性电平时,但检测出显性电平时,将不视为位错误,而是填充错误。
​​​​​​​        · 发送单元在 ACK段输出隐性电平,但检测到显性电平时,将被判断为其它单元的ACK应答,而非位错误。
​​​​​​​        · 输出被动错误标志(6 个位隐性位)但检测出显性电平时,将遵从错误标志的结束条件,等待检测出连续相同 6 个位的值(显性或隐性),并不视为位错误。

2. 格式错误
​​​​​​​        · 即使接收单元检测出EOF(7 个位的隐性位)的最后一位(第 8 个位)为显性电平,也不视为格式错误。
​​​​​​​        · 即使接收单元检测出数据长度码(DLC)中 9∼15 的值时,也不视为格式错误。

        

3.3.3 CAN 错误帧的输出

        检测出满足错误条件的单元输出错误标志通报错误。
        发送单元发送完错误帧后,将再次发送数据帧或遥控帧。

        

3.4 CAN 位时序与同步

3.4.1 CAN 位时序介绍

        CAN总线以“位同步”机制,实现对电平的正确采样。位数据都由四段组成:同步段(SS)、传播时间段(PTS)、相位缓冲段1(PBS1)和相位缓冲段2(PBS2),每段又由多个位时序Tq组成。
        采样点是指读取总线电平,并将读到的电平作为位值的点。

        Time Quantum(以下称为 Tq)的最小时间单位构成。

        

3.4.2 CAN 位时序段介绍

· SS 段(SYNC SEG):
同步段,比如当总线上出现帧起始信号(SOF)时,其它节点上的控制器根据总线上的这个下降沿,对自己的位时序进行调整,把该下降沿包含到 SS 段内,这样根据起始帧来进行同步的方式称为硬同步。其中 SS 段的大小为 1Tq。总线上信号的跳变沿被包含在节点的 SS 段的范围之内,则表示节点与总线的时序是同步的,采样点采集到的总线电平即可被确定为该位的电平。

· PTS 段(PROP SEG):
传播时间段,这个时间段是用于补偿网络的物理延时时间,包括发送单元的输出延迟、总线上信号的传播延迟、接收单元的输入延迟,这个段的时间为以上各延迟时间的和的两倍。大小可以为 1~8Tq。

· PBS1 段(PHASE SEG1):
相位缓冲段,主要用来补偿边沿阶段的误差,它的时间长度在重新同步的时候可以加长。 PBS1 段的初始大小可以为 1~8Tq。

· PBS2 段(PHASE SEG2):
另一个相位缓冲段,也是用来补偿边沿阶段误差的,它的时间长度在重新同步时可以缩短。 PBS2 段的初始大小可以为 2~8Tq。(对于PBS段而言,当信号边沿不能被包含于 SS 段中时,可在此段进行补偿,以及可以吸收时钟误差)

· SJW (reSynchronization Jump Width):重新同步补偿宽度,即在重新同步的时候,PBS1 和 PBS2 段的允许加长或缩短的时间长度,SJW 加大后允许误差加大,但通信速度下降。SJW 为补偿此误差的最大值(即每一次误差补偿都不能超过这个值,1~4Tq)。

 

3.4.3 CAN 位时序 - 波特率计算

· 波特率 = 1/一个数据位的时长 = 1/(T_SS + T_PTS + T_PBS1 + T_PBS2)

· 例如:T_SS = 1Tq、 T_PTS = 3Tq、T_PBS1 = 3Tq、T_PBS2 = 3Tq、Tq = 0.5us
        波特率 =1/(0.5us +1.5us + 1.5us + 1.5us)=200kbps

        

3.4.4 CAN 同步 - 取得同步方法

CAN协议的通信方法为 NRZ(Non-Return to Zero)方式。各个位的开头或者结尾都没有附加同步信号。发送单元以与位时序同步的方式开始发送数据。另外,接收单元根据总线上电平的变化进行同步并进行接收工作。

但是,发送单元和接收单元存在的时钟频率误差及传输路径上的(电缆、驱动器等)相位延迟会引起同步偏差。因此接收单元通过硬件同步或者再同步的方法调整时序进行接收。

3.4.5 CAN 同步 - 硬同步

节点通过CAN总线发送数据,一开始发送帧起始信号。总线上其他节点会检测帧起始信号在不在位数据的SS段内,判断内部时序与总线是否同步。

假如不在SS段内,这种情况下,采样点获得的电平状态是不正确的。所以,节点会使用硬件同步方式调整, 把自己的SS段平移到检测到边沿的地方,获得同步,同步情况下,采样点获得的电平状态才是正确的。

即每个设备都有一个位时序计时周期,当某个设备(发送方)率先发送报文,其他所有设备(接收方)收到SOF的下降沿时,接收方会将自己的位时序计时周期拨到SS段位置。

        

        

3.4.6 CAN 同步 - 再同步

        · 再同步利用普通数据位的边沿信号(帧起始信号是特殊的边沿信号)进行同步。
        · 再同步的方式分为两种情况:超前和滞后,即边沿信号与SS段的相对位置。
        · 再同步时,PSB1和PSB2中增加或者减少的时间被称为“再同步补偿宽度(SJW)”,其范围:1~4 Tq。
        · ​​​​​​​限定了SJW值后,再同步时,不能增加限定长度的SJW值。SJW值较大时,吸收误差能力更强,但是通讯速度会下降。

        

        

四、高速CAN接口芯片 - SIT1042AQ

4.1 芯片引脚

4.1.1 芯片引脚分布图

        

4.1.2 芯片引脚定义

        

4.2 芯片内部框图

        

五、CAN硬件外设

5.1 CAN 外设介绍

5.1.1 CAN 外设简介

        支持 CAN 协议 2.0A 2.0B 主动模式
        波特率最高达 1Mbps
        支持时间触发通信
        具有 3 个发送邮箱
        具有 3 级深度的 2 个接收 FIFO
        可变的过滤器组(最多 28 个)
        时间触发通信、自动离线恢复、自动唤醒、禁止自动重传、接收FIFO溢出处理方式可配置、  发送优先级可配置、双CAN模式

5.1.2 CAN 硬件框图

1. CAN内核
        包含各种控制/状态/配置寄存器,可以配置模式、波特率等
2. 发送邮箱
        用来缓存待发送的报文,最多可以缓存3个报文
3. 接收FIFO
        缓存接收到的有效报文
4.接收过滤器
        筛选有效报文

        

        

5.2 CAN 外设工作模式

5.2.1 CAN 外设低功耗

1. ​​​​​​​初始化模式:用于配置CAN外设,禁止报文的接收和发送
2. 正常模式:配置CAN外设后进入正常模式,以便正常接收和发送报文
3. 睡眠模式:低功耗,CAN外设时钟停止,可使用软件唤醒或者硬件自动唤醒
4. AWUM:置1,自动唤醒,一旦检测到CAN总线活动,硬件就自动清零SLEEP,唤醒CAN外设;置0,手动唤醒,软件清零SLEEP,唤醒CAN外设

CAN 主状态寄存器 - CAN_MSR

CAN_MSR.INAK:初始化确认
        硬件置 1,用于向软件指示 CAN 硬件此时处于初始化模式。此位可确认软件的初始化请求(CAN_MCR 寄存器的
INRQ 位置 1)。
        CAN 硬件退出初始化模式(以在 CAN 总线上进行同步)时,此位由硬件清零。
CAN_MSR.SLAK:睡眠确认
        此位由硬件置 1
,用于向软件指示 CAN 硬件此时处于睡眠模式。此位可确认软件的睡眠模式请求(CAN_MCR 寄存器的 SLEEP 位置 1)。
        CAN
硬件退出睡眠模式(以在 CAN 总线上进行同步)时,此位由硬件清零。

CAN 主控制寄存器 - CAN_MCR

CAN_MCR.INRQ:初始化请求
        ​​​​​​​此位由软件置
1,用于请求 CAN 硬件进入睡眠模式。此位由软件清零时,将退出睡眠模式。​​​​​​​
CAN_MCR.SLEEP:睡眠模式请求
        软件通过将此位置
1 来请求 CAN 硬件进入初始化模式。软件通过将此位清零,来将硬件切换到正常模式。

1. ACK = 硬件通过将 CAN_MSR 寄存器的 INAK SLAK 位置 1 来确认请求的等待状态
2. SYNC = bxCAN 等待
CAN 总线变为空闲(即在 CANRX 上监测到连续 11 个隐性位)的状态

        

5.2.2 CAN 外设物理接口模式

1. CAN_BTR 寄存器的 SILM 位置 1,将 bxCAN 置于静默模式。
        

2. CAN_BTR 寄存器的 LBKM 位置 1,将 bxCAN 置于环回模式。
        

3. CAN_BTR 寄存器的 LBKM SILM 位置 1,将环回模式和静默模式组合起来。
        

5.2.3 CAN 外设发送过程

CAN 发送状态寄存器 - CAN_TSR
CAN 发送邮箱标识符寄存器 - CAN_TIxR

CAN_TSR.RQCPx邮箱 x 请求完成
        最后一个请求(发送或中止)执行完毕时,由硬件置 1。
        软件通过写入“1
”清零,或是在发生发送请求(CAN_TI0R 寄存器的 TXRQ0 位置 1)时 由硬件清零。
CAN_TSR.TXOKx:邮箱 x 发送成功
        每次发送尝试后,硬件都将更新此位。
        0:上一次发送失败
        1:上一次发送成功

CAN_TSR.TMEx:发送邮箱 x 空
        当邮箱
0 没有挂起的发送请求时,此位由硬件置 1。
CAN_TSR.ABRQx:邮箱 x 中止请求
        由软件置 1,用于中止相应邮箱的发送请求。
        邮箱变为空后,此位由硬件清零。
        邮箱未挂起等待发送时,将此位置 1 没有任何作用。

CAN_TIxR.TXRQ发送邮箱请求

CAN_MCR.NART:禁止自动重发送
        0:
CAN 硬件将自动重发送消息,直到根据 CAN 标准消息发送成功。
        1:无论发送结果如何(成功、错误或仲裁丢失),消息均只发送一次。

        

5.2.4 CAN 外设接收过程

CAN 接收 FIFO x 寄存器 - CAN_RFxR

CAN_RFxR.FMPx[1:0]:FIFO x 消息挂起
        硬件每向 FIFO
存储一条新消息,FMP 即会增加。软件每次通过将 RFOM0 位置 1 来释放输出邮箱,FMP 即会减小。
CAN_RFxR.FOVRx:FIFO x 上溢
        FIFO 填满时,如果接收到新消息并且通过筛选器,此位将由硬件置 1。
CAN_RFxR.RFOMx:释放 FIFO x 输出邮箱
        由软件置 1
,用于释放 FIFO 的输出邮箱。FIFO 中至少有一条消息挂起时,才能释放输出邮箱。FIFO 为空时,将此位置 1 没有任何作用。如果 FIFO 中至少有两条消息挂起,软件必须释放输出邮箱,才能访问下一条消息。输出邮箱释放后,此位由硬件清零。

        

5.2.5 CAN 外设发送和接收配置位

1. NART:
        置1,关闭自动重传,CAN报文只被发送1次,不管发送的结果如何(成功、出错或仲裁丢失); 
        置0, 自动重传, CAN硬件在发送报文失败时会一直自动重传直到发送成功
2. TXFP:
        置1,优先级由发送请求的顺序来决定,先请求的先发送;
        置0,优先级由报文标识符来决定,标识符值小的先发送(标识符值相等时,邮箱号小的报文先发送)
3. RFLM:
        置1,接收FIFO锁定,FIFO溢出时,新收到的报文会被丢弃;
        置0,禁用FIFO锁定,FIFO溢出时,FIFO中最后收到的报文被新报文覆盖

5.3 CAN 外设过滤器

1. FSCx: 位宽设置
        置0,16位;置1,32位
2. FBMx:模式设置
        置0,屏蔽模式;置1,列表模式
3. FFAx:关联设置
        置0,FIFO 0; 置1,FIFO 1
4. FACTx:激活设置
        置0,禁用;置1,启用

        

        

5.4 CAN 外设位时间计算

        波特率 = APB1时钟频率 / 分频系数 / 一位的Tq数量
                    = 36MHz / (BRP[9:0]+1) / (1 + (TS1[3:0]+1) + (TS2[2:0]+1))

        

5.5 CAN 外设中断

CAN外设占用4个专用的中断向量:

1. 发送中断可由以下事件产生:
        · 发送邮箱 0 变为空,CAN_TSR
寄存器的 RQCP0 位置 1。
        · 发送邮箱 1 变为空,CAN_TSR 寄存器的
RQCP1 位置 1

        · 发送邮箱 2 变为空,CAN_TSR 寄存器的 RQCP2 位置 1

2. FIFO 0 中断可由以下事件产生:
        · 接收到新消息,CAN_RF0R 寄存器的 FMP0 位不是“
00”。
        · FIFO0 满,CAN_RF0R
寄存器的 FULL0 位置 1。
        · FIFO0 上溢,CAN_RF0R
寄存器的 FOVR0 位置 1

3. FIFO 1 中断可由以下事件产生:

        · 接收到新消息,CAN_RF1R 寄存器的 FMP1 位不是“00”。
        · ​​​​​​​FIFO1 满,CAN_RF1R
寄存器的 FULL1 位置 1。
        · ​​​​​​​FIFO1 上溢,CAN_RF1R
寄存器的 FOVR1 位置 1
4. 错误和状态改变中断可由以下事件产生:

        · 错误状况,有关错误状况的更多详细信息,请参见 CAN 错误状态寄存器 (CAN_ESR)。
        · 唤醒状况,CAN Rx 信号上监测到 SOF。
        · 进入睡眠模式

        

5.6 CAN 外设错误状态切换

        TEC和REC根据错误的情况增加或减少
        ABOM:置1,开启离线自动恢复,进入离线状态后,就自动开启恢复过程;置0,关闭离线自动恢复,软件必须先请求进入然后再退出初始化模式,随后恢复过程才被开启

        

六、基于HAL库配置CAN外设

6.1 基于STM32CubeMX配置CAN外设

6.1.1 STM32CubeMX配置CAN外设

1. TTCM:时间触发通信模式 (Time triggered communication mode)
        0:禁止时间触发通信模式。
        1:使能时间触发通信模式。
        
在时间触发通信模式下,CAN硬件的内部计数器被激活。这个计数器的主要功能是为接收和发送的邮箱(mailbox)生成时间戳值,用于记录通信事件的具体时间。这些时间戳值分别存储在以下两个寄存器中:
                CAN_RDTxR(接收数据时间戳寄存器):记录接收到数据帧的时间。
                CAN_TDTxR(发送数据时间戳寄存器):记录发送数据帧的时间。

2. ABOM:自动的总线关闭管理 (Automatic bus-off management)
        ​​​​​​​此位控制 CAN 硬件在退出总线关闭状态时的行为。
        0:在软件发出请求后,一旦监测到 128
次连续 11 个隐性位,并且软件将 CAN_MCR 寄存 器的 INRQ 位先置 1 再清零,即退出总线关闭状态。
        1:一旦监测到 128
次连续 11 个隐性位,即通过硬件自动退出总线关闭状态。

3. AWUM:自动唤醒模式 (Automatic wakeup mode)
        此位控制 CAN 硬件在睡眠模式下接收到消息时的行为。
        0:在软件通过将 CAN_MCR
寄存器的 SLEEP 位清零发出请求后,退出睡眠模式。
        1:一旦监测到 CAN
消息,即通过硬件自动退出睡眠模式。 CAN_MCR 寄存器的 SLEEP 位和 CAN_MCR 寄存器的 SLAK 位由硬件清零

4. NART禁止自动重发送 (No automatic retransmission)
        0:CAN
硬件将自动重发送消息,直到根据 CAN 标准消息发送成功。
        1:无论发送结果如何(成功、错误或仲裁丢失),消息均只发送一次。

5. RFLM接收 FIFO 锁定模式 (Receive FIFO locked mode)
        0:接收 FIFO
上溢后不锁定。接收 FIFO 装满后,下一条传入消息将覆盖前一条消息。
        1:接收 FIFO
上溢后锁定。接收 FIFO 装满后,下一条传入消息将被丢弃。

6. TXFP发送 FIFO 优先级 (Transmit FIFO priority)
        此位用于控制在几个邮箱同时挂起时的发送顺序。
        0:优先级由消息标识符确定
        1:优先级由请求顺序(时间顺序)确定

        

        

6.1.2 编写CAN 输入过滤器

HAL_StatusTypeDef MyCAN_SetFilters(void)
{CAN_FilterTypeDef canFilter;canFilter.FilterBank = 0;canFilter.FilterMode = CAN_FILTERMODE_IDMASK;canFilter.FilterScale = CAN_FILTERSCALE_32BIT;canFilter.FilterIdHigh = 0x0000;canFilter.FilterIdLow = 0x0000;canFilter.FilterMaskIdHigh = 0x0000;canFilter.FilterMaskIdLow = 0x0000;canFilter.FilterFIFOAssignment = CAN_FILTER_FIFO0;canFilter.FilterActivation = CAN_FILTER_ENABLE;HAL_StatusTypeDef result = HAL_CAN_ConfigFilter(&hcan1, &canFilter);return result;
}

6.2 CAN外设实践1 - 3个节点通信

6.2.1 实践1 - 回环自测 - 轮询

        

        

/** mycan.c** Created: 2025-05-11 15:02:23* Author: user*  function:  CAN通信相关驱动函数 - 自用**/#include "mycan.h"
#include "can.h"
#include "bsp_lcd_fsmc.h"
#include "stdio.h"extern CAN_HandleTypeDef hcan1;HAL_StatusTypeDef MyCAN_SetFilters(void)
{CAN_FilterTypeDef canFilter;canFilter.FilterBank = 0;canFilter.FilterMode = CAN_FILTERMODE_IDMASK;canFilter.FilterScale = CAN_FILTERSCALE_32BIT;canFilter.FilterIdHigh = 0x0000;canFilter.FilterIdLow = 0x0000;canFilter.FilterMaskIdHigh = 0x0000;canFilter.FilterMaskIdLow = 0x0000;canFilter.FilterFIFOAssignment = CAN_FILTER_FIFO0;canFilter.FilterActivation = CAN_FILTER_ENABLE;HAL_StatusTypeDef result = HAL_CAN_ConfigFilter(&hcan1, &canFilter);return result;
}uint8_t MyCAN_ReceiveFlag(void)
{if (HAL_CAN_GetRxFifoFillLevel(&hcan1, CAN_RX_FIFO0) > 0){return 1;}return 0;
}void MyCAN_Transmit(uint32_t ID, uint8_t Length, uint8_t *Data, uint8_t frameType)
{CAN_TxHeaderTypeDef TxMessage;TxMessage.StdId = ID;TxMessage.ExtId = ID;TxMessage.IDE = frameType;TxMessage.RTR = CAN_RTR_DATA;TxMessage.DLC = Length;TxMessage.TransmitGlobalTime = DISABLE;uint8_t TxData[Length];for (int i = 0; i < Length; i++){TxData[i] = Data[i];}uint32_t TxMailbox;uint32_t Timeout = 0;uint8_t tempStr[30];while (HAL_CAN_GetTxMailboxesFreeLevel(&hcan1) < 1){Timeout++;if (Timeout > 100000){break;}}if (HAL_CAN_AddTxMessage(&hcan1, &TxMessage, TxData, &TxMailbox) != HAL_OK){lcd_show_string(10, 30, 240, 16, 16, "Send to mailbox error", RED);return;}sprintf((char *)tempStr, "Send MsgID = %4X", ID);lcd_show_string(10, 30, 240, 16, 16, (char *)tempStr, RED);
}void MyCAN_Receive(uint32_t *ID, uint8_t *Length, uint8_t *Data)
{uint8_t tempStr[30];if (HAL_CAN_GetRxFifoFillLevel(&hcan1, CAN_RX_FIFO0) != 1){lcd_show_string(10, 50, 240, 16, 16, "Send to mailbox error", RED);return;}CAN_RxHeaderTypeDef RxMessage;if (HAL_CAN_GetRxMessage(&hcan1, CAN_RX_FIFO0, &RxMessage, Data) == HAL_OK){if (RxMessage.IDE == CAN_ID_STD){*ID = RxMessage.StdId;}else{*ID = RxMessage.ExtId;}sprintf((char *)tempStr, "Rece MsgID = %4X", *ID);lcd_show_string(10, 50, 240, 16, 16, (char *)tempStr, RED);*Length = RxMessage.DLC;}
}

6.2.2 实践1 - 回环自测 - 中断

        
        
        

6.2.3 实践1 - 3个节点通信

        

6.3 CAN外设实践2 - 2个节点之间(定时传输、触发传输和请求传输)

6.3.1 实践2 - 发送方

// 全局变量
uint32_t TxID1 = 0x550;
uint32_t TxID2 = 0x560;
uint32_t TxID3 = 0x570;
uint32_t TxID4 = 0x580;
uint8_t TxLength = 4;
uint8_t TxData[8] = {0x11, 0x22, 0x33, 0x44};uint32_t RxID;
uint8_t RxLength;
uint8_t RxData[8];extern CAN_HandleTypeDef hcan1;
extern uint8_t can_fifo0_status;
extern uint8_t TimingFlag;// CAN外设、CAN中断、定时器中断使能
MyCAN_SetFilters();
HAL_CAN_ActivateNotification(&hcan1, CAN_IT_RX_FIFO0_MSG_PENDING);
HAL_CAN_Start(&hcan1);
HAL_TIM_Base_Start_IT(&htim6);// 应用代码
// 定时发送
if (TimingFlag == 1)
{TimingFlag = 0;TxData[0]++;TxData[1]++;TxData[2]++;TxData[3]++;MyCAN_Transmit(TxID1, TxLength, TxData, CAN_ID_STD);sprintf((char *)tempstr, "1. TxData = %2X %2X %2X %2X", TxData[0], TxData[1], TxData[2], TxData[3]);lcd_show_string(10, 90, 240, 16, 16, (char *)tempstr, RED);
}// 触发发送
key_val = key_scan(0);
if (key_val == 1)
{TimingFlag = 0;TxData[0]++;TxData[1]++;TxData[2]++;TxData[3]++;MyCAN_Transmit(TxID2, TxLength, TxData, CAN_ID_STD);sprintf((char *)tempstr, "2. TxData = %2X %2X %2X %2X", TxData[0], TxData[1], TxData[2], TxData[3]);lcd_show_string(10, 110, 240, 16, 16, (char *)tempstr, RED);
}// 请求发送
if (can_fifo0_status == 1)
{if (RxID == 0x666){RequestFlag = 1;}can_fifo0_status = 0;if (RxID == 0x777){RequestFlag = 2;}can_fifo0_status = 0;
}
if (RequestFlag == 1)
{RequestFlag = 0;TxData[0]++;TxData[1]++;TxData[2]++;TxData[3]++;MyCAN_Transmit(TxID3, TxLength, TxData, CAN_ID_STD);sprintf((char *)tempstr, "3.1 TxData = %2X %2X %2X %2X", TxData[0], TxData[1], TxData[2], TxData[3]);lcd_show_string(10, 130, 240, 16, 16, (char *)tempstr, RED);
}
if (RequestFlag == 2)
{RequestFlag = 0;TxData[0]++;TxData[1]++;TxData[2]++;TxData[3]++;MyCAN_Transmit(TxID4, TxLength, TxData, CAN_ID_STD);sprintf((char *)tempstr, "3.2 TxData = %2X %2X %2X %2X", TxData[0], TxData[1], TxData[2], TxData[3]);lcd_show_string(10, 130, 240, 16, 16, (char *)tempstr, RED);
}

6.3.2 实践2 - 接收方

原理差不多,直接参考江科大程序如下:

#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"
#include "Key.h"
#include "MyCAN.h"uint8_t KeyNum;CanTxMsg TxMsg_Request_Remote = {.StdId = 0x666,.ExtId = 0x00000000,.IDE = CAN_Id_Standard,.RTR = CAN_RTR_Remote,.DLC = 0,.Data = {0x00}
};CanTxMsg TxMsg_Request_Data = {.StdId = 0x777,.ExtId = 0x00000000,.IDE = CAN_Id_Standard,.RTR = CAN_RTR_Data,.DLC = 0,.Data = {0x00}
};CanRxMsg RxMsg;int main(void)
{OLED_Init();Key_Init();MyCAN_Init();OLED_ShowString(1, 1, "Rx");OLED_ShowString(2, 1, "Tim:");OLED_ShowString(3, 1, "Tri:");OLED_ShowString(4, 1, "Req:");while (1){/*请求部分*/KeyNum = Key_GetNum();if (KeyNum == 1){MyCAN_Transmit(&TxMsg_Request_Remote);}if (KeyNum == 2){MyCAN_Transmit(&TxMsg_Request_Data);}/*接收部分*/if (MyCAN_ReceiveFlag()){MyCAN_Receive(&RxMsg);if (RxMsg.RTR == CAN_RTR_Data){/*收到定时数据帧*/if (RxMsg.StdId == 0x550 && RxMsg.IDE == CAN_Id_Standard){OLED_ShowHexNum(2, 5, RxMsg.Data[0], 2);OLED_ShowHexNum(2, 8, RxMsg.Data[1], 2);OLED_ShowHexNum(2, 11, RxMsg.Data[2], 2);OLED_ShowHexNum(2, 14, RxMsg.Data[3], 2);}/*收到触发数据帧*/if (RxMsg.StdId == 0x560 && RxMsg.IDE == CAN_Id_Standard){OLED_ShowHexNum(3, 5, RxMsg.Data[0], 2);OLED_ShowHexNum(3, 8, RxMsg.Data[1], 2);OLED_ShowHexNum(3, 11, RxMsg.Data[2], 2);OLED_ShowHexNum(3, 14, RxMsg.Data[3], 2);}/*收到请求数据帧*/if (RxMsg.StdId == 0x570 && RxMsg.IDE == CAN_Id_Standard){OLED_ShowHexNum(4, 5, RxMsg.Data[0], 2);OLED_ShowHexNum(4, 8, RxMsg.Data[1], 2);OLED_ShowHexNum(4, 11, RxMsg.Data[2], 2);OLED_ShowHexNum(4, 14, RxMsg.Data[3], 2);}if (RxMsg.StdId == 0x580 && RxMsg.IDE == CAN_Id_Standard){OLED_ShowHexNum(4, 5, RxMsg.Data[0], 2);OLED_ShowHexNum(4, 8, RxMsg.Data[1], 2);OLED_ShowHexNum(4, 11, RxMsg.Data[2], 2);OLED_ShowHexNum(4, 14, RxMsg.Data[3], 2);}}}}
}

6.3.3 实践2 - 实验效果

七、本文的工程文件下载链接

工程Github下载链接:https://github.com/chipdynkid/MCU-DL-STM32
(国内)工程Gitcode下载链接:https://gitcode.com/chipdynkid/MCU-DL-STM32

http://www.xdnf.cn/news/577261.html

相关文章:

  • Python 代码缩进与结构化编程:从基础到风格规范
  • Robotaxi新消息密集释放,量产元年来临谁在领跑?
  • [Java恶补day2] 49. 字母异位词分组
  • 【SW】从3D模型导出dxf图纸
  • 【算法专题十五】BFS解决最短路问题
  • 04_Prometheus监控docker容器(4)
  • 智慧社区新防线:华奥系AI技术如何让夏季安防“零隐患”
  • 如何在JavaScript中将数值转换为字符串并赋值给数组——以RuoYi-Vue项目为例
  • Redis Cluster动态扩容:架构原理与核心机制解析
  • 航电系统之航点跟踪系统篇
  • C++(27): 标准库 <iterator>
  • 逆向音乐APP:Python爬虫获取音乐榜单 (1)
  • Podman(Pod Manager)简介
  • 嵌入式STM32学习——串口USART 2.1(串口发送字符串和字符)
  • 应用分享 | 软件定义架构如何满足GNSS模拟测试的开放性需求?
  • JDK9~17语法新特性全览:Java语言的持续进化
  • Python数据可视化高级实战之二——热力图绘制探究
  • C++ 输出流格式控制
  • 起重的技术
  • wd软件安装
  • origin绘图之【如何将横坐标/x设置为文字、字母形式】
  • 升级SpringBoot2到3导致的WebServices升级
  • 数字化,一个泛化的概念
  • 使用Mathematica生成随机曼陀罗花
  • vue3请求设置responseType: ‘blob‘,导致失败后获取不到返回信息
  • 基于vue框架的动漫论坛g2392(程序+源码+数据库+调试部署+开发环境)带论文文档1万字以上,文末可获取,系统界面在最后面。
  • ISO 26262-5 硬件验证
  • Python雷达图实战教程:从入门到精通
  • 磁盘分区与挂载——笔记
  • 深入理解Java虚拟机之垃圾收集器篇(垃圾回收器的深入解析待完成TODO)