3. STM32 FDCAN 外设概览:从 G4 到 H7 的实战视角
好,咱们进入正题。这一章我打算把 STM32 的 FDCAN 外设掰开揉碎讲清楚。你可能会问,不就是个 CAN 控制器吗,有什么好讲的?
其实不然。FDCAN 和传统 bxCAN 差别很大,尤其是消息 RAM 和过滤器的设计思路,完全换了一套玩法。我当年从 F1 切到 G4 时,第一反应是——这寄存器怎么变这么少?后来才发现,原来都藏到 RAM 里去了。
3.1 STM32G4/H7 系列 FDCAN 模块
STM32 的 FDCAN 模块基于 Bosch M_CAN 内核,G4 和 H7 系列都搭载了这个 IP。但两者还是有些细微差别,我列个表给你看:
| 特性 | STM32G4 | STM32H7 |
|---|---|---|
| FDCAN 实例数 | 1 个(FDCAN1) | 2 个(FDCAN1/FDCAN2) |
| 消息 RAM 大小 | 2.5 KB | 10 KB |
| 支持 CAN FD | 是(最高 8 Mbps) | 是(最高 8 Mbps) |
| Rx/Tx FIFO | 各 3 个 | 各 3 个 |
| 专用 Tx 缓冲 | 最多 32 个 | 最多 32 个 |
| 硬件时间戳 | 16 位 | 32 位 |
看到没?H7 的消息 RAM 大了整整 4 倍。我在做多节点数据采集项目时,G4 的 2.5 KB 勉强够用,但 H7 就从容多了。如果你要处理大量 ID 过滤或长报文缓存,选 H7 会省心不少。
3.2 FDCAN 内核架构
FDCAN 的内核分三层,我画了张图帮你理解:
这张图我建议你多看两眼。CPU 接口层负责你写的寄存器操作,消息处理层是核心调度器,CAN 协议层管物理层时序。三层之间通过内部总线通信,你基本不用操心。
我遇到过一个问题:某次调试时 FDCAN 一直报总线关闭,查了半天发现是位时序配置错了。协议层对时钟精度要求很高,尤其是 CAN FD 的高速数据段,采样点位置差一点就翻车。
3.3 消息 RAM 布局
消息 RAM 是 FDCAN 的「记忆体」。所有待发送、已接收、待过滤的报文都放在这里。布局是固定的,但你可以通过寄存器配置每个区域的大小。
标准布局如下:
| 区域 | 起始偏移 | 说明 |
|---|---|---|
| Rx FIFO 0 | 0x000 | 接收 FIFO 0,优先级最高 |
| Rx FIFO 1 | 可配置 | 接收 FIFO 1,优先级次之 |
| Rx 缓冲 | 可配置 | 专用接收缓冲,用于特定 ID |
| Tx 事件 FIFO | 可配置 | 记录发送完成的时间戳 |
| Tx 缓冲 | 可配置 | 待发送报文,支持优先级调度 |
| Tx FIFO/队列 | 可配置 | 按顺序发送的队列模式 |
配置消息 RAM 时,我建议按这个顺序来:
- 先确定需要的 Rx FIFO 深度(一般 16-32 个元素够用)
- 再算 Tx 缓冲数量(根据应用层发送频率)
- 最后看要不要 Tx 事件 FIFO(调试时很有用)
- 剩下的空间给 Rx 缓冲(非必须)
3.4 过滤器配置
FDCAN 的过滤器比传统 bxCAN 灵活得多。它支持两种模式:
- 经典过滤: 和 bxCAN 一样,用 ID + 掩码匹配
- 范围过滤: 指定一个 ID 范围,落在区间内的都接收
我个人更喜欢范围过滤。为什么?因为经典过滤的掩码位操作容易把人绕晕。你想想看,要过滤 0x100-0x1FF 这 256 个 ID,经典过滤得算掩码,范围过滤直接写起始和结束 ID 就行。
过滤器配置的步骤:
- 设置过滤器标准(11 位或 29 位 ID)
- 选择过滤模式(经典或范围)
- 配置过滤元素(ID + 掩码,或起始/结束 ID)
- 关联到 Rx FIFO 0 或 Rx FIFO 1
避坑指南: 我曾经把过滤器配成了「拒绝所有」,结果总线上一帧数据都收不到。查了两天才发现是过滤元素配置反了——起始 ID 大于结束 ID,范围过滤直接失效。记住,起始 ID 必须小于等于结束 ID。
3.5 中断与标志位
FDCAN 的中断系统很丰富,但别被吓到。常用的就这几个:
| 中断源 | 标志位 | 触发条件 |
|---|---|---|
| Rx FIFO 0 新消息 | RF0N | FIFO 0 收到新报文 |
| Rx FIFO 1 新消息 | RF1N | FIFO 1 收到新报文 |
| Tx 完成 | TC | 某 Tx 缓冲发送完成 |
| 总线错误 | EW / EP / BO | 错误状态变化 |
| 消息 RAM 溢出 | RF0L / RF1L | FIFO 满时新报文被丢弃 |
我的做法是:接收用中断,发送用轮询。为什么?接收不及时会丢帧,中断响应最快。发送嘛,应用层自己知道什么时候发,轮询更可控。
中断处理函数里,记得先读中断标志位,再清标志,最后处理数据。顺序错了容易漏中断。嗯,这个坑我也踩过。