【正点原子STM32】CAN串行通信总线协议(CAN总线特点、应用场景、物理层、协议层、位时序、硬件同步和再同步、总线仲裁、STM32CAN控制器、CAN相关寄存器和HAL库驱动、CAN基本驱动步骤)

一、CAN基础知识介绍

二、STM32 CAN控制器介绍

三、CAN相关寄存器介绍
四、CAN相关HAL库驱动介绍
五、CAN基本驱动步骤
六、编程实战
七、总结

一、CAN基础知识介绍

1.1、CAN介绍

CAN(Controller Area Network)是一种串行通信总线协议,最初由德国博世公司在20世纪80年代中期研发,主要用于汽车内部电子控制单元(ECU)之间的通信。随着时间的发展,CAN协议因其高性能、可靠性和灵活性而逐渐被广泛应用于工业控制、航空航天、船舶、医疗设备、智能家居等领域。

CAN协议的主要特点:

  1. 多主站结构:网络中的任何一个节点在满足一定条件时都可以主动发送数据,无需主从设备的概念。
  2. 消息仲裁:采用非破坏性的位仲裁机制,即在总线冲突时,具有较高优先级的消息将继续传输,低优先级消息自动重试,这大大提高了总线利用率和实时性。
  3. 错误检测和处理:具有强大的错误检测机制,包括循环冗余校验(CRC)、位填充、应答错误、总线活动监控等多种错误检测手段,以及错误界定、错误警告、总线关闭等错误处理措施。
  4. 消息帧结构:CAN协议的消息帧包括帧起始、仲裁场、控制场、数据场、CRC场、应答场和帧结束等部分。

在CAN网络中,每个节点通过CAN控制器与总线连接,并通过CAN收发器进行电气隔离和信号转换。节点发送数据时,将其封装成CAN消息并通过总线广播出去,其他节点根据消息ID( arbitration ID)判断是否接收和处理该消息。

CAN协议有两种版本:经典CAN(也称为CAN 2.0)和CAN FD(Flexible Data-Rate),其中CAN FD引入了更高的比特率和更大的数据长度,以满足现代应用对更大带宽和数据传输效率的需求。

a) 什么是CAN?

CAN in Automation(CiA) 官网
在这里插入图片描述
CAN(Controller Area Network)作为一种广泛应用于汽车以及其他诸多行业的串行通信协议,由Robert Bosch GmbH在1980年代初期开发,并成为ISO国际标准化的通信协议。CAN协议的设计初衷就是为了满足汽车产业对于降低线束复杂度、提高通信效率和可靠性的需求。

ISO发布了多个CAN标准,其中关键的是:

  • ISO 11898:针对高速CAN(High-Speed CAN),其通信速率范围通常在125Kbps至1Mbps之间,适用于短距离通信,总线长度一般不超过40米,但由于其高速性能,特别适合车辆内部的快速数据交换。
  • ISO 11519:针对低速容错CAN(Fault-Tolerant CAN,也被称为Low-Speed CAN),其通信速率通常在10Kbps至125Kbps之间,适合于较长距离和对电磁干扰较敏感的应用场合,总线长度可延伸至1000米。

此外,随着技术进步和需求增加,出现了CAN FD(CAN with Flexible Data-rate)标准,它在保持与经典CAN兼容的同时,大幅提升了数据传输速率,理论上最高可达5Mbps或更高,同时还增加了数据帧的有效负载长度,更适合于大数据量传输的应用场景。CAN FD遵循ISO 11898-1标准进行数据的发送和接收。

对于CAN协议的更深入历史背景和技术发展信息,用户可以访问CAN in Automation (CiA)官网获取权威资料和最新标准动态。CiA是一个致力于CAN技术和应用推广的国际协会,为CAN协议的发展和标准化做出了重要贡献。

b) CAN总线特点

在这里插入图片描述
在这里插入图片描述
CAN(Controller Area Network)总线具有以下特点:

  1. 多主控制

    • 在CAN总线上,任何连接的节点都可以主动发送数据,而非传统的主从结构。每个节点根据其发送的消息标识符(ID)竞争总线使用权,采用非破坏性仲裁机制,优先级高的消息可以继续传输。
  2. 系统的灵活性

    • CAN总线上的通信并不依赖于设备地址,而是基于消息ID,因此在不改变原有网络配置的情况下,可以轻松添加或删除节点,提高了系统的柔韧性和可扩展性。
  3. 通信速度与距离

    • CAN总线支持不同速率级别的通信,高速CAN(ISO 11898)速率高达1Mbps,适用于短距离通信;低速容错CAN(ISO 11519)速率较低但通信距离更长,可达1000米。
    • CAN FD(Flexible Data-rate)进一步提升了数据传输速率,但仍保持了对经典CAN的良好兼容性。
  4. 错误检测、通知与恢复功能

    • CAN总线具备强大的错误检测机制,包括位错误、填充错误、CRC校验错误、格式错误和应答错误等,一旦检测到错误,发送出错的节点会发出错误帧通知其他节点。
    • 错误恢复机制包括错误界定、错误被动状态和错误活跃状态,能够迅速恢复通信并确保网络的稳定性。
  5. 故障封闭

    • 当CAN节点检测到总线错误时,它可以判断错误的类型,并采取相应的措施,如进入被动错误状态或主动切断与总线的联系,避免错误扩散,保持整个网络的健壮性。
  6. 连接节点多

    • CAN总线支持较多节点同时在线,但实际节点数量受到总线负荷、数据传输速率、总线长度和电气特性等因素的综合影响,需要根据具体应用需求和总线负载情况找到速度与节点数量之间的平衡。在理想条件下,高速CAN网络中可连接数十个节点,而在低速容错CAN网络中,由于较低的数据速率和较强的抗干扰能力,可连接的节点数量更多。

c) CAN应用场景

在这里插入图片描述
CAN总线协议因其独特的优点和特性,在众多领域有着广泛的应用:

  1. 汽车电子:CAN总线是最早也是最主要的应用领域,几乎所有的现代汽车都采用了CAN总线技术来连接各种电子控制单元(ECUs),如发动机控制模块、防抱死制动系统(ABS)、车身电子稳定系统(ESP)、空调系统、仪表盘、车窗控制等,实现各部件间高效、可靠的数据通信。

  2. 工业自动化:在工业生产线上,CAN总线用于连接各种传感器、执行器、控制器等设备,实现工厂自动化流水线的控制与监控,以及设备间的协调工作,提高生产效率和系统的稳定性。

  3. 船舶:在船舶行业,CAN总线用于船载系统中各电子设备的互联,如导航系统、推进系统、电力系统、安全系统等,保证了信息的实时传输和系统的整合管理。

  4. 医疗设备:医疗设备中,CAN总线被用来实现医疗设备之间的数据交换和系统集成,如监护仪、呼吸机、影像设备等,确保设备间通信的准确性和可靠性。

  5. 航空航天:在飞机、卫星等航空航天领域,CAN总线也被用于机载电子设备间的通信,提供了一种轻便、高效的通信解决方案。

  6. 其他领域:除此之外,CAN总线还在农业机械、建筑设施、新能源系统(风力发电、太阳能等)、安防系统、智能家居等领域有所应用。总之,只要涉及到设备间需要高效、可靠、低成本通信的场景,都有可能使用到CAN总线技术。

1.2、CAN物理层

a) CAN物理层特性

在这里插入图片描述
CAN(Controller Area Network)物理层是CAN通信的基础层,它定义了CAN总线的物理连接、信号表示和传输方式。具体特点如下:

  1. 差分信号传输:CAN总线采用两条信号线,分别是CAN_H(高电平线)和CAN_L(低电平线),通过这两条线之间的电压差(电位差)来表示数据。这种方式可以有效抵抗共模干扰,提高通信的鲁棒性。

  2. 总线电平

    • 隐性电平(逻辑1):当CAN_H和CAN_L两条线的电压差约为0伏(严格地说,CAN_H比CAN_L高约200毫伏至600毫伏),此时认为总线处于隐性状态,代表逻辑“1”。在没有节点发送数据时,总线默认保持隐性电平。
    • 显性电平(逻辑0):当CAN_H线电压低于CAN_L线电压(电压差约为0.9伏至1.1伏),此时总线处于显性状态,代表逻辑“0”。显性电平具有更高的优先级,当多个节点同时发送数据时,具有显性电平的节点将“赢得”总线使用权。
  3. 消息发送:发送方通过控制CAN_H和CAN_L线上的电压差,使其从隐性状态变为显性状态,进而将消息内容(即数据位)发送到总线上。所有连接到总线的节点都可以监听总线状态的变化,并据此解码接收到的数据。

  4. 错误检测:物理层还包含错误检测机制,如位填充、CRC校验和总线唤醒等,以确保数据的正确传输和节点的正常工作。

  5. 仲裁机制:在多主控网络中,CAN的物理层通过比较总线上的电位变化来实现非破坏性的仲裁,即当两个或多个节点同时尝试发送数据时,具有较低优先级(较高ID)的节点会自动停止发送,转而监听总线上的数据,从而避免了数据冲突和总线冲突。

