3. STM32 CAN FD 外设剖析:FDCAN 内核与消息 RAM
好,咱们进入正题。STM32 的 CAN FD 外设,和传统 CAN 2.0 的 bxCAN 完全是两码事。我第一次从 bxCAN 切到 FDCAN 时,看着寄存器手册差点懵了——结构完全不一样。说白了,FDCAN 是继承自 Bosch M_CAN 的内核,ST 只是把它封装进了自家芯片里。
你想想看,CAN FD 的数据段速率能飙到 8Mbps,传统 CAN 的 FIFO 结构根本扛不住。所以 FDCAN 引入了 消息 RAM 的概念,把缓冲区、过滤器、FIFO 全部映射到一块专用的 SRAM 里。这块 RAM 不在系统总线上,CPU 不能直接寻址,只能通过 FDCAN 的配置接口去读写。
核心要点:FDCAN 外设 = M_CAN 内核 + 消息 RAM + 专用配置接口。消息 RAM 是灵魂,所有 Tx/Rx 数据都在里面流转。
3.1 FDCAN 内核架构
FDCAN 的内核分三层:
- CAN 协议控制器层:负责物理层信号解析,处理位时序、同步、CRC 计算。这部分和传统 CAN 类似,但多了 FD 帧的 BRS 位和 EDL 位处理。
- 消息处理层:这是核心。它管理消息 RAM 的读写仲裁,决定哪个 Tx 缓冲区先发,哪个 Rx FIFO 满了要通知 CPU。
- 寄存器接口层:CPU 通过 APB 总线访问配置寄存器,设置波特率、过滤器、中断等。
我习惯把这三层想象成:协议层是收发器的大脑,消息层是快递分拣中心,寄存器接口层是前台窗口。CPU 只跟窗口打交道,分拣和收发由内部搞定。
3.2 消息 RAM 配置
消息 RAM 的大小因芯片型号而异。以 STM32G4 为例,FDCAN 的消息 RAM 是 2.5KB(2560 字节)。这块 RAM 被划分为多个区域:
| 区域 | 偏移地址 | 大小 | 说明 |
|---|---|---|---|
| Rx FIFO 0 | 0x000 | 可配置 | 接收标准/扩展帧,支持 FIFO 溢出处理 |
| Rx FIFO 1 | 可配置 | 可配置 | 高优先级接收 FIFO,通常用于紧急消息 |
| Rx 专用缓冲区 | 可配置 | 可配置 | 每个 ID 固定一个槽位,无覆盖风险 |
| Tx 事件 FIFO | 可配置 | 可配置 | 记录已发送帧的时间戳和状态 |
| Tx 缓冲区 | 可配置 | 可配置 | 最多 32 个 Tx 缓冲,支持优先级调度 |
| 标准/扩展过滤器 | 可配置 | 可配置 | 最多 128 个过滤元素 |
我的经验:配置消息 RAM 时,一定要先算好总大小。我曾经在项目里把 Rx FIFO 配得太大,导致 Tx 缓冲区不够用,结果发送队列堵死。建议先画个内存布局图,再填寄存器。
3.3 Tx/Rx FIFO 与专用缓冲区
FDCAN 提供了两种接收方式:FIFO 模式和专用缓冲区模式。
Rx FIFO 是典型的先进先出队列。每个 FIFO 深度可配(1~64 个消息对象)。适合不确定 ID 的广播消息。但有个坑:如果 CPU 来不及读,FIFO 会溢出,旧消息被新消息覆盖。
Rx 专用缓冲区 则是一对一的。每个缓冲区绑定一个特定的 CAN ID。比如你只关心 ID=0x123 的报文,就给它分配一个专用槽位。这样永远不会被其他 ID 挤掉。
我个人的习惯是:
- 关键控制报文(比如刹车、转向)用 专用缓冲区,确保不丢帧。
- 诊断、状态、日志类报文用 Rx FIFO 0,丢了也不致命。
- 高优先级报警用 Rx FIFO 1,配合中断立即处理。
Tx 方面,FDCAN 提供了最多 32 个 Tx 缓冲区。每个缓冲区可以配置为:
- 专用 Tx 缓冲区:固定给某个 ID 使用,适合周期性发送。
- Tx FIFO:多个消息排队发送,适合事件触发。
- Tx 队列:按优先级排序发送,适合混合场景。
注意:Tx 缓冲区总数不能超过 32。如果你配了 10 个专用 Tx 缓冲,那 Tx FIFO 最多只能再配 22 个。我曾经在调试时配了 20 个专用缓冲 + 20 个 FIFO,结果 FDCAN 直接罢工——因为总数超了 32。
3.4 过滤器配置:标准帧与扩展帧
FDCAN 的过滤器比传统 CAN 灵活得多。它支持两种过滤模式:
- 经典过滤:和 bxCAN 类似,用 ID + 掩码匹配。
- 范围过滤:指定一个 ID 范围,落在区间内的都接收。
每个过滤元素可以配置为:
| 过滤类型 | 标准帧 (11-bit ID) | 扩展帧 (29-bit ID) |
|---|---|---|
| 经典过滤 | ID + 掩码,匹配 11 位 | ID + 掩码,匹配 29 位 |
| 范围过滤 | ID1 ~ ID2,闭区间 | ID1 ~ ID2,闭区间 |
| 专用 ID 过滤 | 只接收一个特定 ID | 只接收一个特定 ID |
| 拒绝过滤 | 排除某个 ID 或范围 | 排除某个 ID 或范围 |
配置过滤器时,有个细节容易忽略:标准帧和扩展帧的过滤元素是分开的。你在标准帧过滤列表里配了 ID=0x123,它不会影响扩展帧 ID=0x00000123 的接收。两者独立。
避坑指南:我曾经在项目里同时接收标准帧和扩展帧,结果发现扩展帧的 ID 被截断了——因为我只配了标准帧过滤器,扩展帧过滤器默认全拒绝。后来在 FDCAN 的全局配置里打开了「自动拒绝未过滤帧」选项,才解决问题。
3.5 配置示例:初始化 FDCAN 过滤器
下面是一个实际配置片段,基于 STM32G4 HAL 库。我习惯把过滤器配置放在 CAN 初始化之后:
/* 配置标准帧过滤器:只接收 ID=0x321 和 ID=0x456 */
FDCAN_FilterTypeDef sFilterConfig;
sFilterConfig.IdType = FDCAN_STANDARD_ID;
sFilterConfig.FilterIndex = 0;
sFilterConfig.FilterType = FDCAN_FILTER_DUAL;
sFilterConfig.FilterConfig = FDCAN_FILTER_TO_RXFIFO0;
sFilterConfig.FilterID1 = 0x321;
sFilterConfig.FilterID2 = 0x456;
if (HAL_FDCAN_ConfigFilter(&hfdcan1, &sFilterConfig) != HAL_OK)
{
Error_Handler();
}
/* 配置扩展帧过滤器:接收 ID 范围 0x100 ~ 0x1FF */
sFilterConfig.IdType = FDCAN_EXTENDED_ID;
sFilterConfig.FilterIndex = 1;
sFilterConfig.FilterType = FDCAN_FILTER_RANGE;
sFilterConfig.FilterConfig = FDCAN_FILTER_TO_RXFIFO1;
sFilterConfig.FilterID1 = 0x100;
sFilterConfig.FilterID2 = 0x1FF;
HAL_FDCAN_ConfigFilter(&hfdcan1, &sFilterConfig);
嗯,这里要注意:FilterIndex 不能重复。标准帧和扩展帧的索引空间是独立的,但同一类型内索引必须连续且不冲突。
3.6 小结
FDCAN 的消息 RAM 架构,说白了就是一块精心规划的共享内存。你只要把 Rx FIFO、专用缓冲区、Tx 缓冲区、过滤器这几个区域的大小算清楚,剩下的就是填寄存器的事。
我个人建议:在写代码之前,先拿张纸画出消息 RAM 的布局图。标出每个区域的起始地址和大小,再对照手册确认总和不超限。这一步省不了,否则调试时你会被各种「消息丢失」「发送超时」折磨到怀疑人生。