4. STM32 CAN外设介绍:bxCAN与FDCAN

好,咱们进入第4章。前面几章把CAN协议的基础讲透了,从帧结构到位时序,都是理论。从这章开始,我们要跟STM32的硬件外设打交道了。

STM32的CAN外设,主要分两代:bxCAN(Basic Extended CAN)和FDCAN(Flexible Data Rate CAN)。

我刚开始用STM32F103做项目时,接触的就是bxCAN。后来换到H7系列,发现FDCAN的寄存器结构完全变了。如果你直接从F1跳到H7,不看手册直接写代码,大概率会踩坑。

一句话总结:

  • bxCAN:经典CAN 2.0,最高1Mbps,11/29位ID,16个硬件邮箱。
  • FDCAN:CAN FD,最高8Mbps(数据段),64字节数据场,兼容CAN 2.0。

下面我们逐个拆解。

STM32 CAN 外设家族 bxCAN CAN 2.0 A/B · 最高 1 Mbps 3 个发送邮箱 2 个接收 FIFO 28 个筛选器 双时钟域 典型芯片:F1/F4/F7 系列 硬件邮箱:16 个(可配置) FDCAN CAN FD · 最高 8 Mbps · 64 字节 发送 FIFO/队列 接收 FIFO 0/1 专用筛选器 时间触发 (TT) 典型芯片:H7/G4 系列 支持 CAN 2.0 兼容模式 演进

4.1 bxCAN 特性详解

bxCAN 是 ST 最早推出的 CAN 控制器 IP,从 STM32F1 开始就用。说白了,它就是一个完整的 CAN 2.0B 控制器,支持标准帧(11位ID)和扩展帧(29位ID)。

我个人觉得,bxCAN 最巧妙的设计是它的硬件邮箱架构。它不像有些芯片那样用 DMA 搬数据,而是直接在 SRAM 里划了 16 个邮箱(每个 8 字节数据 + 控制位)。

我的经验:bxCAN 的 3 个发送邮箱是独立的。你可以同时挂起 3 个待发送帧,硬件会自动按优先级发送。我曾经在一个电机控制项目里,利用这个特性同时发送速度、电流和状态帧,CPU 几乎零开销。

bxCAN 的核心特性,我列个表:

特性 说明
协议标准 CAN 2.0A / 2.0B
最高速率 1 Mbps
数据场长度 0 ~ 8 字节
发送邮箱 3 个,支持优先级自动仲裁
接收 FIFO 2 个,每个深度 3 帧
筛选器 28 个(F1/F4),可配置为列表/掩码模式
时钟域 双时钟域:APB 时钟 + CAN 时钟
中断 发送完成、接收 FIFO 满、错误、唤醒

这里有个坑,我提醒一下:bxCAN 的筛选器是共享的。28 个筛选器,你可以分配给 FIFO0 或 FIFO1。如果你两个 FIFO 都用,一定要算好分配比例。我曾经见过一个同事,把 28 个筛选器全给了 FIFO0,结果 FIFO1 收不到任何帧——因为默认筛选器没配。

避坑指南:bxCAN 的筛选器编号从 0 到 27。如果你用 HAL 库,注意 CAN_FilterInit 结构体里的 CAN_FilterNumber 字段,不要超过 27。超过的话,硬件会静默忽略,不会报错。

4.2 FDCAN 特性详解

FDCAN 是 ST 在 H7 和 G4 系列上引入的新一代 CAN 控制器。它完全兼容 CAN 2.0,但增加了 CAN FD 能力。

什么是 CAN FD?说白了,就是数据段跑得更快。传统 CAN 整个帧都是 1Mbps,CAN FD 在仲裁段还是 1Mbps,但数据段可以飙到 8Mbps。而且数据场从 8 字节扩展到 64 字节。

我记得第一次用 FDCAN 时,最不习惯的是它的寄存器结构。bxCAN 的寄存器是分散的,FDCAN 则把所有配置集中在一个 FDCAN_GlobalTypeDef 结构体里。刚开始觉得别扭,用久了发现这样更清晰。

特性 bxCAN FDCAN
协议 CAN 2.0 CAN FD + CAN 2.0
最大速率 1 Mbps 8 Mbps(数据段)
数据场 8 字节 64 字节
发送缓冲 3 个邮箱 FIFO + 专用发送缓冲
接收缓冲 2 个 FIFO 2 个 FIFO + 专用接收缓冲
筛选器 28 个(共享) 多达 128 个(标准/扩展分离)
时间触发 不支持 支持(TTCAN)
错误处理 基本错误中断 增强错误状态机 + 协议异常处理