在这里插入图片描述

b) CAN收发器芯片介绍

在这里插入图片描述
CAN收发器芯片是连接CAN控制器与物理总线之间的关键元件,它们负责将CAN控制器发出的数字信号转换为符合CAN总线规范的差分信号,并将总线上的差分信号转换回数字信号供CAN控制器处理。下面列举了几款常见的CAN收发器芯片:

  1. TJA1050

    • TJA1050是NXP(恩智浦)生产的CAN收发器,支持高速CAN通信,工作在1Mbps的数据传输速率下。它具有高电磁兼容性(EMC),适用于汽车和工业环境。
  2. TJA1042

    • 同样来自NXP的TJA1042也是专为高速CAN设计的收发器,同样支持最高1Mbps的数据速率,常见于汽车和其他需要高速CAN通信的场合。
  3. SIT1050T

    • SIT1050T是由芯力特等国内厂商制造的高速CAN收发器,同样支持最高1Mbps的传输速率,具备良好的性价比和广泛的适用性。

SIT1050T的引脚功能说明:

  • D:CAN数据发送引脚,连接到CAN控制器的发送输出端。
  • R:CAN数据接收引脚,连接到CAN控制器的接收输入端。
  • Vref:参考电压输出引脚,用于为CAN收发器内部电路提供参考电压。
  • CANL:低电位CAN电压输入输出端,与CAN总线的CAN_L线相连,通过测量CANL和CANH之间的电压差来识别数据。
  • CANH:高电位CAN电压输入输出端,与CAN总线的CAN_H线相连。
  • RS:高速/静音模式选择引脚,通常情况下,当该引脚为低电平,SIT1050T工作在高速模式下,支持最高1Mbps的数据传输速率;如果需要进入静音模式或者其他特殊工作模式,可以通过该引脚进行控制。

这些收发器芯片在设计和应用中都起到了关键作用,确保了CAN网络中数据的可靠传输和系统稳定运行。

1.3、CAN协议层

a) CAN帧种类介绍

在这里插入图片描述

  • 数据帧(Data Frame):是最主要的通信载体,包含有完整的信息数据,包括标识符(用于决定优先级和目的地址)、控制位、数据域以及校验位。数据帧用于一个节点向其他节点发送数据信息。

  • 遥控帧(Remote Frame):与数据帧类似,但它不携带实际数据,只是请求具有相同标识符的发送单元发送数据。遥控帧由需要接收数据的节点发送,以请求数据更新。

  • 错误帧(Error Frame):当CAN网络中的节点检测到总线错误时,会发送错误帧通知其他节点。错误类型包括位错误、填充错误、格式错误、应答错误等。通过错误帧,节点可以快速识别网络中存在的问题并采取适当措施。

  • 过载帧(Overload Frame):在连续发送多帧数据时,如果接收节点来不及处理,就会发送过载帧来告诉发送节点暂停一下,给接收节点预留足够的时间处理先前接收到的数据。过载帧的出现可以帮助维护总线通信的稳定性。

  • 间隔帧(Inter Frame Space,IFS):又称为帧间空间或帧间隔,它不是一个具体的物理帧,而是指两个连续帧之间的一段时间间隔。这个间隔确保了帧的清晰分离,让网络中的所有节点都能识别出帧的开始和结束,同时也是为了防止总线过于繁忙而导致的冲突或错误。在实际通信中,间隔帧表现为一段没有有效信号的时间段。

b) CAN数据帧介绍

在这里插入图片描述
是的,您说得很对。数据帧在CAN协议中确实由七个主要部分组成,对于标准帧(CAN 2.0A)和扩展帧(CAN 2.0B)而言,它们的组成大致相同,但在仲裁段和控制段存在差异:

  1. 帧起始(Start of Frame,SOF):由一个显性位(逻辑0)组成,标志着数据帧的开始。

  2. 仲裁段(Arbitration Field)

    • 对于标准帧(CAN 2.0A),仲裁段包含11位标识符,其中1-10位是标识符(Identifier)的高位部分,第11位是远程传输请求位(RTR)。
    • 对于扩展帧(CAN 2.0B),仲裁段首先是一个标识符扩展位(IDE),接下来是18位标识符,分为基础标识符(Base Identifier)的11位和扩展标识符(Extended Identifier)的7位,同样包含RTR位。
  3. 控制段(Control Field)

    • 标准帧的控制段由保留位(r0)和数据长度码(DLC),共计6位组成,用于指示后续数据段的字节数。
    • 扩展帧的控制段在标识符扩展位之后立即开始,格式与标准帧相同。
  4. 数据段(Data Field):包含0至8个字节的数据,具体长度由控制段的DLC决定。

  5. CRC段(CRC Field):包括15位循环冗余校验码,用于检测帧传输过程中的错误。

  6. ACK段(Acknowledgment Field):包含两个位,第一个位由发送节点发送隐性位,接收节点在检测到帧无误后发送显性位进行应答。

  7. 帧结束(End of Frame,EOF):由7个连续的隐性位(逻辑1)组成,标志着数据帧的结束。

以上就是CAN数据帧的结构概览,它确保了数据在CAN总线上传输的可靠性、实时性和有效性。

c) CAN位时序介绍

在这里插入图片描述
CAN(Controller Area Network)总线的位时序是其通信机制的核心,它确保了网络中各节点能够在没有全局时钟的情况下实现同步通信。CAN总线在一个位周期内的确分为四段:

  1. 同步段(Synchronization Segment, SS)

    • 同步段位于位周期的开始,用于网络中的所有节点同步到第一位的起点。在这个时间段内,总线上会发生从隐性到显性(或从显性到隐性)的位边缘跳变。节点检测到这个跳变,就可以开始新的位周期的计时。
  2. 传播时间段(Propagation Time Segment, PTS)

    • 传播时间段是信号在总线上传播所需的时间,由于信号在不同长度的总线段上传播速度略有差异,这段时期是为了确保信号在整个总线上均匀分布。
  3. 相位缓冲段1(Phase Buffer Segment 1, PBS1)

    • 相位缓冲段1主要用于补偿网络中各节点的内部延时,确保在采样点到来时,所有节点都已经接收到一致的信号。
  4. 相位缓冲段2(Phase Buffer Segment 2, PBS2)

    • 相位缓冲段2进一步提供延时补偿,尤其是在仲裁过程中,确保即使在仲裁失败的节点撤销驱动总线信号后,其他节点仍有足够时间采样到正确的电平。

采样点(Sampling Point)

  • 采样点位于PBS1和PBS2之间,通常是在PBS1结束时或结束后的某个时序Tq(Time Quantum,基本时间单位)。在采样点处,节点会读取总线上的电平,并以此电平作为该位的值。对于CAN总线,采样点的位置是可以配置的,一般配置在位周期的80%到90%之间,以增加对噪声的抵抗力。

波特率计算

  • 通过对位时序的精准配置,可以计算得出CAN总线的波特率。波特率计算通常涉及同步段、传播时间段、相位缓冲段1和2的总时序Tq数,以及预分频系数。波特率的计算公式通常是基于总线时钟频率和这些位时序段的配置来确定的。
    在这里插入图片描述
    在这里插入图片描述
    数据同步过程在CAN总线中至关重要,以确保所有节点能够在正确的时间点采样总线上的信号。以下是关于硬件同步和再同步的详细说明:

硬件同步(Hard Synchronization)

  • 当一个节点开始发送数据帧时,它首先发送帧起始(SOF)信号,即位边缘跳变。其他节点检测到这个跳变,并将其与自身内部时钟的同步段(SS)进行比较。
  • 如果节点检测到的边沿信号不在自身的同步段内,意味着节点内部时钟与总线时钟不同步。这时,节点会通过硬件同步机制,立即将自身的下一个同步段调整到检测到的边沿位置,从而实现同步。

