一文学会手写CAN通信,CUBEMX+STM32+HAL库
前言
CAN 通信原理、协议格式等内容网络资料丰富,本文几乎不会提及,重心将放在 CUBEMX 配置和 Keil 代码实现上。目的是提供一个通用模版,让读者能够根据数据手册,手写任意产品的 CAN 数据收发代码。
本文将默认读者已经粗略了解 CAN 总线相关硬件知识,若文中有不明白的名词,劳烦自行搜索。
本文 CUBEMX 配置基于 STM32H7 系列 FDCAN。
报文核心格式
CAN 报文有“帧(Frame)”的概念,一份报文分为数据帧、遥控帧、错误帧、过载帧、帧间空间。而在软件应用中,我们通常只需要关注 数据帧(Data frame):用于发送单元向接收单元传送数据的帧。
数据帧由 7 个段构成,如下图,分别为:帧起始、仲裁段、控制段、数据段、CRC段、ACK段、帧结束。

帧起始
表示一个数据帧的开始,1 个显性位。
仲裁段
写明报文 ID,确定发送的帧类型(数据帧 or 遥控帧)。
在标准格式帧和扩展格式帧中,仲裁段的长度不同:
- 标准格式:11 位 ID
- 扩展格式:29 位 ID
对于前 11 位 ID 相同的标准帧和扩展帧,标准帧优先级更高。
对于两种格式的帧,在应用中我们只需要记住他们的 ID 长度不同即可,其余内容几乎完全一致。
控制段
指示了数据段的长度。
数据段
实际要传输的数据,0~8 字节,从 MSB 至 LSB。
CRC段
校验段,检测数据在传输过程中是否出错。
ACK段
接收单元通过 ACK 段通知发送单元数据是否正确接收。
帧结束
表示一个数据帧的结束,7 个隐性位。
CUBEMX配置
起手丝滑小连招,新建工程、选芯片、配时钟树等常规流程,此处跳过。
Connectivity 中选择 FDCAN1,选中 Activated,进入配置界面。(FDCAN 包含了普通 CAN,看懂 FDCAN 就两个都懂了)
FDCAN参数详解

