3. STM32 CANFD外设架构:FDCAN内核介绍、FDCAN消息RAM管理、FDCAN中断与标志位、FDCAN时钟配置

好,咱们进入正题。这一章我打算把 STM32 的 FDCAN 外设架构拆开揉碎了讲给你听。说实话,我刚接触 FDCAN 的时候,也被那复杂的消息 RAM 和一堆中断标志位搞得有点晕。但后来我发现,只要理清了内核、存储和中断这三条线,剩下的就是水到渠成的事。

3.1 FDCAN内核介绍

FDCAN 内核,说白了就是 CANFD 协议的硬件实现核心。STM32 用的这个内核是基于 Bosch M_CAN 的,这是个非常成熟的 IP 核。我个人习惯把 FDCAN 内核想象成一个独立的小处理器——它有自己的状态机、自己的 RAM、自己的中断控制器。

内核主要分三块:

  • 协议控制器:负责处理 CANFD 帧的收发、位时序、错误检测。说白了就是跟总线打交道的那部分。
  • 消息处理单元:负责把收到的报文存到 RAM 里,或者从 RAM 里取出要发送的报文。这是内核的“大脑”。
  • 配置寄存器组:我们通过操作这些寄存器来告诉内核怎么工作。

我在项目中遇到过一个问题:同样的 FDCAN 配置,在 STM32H7 上跑得好好的,换到 G4 上就不行了。后来发现是内核版本有细微差异——H7 用的是 M_CAN v3.x,G4 用的是 v2.x。所以啊,别以为所有 STM32 的 FDCAN 都一样,还是要看参考手册。

核心要点:FDCAN 内核的工作模式有三种——经典 CAN 模式、CANFD 模式(带 BRS)和 CANFD 模式(不带 BRS)。我个人建议,只要总线上的节点都支持,就开启 BRS,不然你买 FDCAN 干嘛?

3.2 FDCAN消息RAM管理

嗯,这里要重点讲。FDCAN 的消息 RAM 是很多人踩坑的地方。

STM32 的 FDCAN 内部有一块专用的 SRAM,大小通常是 2.5KB 或者 10KB(具体看型号)。这块 RAM 不是让你随便用的,它被划分成了几个固定的区域:

区域 用途 大小(典型值)
Rx FIFO 0 接收缓冲区 0 可配置
Rx FIFO 1 接收缓冲区 1 可配置
Rx Buffer 专用接收缓冲区 可配置
Tx Event FIFO 发送事件记录 可配置
Tx Buffer 发送缓冲区 可配置
Tx FIFO/Queue 发送队列 可配置

你可能会问:为什么搞这么复杂?其实是为了灵活性。比如,你可以把高优先级报文放到 Rx Buffer 里,用硬件直接路由;普通报文走 FIFO,按顺序处理。

避坑指南:我曾经在配置消息 RAM 时,把 Rx FIFO 和 Tx Buffer 的总大小算错了,导致程序跑起来后,发送几帧就卡死。后来才发现是 RAM 溢出,后面的配置覆盖了前面的。记住:所有区域的大小加起来不能超过总 RAM 大小!

配置消息 RAM 的步骤,我一般这么干:

  1. 先算好需要多少个 Rx FIFO 元素、多少个 Tx Buffer 元素。
  2. 每个元素的大小:经典 CAN 是 18 字节,CANFD 是 72 字节(含 64 字节数据)。
  3. FDCAN_RxFifoConfig()FDCAN_TxBufferConfig() 设置起始地址和大小。
  4. 检查 FDCAN_GetRAMUsedSize() 是否超过总 RAM。
/* 消息RAM配置示例 */
FDCAN_RxHeaderTypeDef   RxHeader;
FDCAN_TxHeaderTypeDef   TxHeader;

/* 配置Rx FIFO 0:起始地址0,深度32个元素 */
hfdcan1.RxBuffersNbr = 0;          /* 不使用专用Rx Buffer */
hfdcan1.RxFifo0ElmtsNbr = 32;      /* FIFO 0 深度 */
hfdcan1.RxFifo0ElmtSize = FDCAN_DATA_BYTES_64;  /* CANFD 64字节数据 */

/* 配置Tx Buffer:起始地址自动计算,深度8个元素 */
hfdcan1.TxElmtsNbr = 8;
hfdcan1.TxElmtSize = FDCAN_DATA_BYTES_64;
hfdcan1.TxFifoQueueMode = FDCAN_TX_FIFO_OPERATION;  /* 使用FIFO模式 */

HAL_FDCAN_Init(&hfdcan1);

我的小技巧:如果你不确定 RAM 怎么分配,可以用 STM32CubeMX 的图形化配置工具。它会自动计算剩余空间,还会用颜色标出是否溢出。我刚开始学的时候,全靠这个工具帮我避坑。