再同步(Resynchronization)

  • 在数据传输过程中,节点可能会因为各种原因(如电气干扰、时钟漂移等)而失去同步。这时,节点会利用普通数据位的边沿信号进行再同步。

  • 再同步有两种情况:

    • 超前(Leading Edge Resynchronization):当节点检测到的数据位前沿早于预期的同步段时,它会在相位缓冲段1(PBS1)和相位缓冲段2(PBS2)之间进行调整,增加相位缓冲段的时间长度,使采样点重新对齐到正确的电平变化位置。
    • 滞后(Trailing Edge Resynchronization):反之,当节点检测到的数据位后沿晚于预期的同步段时,也会进行类似调整,只不过这次是通过减少相位缓冲段的时间长度来实现同步。
  • 再同步补偿宽度(SJW, Synchronization Jump Width)

    • SJW决定了在进行再同步时,PBS1和PBS2之间能够增加或减少的最大时间长度,其范围通常为1到4个时间量子(Tq)。
    • 较大的SJW值允许节点更灵活地应对同步误差,但也可能导致数据传输速率降低,因为它允许采样点位置在更大的窗口内移动。

通过这些机制,CAN总线可以在没有全局时钟的情况下保持节点间的数据同步,确保了通信的准确性与可靠性。

d) CAN总线仲裁

在这里插入图片描述
CAN(Controller Area Network)总线的仲裁机制是一种非破坏性的仲裁方式,确保了在多节点同时尝试发送数据时,优先级最高的消息能够优先传输。具体仲裁过程如下:

  1. 当CAN总线处于空闲状态时,任何一个节点(单元)都可以尝试发送数据,首先开始发送的节点暂时获得总线控制权。

  2. 若有两个或更多的节点几乎同时开始发送消息,仲裁过程将在仲裁段(即标识符ID字段)开始时启动。标识符ID是按位比较的,每一位都是从最低有效位(LSB)开始。

  3. 在仲裁过程中,所有参与仲裁的节点同时在总线上发送其消息ID的第一位。如果所有节点发送的这一位都是显性位(逻辑0),则所有节点继续保持竞争状态,继续发送下一位。

  4. 当某一节点发送的某一位是隐性位(逻辑1),而其他节点发送的是显性位时,这位隐性位的节点会检测到总线电平与其发送的不同,从而自动退出发送,并转换到接收状态。换句话说,持续发送显性电平(逻辑0)的节点拥有更高的优先级。

  5. 这个仲裁过程会一直持续到仲裁段的所有位都被比较完,最后剩下的一台仍在发送显性位的节点就赢得了仲裁,可以继续发送其剩余的消息内容。

  6. 未能赢得仲裁的节点,在检测到总线空闲后,会根据其内部优先级安排,尽快再次尝试发送数据,整个过程无需人工干预,完全由硬件自动完成。

通过这种仲裁机制,CAN总线有效地解决了总线冲突的问题,确保了在多节点通信系统中数据的高效和有序传输。同时,由于仲裁是非破坏性的,即使在仲裁过程中也没有数据丢失,提高了总线的安全性和可靠性。

二、STM32 CAN控制器介绍

2.1、CAN控制器介绍

在这里插入图片描述
STM32 CAN控制器(bxCAN)是意法半导体(STMicroelectronics)在其STM32系列微控制器中集成的一种高性能CAN控制器,它支持CAN(Controller Area Network)协议的2.0A和2.0B Active版本。

CAN协议版本差异

  • CAN 2.0A:支持标准数据帧格式,这类帧的标识符长度为11位,适用于不需要高标识符灵活性的应用场景。当CAN 2.0A控制器遇到扩展数据帧时,它无法正确解析扩展标识符,可能会识别错误。

  • CAN 2.0B Active:不仅支持标准数据帧,还支持扩展数据帧格式,扩展帧的标识符长度为29位,允许更大的地址空间和更灵活的数据路由。这对于需要更多节点标识符或者更复杂网络结构的应用来说至关重要。

bxCAN主要特点概述

  • 高速通信:最大通信波特率可达1Mbps,保证了快速的数据传输能力。

  • 时间触发通信(TTCAN)支持:bxCAN内含硬件定时器,能够在发送或接收帧的特定时刻(如帧起始位的采样点)生成时间戳,这对于时间同步和实时性要求高的系统非常有用。

  • 发送邮箱:具备3级发送邮箱功能,这意味着它可以同时准备并排队多个待发送的消息,每个邮箱对应一个单独的发送请求。

  • 接收FIFO:配备2个具有3级深度的接收FIFO,用于暂存接收到的消息,按照先进先出的原则组织数据,并通过不同的过滤策略将接收到的消息分发给应用程序,有助于提高数据处理效率和减少数据丢失的风险。

  • 可配置过滤器组:bxCAN通常提供多个过滤器组,允许用户根据需求配置多达28个不同的标识符过滤规则,这样可以根据不同的标识符范围或单个标识符来筛选出感兴趣的消息,减轻处理器负担并优化总线利用率。

2.2、CAN控制器模式

在这里插入图片描述
CAN控制器的工作模式通常包括以下三种基本模式:

  1. 初始化模式(Initialization Mode):

    • 在这个模式下,CAN控制器处于初始配置状态,主要用于设置CAN控制器的各种参数,例如比特率、滤波器、接受屏蔽码等。在此模式下,CAN总线不进行任何实际的数据传输活动。
  2. 正常模式(Normal Mode):

    • 当CAN控制器完成初始化配置之后,切换到正常模式开始参与总线通信。在正常模式下,CAN控制器能够发送和接收数据帧,执行所有的总线错误检测和恢复机制,以及其他必要的通信任务。
  3. 睡眠模式(Sleep Mode):

    • 这是一种低功耗模式,当CAN总线暂时不使用或者为了节省电源时,控制器可以被置入睡眠模式。在睡眠模式下,CAN控制器停止大部分操作,显著降低了功耗,但通常仍保留部分关键电路以便能迅速唤醒并重新加入总线通信。

此外,某些CAN控制器(如STM32的bxCAN)可能还支持其他特殊测试模式,如静默模式(Silent Mode)、环回模式(Loopback Mode)和静默环回模式(Silent Loopback Mode),这些模式主要用于调试和测试目的,而不是常规通信。在静默模式下,CAN控制器不再向总线发送任何数据,而在环回模式中,发送出去的数据会被内部反馈作为接收数据,以便于检查发送逻辑和接收逻辑的功能完整性。
在这里插入图片描述
没错,CAN控制器的测试模式主要包括以下三种:

  1. 静默模式(Silent Mode)
    在静默模式下,CAN控制器仍然能够接收总线上的数据,但不会发送任何数据,也就是说,即便有数据准备好发送,也不会真正输出到CAN总线上。这种模式主要用于诊断和调试,可以观察总线上的其他节点活动而不影响总线通信。

  2. 环回模式(Loopback Mode)
    在环回模式下,CAN控制器发送的数据并不会实际传输到CAN总线上,而是直接反馈到接收通道上,仿佛数据在总线上进行了传输并成功返回到发送节点。这样,开发者可以测试CAN控制器的发送和接收功能,而无需实际连接到CAN总线的其他节点。此模式可以用来验证控制器本身的硬件和软件功能,排除物理总线上的干扰因素。

  3. 环回静默模式(Silent Loopback Mode)
    这是一种结合了静默模式和环回模式的测试模式,在这种模式下,CAN控制器既不会向总线发送任何数据,同时其发送的数据也会像在环回模式下那样被内部反馈到接收通道。这种模式可以用来彻底隔离控制器,仅对其内部功能进行测试,避免对外部总线造成任何影响。

2.3、CAN控制器框图

在这里插入图片描述
CAN控制器在嵌入式系统中通常是以硬件IP模块的形式存在的,其内部结构可以抽象为以下部分:

  1. CAN内核(Core)

    • CAN内核是控制器的核心,包含了一系列的控制和状态寄存器,通过配置这些寄存器可以设定CAN控制器的工作模式(例如工作在CAN 2.0A或2.0B模式下)、波特率、错误检测和恢复策略等关键参数。内核还负责处理总线上的仲裁、错误检测、帧发送和接收等工作。
  2. 发送邮箱(Transmit Mailboxes)

    • 发送邮箱是用于临时存储待发送报文的缓冲区,每个邮箱可以容纳一个完整的CAN报文,并附带相关控制信息。在STM32的某些系列产品中,如F1、F4和F7等,提供了至少3个发送邮箱,这样可以同时准备多个报文并发发送,提高通信效率。
  3. 接收FIFO(First-In-First-Out,先进先出队列)

    • 接收FIFO用于暂存从CAN总线上接收到的有效报文。当CAN控制器接收到报文时,会将其放入接收FIFO中,应用程序可以从FIFO中取出并处理这些报文。FIFO设计可以避免在接收过程中丢失数据,特别是在短时间内连续接收到多个报文的情况下。
  4. 接收过滤器(Receive Filters)

    • 接收过滤器用于筛选从CAN总线上接收到的报文,只有经过过滤器匹配的报文才会被存入接收FIFO中,这样可以减少不必要的处理器负载,并允许系统只关注感兴趣的特定报文。过滤器可以根据标识符、标识符掩码等多种规则进行配置。