注:以下参数若标明
根据计算得出,则先不必纠结具体数值,后文会讲解如何计算。
-
Frame Format:
Classic mode,经典 CAN 模式(CAN2.0)。 -
Mode:Normal。
-
Auto Retransmission(自动重发):
Disable。根据需求,多个节点发生阻塞情况可能需要开启。 -
Transmit Pause(发送延时):
Disable。FDCAN 特有的选项,通常用不到。 -
Protocol Exception(协议异常处理):
Disable。常规应用一般用不到,Enable 也可。 -
Nominal Sync Jump Width(仲裁段同步跳转宽度):
根据计算得出。用于调整时钟同步的容差范围。 -
Data Prescaler(数据段预分频器):
根据计算得出。时钟分频。 -
Data Sync Jump Width(数据段同步跳转宽度):
根据计算得出。 -
Data Time Seg1、Data Time Seg2(数据段时间段):
根据计算得出。数据段采样点位置。 -
Message Ram Offset(消息 RAM 地址偏移):
根据计算得出。此 RAM 是所有 FDCAN 共用的,如果配置了多路 FDCAN,强烈建议设置此偏移参数。 -
Std Filters Nbr(标准格式过滤器数量):标准格式滤波器数量,用于在仲裁段过滤(11 位)ID。如果外设为标准格式 CAN,此参数至少为 1。
-
Ext Filters Nbr(扩展格式过滤器数量):扩展格式滤波器数量,用于在仲裁段过滤(29 位)ID。如果外设为扩展格式 CAN,此参数至少为 1。
-
Rx Fifo0 Elmts Nbr、Rx Fifo1 Elmts Nbr(接收 FIFO 的数量):这两个 FIFO 相互独立,如果只用一个,另一个可以直接填 0。具体需要的数量受外设数量、外设发送频率影响,可以尽量开大些。
-
Rx Fifo0 Elmt Size、Rx Fifo1 Elmt Size(每个 FIFO 的大小):通常
8 bytes data field即可,一个数据段最大也就 8 字节。注:Fifo Elmts Nbr 和 Fifo Elmt Size 两个参数的严格计算方式笔者理解不深,但通常不影响应用,这里贴上一篇笔者读过的文章《STM32H7系列FDCAN配置成经典CAN的经验教程和注意事项》,供参考。
-
Rx Buffers Nbr、Rx Buffer Size(接收缓冲区数量、大小):
0。如果用 FIFO 就不需要缓冲区,填 0 即可。 -
Tx Events Nbr、Tx Buffers Nbr(发送事件数量、发送缓冲区数量):
0。如果不使用 Events 和 Buffers 发送,填 0 即可。 -
Tx Fifo Queue Elmts Nbr(发送 FIFO 队列数量):和接收的类似。
-
Tx Fifo Queue Mode(发送 FIFO 队列模式):
FIFO mode。使用 FIFO 模式。 -
Tx Elmt Size(每个发送区的大小):通常
8 bytes data field即可。 -
Bit Timings Parameters(仲裁段时间参数):
根据计算得出。和前面数据段类似,经典 CAN 要求仲裁段和数据段波特率一致,填写一样的参数即可。
FDCAN参数计算
数据段/仲裁段波特率
包含 Data Prescaler、Data Sync Jump Width、Data Time Seg1、Data Time Seg2、Norminal Prescaler、Nominal Time Seg1、Nominal Time Seg2 参数。
先放一个公式:
这里了解即可,具体计算推荐使用 CAN 波特率计算器。
-
FDCANFrequency:FDCAN 外设时钟频率,单位 Hz。从时钟树中可以找到。

-
Prescaler:预分频器,分频后得到一个更小的频率。
-
TimeSeg1、TimeSeg2:采样点位置。
推荐一个好用的 CAN 波特率计算器:ZCANPRO 致远电子

下载后,打开 ZBaudcalTool.exe。(整个文件夹包含的工具很多,所以比较大,要是能把 ZBaudcalTool 单独拎出来就好了)

- 设备类型选择 CANFDNET_TCP 或 CANFDNET_UDP

- 填写时钟频率(上文的 FDCANFrequency)、想要的波特率、允许误差。

- 选择一组合适的参数,其中:
- BPR 应尽量小,以保证 Tq 尽可能小,减少误差;
- 采样点推荐范围:75%~87.5%;
- 波特率 <= 500 kbps时,推荐采样点87.5%;
- 波特率 > 500 kbps时,推荐采样点80%;
- 波特率 > 800 kbps时,推荐采样点75%。
- 注意 CUBEMX 中参数的可填写范围
这里我们选择了一组合适的参数,然后修改同步跳转宽度(Sync Jump Width),应尽量大且尽量与 TSEG2 一致。


然后就可以将这组参数填入 CUBEMX 中,填好后如下图。
消息RAM地址偏移(Message Ram Offset)
STM32H7 中,多路 CAN 是共用一个 RAM 的,如果配置不当,设备一多很容易出现丢包、无法通信的情况。

上图出自 ST 的《AN5348 Introduction to FDCAN peripherals for STM32 MCUs》
一路 CAN可以分配最大 2560 words 的 RAM 空间,当我们使用多路 CAN 时,应该给除第一路 CAN 外的其他 CAN 配置 RAM 偏移地址。
比如使用 3 路 CAN,最简单的方式就是:FDCAN1 偏移填 0,FDCAN2 偏移填 ,FDCAN3 偏移填 。填写时注意进制。
开启中断
进入 NVIC Settings,打开 FDCAN 中断

