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 只跟窗口打交道,分拣和收发由内部搞定。

FDCAN 内核三层架构 寄存器接口层 APB 总线 · 配置寄存器 · 中断控制 消息处理层 消息 RAM 仲裁 · Tx 调度 · Rx 过滤 · 事件 FIFO 内部 DMA 引擎 · 时间戳捕获 CAN 协议控制器层 位时序 · 同步 · CRC · BRS/EDL 解析 数据流向:CAN 总线 → 协议层 → 消息层 → 寄存器接口层 → 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 的布局图。标出每个区域的起始地址和大小,再对照手册确认总和不超限。这一步省不了,否则调试时你会被各种「消息丢失」「发送超时」折磨到怀疑人生。