一、CAN通信的背景
CAN通信的背景可以追溯到上世纪80年代初,当时汽车制造商面临着一个共同的挑战:如何有效地传输和共享大量的传感器数据和控制信息。传统的电缆布线方式非常复杂且容易出错,而且无法满足日益增长的数据传输需求。
为了解决这个问题,德国的汽车制造商奔驰(Mercedes-Benz)与德国电信公司Bosch合作开发了一种新的通信协议,即CAN(Controller Area Network)控制器局域网络通信。CAN通信最初是为了满足汽车电子系统的数据传输需求而设计的,但随后被广泛应用于其他领域。
CAN通信的设计目标是实现高可靠性、实时性和灵活性。它采用了差分信号传输方式,具有较强的抗干扰能力,能够在较长距离上进行可靠的数据传输。CAN总线支持多帧并发传输,数据传输速度快,能够满足实时控制系统的要求。此外,CAN总线支持多主机和多节点的连接,能够方便地扩展和改变系统结构。
CAN通信的成功应用在汽车领域得到了广泛认可,成为现代汽车电子系统中的核心技术。随着技术的发展,CAN通信逐渐演变为更高速的版本,如CAN-FD(CAN with Flexible Data-Rate),以满足更高带宽的数据传输需求。CAN通信也被应用于其他领域,如工业自动化、航空航天等,成为一种可靠性高、实时性强的通信方式。
二、CAN通信基本介绍
CAN通信是一种基于CAN(Controller Area Network)总线的通信协议。CAN总线是一种高速、可靠的串行总线,主要用于实时控制系统中的数据传输。
CAN通信的特点包括:
高可靠性:CAN总线采用差分信号传输,具有较强的抗干扰能力,能够在较长距离上进行可靠的数据传输。
实时性:CAN总线支持多帧并发传输,数据传输速度快,能够满足实时控制系统的要求。
灵活性:CAN总线支持多主机和多节点的连接,能够方便地扩展和改变系统结构。
低成本:CAN总线的硬件成本相对较低,易于实现和维护。
在CAN通信中,每个节点都有一个唯一的标识符,用于区分不同的节点。节点之间通过CAN总线进行数据通信,发送和接收数据帧。数据帧分为数据帧和远程帧两种类型,数据帧用于传输实际数据,远程帧用于请求数据。节点之间的通信是基于优先级的,优先级高的节点具有更高的发送优先级。
三、CAN通信的直观硬件连接图
看这个图,主要分成了三个部分:CPU控制器部分、车载传感器监测部分、数据总线部分,接下来逐一介绍:
(1)CPU控制器部分
通常情况下,中央处理器(CPU)不直接集成CAN(Controller Area Network)的收发器。CAN收发器是一种专门用于CAN总线通信的硬件设备,它负责将CPU产生的CAN数据转换成CAN总线上的电信号,并将CAN总线上的电信号转换成CPU可以处理的数据格式。
在一个典型的汽车电子系统中,CAN收发器通常是作为外部组件与CPU进行连接的。CPU通过总线接口(如SPI、UART等)与CAN收发器进行通信,将要发送的CAN数据传输给CAN收发器,并从CAN收发器接收CAN总线上的数据。
虽然CPU本身不集成CAN收发器,但现代汽车电子系统的一些高级处理器(如汽车级MCU或SoC)可能会集成CAN控制器,该控制器可以与外部的CAN收发器进行通信。这样的集成设计可以简化系统布局和连接,并提高系统集成度。
(2)车载传感器部分
顾名思义,就是部署在车内各个位置用于检测各项数据并反馈给CPU。以这个图里为例:
ABS:ABS是汽车中的防抱死制动系统(Anti-lock Braking System)。它是一种安全系统,通过在制动过程中动态调节刹车力度,防止车轮锁死,提供车辆在制动时的稳定性和操控性。
SAS:SAS是汽车中的转向角度传感器(Steering Angle Sensor)。它是一种传感器装置,用于检测驾驶员转动方向盘的角度和速度,以便提供给车辆的稳定性控制系统和驾驶辅助系统。
ETM:ETM是汽车中的电子节气门(Electronic Throttle Module)。它是一种控制发动机节气门开合的电子装置,用于调节发动机的进气量和加速踏板的响应,以提供更好的动力性能和燃油经济性。
ECM:ECM是汽车中的发动机控制模块(Engine Control Module)。它是一种电子控制单元,负责监测和控制发动机的运行。ECM接收各种传感器的数据,并根据预设的算法来控制点火时机、燃油喷射量等参数,以确保发动机的正常运行和最佳性能。
DDM:DDM是分布式驱动模块(Distributed Drive Module)的缩写。它是一种分布式电子控制系统,将车辆中的驱动器和传感器连接到控制单元。DDM系统将电控单元分散安装在车辆各个位置,通过网络连接进行数据交换和控制。每个DDM单元负责控制特定的功能模块,例如电动驱动系统、制动系统或底盘控制系统。
PDM:PDM是功率分配模块(Power Distribution Module)的缩写。它是一种电源管理系统,用于控制车辆中的电力传输和分配。PDM系统通过一个集中的电源管理模块,将电能从电池传输到车辆的各个电子设备和系统。PDM负责控制和保护电路,同时提供对电路的监测和诊断功能。它能够有效地管理车辆中的电力,确保各个电子设备和系统的正常运行。
(3)通信总线部分
把每个部署的传感器作为一个节点,所有节点通过两条线连接起来。两条线分别称为CAN_H和CAN_L。
CAN总线采用双线制,即CAN_H(CAN High)和CAN_L(CAN Low)两根线。这两根线通过终端电阻连接在一起,形成一个环路或线性拓扑结构,所有节点都连接到这个总线上。
在CAN总线上,各个节点通过CAN收发器将数据发送和接收。每个节点通过识别CAN总线上的电位差来判断数据位是0还是1。CAN总线使用差分信号传输,即CAN_H和CAN_L线上的电位差表示数据位的状态。当CAN_H线的电位高于CAN_L线时,表示数据位为1;当CAN_L线的电位高于CAN_H线时,表示数据位为0。
网络的两端必须有120Ω的终端电阻。所以在设计线路板的时候都要有一个120欧的电阻。通过跳线或者拨码开关选择是否使用这个电阻。为什么是120Ω,因为电缆的特性阻抗为120Ω,为了模拟无限远的传输线。
四、具体分析环路双线的电平信号
node是每个节点,CAN_H和CAN_L以及120欧电阻构成了一个环路结构。
1.什么是差分信号
CANBUS上的总线电平称为隐性电平和显性电平:
性质 | 逻辑 | CAN_H | CAN_L | 两条线上的电压差 |
---|---|---|---|---|
隐性 | 逻辑1 | 2.5V | 2.5V | 0V |
显性 | 逻辑0 | 3.5V | 1.5V | 2V |
如果CAN控制器发送逻辑1时,CAN收发器使CAN_H和CAN_L都为2.5V,这时,两条线上的电压差为0V。总线上称为隐性电平。
如果CAN控制器发送逻辑0时,CAN收发器使CAN_H为3.5V,CAN_L为1.5V,这时,两条线上的电压差为2V。总线上称为显性电平。
同理,如果我某个节点想发送数据1,就使CAN_H和CAN_L均输出2.5V电压使得差分电压为0。注意,这里3.5V、2.5V的电平是不一定的,不同的国际标准有不同的规定。
多个节点同时开始发送时,会涉及到总线仲裁(也就是谁可以继续发送),这里就涉及到一个优先级的问题了。
2.仲裁的优先级
CAN总线上各节点仲裁的优先级是通过标识符(Identifier)来确定的。CAN总线上发送信息的节点会在标识符中包含一个优先级字段,该字段决定了节点的发送优先级。
在CAN总线上,标识符由11位或29位组成,其中包含一个优先级字段。优先级字段的值越小,表示节点的优先级越高。当多个节点同时尝试发送信息时,较低优先级的节点会让出总线让较高优先级的节点先发送。
CAN总线采用的是非破坏性仲裁机制,即在多个节点同时尝试发送信息时,仅有一个节点能够成功发送,其他节点会检测到总线上的差分信号与自己发送的信号不一致,从而意识到发生了冲突,并停止发送。
在仲裁过程中,CAN总线上的节点会根据标识符的优先级字段进行比较。节点会逐位比较标识符的优先级字段,较高位的比较结果会优先确定优先级,如果出现比较结果一致的情况,则继续比较下一位,直到得出最终的优先级结果。
3.CAN收发器
CAN收发器是用于连接CAN控制器和CAN总线的接口电路,它负责将CAN控制器产生的差分信号转换为CAN总线上的电气信号,并将CAN总线上的电气信号转换为CAN控制器可识别的差分信号。(双向作用)
CAN收发器通常由发送部分和接收部分组成。
发送部分负责将CAN控制器输出的差分信号转换为CAN总线上的电气信号。它会根据CAN控制器发送的数据和控制信号,驱动CAN总线上的CAN_H和CAN_L线,产生相应的电压差。
接收部分负责将CAN总线上的电气信号转换为CAN控制器可识别的差分信号。它会对CAN_H和CAN_L线的电压差进行测量,将其转换为CAN控制器可处理的差分信号,供CAN控制器解读。
CAN收发器通常还具备抗干扰能力,能够抵抗来自外部环境的噪声和干扰。它可以通过滤波和电压调节等方式来保证CAN总线上的信号质量和可靠性。
4.差分信号的优势
相对于单信号线传输的方式,使用差分信号传输具有如下优点:
• 抗干扰能力强,当外界存在噪声干扰时,几乎会同时耦合到两条信号线上,而接收端只关心两个信号的差值,所以外界的共模噪声可以被完全抵消。
举一个例子,正常的单线假设逻辑1是3.3V,逻辑0假设是0V,但是如果有噪声,把3.3V弄成了0V(极端),把0V弄成了-3.3V,此时就逻辑错误,但是有Can高/Can低一般都作用于两根线,所以两个虽然都有噪声影响,但是差值还是不变的
• 能有效抑制它对外部的电磁干扰,同样的道理,由于两根信号的极性相反,他们对外辐射的电磁场可以相互抵消,耦合的越紧密,泄放到外界的电磁能量越少。
举一个例子,假设一根是10V,一根是-10V,单跟都会对外部造成电磁干扰,但是CAN可以把线拧在一起,跟编麻花一样,可以互相抵消电子干扰
• 时序定位精确,由于差分信号的开关变化是位于两个信号的交点,而不像普通单端信号依靠高低两个阈值电压判断,因而受工艺,温度的影响小,能降低时序上的误差,同时也更适合于低幅度信号的电路。
由于差分信号线具有这些优点,所以在 USB 协议、485 协议、以太网协议及 CAN 协议的物理层中,都使用了差分信号传输。
五、CAN通信用到的帧结构
每一个节点都可以主动发送帧。(帧是CAN协议规定的发送或接收的单位) 。帧由段组成,段由二进制位组成。
序号 | 名称 | 帧用途 |
---|---|---|
1 | 数据帧 | 用于发送单元向接收单元传送数据的帧。 |
2 | 遥控帧 | 用于接收单元向具有相同 ID 的发送单元请求数据的帧。 |
3 | 错误帧 | 用于当检测出错误时向其它单元通知错误的帧。 (硬件自动完成) |
4 | 过载帧 | 当一个节点正忙于处理接收的信息,可以通知其它节点暂缓发送新报文。(硬件自动完成) |
5 | 间隔帧 | 用于将数据帧及遥控帧与前面的帧分离开来的帧(硬件自动完成) |
帧的种类有很多,其中错误帧、过载帧、帧间隔都是由硬件完成的,没有办法用软件来控制。对于一般使用者来说,只需要掌握数据帧与遥控帧。数据帧和遥控帧有标准格式与扩展格式。标准格式有11位标识符,扩展格式有29位标识符。
1.数据帧
这个东西在串口、IIC、SPI中已经接触过了,就是一个带有数据的数据段。
回忆起:
串口的帧结构,包括起始位-数据段-校验位-停止位。
IIC的帧结构:包括一个起始位、7位或10位的设备地址、读/写位、数据字节和一个终止位。
SPI的帧结构:由一个起始位、一个或多个数据字节和一个终止位组成。
而CAN的数据帧结构如下:
以标准格式为例
(1)帧的起始位
帧的起始位是一个低电平位,称为“SOF”(Start of Frame)。起始位用于标识帧的开始,它的存在可以帮助接收器同步数据位的时钟。
(2)仲裁段
复杂解释:
仲裁段(Arbitration Segment)是CAN帧中的一个重要部分。仲裁段用于决定优先级较高的节点能够成功发送其数据帧,而优先级较低的节点将主动放弃发送。
仲裁段的长度取决于CAN总线的位定时参数,由CAN控制器和通信系统的配置决定。一般情况下,仲裁段的长度为1到8个时间段(Time Quanta)。
在仲裁段中,CAN总线上的所有节点以并行的方式发送其标识符(Identifier)和远程帧标志位(RTR)。标识符是一个唯一的识别码,用于区分不同节点发送的数据帧。
在仲裁段中,每个节点都将其标识符和RTR以位的形式发送到CAN总线上。CAN总线上的所有节点同时检测总线上的位值,并根据标识符的比较结果来判断是否继续发送数据。
在仲裁段中,标识符的比较是基于“非破坏性位定时”(Non-Destructive Bit Timing)的原则进行的。即,每个节点在发送位的同时也在接收位,并与其发送的位进行比较。如果接收到的位与发送的位不一致,节点将意识到有更高优先级的节点正在发送数据,并主动放弃发送。
通过仲裁段的机制,CAN总线能够实现多个节点之间的冲突检测和冲突解决,以确保数据的可靠传输和优先级的正确性。仲裁段是CAN总线通信中的关键环节,为多节点通信提供了有效的调度和控制机制。
我的理解:
数据相当于信,仲裁段相当于这个信的信封的ID编号,车内有许多传感器都要发送数据给CPU,不同种类的数据对应不同编号的信件,仲裁段的编号的不同直接决定了数据种类的不同(节点的不同),也间接决定了收发数据的优先级。
(3)控制段
如图,控制断有这么几个位:
保留位(r0、r1) 必须全部以显性电平发送。但接收方可以接收显性、隐性及其任意组合的电平
RTR(Remote Transmission Request):RTR是CAN帧的一个位,用于指示发送的帧是数据帧还是远程帧。当RTR位为0时,表示发送的是数据帧;当RTR位为1时,表示发送的是远程帧。数据帧包含实际的数据信息,而远程帧用于请求其他节点发送数据帧。
IDE(Identifier Extension):IDE是CAN帧的一个位,用于指示标识符(Identifier)的类型。当IDE位为0时,表示标识符是标准格式的11位标识符;当IDE位为1时,表示标识符是扩展格式的29位标识符。标识符用于区分不同节点发送的数据帧。
DLC(Data Length Code):DLC是CAN帧的一个字段,用于指示数据帧中数据的长度。DLC的取值范围是0到8。不同的DLC值表示数据帧中包含的数据字节数。例如,DLC为2表示数据帧中包含2个字节的数据信息。
(4)数据段
用于承载实际的数据信息。数据段包含了发送或接收的数据帧中的有效数据。
数据段的长度(Data Length)由DLC字段指定,DLC字段的取值范围是0到8,表示数据段中所包含的数据字节数。例如,DLC为2表示数据段中包含2个字节的数据信息。
数据段的具体内容由发送节点确定,并在CAN帧中进行传输。接收节点根据CAN帧中的数据段来解析和处理接收到的数据。
在数据段中,每个数据字节被传输为一个8位的字节,可以是任何有效的8位数据。数据段的内容可以是传感器数据、控制指令、状态信息等,具体取决于应用领域和通信需求。
(5)CRC段、ACK段和结束帧(不是重点,硬件完成)
CRC段:CRC(Cyclic Redundancy Check)段是CAN帧中的一部分,用于校验数据的完整性。CRC是一种通过将数据帧中的数据进行计算和生成的校验码,用于检测数据传输过程中是否出现错误。发送节点在发送数据帧时会计算CRC值,并将其放置在CRC段中。接收节点在接收数据帧时会重新计算CRC值,并与接收到的CRC段中的值进行比较,以判断数据是否完整和正确。如果CRC值匹配,表示数据传输无误;如果CRC值不匹配,表示数据可能出现错误,接收节点可以选择丢弃数据或请求重新发送。
ACK段:ACK(Acknowledgment)段是CAN帧中的一位,用于表示数据的接收状态。在CAN总线中,每个节点都有ACK位用于发送和接收。当发送节点发送数据帧时,它会在CAN总线上检测ACK位。如果接收节点正确接收到数据帧并校验无误,它会发送一个ACK位为0的ACK帧作为应答。发送节点在接收到ACK帧后,确认数据帧已被正确接收并继续发送下一个帧。如果接收节点未能正确接收数据帧或校验失败,它将不会发送ACK帧,发送节点会检测到ACK缺失,可能会进行错误处理或重新发送数据帧。
总结一下就是:
序号 | 名称 | 描述 |
---|---|---|
1 | 帧起始 | 表示帧的开始,产生一个bit的显性电平。 |
2 | 仲裁段 | 表示帧的优先级, 由标识符(ID)和传送帧类型(RTR)组成。 |
3 | 控制端 | 表示数据的字节数,由6个bit构成 |
4 | 数据段 | 数据的具体内容,可发送0~8 个字节的数据。 |
5 | CRC段 | 用于校验传输是否正确。 |
6 | ACK段 | 表示确认是否正常接收。 |
7 | 帧结束 | 表示此帧结束。 |
2.远程帧(遥控帧)
远程帧用于请求其他节点发送数据,而不是传输实际的数据内容。它在通信过程中充当了一种请求机制,允许一个节点向其他节点发送一个请求,以获取特定的数据。
远程帧的格式与普通的数据帧相似,但是数据段为空。它包含以下几个重要的字段:
帧ID(Frame ID):远程帧的帧ID字段用于指定请求的目标节点。帧ID表示了数据帧或远程帧所属的消息标识符。
RTR(Remote Transmission Request):RTR位是远程帧中的一个位,用于指示该帧是一个远程帧。RTR位为1表示该帧是远程帧,用于请求其他节点发送数据。
DLC(Data Length Code):DLC字段指定了远程帧中数据段的长度。因为远程帧不包含实际的数据内容,所以DLC字段通常被设置为0。
当一个节点发送一个远程帧时,它会将帧ID和RTR位设置为指定的值,并将DLC字段设置为0。其他节点在接收到远程帧后,根据帧ID和RTR位来判断请求的目标,并准备相应的数据进行回应。
接收到远程帧的节点可以根据请求的帧ID和其他信息来准备响应数据帧,然后将其发送回请求节点。这种机制允许节点之间进行灵活的数据交换和通信。
六、CAN的仲裁机制
CAN(Controller Area Network)的仲裁机制是用于处理多个节点同时发送数据时的冲突,确保数据传输的可靠性和顺序性。CAN总线上的仲裁机制基于“非破坏性位定时”和“优先级”原则。
以下是CAN的仲裁机制的主要步骤:
位定时:CAN总线上的每个节点都有一个内部时钟,用于确定发送数据的时间。在数据传输开始之前,发送节点会监测总线上的电平,直到检测到总线电平从高电平(逻辑1)变为低电平(逻辑0)时,数据传输才开始。这个时间点称为“位定时”。
抢占优先级:CAN总线上的每个节点都有唯一的标识符(ID)来确定其优先级。较低ID的节点具有较高的优先级。当多个节点同时开始发送数据时,会根据节点的ID来进行优先级判断。
按位比较:发送节点在发送数据时,会逐个比较每一位的值。如果在某一位的比较中,发送节点的值与总线上的值不匹配,发送节点会停止发送数据,并等待其他节点继续发送。
非破坏性位定时:在冲突发生后,发送节点会停止发送数据,并等待冲突结束。此时,节点会继续监听总线上的电平。在总线上的每个节点都会根据自己的时钟进行非破坏性位定时,直到冲突结束。
传输完成:一旦其他节点完成数据的传输,发送节点会根据优先级重新开始发送数据。最高优先级的节点将能够成功发送其数据,其他节点会在下一个时间窗口中重新尝试发送。
这个图里,三个节点同时发送数据,而仲裁段中node3最低位是显性低电平,具有更高的优先级,因此会先发送node3的数据。
- 在总线空闲时,最先开始发送的节点获得发送权,一旦开始发送,不会被其他节点抢占。
- 多个节点同时开始发送时,各发送节点从仲裁段的第一位开始进行仲裁。连续输出显性电平最多的节点可继续发送。(Dominant :显性优先)
- 具有相同ID的数据帧和遥控帧在总线上竞争时,仲裁段的最后一位(RTR)为显性位的数据帧具有优先权可继续发送。
- 标准格式ID与具有相同ID的遥控帧或者扩展格式的数据帧在总线上竞争时,标准格式的RTR 位为显性位的具有优先权可继续发送。
七、STM32部署CAN通信
1.CAN控制器
STM32 的芯片中具有 bxCAN 控制器 (Basic Extended CAN),它支持 CAN 协议 2.0A 和 2.0B 标准。该 CAN 控制器支持最高的通讯速率为 1Mb/s;可以自动地接收和发送 CAN 报文,支持使用标准ID 和扩展 ID 的报文;外设中具有 3 个发送邮箱,发送报文的优先级可以使用软件控制,还可以记录发送的时间;具有 2 个 3 级深度的接收 FIFO,可使用过滤功能只接收或不接收某些 ID 号的报文;可配置成自动重发;不支持使用 DMA 进行数据收发。框架示意图如下:
STM32 的有两组 CAN 控制器,其中 CAN1 是主设备,框图中的“存储访问控制器”是由 CAN1控制的,CAN2 无法直接访问存储区域,所以使用 CAN2 的时候必须使能 CAN1 外设的时钟。框图中主要包含 CAN 控制内核、发送邮箱、接收 FIFO 以及验收筛选器。
①FIFO是什么?
FIFO(First In, First Out)是一种数据结构,可以用来存储和管理数据。它类似于一个队列,数据按照先进先出的顺序进行处理。
在通信中,FIFO常用于缓存数据。以下是一些使用FIFO的原因:
(1)数据传输速率不匹配:发送和接收设备可能具有不同的数据传输速率。使用FIFO可以缓存发送和接收的数据,以便在速率不匹配的情况下进行数据的暂存和调整。
(2)数据处理延迟:在通信过程中,数据的处理可能需要花费一定的时间。使用FIFO可以在数据处理的同时,缓存接收到的数据,确保数据不会丢失。
(3)数据突发性:在某些情况下,数据可能以突发的方式传输,即在短时间内连续传输大量的数据。使用FIFO可以暂存这些突发数据,以便后续逐个处理。
(4)数据优先级:在多个数据同时到达时,可能需要按照优先级进行处理。使用FIFO可以根据数据的优先级进行排序和处理。
(5)数据流控制:当发送和接收设备之间的数据流量不平衡时,使用FIFO可以实现数据的流控制,避免数据丢失或溢出。
②发送邮箱和接收邮箱是什么?
在CAN通信中,发送邮箱(Transmit Mailbox)和接收邮箱(Receive Mailbox)是CAN控制器中的缓冲区,用于存储发送和接收的CAN帧。
发送邮箱(Transmit Mailbox): 发送邮箱用于将要发送的CAN帧暂存到CAN控制器中,等待发送。通常,CAN控制器会提供多个发送邮箱,可以同时存储多个待发送的CAN帧。每个发送邮箱都有自己的标识符、数据和控制位,用于配置和标识发送的CAN帧。当发送邮箱中的CAN帧被发送出去后,发送邮箱就可以用来存储新的待发送CAN帧。
接收邮箱(Receive Mailbox): 接收邮箱用于存储接收到的CAN帧。CAN控制器通常提供多个接收邮箱,用于存储不同的CAN帧。每个接收邮箱都有自己的标识符、数据和控制位,用于存储接收到的CAN帧的相关信息。当CAN控制器接收到一个CAN帧时,它会将该帧存储到一个可用的接收邮箱中,供主机处理和读取。
发送邮箱和接收邮箱的数量和配置取决于所使用的CAN控制器的型号和功能。不同的CAN控制器可能提供不同数量和特性的发送邮箱和接收邮箱。这些邮箱的使用可以通过相应的寄存器进行配置和管理,以实现对CAN帧的发送和接收控制。
③接收过滤器是什么?
接收过滤器(Receive Filters)是用于过滤接收到的CAN帧的机制。在CAN通信中,接收过滤器用于筛选和选择感兴趣的CAN帧,以便只接收需要的数据。
接收过滤器通过配置一些参数来确定接收哪些CAN帧,而忽略其他不需要的CAN帧。这些参数可以包括以下内容:
标识符(Identifier):接收过滤器可以配置一个或多个标识符,以识别需要接收的CAN帧。只有匹配这些标识符的CAN帧才会被接收和处理。
掩码(Mask):**掩码用于与CAN帧的标识符进行逻辑与运算,以过滤出符合条件的CAN帧。**掩码的每个位与标识符的对应位进行比较,如果相匹配,则继续进行后续的过滤和处理。
过滤器模式(Filter Mode):接收过滤器可以配置为不同的模式,例如屏蔽位模式(Mask Mode)和列表模式(List Mode)。在屏蔽位模式下,使用一个或多个屏蔽位和标识符进行过滤。在列表模式下,使用多个过滤器进行匹配和过滤。
通过合理配置接收过滤器,可以实现对CAN帧的灵活过滤和选择,只接收和处理需要的数据。这对于提高CAN通信的效率和可靠性非常重要,特别是在复杂的系统中,其中可能存在多个CAN节点和大量的CAN帧。在使用STM32微控制器进行CAN通信时,可以使用相应的寄存器来配置和管理接收过滤器。
2.STM32使用CAN需要哪些寄存器?
在STM32微控制器中,使用CAN控制器进行CAN通信需要配置和操作以下一些常见的与CAN相关的寄存器:
寄存器名 | 作用 |
---|---|
CAN控制器配置寄存器(CAN_CRx) | 用于配置CAN控制器的工作模式、使能CAN控制器、设置CAN的时钟源等。 |
CAN接收过滤器配置寄存器(CAN_FMR | 用于配置CAN接收过滤器的参数,包括过滤器模式、过滤器数量等。 |
CAN接收过滤器组X标识符寄存器(CAN_FxR1)和CAN接收过滤器组X掩码寄存器(CAN_FxR2) | 用于配置接收过滤器组X的标识符和掩码。 |
CAN中断使能寄存器(CAN_IER) | 用于配置CAN中断的使能和屏蔽。 |
CAN状态寄存器(CAN_SR) | 用于读取CAN控制器的状态信息,例如是否接收到新的CAN帧、是否发送完成等。 |
CAN发送邮箱X标识符寄存器(CAN_TXxR)和CAN发送邮箱X数据长度寄存器(CAN_TXxDLC) | 用于配置发送邮箱X的标识符和数据长度。 |
CAN发送邮箱X数据寄存器(CAN_TXxDTx) | 用于配置和发送发送邮箱X的数据。 |
CAN接收邮箱X标识符寄存器(CAN_RXxR)和CAN接收邮箱X数据长度寄存器(CAN_RXxDLC) | 用于读取接收邮箱X中的CAN帧的标识符和数据长度。 |
CAN接收邮箱X数据寄存器(CAN_RXxDTx) | 用于读取接收邮箱X中的CAN帧的数据。 |
CAN错误状态寄存器(CAN_ESR) | 用于读取CAN控制器的错误状态信息,例如错误计数器、错误类型等。 |
CAN错误管理寄存器(CAN_MSR) | 用于清除和管理CAN控制器的错误状态。 |
八、以正点原子例程解析CAN通信
1.初始化程序
CAN_Mode_Init(CAN_SJW_1tq,CAN_BS2_8tq,CAN_BS1_9tq,4,CAN_Mode_LoopBack);
//CAN初始化环回模式,波特率500Kbps
这些传参都是什么意思?怎么初始化的呢?进入具体函数定义:
//tsjw:重新同步跳跃时间单元.范围:CAN_SJW_1tq~ CAN_SJW_4tq
//tbs2:时间段2的时间单元. 范围:CAN_BS2_1tq~CAN_BS2_8tq;
//tbs1:时间段1的时间单元. 范围:CAN_BS1_1tq ~CAN_BS1_16tq
//brp :波特率分频器.范围:1~1024; tq=(brp)*tpclk1
//波特率=Fpclk1/((tbs1+1+tbs2+1+1)*brp);
//mode:CAN_Mode_Normal,普通模式;CAN_Mode_LoopBack,回环模式;
//Fpclk1的时钟在初始化的时候设置为36M,如果设置CAN_Mode_Init(CAN_SJW_1tq,CAN_BS2_8tq,CAN_BS1_9tq,4,CAN_Mode_LoopBack);
//则波特率为:36M/((8+9+1)*4)=500Kbps
//返回值:0,初始化OK;
// 其他,初始化失败;
u8 CAN_Mode_Init(u8 tsjw,u8 tbs2,u8 tbs1,u16 brp,u8 mode)
{
GPIO_InitTypeDef GPIO_InitStructure;
CAN_InitTypeDef CAN_InitStructure;
CAN_FilterInitTypeDef CAN_FilterInitStructure;
#if CAN_RX0_INT_ENABLE
NVIC_InitTypeDef NVIC_InitStructure;
#endif
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);//使能PORTA时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_CAN1, ENABLE);//使能CAN1时钟
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_12;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //复用推挽
GPIO_Init(GPIOA, &GPIO_InitStructure); //初始化IO
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_11;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;//上拉输入
GPIO_Init(GPIOA, &GPIO_InitStructure);//初始化IO
//CAN单元设置
CAN_InitStructure.CAN_TTCM=DISABLE; //非时间触发通信模式 //
CAN_InitStructure.CAN_ABOM=DISABLE; //软件自动离线管理 //
CAN_InitStructure.CAN_AWUM=DISABLE; //睡眠模式通过软件唤醒(清除CAN->MCR的SLEEP位)//
CAN_InitStructure.CAN_NART=ENABLE; //禁止报文自动传送 //
CAN_InitStructure.CAN_RFLM=DISABLE; //报文不锁定,新的覆盖旧的 //
CAN_InitStructure.CAN_TXFP=DISABLE; //优先级由报文标识符决定 //
CAN_InitStructure.CAN_Mode= mode; //模式设置: mode:0,普通模式;1,回环模式; //
//设置波特率
CAN_InitStructure.CAN_SJW=tsjw; //重新同步跳跃宽度(Tsjw)为tsjw+1个时间单位 CAN_SJW_1tq CAN_SJW_2tq CAN_SJW_3tq CAN_SJW_4tq
CAN_InitStructure.CAN_BS1=tbs1; //Tbs1=tbs1+1个时间单位CAN_BS1_1tq ~CAN_BS1_16tq
CAN_InitStructure.CAN_BS2=tbs2;//Tbs2=tbs2+1个时间单位CAN_BS2_1tq ~ CAN_BS2_8tq
CAN_InitStructure.CAN_Prescaler=brp; //分频系数(Fdiv)为brp+1 //
CAN_Init(CAN1, &CAN_InitStructure); // 初始化CAN1
CAN_FilterInitStructure.CAN_FilterNumber=0; //过滤器0
CAN_FilterInitStructure.CAN_FilterMode=CAN_FilterMode_IdMask;
CAN_FilterInitStructure.CAN_FilterScale=CAN_FilterScale_32bit; //32位
CAN_FilterInitStructure.CAN_FilterIdHigh=0x0000;32位ID
CAN_FilterInitStructure.CAN_FilterIdLow=0x0000;
CAN_FilterInitStructure.CAN_FilterMaskIdHigh=0x0000;//32位MASK
CAN_FilterInitStructure.CAN_FilterMaskIdLow=0x0000;
CAN_FilterInitStructure.CAN_FilterFIFOAssignment=CAN_Filter_FIFO0;//过滤器0关联到FIFO0
CAN_FilterInitStructure.CAN_FilterActivation=ENABLE; //激活过滤器0
CAN_FilterInit(&CAN_FilterInitStructure);//滤波器初始化
#if CAN_RX0_INT_ENABLE
CAN_ITConfig(CAN1,CAN_IT_FMP0,ENABLE);//FIFO0消息挂号中断允许.
NVIC_InitStructure.NVIC_IRQChannel = USB_LP_CAN1_RX0_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1; // 主优先级为1
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0; // 次优先级为0
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
#endif
return 0;
}
(1)首先,PA11和PA12引脚是CAN1的收发引脚。正常对GPIO初始化。
(2)这里注意到,CAN有两个结构体,
CAN_InitTypeDef 和CAN_FilterInitTypeDef 。看看其中第一个的具体的结构体成员:
typedef struct
{
uint16_t CAN_Prescaler;
uint8_t CAN_Mode;
uint8_t CAN_SJW;
uint8_t CAN_BS1;
uint8_t CAN_BS2;
FunctionalState CAN_TTCM;
FunctionalState CAN_ABOM;
FunctionalState CAN_AWUM;
FunctionalState CAN_NART;
FunctionalState CAN_RFLM;
FunctionalState CAN_TXFP;
} CAN_InitTypeDef;
} CAN_InitTypeDef;
这个结构体看起来成员变量比较多,实际上参数可以分为两类。
前面 5 个参数是用来设置寄存器 CAN_BTR,用来设置模式以及波特率相关的参数,这在前面有讲解过,设置模式的参数是CAN_Mode,我们实验中用到回环模式 CAN_Mode_LoopBack 和常规模式 CAN_Mode_Normal,还可以选择静默模式以及静默回环模式测试。
其他设置波特率相关的参数 CAN_Prescaler,CAN_SJW,CAN_BS1 和 CAN_BS2 分别用来设置波特率分频器,重新同步跳跃宽度以及时间段 1 和时间段 2 占用的时间单元数。后面 6 个成员变量用来设置寄存器 CAN_MCR,也就是设置 CAN 通信相关的控制位。具体注释为:
CAN_InitStructure.CAN_TTCM=DISABLE; //非时间触发通信模式
CAN_InitStructure.CAN_ABOM=DISABLE; //软件自动离线管理
CAN_InitStructure.CAN_AWUM=DISABLE; //睡眠模式通过软件唤醒
CAN_InitStructure.CAN_NART=ENABLE; //禁止报文自动传送
CAN_InitStructure.CAN_RFLM=DISABLE; //报文不锁定,新的覆盖旧的
CAN_InitStructure.CAN_TXFP=DISABLE; //优先级由报文标识符决定
CAN_InitStructure.CAN_Mode= CAN_Mode_LoopBack; //模式设置: 1,回环模式;
//设置波特率
CAN_InitStructure.CAN_SJW=CAN_SJW_1tq;//重新同步跳跃宽度为个时间单位
CAN_InitStructure.CAN_BS1=CAN_BS1_8tq; //时间段 1 占用 8 个时间单位
CAN_InitStructure.CAN_BS2=CAN_BS2_7tq;//时间段 2 占用 7 个时间单位
CAN_InitStructure.CAN_Prescaler=5; //分频系数(Fdiv)
CAN_Init(CAN1, &CAN_InitStructure); // 初始化 CAN1
而滤波器设置:
typedef struct
{
uint16_t CAN_FilterIdHigh;
uint16_t CAN_FilterIdLow;
uint16_t CAN_FilterMaskIdHigh;
uint16_t CAN_FilterMaskIdLow;
uint16_t CAN_FilterFIFOAssignment;
uint8_t CAN_FilterNumber;
uint8_t CAN_FilterMode;
uint8_t CAN_FilterScale;
FunctionalState CAN_FilterActivation;
} CAN_FilterInitTypeDef;
CAN_FilterIdHigh:过滤器标识符高位,用于指定过滤器的标识符的高16位。
CAN_FilterIdLow:过滤器标识符低位,用于指定过滤器的标识符的低16位。
CAN_FilterMaskIdHigh:过滤器屏蔽标识符高位,用于指定过滤器的屏蔽标识符的高16位。
CAN_FilterMaskIdLow:过滤器屏蔽标识符低位,用于指定过滤器的屏蔽标识符的低16位。
CAN_FilterFIFOAssignment:过滤器FIFO分配,用于指定过滤器匹配的CAN帧应该存储在哪个FIFO中。
CAN_FilterNumber:过滤器编号,用于指定过滤器的编号。
CAN_FilterMode:过滤器模式,用于指定过滤器的工作模式,可以是以下值之一:单滤波器模式、双滤波器模式、列表模式。
CAN_FilterScale:过滤器比例,用于指定过滤器的比例,可以是以下值之一:16位比例、32位比例。
CAN_FilterActivation:过滤器激活状态,用于启用或禁用过滤器。
2.CAN发送和接收消息
1.发送和接收结构体
typedef struct
{
uint32_t StdId;
uint32_t ExtId;
uint8_t IDE;
uint8_t RTR;
uint8_t DLC;
uint8_t Data[8];
} CanTxMsg;
其实前文已经说过了,这段代码定义了CAN消息的结构体类型CanTxMsg,用于表示CAN消息的各个字段。具体字段如下:
StdId:标准标识符,用于指定CAN消息的标准标识符(11位)。
ExtId:扩展标识符,用于指定CAN消息的扩展标识符(29位)。
IDE:标识符扩展位,用于指定CAN消息的标识符类型。0表示标准标识符,1表示扩展标识符。
RTR:远程发送请求位,用于指定CAN消息的发送类型。0表示数据帧,1表示远程帧。
DLC:数据长度码,用于指定CAN消息的数据长度(0-8字节)。
Data:CAN消息的数据,用于存储CAN消息的数据内容。数据长度由DLC字段指定,最大长度为8字节。
//can发送一组数据(固定格式:ID为0X12,标准帧,数据帧)
//len:数据长度(最大为8)
//msg:数据指针,最大为8个字节.
//返回值:0,成功;
// 其他,失败;
u8 Can_Send_Msg(u8* msg,u8 len)
{
u8 mbox;
u16 i=0;
CanTxMsg TxMessage;
TxMessage.StdId=0x12; // 标准标识符
TxMessage.ExtId=0x12; // 设置扩展标示符
TxMessage.IDE=CAN_Id_Standard; // 标准帧
TxMessage.RTR=CAN_RTR_Data; // 数据帧
TxMessage.DLC=len; // 要发送的数据长度
for(i=0;i<len;i++)
TxMessage.Data[i]=msg[i];
mbox= CAN_Transmit(CAN1, &TxMessage);
i=0;
while((CAN_TransmitStatus(CAN1, mbox)==CAN_TxStatus_Failed)&&(i<0XFFF))i++; //等待发送结束
if(i>=0XFFF)return 1;
return 0;
}
//can口接收数据查询
//buf:数据缓存区;
//返回值:0,无数据被收到;
// 其他,接收的数据长度;
u8 Can_Receive_Msg(u8 *buf)
{
u32 i;
CanRxMsg RxMessage;
if( CAN_MessagePending(CAN1,CAN_FIFO0)==0)return 0; //没有接收到数据,直接退出
CAN_Receive(CAN1, CAN_FIFO0, &RxMessage);//读取数据
for(i=0;i<8;i++)
buf[i]=RxMessage.Data[i];
return RxMessage.DLC;
}
发送函数的核心是:
使用循环语句将msg数组中的数据逐个复制到TxMessage的Data字段中。
调用CAN_Transmit函数,将TxMessage发送到CAN1控制器,并将返回的发送邮箱索引保存到mbox变量中。
初始化i为0。
使用循环语句判断发送是否完成,如果发送失败且循环次数小于0XFFF,则继续等待发送结束。
如果循环次数达到0XFFF仍未发送成功,则返回1表示发送失败。
如果发送成功,返回0表示发送成功。
接收函数的核心是:
声明一个CanRxMsg类型的变量RxMessage,用于接收CAN消息。
使用CAN_MessagePending函数判断CAN1控制器的FIFO0是否有待处理的消息。如果没有消息,则直接返回0表示没有接收到数据。
使用CAN_Receive函数从CAN1控制器的FIFO0中读取数据,并将读取到的数据保存到RxMessage变量中。
使用循环语句将RxMessage的数据逐个复制到buf缓冲区中。
返回RxMessage的DLC字段,表示接收到的消息数据长度。
3.主函数部分
while(1)
{
key=KEY_Scan(0);
if(key==KEY0_PRES)//KEY0按下,发送一次数据
{
for(i=0;i<8;i++)
{
canbuf[i]=cnt+i;//填充发送缓冲区
if(i<4) printf("canbuf[%d]=%d",i,canbuf[i]); //显示数据
else printf("canbuf[%d]=%d",i,canbuf[i]); //显示数据
}
res=Can_Send_Msg(canbuf,8);//发送8个字节
if(res)LCD_ShowString(60+80,190,200,16,16,"Failed"); //提示发送失败
else LCD_ShowString(60+80,190,200,16,16,"OK "); //提示发送成功
}else if(key==WKUP_PRES)//WK_UP按下,改变CAN的工作模式
{
mode=!mode;
CAN_Mode_Init(CAN_SJW_1tq,CAN_BS2_8tq,CAN_BS1_9tq,4,mode);//CAN普通模式初始化, 波特率500Kbps
POINT_COLOR=RED;//设置字体为红色
if(mode==0)//普通模式,需要2个开发板
{
LCD_ShowString(60,130,200,16,16,"Nnormal Mode ");
}else //回环模式,一个开发板就可以测试了.
{
LCD_ShowString(60,130,200,16,16,"LoopBack Mode");
}
POINT_COLOR=BLUE;//设置字体为蓝色
}
key=Can_Receive_Msg(canbuf);
if(key)//接收到有数据
{
LCD_Fill(60,270,130,310,WHITE);//清除之前的显示
for(i=0;i<key;i++)
{
if(i<4)printf("canbuf[%d]=%d",i,canbuf[i]); //显示数据
else printf("canbuf[%d]=%d",i,canbuf[i]); //显示数据
}
}
t++;
delay_ms(10);
if(t==20)
{
LED0=!LED0;//提示系统正在运行
t=0;
cnt++;
printf("cnt=%d",cnt);
//LCD_ShowxNum(60+48,170,cnt,3,16,0X80); //显示数据
}
}
}
比较简单,就是建立了一个8个字节的数组,把cnt数值用for循环存进数组中,按下按钮,通过CAN发送函数发送到节点去。
总结
STM32驱动CAN通信的要点如下:
配置CAN控制器:使用寄存器或库函数配置CAN控制器的参数,包括波特率、工作模式、过滤器、中断等。可以选择使用标准帧还是扩展帧。
初始化CAN控制器:使用库函数初始化CAN控制器,并使能CAN总线。
发送CAN消息:构建CAN消息的结构体,填充相应的字段,如标识符、数据长度、数据内容等。通过库函数将CAN消息发送到CAN总线上。
接收CAN消息:使用库函数判断CAN总线上是否有待处理的消息,并使用库函数读取接收到的CAN消息的数据。
处理CAN中断:根据实际需求,配置CAN控制器的中断,并编写相应的中断处理函数。在中断处理函数中处理CAN通信相关的操作。
处理CAN错误:通过库函数检测CAN控制器的错误状态,并进行相应的错误处理。
配置GPIO引脚:配置CAN控制器所使用的GPIO引脚,使其与CAN总线相连接。
配置时钟:配置系统时钟和CAN控制器所使用的时钟,确保CAN通信的稳定性。文章来源:https://www.toymoban.com/news/detail-773515.html
调试和验证:使用适当的调试工具和方法,验证CAN通信的正确性和稳定性。可以使用CAN分析仪或者其他设备进行验证。文章来源地址https://www.toymoban.com/news/detail-773515.html
到了这里,关于STM32进阶学习(6)-通信协议之CAN详解的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!