生成代码
配置好工程其他设置,就可以生成代码了。
Keil代码
定义CAN过滤器初始化函数
过滤器是 CAN 通信中非常重要的一部分,即使不需要过滤任何数据,也 必须 配置过滤器,不然无法正常通信。
1 | void fdcan_filter_init(FDCAN_HandleTypeDef *hfdcan){ |
FDCAN_FilterTypeDef 结构体的每个参数,在 stm32h7xx_hal_fdcan.h 文件中都有很详细地说明,在这里简单概括下。
IdType:帧格式。标准格式(FDCAN_STANDARD_ID)或扩展格式(FDCAN_EXTENDED_ID)。FilterIndex:过滤器编号,从 0 开始。标准帧最多 128 个,扩展帧最多 64 个。FilterType、FilterID1、FilterID2:第一个参数是过滤器类型,后两个参数与FilterType配合。类型共 4 种,FDCAN_FILTER_RANGE(接收 ID 在 ID1~ID2 的消息,包含 ID1、ID2)、FDCAN_FILTER_DUAL(只接收 ID 等于 ID1 或 ID2 的消息)、FDCAN_FILTER_MASK(ID2 作为掩码,对比消息 ID 和 ID1,只接收掩码位相等的消息)、FDCAN_FILTER_RANGE_NO_EIDM(接收 ID 在 ID1~ID2 的消息,包含 ID1、ID2,但不应用 EIDM mask)。
关于掩码模式,如果 ID2 设为 0,那么就没有掩码位,也就不会过滤任何消息。所以无需过滤消息的场景中,通常都会配置成掩码模式、ID1=0、ID2=0。
HAL_FDCAN_ConfigFilter(hfdcan,&fdcan_filter);:调用函数,应用过滤器配置。
HAL_FDCAN_ConfigGlobalFilter(FDCAN_HandleTypeDef *hfdcan, uint32_t NonMatchingStd, uint32_t NonMatchingExt, uint32_t RejectRemoteStd, uint32_t RejectRemoteExt):决定如何处理未与过滤器匹配的数据帧,以及全部遥控帧。
NonMatchingStd:如何处理未与过滤器匹配的标准帧。NonMatchingExt:如何处理未与过滤器匹配的扩展帧。
上述两个参数的可选项有FDCAN_ACCEPT_IN_RX_FIFO0、FDCAN_ACCEPT_IN_RX_FIFO1、FDCAN_REJECT,分别表示接收至 FIFO0、FIFO1 和拒绝。RejectRemoteStd:是否拒绝标准格式的遥控帧。RejectRemoteExt:是否拒绝扩展格式的遥控帧。
上述两个参数的可选项有FDCAN_FILTER_REMOTE、FDCAN_REJECT_REMOTE,分别表示放入过滤器或拒绝。
HAL_FDCAN_ConfigFifoWatermark(FDCAN_HandleTypeDef *hfdcan, uint32_t FIFO, uint32_t Watermark):配置 FIFO 的水位线,当 FIFO 中消息量达到水位线(阈值)时,触发中断。
CAN 初始化(main.c)
1 | /* USER CODE BEGIN 2 */ |
消息接收
重写中断回调函数
接收的消息触发水位线中断时,会进入此函数。
1 | void HAL_FDCAN_RxFifo0Callback(FDCAN_HandleTypeDef *hfdcan, uint32_t RxFifo0ITs){ |
定义消息处理函数
这里提供两种形式,都可以使用。
1 | void FDCAN1RCV(FDCAN_HandleTypeDef *hfdcan){ |
rx_data 这个数组就是数据段(Data Field)中的数据。
1 | void FDCAN1RCV(FDCAN_HandleTypeDef *hfdcan){ |
消息发送
1 | FDCAN_TxHeaderTypeDef fdcan_tx_header; |
发送函数的传入参数根据实际情况修改,定义之后在需要发送消息时调用即可。
结语
至此,配置、应用 CAN 通信的流程就全部结束了,望本篇文章能让你有所收获,若有表述错误,恳请指正。