对于STM32系列微控制器,一些互联型(Connectivity Line)产品只包含一个CAN控制器,而F4和F7系列的产品通常具有两个独立的CAN控制器,这样可以同时支持两个独立的CAN网络,或是在单一网络中实现更复杂的通信方案。

发送处理流程、接收处理流程

在这里插入图片描述
发送处理流程:

  1. 选择空置邮箱:首先,CAN控制器检查发送邮箱的状态,选择一个当前空置的邮箱(即未被占用的邮箱)来进行数据发送准备工作。

  2. 设置报文内容:将待发送的报文内容填充到选定的邮箱中,包括设置报文的标识符(ID)、数据长度代码(DLC)以及具体的数据内容。

  3. 设置优先级:在CAN协议中,报文的优先级通常由标识符的数值决定,标识符数值越小,优先级越高。若多个邮箱中的报文标识符相同,则按照邮箱编号从小到大排序,编号小的邮箱具有更高的发送优先级。

  4. 邮箱退出空状态:填充完报文内容后,邮箱不再为空,此时向CAN控制器请求将该邮箱中的报文发送到总线上。

  5. 挂号状态:请求发送后,邮箱进入挂号状态,等待成为最高优先级的邮箱以便获得发送机会。

  6. 预定发送状态:当邮箱成为最高优先级并且总线进入空闲状态时,邮箱进入预定发送状态,准备在下一个发送窗口开始时正式发送报文。

  7. 发送状态:当报文成功发送到总线上,CAN控制器的状态寄存器(CAN_TSR)的RQCP位(Request Completed Flag)和TXOK位(Transmission OK Flag)将会被置1,表示该邮箱中的报文已发送成功。

  8. 空置状态:报文发送完成后,邮箱再次回到空置状态,准备接收下一个待发送的任务。

接收处理流程:

  1. 空置FIFO:接收FIFO在开始接收报文前为空,等待接收有效报文。

  2. 接收有效报文:当CAN控制器接收到有效报文(即数据帧的起始到结束均无错误,并且报文标识符通过过滤器组的筛选)时,报文将被存入接收FIFO中。

  3. FIFO状态变更:随着有效报文不断被存入,FIFO依次经历空置、挂号_1、挂号_2、挂号_3等状态,直至满载。

  4. FIFO溢出状态:若在FIFO未清空的情况下继续接收到有效报文,而FIFO已满,则会进入溢出状态,可能出现报文丢失问题。具体是否会丢失报文取决于FIFO锁定功能的配置,若开启了锁定功能,则新接收到的报文会导致旧报文丢失;若未开启锁定功能,新报文可能会替代FIFO中最旧的未读报文。

  5. 空置状态:当FIFO中的报文被应用程序读取并处理后,空出来的空间可以继续接收新的有效报文,FIFO回归空置状态,等待接收下一个有效报文。

接收过滤器

在这里插入图片描述
接收过滤器在CAN总线通信中扮演着重要的角色,它允许CAN控制器根据预设的规则筛选接收到的报文,仅将满足条件的报文存入接收FIFO,从而避免了无效或不关心的报文对CPU资源的占用,提升系统的运行效率。

在STM32等微控制器中,CAN控制器的接收过滤器通常被组织为若干个过滤器组,每个过滤器组由两个32位寄存器CAN_FxR1和CAN_FxR2组成,其功能和作用根据过滤器的工作模式有所不同。

  • 位宽设置

    • 根据过滤器配置,位宽可以设置为32位或16位。如果是32位模式,两个寄存器联合起来作为一个整体使用;如果是16位模式,则每个寄存器单独作为一个过滤器。
  • 屏蔽位模式

    • 在屏蔽位模式下,CAN_FxR1和CAN_FxR2寄存器存储的是标识符的掩码和标识符本身。掩码位为1时,表示对应标识符位参与过滤匹配;为0时,表示该位在匹配时不考虑。通过对比接收到的报文标识符与过滤器中存储的标识符及其掩码,可以选择出一组满足条件的报文。
  • 标识符列表模式

    • 在标识符列表模式下,CAN_FxR1和CAN_FxR2寄存器存储的是期望接收的特定标识符。当接收到的报文标识符与过滤器中存储的标识符匹配时,该报文被认为有效并被存入接收FIFO。这种方式可以精确筛选出几个特定标识符的报文。

通过合理配置接收过滤器,可以极大地优化CAN总线数据处理流程,确保系统仅处理关键和有效的通信数据,降低系统负载,提高响应速度。
在这里插入图片描述
在这里插入图片描述
接收过滤器的运作机制如下:

  1. 屏蔽位寄存器的使用

    • 在屏蔽位模式下,过滤器的寄存器中,每位的值代表不同的含义。当屏蔽位寄存器中的某一位为1时,表示对应的报文ID该位必须与过滤器中设置的标识符位精确匹配;而当位值为0时,则表示该位在匹配时可以忽略,即无论该位的实际值是什么,都会通过过滤器。
  2. 报文过滤

    • 当接收过滤器启用后,CAN控制器会将总线上接收到的每个报文ID与过滤器的配置进行比较。如果报文ID与任何激活的过滤器设置都不匹配,那么该报文将不会被存入接收FIFO,相当于被控制器丢弃。
  3. 标识符匹配要求

    • 在匹配过程中,不仅需要考虑ID的值,还必须确保标识符选择位(IDE)和帧类型(RTR)也与过滤器配置一致。例如,如果过滤器配置为标准标识符并且数据帧,则只有那些标识符类型为标准标识符且为数据帧的报文才能通过过滤器。
  4. 过滤器组工作模式的多样性

    • 不同的过滤器组可以设置为不同的工作模式(如屏蔽位模式或标识符列表模式),也可以针对不同的应用场景配置不同的过滤规则,以满足系统对不同类型报文的接收需求。

总之,接收过滤器通过灵活的配置,可以精准地筛选接收到的报文,确保系统仅处理所需的报文,从而提高系统效率并减少不必要的资源消耗。

2.4、CAN控制器位时序

在这里插入图片描述
STM32系列微控制器的CAN外设位时序分为三个部分:

  1. 同步段(SYNC_SEG):在每个位时间的开始,用于同步所有连接到CAN总线的节点。

  2. 时间段1(BS1,也称为TS1):在同步段之后,用于信号传播和相位缓冲,确保所有节点在采样点(Sampling Point)处正确同步。

  3. 时间段2(BS2,也称为TS2):在时间段1之后,提供额外的相位缓冲,确保即使在总线长度较大或存在噪声的环境下也能正确采样。

波特率的计算公式为:

波特率 = C A N 时钟频率 / ( 1 t q + t q ∗ ( T S 1 + 1 ) + t q ∗ ( T S 2 + 1 ) ) 波特率 = CAN时钟频率 / (1tq + tq * (TS1 + 1) + tq * (TS2 + 1)) 波特率=CAN时钟频率/(1tq+tq(TS1+1)+tq(TS2+1))

这里的tq(Time Quantum)是基本时间单位,由BRP(波特率预分频器)设置决定。

举例说明:

  • 对于STM32F103,假设CAN时钟频率为36MHz,TS1=8,TS2=7,BRP=3,那么:

    • tq = CAN时钟频率 / BRP = 36MHz / 3 = 12MHz
    • 波特率 = 1/tq * (1 + 8 + 1 + 7) = 500Kbps
  • 对于STM32F407,假设CAN时钟频率为42MHz,TS1=6,TS2=5,BRP=5,那么:

    • tq = CAN时钟频率 / BRP = 42MHz / 5 = 8.4MHz
    • 波特率 = 1/tq * (1 + 6 + 1 + 5) ≈ 500Kbps

为了保证通信的成功,所有连接到CAN总线的节点都需要设置相同的波特率。

三、CAN相关寄存器介绍