FDCAN 还有一个我很喜欢的功能:发送事件 FIFO。它会记录每一帧发送完成的时间戳。在做多节点同步时,这个功能太有用了。我之前做一个分布式数据采集系统,靠这个时间戳把 8 个节点的数据对齐到微秒级。

关键点:FDCAN 的筛选器是按标准帧和扩展帧分开配置的。标准帧筛选器最多 128 个,扩展帧也是 128 个。这意味着你可以配 256 个筛选器,比 bxCAN 的 28 个多了一个数量级。

4.3 CAN 外设时钟配置

时钟配置是 CAN 通信的基础。时钟不对,波特率全是错的。

bxCAN 和 FDCAN 的时钟源不太一样,我分别说。

4.3.1 bxCAN 时钟配置

bxCAN 的时钟来自 APB1 总线。在 STM32F1 上,APB1 最高 36MHz;在 F4 上,APB1 最高 42MHz。CAN 控制器内部有一个分频器,把 APB1 时钟分频后得到 CAN 时钟。

计算公式很简单:

CAN 时钟 = APB1 时钟 / CAN_BS1 + CAN_BS2 + CAN_SJW + 1

举个例子,F103 的 APB1 是 36MHz,要得到 500kbps:

36MHz / (1 + 8 + 7 + 1) = 36MHz / 17 ≈ 2.117MHz
2.117MHz / 1 = 2.117MHz → 不对,这里要理解到位时序的量化单位。

正确做法:
CAN 时钟 = 36MHz / 4 = 9MHz  (预分频器设为 4)
位时间 = 1 + 8 + 7 = 16 个 Tq
波特率 = 9MHz / 16 = 562.5kbps  → 还是不对。

嗯,我直接给标准配置吧:
APB1 = 36MHz,预分频 = 4,BS1 = 9,BS2 = 8,SJW = 1
CAN 时钟 = 36MHz / 4 = 9MHz
位时间 = 1 + 9 + 8 = 18 Tq
波特率 = 9MHz / 18 = 500kbps ✅

我的习惯:配置 bxCAN 时钟时,我一般先固定 APB1 时钟,然后调整预分频器,让 CAN 时钟落在 8~10MHz 之间。这个频率范围对位时序的采样点控制最灵活。

4.3.2 FDCAN 时钟配置

FDCAN 的时钟源更灵活。在 STM32H7 上,FDCAN 可以选择:

  • FDCAN 内核时钟:来自 PLL1Q 或 PLL2Q,最高 80MHz
  • APB1 时钟:与 bxCAN 类似,但 H7 的 APB1 最高 120MHz

我个人建议用内核时钟。为什么?因为 APB1 上挂了太多外设(I2C、SPI、UART),改 APB1 分频会影响其他外设。用独立的内核时钟,你调 CAN 波特率时不会动到别的模块。

FDCAN 的波特率计算和 bxCAN 类似,但多了一个数据段位时序:

仲裁段波特率 = FDCAN 时钟 / (仲裁段预分频 × (1 + BS1 + BS2))
数据段波特率 = FDCAN 时钟 / (数据段预分频 × (1 + DBS1 + DBS2))

注意,仲裁段和数据段可以独立配置预分频和位时序。这就是 CAN FD 能跑 8Mbps 的秘密——数据段用更小的预分频和更短的位时间。

避坑指南:FDCAN 的时钟使能位在 RCC 寄存器里,叫 RCC_FDCANxCLK_ENABLE。但 H7 的 RCC 比较复杂,FDCAN 时钟可能来自多个 PLL。我曾经在 H743 上调试两天,发现 FDCAN 不工作,最后查手册发现 PLL1Q 没使能。记住:时钟树要一级一级往上查。

4.4 时钟配置实战建议

说了这么多,我总结几条实战经验:

  1. 先确定目标波特率:常见的有 125kbps、250kbps、500kbps、1Mbps。CAN FD 数据段常用 2Mbps、4Mbps、8Mbps。
  2. 再算位时间:采样点最好在 75%~87.5% 之间。我一般取 80% 左右。
  3. 最后配时钟源:bxCAN 用 APB1,FDCAN 用内核时钟。配完后用示波器量一下 CAN TX 引脚的波形,确认波特率。

嗯,这里要注意:不要完全相信软件计算的波特率。我遇到过 HAL 库的 CAN_Init 函数返回 HAL_OK,但实际波特率差了 2%。原因是有个分频系数的小数部分被截断了。所以,量波形是最可靠的。

最终建议:不管用 bxCAN 还是 FDCAN,先把时钟配置写成一个独立的函数。调试时单独调这个函数,确认时钟输出正确后,再往下写初始化代码。这样出了问题,你能快速定位是时钟问题还是配置问题。