深入理解STM32通信协议之CAN

文章目录

  • 一、CAN通信的背景
  • 二、CAN通信基本介绍
  • 三、CAN通信的直观硬件连接图
  • (1)CPU控制器部分
  • (2)车载传感器部分
  • (3)通信总线部分
  • 四、具体分析环路双线的电平信号
  • 1.什么是差分信号
  • 2.仲裁的优先级
  • 3.CAN收发器
  • 4.差分信号的优势
  • 五、CAN通信用到的帧结构
  • 1.数据帧
  • 以标准格式为例
  • 2.远程帧(遥控帧)
  • 六、CAN的仲裁机制
  • 七、STM32部署CAN通信
  • 1.CAN控制器
  • ①FIFO是什么?
  • ②发送邮箱和接收邮箱是什么?
  • ③接收过滤器是什么?
  • 2.STM32使用CAN需要哪些寄存器?
  • 八、以正点原子例程解析CAN通信
  • 1.初始化程序
  • 2.CAN发送和接收消息
  • 1.发送和接收结构体
  • 3.主函数部分
  • 总结

  • 一、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还是1CAN总线使用差分信号传输,即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的数据。

    1. 在总线空闲时,最先开始发送的节点获得发送权,一旦开始发送,不会被其他节点抢占。
    2. 多个节点同时开始发送时,各发送节点从仲裁段的第一位开始进行仲裁。连续输出显性电平最多的节点可继续发送。(Dominant :显性优先)
    3. 具有相同ID的数据帧和遥控帧在总线上竞争时,仲裁段的最后一位(RTR)为显性位的数据帧具有优先权可继续发送。
    4. ​标准格式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通信的稳定性。

    调试和验证:使用适当的调试工具和方法,验证CAN通信的正确性和稳定性。可以使用CAN分析仪或者其他设备进行验证。

    物联沃分享整理
    物联沃-IOTWORD物联网 » 深入理解STM32通信协议之CAN

    发表评论