3.3 FDCAN中断与标志位

FDCAN 的中断系统,说实话,比经典 CAN 复杂了不少。但别怕,咱们一条一条理清楚。

FDCAN 的中断源主要分这几类:

  • 接收中断:Rx FIFO 有新消息、Rx Buffer 有新消息、消息丢失等。
  • 发送中断:Tx Buffer 发送完成、Tx FIFO 发送完成、发送取消等。
  • 错误中断:总线错误、协议错误、错误状态变化等。
  • 状态中断:时钟校准、时间戳溢出等。

每个中断源都有对应的标志位寄存器(IR)和使能寄存器(IE)。我习惯的做法是:先使能需要的中断,然后在中断服务函数里读 IR 寄存器,判断是哪个事件触发的。

/* 使能接收中断 */
HAL_FDCAN_ActivateNotification(&hfdcan1, FDCAN_IT_RX_FIFO0_NEW_MESSAGE, 0);

/* 中断服务函数 */
void FDCAN1_IT0_IRQHandler(void)
{
    HAL_FDCAN_IRQHandler(&hfdcan1);
}

/* HAL库的回调函数 */
void HAL_FDCAN_RxFifo0Callback(FDCAN_HandleTypeDef *hfdcan, uint32_t RxFifo0ITs)
{
    FDCAN_RxHeaderTypeDef RxHeader;
    uint8_t RxData[64];

    if(RxFifo0ITs & FDCAN_IT_RX_FIFO0_NEW_MESSAGE)
    {
        HAL_FDCAN_GetRxMessage(hfdcan, FDCAN_RX_FIFO0, &RxHeader, RxData);
        /* 处理接收到的数据 */
    }
}

你可能会问:为什么有两个中断线(IT0 和 IT1)?这是为了把不同优先级的中断分开。比如,把错误中断放到 IT1,接收中断放到 IT0。这样高优先级的接收不会被错误处理阻塞。

经验之谈:我在做车载项目时,把所有的 FDCAN 中断都扔到了同一个中断线。结果有一次总线出现大量错误,中断服务函数里一直在处理错误,导致接收丢帧。后来我把错误中断单独拎出来,用轮询方式处理,问题就解决了。

3.4 FDCAN时钟配置

时钟配置是 FDCAN 能跑多快的决定性因素。FDCAN 的时钟源通常有两个选择:

  • APB 时钟(通常是 APB1 或 APB2)
  • 外部时钟(从某个引脚输入)

我个人习惯用 APB 时钟,因为省事。但要注意:FDCAN 的时钟频率决定了它能支持的最高比特率。比如,STM32H7 的 FDCAN 时钟最高可以到 80MHz,那理论上数据段比特率可以跑到 8Mbps。

时钟配置的核心是位时序(Bit Timing)。FDCAN 的位时间由三部分组成:

  • 同步段(Sync Seg):固定 1 个时间量子(tq)
  • 传播段(Prop Seg):可配置,1-8 tq
  • 相位缓冲段(Phase Seg1 + Phase Seg2):可配置,各 1-8 tq

采样点位置 = (Sync + Prop + Phase1) / (Sync + Prop + Phase1 + Phase2)。我一般把采样点设在 75%-85% 之间,这样对总线延迟的容忍度最好。

/* 时钟配置示例:40MHz时钟,1Mbps仲裁段,4Mbps数据段 */
hfdcan1.Init.DataBaudRatePrescaler = 2;     /* 预分频器 */
hfdcan1.Init.DataSyncJumpWidth = 2;         /* 同步跳转宽度 */
hfdcan1.Init.DataTimeSeg1 = 4;              /* 时间段1 */
hfdcan1.Init.DataTimeSeg2 = 3;              /* 时间段2 */

/* 计算:tq = 2/40MHz = 50ns */
/* 位时间 = (1 + 4 + 3) * 50ns = 400ns = 2.5Mbps */
/* 采样点 = (1 + 4) / (1 + 4 + 3) = 62.5% */

注意:仲裁段和数据段的位时序是分开配置的!仲裁段通常用 1Mbps,数据段可以更高。但别忘了,总线上的所有节点必须用相同的仲裁段比特率,数据段比特率可以不同(只要都支持 BRS)。

最后,别忘了检查时钟使能。我见过有人配了半天,结果发现 FDCAN 的时钟根本没打开:

__HAL_RCC_FDCAN_CLK_ENABLE();  /* 使能FDCAN时钟 */

嗯,这一章的内容就到这里。消息 RAM 的管理和中断配置是 FDCAN 开发中最容易出问题的地方,建议你动手写代码的时候多留个心眼。下一章咱们会讲 FDCAN 的初始化流程和收发实现,到时候会用到今天讲的知识。