在这里插入图片描述
STM32系列微控制器中CAN外设的一部分关键寄存器,它们的具体功能和用途如下:

  1. CAN_MCR(CAN Main Control Register,CAN主控制寄存器)

    • 主要用于配置CAN控制器的工作模式,如初始化、正常运行、睡眠模式、自检、唤醒等,并控制错误警告和总线关闭功能。
  2. CAN_BTR(Bit Timing Register,位时序寄存器)

    • 用于设置CAN控制器的波特率分频系数(BRP)、同步跳转宽度(SJW)、时间段1(TBS1,也称TS1)和时间段2(TBS2,也称TS2)等参数,以及测试模式的启用。
  3. CAN_(T/R)IxR(Transmit/Receive Identifier Registers,发送/接收标识符寄存器)

    • 存储待发送或已接收到的报文的标识符(ID),包括标准标识符和扩展标识符(IDE位决定),以及远程传输请求(RTR)位。
  4. CAN_(T/R)DTxR(Transmit/Receive Data Length and Time Stamp Registers,发送/接收数据长度和时间戳寄存器)

    • 存储待发送或已接收到报文的数据长度代码(DLC),在某些具有时间戳功能的控制器中,还可能用于存储时间戳信息。
  5. CAN_(T/R)DLxR(Transmit/Receive Data Low Registers,发送/接收数据低位寄存器)

    • 存储待发送或已接收到报文的数据段中Data0至Data3的内容。
  6. CAN_(T/R)DHxR(Transmit/Receive Data High Registers,发送/接收数据高位寄存器)

    • 存储待发送或已接收到报文的数据段中Data4至Data7的内容。
  7. CAN_FM1R(Filter Mode Register 1,过滤器模式寄存器1)

    • 用于设置各个过滤器组的工作模式,如标识符列表模式、屏蔽位模式等。
  8. CAN_FS1R(Filter Scale Register 1,过滤器位宽寄存器1)

    • 用于设置各个过滤器组的位宽,如16位或32位模式。
  9. CAN_FFA1R(Filter FIFO Assignment Register 1,过滤器FIFO关联寄存器1)

    • 用于设置哪些过滤器组的结果将被送到哪个接收FIFO中。
  10. CAN_FA1R(Filter Activation Register 1,过滤器激活寄存器1)

    • 用于开启或关闭各个过滤器组的使用。
  11. CAN_FxR1和CAN_FxR2(Filter Register x Part 1 and 2,过滤器组x寄存器1和2)

    • 根据过滤器的工作模式和位宽设置不同,这两个寄存器存储的内容会有不同。在标识符列表模式下,它们可能存储特定的标识符;在屏蔽位模式下,它们可能存储标识符及其对应的屏蔽掩码。具体功能需根据具体的过滤器配置来确定。

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

四、CAN相关HAL库驱动介绍

在这里插入图片描述
CAN相关HAL库驱动函数与寄存器的关系及功能描述如下:

  1. __HAL_RCC_CANx_CLK_ENABLE(…)

    • 关联寄存器:RCC_AHB1ENR(或RCC_APB1ENR、RCC_APB2ENR,取决于具体型号的STM32)
    • 功能描述:使能指定CAN外设的时钟,使其可以正常工作。
  2. HAL_CAN_Init(…)

    • 关联寄存器:CAN_MCR(主控制寄存器)和CAN_BTR(位时序寄存器)
    • 功能描述:初始化CAN控制器,包括设置工作模式、波特率、错误检测等基本参数。
  3. HAL_CAN_ConfigFilter(…)

    • 关联寄存器:CAN过滤器相关的寄存器,如CAN_FM1R、CAN_FS1R、CAN_FFA1R、CAN_FxR1/2等
    • 功能描述:配置CAN接收过滤器,包括过滤器的工作模式、位宽、关联FIFO等,以筛选接收到的报文。
  4. HAL_CAN_Start(…)

    • 关联寄存器:CAN_MCR(主控制寄存器)和CAN_MSR(状态寄存器)
    • 功能描述:启动CAN设备,使其可以开始发送和接收报文。
  5. HAL_CAN_ActivateNotification(…)

    • 关联寄存器:CAN_IER(中断使能寄存器)
    • 功能描述:激活CAN控制器的特定事件通知,如发送完成、接收完成等,通常通过使能相关中断来实现。
  6. __HAL_CAN_ENABLE_IT(…)

    • 关联寄存器:CAN_IER(中断使能寄存器)
    • 功能描述:使能CAN控制器的特定中断,使得当发生对应事件时,微控制器可以接收到中断请求。
  7. HAL_CAN_AddTxMessage(…)

    • 关联寄存器:CAN_TSR(发送状态寄存器)、CAN_TIxR(发送标识符寄存器)、CAN_TDTxR(发送数据长度和时间戳寄存器)、CAN_TDLxR(发送数据低位寄存器)、CAN_TDHxR(发送数据高位寄存器)
    • 功能描述:将一个待发送的消息添加到发送邮箱中,等待发送。
  8. HAL_CAN_GetTxMailboxesFreeLevel(…)

    • 关联寄存器:CAN_TSR(发送状态寄存器)
    • 功能描述:获取可用发送邮箱的数量,判断是否有空闲邮箱可以用来发送新的消息。
  9. HAL_CAN_GetRxFifoFillLevel(…)

    • 关联寄存器:CAN_RF0R(接收FIFO 0状态寄存器)、CAN_RF1R(接收FIFO 1状态寄存器)
    • 功能描述:获取接收FIFO中已接收但未处理的消息数量,用于判断是否有新消息接收完成。
  10. HAL_CAN_GetRxMessage(…)

    • 关联寄存器:CAN_RF0R/CAN_RF1R(接收FIFO状态寄存器)、CAN_RIxR(接收标识符寄存器)、CAN_RDxR(接收数据寄存器)
    • 功能描述:从接收FIFO中获取并清除一条已接收的消息,包括标识符、数据长度和数据内容。
      在这里插入图片描述
      在这里插入图片描述
      在这里插入图片描述

五、CAN基本驱动步骤

在这里插入图片描述
在基于STM32的CAN通信开发中,基本驱动步骤可以概括如下:

  1. CAN参数初始化

    • 使用HAL_CAN_Init()函数进行CAN控制器的基本参数初始化,包括设置工作模式(如正常模式、静默模式等)、波特率、同步跳转宽度(SJW)、时间段1(BS1)和时间段2(BS2)等位时序参数。
  2. 使能CAN时钟和初始化相关引脚

    • 使用__HAL_RCC_CANx_CLK_ENABLE()函数启用CAN外设的时钟。
    • 将相关GPIO引脚配置为复用功能模式,例如通过GPIO_PinAFConfig()函数映射到CAN功能,并设置为推挽输出或开漏输出(具体取决于CAN收发器的要求)。
  3. 设置过滤器

    • 使用HAL_CAN_ConfigFilter()函数或一系列相关API配置CAN接收过滤器,包括设置过滤器的工作模式、标识符、屏蔽位、关联的FIFO等。
  4. CAN数据接收和发送

    • 发送消息:使用HAL_CAN_AddTxMessage()函数将消息添加到发送邮箱,并开始发送。
    • 接收数据:使用HAL_CAN_GetRxMessage()函数从接收FIFO中获取接收到的消息。
  5. 使能CAN相关中断和编写中断服务函数

    • 使用__HAL_CAN_ENABLE_IT()函数启用特定的CAN中断,如发送完成中断、接收中断等。
    • 配置NVIC(Nested Vectored Interrupt Controller),通过HAL_NVIC_SetPriority()HAL_NVIC_EnableIRQ()函数设置CAN中断的优先级并启用中断。
    • 编写CAN中断服务函数(ISR),在中断服务函数中处理相应的中断事件,例如检查发送完成状态、读取接收数据等。

整个过程按照上述顺序进行,确保CAN控制器正确配置和初始化后,便可以进行有效的CAN通信。

六、编程实战

在这里插入图片描述
在这里插入图片描述

can.c

#include "./BSP/CAN/can.h"
#include "./BSP/LED/led.h"
#include "./SYSTEM/delay/delay.h"
#include "./SYSTEM/usart/usart.h"

CAN_HandleTypeDef   g_can1_handler;     /* CAN控制句柄 */
CAN_TxHeaderTypeDef g_can1_txheader;    /* CAN发送结构体 */
CAN_RxHeaderTypeDef g_can1_rxheader;    /* CAN接收结构体 */

/* STM32F103,设TS1=8、TS2=7、BRP=3,波特率 = 36000 / [( 9 + 8 + 1 ) * 4] = 500Kbps */
void can_init(void)
{
    g_can1_handler.Instance  = CAN1;
    g_can1_handler.Init.Mode = CAN_MODE_LOOPBACK;        /* 工作模式设置 环回模式:自发自收 */

    /* 波特率相关 */
    g_can1_handler.Init.Prescaler        = 4;            /* 分频系数 */
    g_can1_handler.Init.TimeSeg1         = CAN_BS1_9TQ;  /* 时间段1 */
    g_can1_handler.Init.TimeSeg2         = CAN_BS2_8TQ;  /* 时间段2 */
    g_can1_handler.Init.SyncJumpWidth    = CAN_SJW_1TQ;  /* 重新同步跳跃宽度 */
    
    /* CAN功能设置 */
    g_can1_handler.Init.AutoBusOff           = DISABLE;  /* 禁止自动离线管理 */
    g_can1_handler.Init.AutoRetransmission   = DISABLE;  /* 禁止自动重发 */
    g_can1_handler.Init.AutoWakeUp           = DISABLE;  /* 禁止自动唤醒 */
    g_can1_handler.Init.ReceiveFifoLocked    = DISABLE;  /* 禁止接收FIFO锁定 */
    g_can1_handler.Init.TimeTriggeredMode    = DISABLE;  /* 禁止时间触发通信模式 */
    g_can1_handler.Init.TransmitFifoPriority = DISABLE;  /* 禁止发送FIFO优先级 */
    
    HAL_CAN_Init(&g_can1_handler);
    
    CAN_FilterTypeDef can_filterconfig;
    /* 过滤器是接收所有报文,不筛选 */
    can_filterconfig.FilterMode = CAN_FILTERMODE_IDMASK;
    can_filterconfig.FilterScale = CAN_FILTERSCALE_32BIT;
    
    can_filterconfig.FilterIdHigh = 0;
    can_filterconfig.FilterIdLow = 0;
    can_filterconfig.FilterMaskIdHigh = 0;
    can_filterconfig.FilterMaskIdLow = 0;
    
    can_filterconfig.FilterBank = 0;
    can_filterconfig.FilterFIFOAssignment = CAN_FilterFIFO0;
    can_filterconfig.FilterActivation = CAN_FILTER_ENABLE;
    can_filterconfig.SlaveStartFilterBank = 14;
    HAL_CAN_ConfigFilter(&g_can1_handler, &can_filterconfig);
    
    HAL_CAN_Start(&g_can1_handler);
}

void HAL_CAN_MspInit(CAN_HandleTypeDef *hcan)
{
    /* 使能时钟 */
    CAN_TX_GPIO_CLK_ENABLE();       /* CAN_TX脚时钟使能 */
    CAN_RX_GPIO_CLK_ENABLE();       /* CAN_RX脚时钟使能 */
    __HAL_RCC_CAN1_CLK_ENABLE();    /* 使能CAN1时钟 */
    
    GPIO_InitTypeDef gpio_init_struct;
    
    gpio_init_struct.Pin    = CAN_TX_GPIO_PIN;          /* 配置CAN的发送引脚 */
    gpio_init_struct.Mode   = GPIO_MODE_AF_PP;          /* 复用推挽输出 */
    gpio_init_struct.Pull   = GPIO_PULLUP;              /* 上拉 */
    gpio_init_struct.Speed  = GPIO_SPEED_FREQ_HIGH;     /* 高速 */
    HAL_GPIO_Init(CAN_TX_GPIO_PORT, &gpio_init_struct); /* 初始化CAN TX引脚 */

    gpio_init_struct.Pin    = CAN_RX_GPIO_PIN;          /* 配置CAN的接收引脚 */
    gpio_init_struct.Mode   = GPIO_MODE_AF_INPUT;       /* 复用输入 */
    HAL_GPIO_Init(CAN_RX_GPIO_PORT, &gpio_init_struct); /* 初始化CAN RX引脚 */
}

/* 发送消息数据函数 */
void can_send_message(uint32_t id, uint8_t *buf, uint8_t len)
{
    uint32_t tx_mail = CAN_TX_MAILBOX0;
    
    g_can1_txheader.ExtId = id;
    g_can1_txheader.DLC = len;
    g_can1_txheader.IDE = CAN_ID_EXT;
    g_can1_txheader.RTR = CAN_RTR_DATA;
    
    HAL_CAN_AddTxMessage(&g_can1_handler, &g_can1_txheader, buf, &tx_mail);
    
    while(HAL_CAN_GetTxMailboxesFreeLevel(&g_can1_handler) != 3);
}

/* 接收数据函数 */
uint8_t can_receive_message(uint8_t *buf)
{
    if (HAL_CAN_GetRxFifoFillLevel(&g_can1_handler, CAN_RX_FIFO0) == 0)
    {
        return 0;
    }
    
    HAL_CAN_GetRxMessage(&g_can1_handler, CAN_RX_FIFO0, &g_can1_rxheader, buf);
    
    return g_can1_rxheader.DLC;
}

can.h

#ifndef __CAN_H
#define __CAN_H

#include "./SYSTEM/sys/sys.h"


/******************************************************************************************/
/* CAN 引脚 定义 */
#define CAN_RX_GPIO_PORT                GPIOA
#define CAN_RX_GPIO_PIN                 GPIO_PIN_11
#define CAN_RX_GPIO_CLK_ENABLE()        do{ __HAL_RCC_GPIOA_CLK_ENABLE(); }while(0)   /* PA口时钟使能 */

#define CAN_TX_GPIO_PORT                GPIOA
#define CAN_TX_GPIO_PIN                 GPIO_PIN_12
#define CAN_TX_GPIO_CLK_ENABLE()        do{ __HAL_RCC_GPIOA_CLK_ENABLE(); }while(0)   /* PA口时钟使能 */

/* 函数声明 */
void can_init(void);											/* 初始化CAN */
uint8_t can_receive_message(uint8_t *buf);						/* 接收数据函数 */
void can_send_message(uint32_t id, uint8_t *buf, uint8_t len);	/* 发送消息数据函数 */

#endif

main.c

#include "./SYSTEM/sys/sys.h"      // 引入系统初始化头文件
#include "./SYSTEM/usart/usart.h"  // 引入USART串口通信头文件
#include "./SYSTEM/delay/delay.h"  // 引入延时函数头文件
#include "./USMART/usmart.h"       // 引入USMART智能调试助手头文件
#include "./BSP/LED/led.h"        // 引入LED驱动控制头文件
#include "./BSP/LCD/lcd.h"        // 引入LCD驱动控制头文件(此处可能并未使用)
#include "./BSP/KEY/key.h"        // 引入按键驱动控制头文件
#include "./BSP/CAN/can.h"        // 引入CAN驱动控制头文件

int main(void)
{
    uint8_t key;                  // 定义按键扫描变量
    uint8_t i = 0, t = 0;         // 循环计数变量
    uint8_t cnt = 0;              // 计数变量,用于在发送数据时增加附加值
    uint8_t canbuf[8];            // 定义发送的CAN数据缓冲区
    uint8_t rec_len = 0;          // 接收CAN数据的长度变量
    uint8_t rec_buf[8];           // 定义接收的CAN数据缓冲区

    // 系统初始化
    HAL_Init();                       // 初始化HAL库
    sys_stm32_clock_init(RCC_PLL_MUL9); // 设置系统时钟为72MHz
    delay_init(72);                   // 根据系统时钟72MHz初始化延时函数
    usart_init(115200);               // 初始化USART串口通信,设置波特率为115200
    usmart_dev.init(72);              // 初始化USMART智能调试助手
    led_init();                       // 初始化LED灯
    key_init();                       // 初始化按键
    can_init();                       // 初始化CAN控制器

    while (1)
    {
        // 扫描按键
        key = key_scan(0);

        // 如果检测到KEY0按键按下
        if (key == KEY0_PRES)
        {
            // 为发送缓冲区赋值,每次发送时加上cnt的值
            for (i = 0; i < 8; i++)
            {
                canbuf[i] = i + cnt;
            }

            // 使用自定义函数发送CAN消息,ID为0x12345678,数据缓冲区为canbuf,长度为8
            can_send_message(0x12345678, canbuf, 8);
        }

        // 获取接收到的CAN数据长度
        rec_len = can_receive_message(rec_buf);

        // 如果接收到数据
        if (rec_len)
        {
            // 打印接收到的CAN数据
            for (uint8_t x = 0; x < rec_len; x++)
            {
                printf("%x ", rec_buf[x]);
            }
            printf("\r\n"); // 输出换行符
        }

        // 延时10ms,递增计数器t
        t++;
        delay_ms(10);

        // 如果t达到20(即每隔200ms)
        if (t == 20)
        {
            // 切换LED0的状态(亮灭交替)
            LED0_TOGGLE();
            // 重置计数器t,并递增cnt
            t = 0;
            cnt++;
        }
    }
}

这段代码是一个典型的STM32嵌入式系统主程序,初始化了系统时钟、USART、USMART调试助手、LED、按键以及CAN控制器。在主循环中,程序不断地扫描按键状态,如果检测到按键按下,则发送一组带有计数增量的数据到CAN总线。同时,它也在不断接收CAN总线上的数据并打印出来。另外,每200ms通过切换LED状态提示系统正在运行,并更新发送数据时附加的计数值。

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

CAN通信源码

在这里插入图片描述

can.c

#include "./BSP/CAN/can.h"
#include "./BSP/LED/led.h"
#include "./SYSTEM/delay/delay.h"
#include "./SYSTEM/usart/usart.h"

CAN_HandleTypeDef   g_canx_handler;     /* CANx句柄 */
CAN_TxHeaderTypeDef g_canx_txheader;    /* 发送参数句柄 */
CAN_RxHeaderTypeDef g_canx_rxheader;    /* 接收参数句柄 */

/**
 * @brief       CAN初始化
 * @param       tsjw    : 重新同步跳跃时间单元.范围: 1~3;
 * @param       tbs2    : 时间段2的时间单元.范围: 1~8;
 * @param       tbs1    : 时间段1的时间单元.范围: 1~16;
 * @param       brp     : 波特率分频器.范围: 1~1024;
 *   @note      以上4个参数, 在函数内部会减1, 所以, 任何一个参数都不能等于0
 *              CAN挂在APB1上面, 其输入时钟频率为 Fpclk1 = PCLK1 = 36Mhz
 *              tq     = brp * tpclk1;
 *              波特率 = Fpclk1 / ((tbs1 + tbs2 + 1) * brp);
 *              我们设置 can_init(1, 8, 9, 4, 1), 则CAN波特率为:
 *              36M / ((8 + 9 + 1) * 4) = 500Kbps
 *
 * @param       mode    : CAN_MODE_NORMAL,  普通模式;
                          CAN_MODE_LOOPBACK,回环模式;
 * @retval      0,  初始化成功; 其他, 初始化失败;
 */
uint8_t can_init(uint32_t tsjw, uint32_t tbs2, uint32_t tbs1, uint16_t brp, uint32_t mode)
{
  g_canx_handler.Instance = CAN1;
  g_canx_handler.Init.Prescaler = brp;                /* 分频系数(Fdiv)为brp+1 */
  g_canx_handler.Init.Mode = mode;                    /* 模式设置 */
  g_canx_handler.Init.SyncJumpWidth = tsjw;           /* 重新同步跳跃宽度(Tsjw)为tsjw+1个时间单位 CAN_SJW_1TQ~CAN_SJW_4TQ */
  g_canx_handler.Init.TimeSeg1 = tbs1;                /* tbs1范围CAN_BS1_1TQ~CAN_BS1_16TQ */
  g_canx_handler.Init.TimeSeg2 = tbs2;                /* tbs2范围CAN_BS2_1TQ~CAN_BS2_8TQ */
  g_canx_handler.Init.TimeTriggeredMode = DISABLE;    /* 非时间触发通信模式 */
  g_canx_handler.Init.AutoBusOff = DISABLE;           /* 软件自动离线管理 */
  g_canx_handler.Init.AutoWakeUp = DISABLE;           /* 睡眠模式通过软件唤醒(清除CAN->MCR的SLEEP位) */
  g_canx_handler.Init.AutoRetransmission = ENABLE;    /* 禁止报文自动传送 */
  g_canx_handler.Init.ReceiveFifoLocked = DISABLE;    /* 报文不锁定,新的覆盖旧的 */
  g_canx_handler.Init.TransmitFifoPriority = DISABLE; /* 优先级由报文标识符决定 */
  if (HAL_CAN_Init(&g_canx_handler) != HAL_OK)
  {
    return 1;
  }

#if CAN_RX0_INT_ENABLE

  /* 使用中断接收 */
  __HAL_CAN_ENABLE_IT(&g_canx_handler, CAN_IT_RX_FIFO0_MSG_PENDING); /* FIFO0消息挂号中断允许 */
  HAL_NVIC_EnableIRQ(USB_LP_CAN1_RX0_IRQn);                          /* 使能CAN中断 */
  HAL_NVIC_SetPriority(USB_LP_CAN1_RX0_IRQn, 1, 0);                  /* 抢占优先级1,子优先级0 */
#endif

  CAN_FilterTypeDef sFilterConfig;

  /* 配置CAN过滤器 */
  sFilterConfig.FilterBank = 0;                             /* 过滤器0 */
  sFilterConfig.FilterMode = CAN_FILTERMODE_IDMASK;
  sFilterConfig.FilterScale = CAN_FILTERSCALE_32BIT;
  sFilterConfig.FilterIdHigh = 0x0000;                      /* 32位ID */
  sFilterConfig.FilterIdLow = 0x0000;
  sFilterConfig.FilterMaskIdHigh = 0x0000;                  /* 32位MASK */
  sFilterConfig.FilterMaskIdLow = 0x0000;
  sFilterConfig.FilterFIFOAssignment = CAN_FILTER_FIFO0;    /* 过滤器0关联到FIFO0 */
  sFilterConfig.FilterActivation = CAN_FILTER_ENABLE;       /* 激活滤波器0 */
  sFilterConfig.SlaveStartFilterBank = 14;

  /* 过滤器配置 */
  if (HAL_CAN_ConfigFilter(&g_canx_handler, &sFilterConfig) != HAL_OK)
  {
    return 2;
  }

  /* 启动CAN外围设备 */
  if (HAL_CAN_Start(&g_canx_handler) != HAL_OK)
  {
    return 3;
  }


  return 0;
}

/**
 * @brief       CAN底层驱动,引脚配置,时钟配置,中断配置
                此函数会被HAL_CAN_Init()调用
 * @param       hcan:CAN句柄
 * @retval      无
 */
void HAL_CAN_MspInit(CAN_HandleTypeDef *hcan)
{
  if (CAN1 == hcan->Instance)
  {
    CAN_RX_GPIO_CLK_ENABLE();       /* CAN_RX脚时钟使能 */
    CAN_TX_GPIO_CLK_ENABLE();       /* CAN_TX脚时钟使能 */
    __HAL_RCC_CAN1_CLK_ENABLE();    /* 使能CAN1时钟 */

    GPIO_InitTypeDef gpio_initure;

    gpio_initure.Pin = CAN_TX_GPIO_PIN;
    gpio_initure.Mode = GPIO_MODE_AF_PP;
    gpio_initure.Pull = GPIO_PULLUP;
    gpio_initure.Speed = GPIO_SPEED_FREQ_HIGH;
    HAL_GPIO_Init(CAN_TX_GPIO_PORT, &gpio_initure); /* CAN_TX脚 模式设置 */

    gpio_initure.Pin = CAN_RX_GPIO_PIN;
    gpio_initure.Mode = GPIO_MODE_AF_INPUT;
    HAL_GPIO_Init(CAN_RX_GPIO_PORT, &gpio_initure); /* CAN_RX脚 必须设置成输入模式 */
  }
}

#if CAN_RX0_INT_ENABLE /* 使能RX0中断 */

/**
 * @brief       CAN RX0 中断服务函数
 *   @note      处理CAN FIFO0的接收中断
 * @param       无
 * @retval      无
 */
void USB_LP_CAN1_RX0_IRQHandler(void)
{
  uint8_t rxbuf[8];
  uint32_t id;
  can_receive_msg(id, rxbuf);
  printf("id:%d\r\n", g_canx_rxheader.StdId);
  printf("ide:%d\r\n", g_canx_rxheader.IDE);
  printf("rtr:%d\r\n", g_canx_rxheader.RTR);
  printf("len:%d\r\n", g_canx_rxheader.DLC);

  printf("rxbuf[0]:%d\r\n", rxbuf[0]);
  printf("rxbuf[1]:%d\r\n", rxbuf[1]);
  printf("rxbuf[2]:%d\r\n", rxbuf[2]);
  printf("rxbuf[3]:%d\r\n", rxbuf[3]);
  printf("rxbuf[4]:%d\r\n", rxbuf[4]);
  printf("rxbuf[5]:%d\r\n", rxbuf[5]);
  printf("rxbuf[6]:%d\r\n", rxbuf[6]);
  printf("rxbuf[7]:%d\r\n", rxbuf[7]);
}

#endif

/**
 * @brief       CAN 发送一组数据
 *   @note      发送格式固定为: 标准ID, 数据帧
 * @param       id      : 标准ID(11位)
 * @retval      发送状态 0, 成功; 1, 失败;
 */
uint8_t can_send_msg(uint32_t id, uint8_t *msg, uint8_t len)
{
  uint32_t TxMailbox = CAN_TX_MAILBOX0;
  g_canx_txheader.StdId = id;         /* 标准标识符 */
//  g_canx_txheader.ExtId = id;         /* 扩展标识符(29位) */
  g_canx_txheader.IDE = CAN_ID_STD;   /* 使用标准帧 */
  g_canx_txheader.RTR = CAN_RTR_DATA; /* 数据帧 */
  g_canx_txheader.DLC = len;

  if (HAL_CAN_AddTxMessage(&g_canx_handler, &g_canx_txheader, msg, &TxMailbox) != HAL_OK) /* 发送消息 */
  {
    return 1;
  }
  while (HAL_CAN_GetTxMailboxesFreeLevel(&g_canx_handler) != 3); /* 等待发送完成,所有邮箱为空 */
  return 0;
}

/**
 * @brief       CAN 接收数据查询
 *   @note      接收数据格式固定为: 标准ID, 数据帧
 * @param       id      : 要查询的 标准ID(11位)
 * @param       buf     : 数据缓存区
 * @retval      接收结果
 *   @arg       0   , 无数据被接收到;
 *   @arg       其他, 接收的数据长度
 */
uint8_t can_receive_msg(uint32_t id, uint8_t *buf)
{
  if (HAL_CAN_GetRxFifoFillLevel(&g_canx_handler, CAN_RX_FIFO0) == 0)     /* 没有接收到数据 */
  {
    return 0;
  }

  if (HAL_CAN_GetRxMessage(&g_canx_handler, CAN_RX_FIFO0, &g_canx_rxheader, buf) != HAL_OK)  /* 读取数据 */
  {
    return 0;
  }
  
  if (g_canx_rxheader.StdId!= id || g_canx_rxheader.IDE != CAN_ID_STD || g_canx_rxheader.RTR != CAN_RTR_DATA)       /* 接收到的ID不对 / 不是标准帧 / 不是数据帧 */
  {
    return 0;
  }

  return g_canx_rxheader.DLC;
}

can.h

#ifndef __CAN_H
#define __CAN_H

#include "./SYSTEM/sys/sys.h"


/******************************************************************************************/
/* CAN 引脚 定义 */

#define CAN_RX_GPIO_PORT                GPIOA
#define CAN_RX_GPIO_PIN                 GPIO_PIN_11
#define CAN_RX_GPIO_CLK_ENABLE()        do{ __HAL_RCC_GPIOA_CLK_ENABLE(); }while(0)   /* PA口时钟使能 */

#define CAN_TX_GPIO_PORT                GPIOA
#define CAN_TX_GPIO_PIN                 GPIO_PIN_12
#define CAN_TX_GPIO_CLK_ENABLE()        do{ __HAL_RCC_GPIOA_CLK_ENABLE(); }while(0)   /* PA口时钟使能 */

/******************************************************************************************/

/* CAN接收RX0中断使能 */
#define CAN_RX0_INT_ENABLE      0               /* 0,不使能; 1,使能; */

/* 对外接口函数 */
uint8_t can_receive_msg(uint32_t id, uint8_t *buf);             /* CAN接收数据, 查询 */
uint8_t can_send_msg(uint32_t id, uint8_t *msg, uint8_t len);   /* CAN发送数据 */
uint8_t can_init(uint32_t tsjw,uint32_t tbs2,uint32_t tbs1,uint16_t brp,uint32_t mode); /* CAN初始化 */

#endif

main.c

#include "./SYSTEM/sys/sys.h"
#include "./SYSTEM/usart/usart.h"
#include "./SYSTEM/delay/delay.h"
#include "./USMART/usmart.h"
#include "./BSP/LED/led.h"
#include "./BSP/LCD/lcd.h"
#include "./BSP/KEY/key.h"
#include "./BSP/CAN/can.h"


int main(void)
{
    uint8_t key;
    uint8_t i = 0, t = 0;
    uint8_t cnt = 0;
    uint8_t canbuf[8];
    uint8_t rxlen = 0;
    uint8_t res;
    uint8_t mode = 1; /* CAN工作模式: 0,普通模式; 1,环回模式 */

    HAL_Init();                                                            /* 初始化HAL库 */
    sys_stm32_clock_init(RCC_PLL_MUL9);                                    /* 设置时钟, 72Mhz */
    delay_init(72);                                                        /* 延时初始化 */
    usart_init(115200);                                                    /* 串口初始化为115200 */
    usmart_dev.init(72);                                                   /* 初始化USMART */
    led_init();                                                            /* 初始化LED */
    lcd_init();                                                            /* 初始化LCD */
    key_init();                                                            /* 初始化按键 */
    can_init(CAN_SJW_1TQ, CAN_BS2_8TQ, CAN_BS1_9TQ, 4, CAN_MODE_LOOPBACK); /* CAN初始化, 环回模式, 波特率500Kbps */

    lcd_show_string(30, 50, 200, 16, 16, "STM32", RED);
    lcd_show_string(30, 70, 200, 16, 16, "CAN TEST", RED);
    lcd_show_string(30, 90, 200, 16, 16, "ATOM@ALIENTEK", RED);
    lcd_show_string(30, 110, 200, 16, 16, "LoopBack Mode", RED);
    lcd_show_string(30, 130, 200, 16, 16, "KEY0:Send KEK_UP:Mode", RED); /* 显示提示信息 */

    lcd_show_string(30, 150, 200, 16, 16, "Count:", RED);        /* 显示当前计数值 */
    lcd_show_string(30, 170, 200, 16, 16, "Send Data:", RED);    /* 提示发送的数据 */
    lcd_show_string(30, 230, 200, 16, 16, "Receive Data:", RED); /* 提示接收到的数据 */

    while (1)
    {
        key = key_scan(0);

        if (key == KEY0_PRES) /* KEY0按下,发送一次数据 */
        {
            for (i = 0; i < 8; i++)
            {
                canbuf[i] = cnt + i; /* 填充发送缓冲区 */

                if (i < 4)
                {
                    lcd_show_xnum(30 + i * 32, 190, canbuf[i], 3, 16, 0X80, BLUE); /* 显示数据 */
                }
                else
                {
                    lcd_show_xnum(30 + (i - 4) * 32, 210, canbuf[i], 3, 16, 0X80, BLUE); /* 显示数据 */
                }
            }

            res = can_send_msg(0X12, canbuf, 8); /* ID = 0X12, 发送8个字节 */

            if (res)
            {
                lcd_show_string(30 + 80, 170, 200, 16, 16, "Failed", BLUE); /* 提示发送失败 */
            }
            else
            {
                lcd_show_string(30 + 80, 170, 200, 16, 16, "OK    ", BLUE); /* 提示发送成功 */
            }
        }
        else if (key == WKUP_PRES) /* WK_UP按下,改变CAN的工作模式 */
        {
            mode = !mode;

            if (mode == 0)  /* 普通模式,需要2个开发板 */
            {
                can_init(CAN_SJW_1TQ, CAN_BS2_8TQ, CAN_BS1_9TQ, 4, CAN_MODE_NORMAL);    /* CAN普通模式初始化, 普通模式, 波特率500Kbps */
                lcd_show_string(30, 110, 200, 16, 16, "Nnormal Mode ", RED);
            }
            else            /* 回环模式,一个开发板就可以测试了. */
            {
                can_init(CAN_SJW_1TQ, CAN_BS2_8TQ, CAN_BS1_9TQ, 4, CAN_MODE_LOOPBACK);  /* CAN普通模式初始化, 回环模式, 波特率500Kbps */
                lcd_show_string(30, 110, 200, 16, 16, "LoopBack Mode", RED);
            }
        }

        rxlen = can_receive_msg(0X12, canbuf); /* CAN ID = 0X12, 接收数据查询 */

        if (rxlen) /* 接收到有数据 */
        {
            lcd_fill(30, 270, 130, 310, WHITE); /* 清除之前的显示 */

            for (i = 0; i < rxlen; i++)
            {
                if (i < 4)
                {
                    lcd_show_xnum(30 + i * 32, 250, canbuf[i], 3, 16, 0X80, BLUE); /* 显示数据 */
                }
                else
                {
                    lcd_show_xnum(30 + (i - 4) * 32, 270, canbuf[i], 3, 16, 0X80, BLUE); /* 显示数据 */
                }
            }
        }

        t++;
        delay_ms(10);

        if (t == 20)
        {
            LED0_TOGGLE(); /* 提示系统正在运行 */
            t = 0;
            cnt++;
            lcd_show_xnum(30 + 48, 150, cnt, 3, 16, 0X80, BLUE); /* 显示数据 */
        }
    }
}

在这里插入图片描述
在这里插入图片描述

过滤器

在这里插入图片描述
在这里插入图片描述

七、总结

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

  • 14
    点赞
  • 52
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 5
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

咖喱年糕

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值