FDCAN 初始化流程:从零开始配置你的 CAN FD

好,咱们进入正题。FDCAN 的初始化,说白了就是让硬件从「复位状态」进入「工作状态」的过程。我刚开始接触 STM32H7 的 FDCAN 时,总觉得这步很简单——不就是配几个寄存器嘛?结果第一次调试时,总线死活没反应,折腾了大半天才发现是初始化顺序搞反了。

嗯,这节课我就把完整的初始化流程拆开来讲。你跟着走一遍,以后就不会踩我踩过的坑了。

FDCAN 初始化步骤概览

先看整体流程。FDCAN 初始化分 4 大步:

  1. 进入初始化模式——让 FDCAN 停止工作,准备配置
  2. 配置 Nominal Bit Timing——设置仲裁段的位时序
  3. 配置 Data Bit Timing——设置数据段的位时序(CAN FD 特有)
  4. 配置全局滤波器——决定哪些报文能通过

这 4 步必须按顺序来。为什么?因为 FDCAN 在正常模式下很多寄存器是写保护的,只有进了初始化模式才能改。我见过有人跳过第一步直接配时序,结果寄存器写不进去,还以为是芯片坏了……

核心要点:初始化模式是 FDCAN 的「配置窗口」。不进初始化模式,所有时序和滤波器配置都无效。

FDCAN 初始化流程 1. 进入初始化模式 2. 配置 Nominal Bit Timing 3. 配置 Data Bit Timing 4. 配置全局滤波器 ⚠ 注意 初始化模式是「配置窗口」 不进模式,寄存器写不进去 顺序不能乱,必须 1→2→3→4

进入初始化模式

这一步其实就干一件事:把 FDCAN 从正常模式切换到初始化模式。具体操作是设置 CCCR.INIT 位为 1。

但有个细节很多人会忽略——设置 INIT 位后,硬件不会立刻进入初始化模式。你得等 CCCR.INIT 位被硬件确认置位,才算真正进入。我习惯的做法是轮询等待:

/* 进入初始化模式 */
fdcan->CCCR |= FDCAN_CCCR_INIT;

/* 等待硬件确认进入初始化模式 */
while (!(fdcan->CCCR & FDCAN_CCCR_INIT)) {
    /* 等待,通常几个时钟周期就完成 */
}

我曾经踩过的坑:有一次我忘了等待确认,直接开始配置时序。结果配置写到一半,硬件才进入初始化模式,部分寄存器被清空,导致时序完全错乱。总线上一片混乱,所有节点都报错。从那以后,我每次都会加这个 while 循环。

退出初始化模式时也一样,清除 INIT 位后要等待硬件确认退出:

/* 退出初始化模式 */
fdcan->CCCR &= ~FDCAN_CCCR_INIT;

/* 等待硬件确认退出 */
while (fdcan->CCCR & FDCAN_CCCR_INIT) {
    /* 等待 */
}

配置 FDCAN 参数:Nominal Bit Timing

Nominal Bit Timing 是 CAN FD 的仲裁段时序。说白了,就是控制报文在总线上的「说话节奏」。CAN FD 和传统 CAN 在仲裁段是兼容的,所以这部分配置和经典 CAN 一样。

时序参数有 3 个:

参数 寄存器位域 说明
Nominal Prescaler (NBRP) NBTP.NBRP 分频系数,决定时间量子 TQ 的长度
Nominal Time Segment 1 (NTSEG1) NBTP.NTSEG1 传播段 + 相位缓冲段 1,单位 TQ
Nominal Time Segment 2 (NTSEG2) NBTP.NTSEG2 相位缓冲段 2,单位 TQ
Nominal SJW (NSJW) NBTP.NSJW 同步跳转宽度,单位 TQ

举个例子。假设系统时钟 80 MHz,目标仲裁速率 500 kbps:

  • 先算 TQ 数:通常取 16 TQ/bit
  • 再算 NBRP:80 MHz / (500 kbps × 16) = 10
  • 分配 TSEG1 = 13 TQ,TSEG2 = 2 TQ,SJW = 1 TQ

代码实现:

/* 配置 Nominal Bit Timing */
fdcan->NBTP = (9 << FDCAN_NBTP_NBRP_Pos)   /* NBRP = 10 - 1 */
            | (12 << FDCAN_NBTP_NTSEG1_Pos) /* NTSEG1 = 13 - 1 */
            | (1 << FDCAN_NBTP_NTSEG2_Pos)  /* NTSEG2 = 2 - 1 */
            | (0 << FDCAN_NBTP_NSJW_Pos);   /* NSJW = 1 - 1 */

小技巧:我习惯把 NBRP、NTSEG1、NTSEG2 的值减 1 后再写入寄存器,因为硬件是从 0 开始计数的。你直接写 10、13、2 进去,实际分频就变成 11、14、3 了,速率会偏慢。

配置 FDCAN 参数:Data Bit Timing

Data Bit Timing 是 CAN FD 的数据段时序。这是 CAN FD 和传统 CAN 最大的区别——数据段可以跑得更快。

配置方式和 Nominal 类似,但寄存器不同:

参数 寄存器位域 说明
Data Prescaler (DBRP) DBTP.DBRP 数据段分频系数
Data Time Segment 1 (DTSEG1) DBTP.DTSEG1 数据段 TSEG1,单位 TQ
Data Time Segment 2 (DTSEG2) DBTP.DTSEG2 数据段 TSEG2,单位 TQ
Data SJW (DSJW) DBTP.DSJW 数据段同步跳转宽度
TDC Offset TDCR.TDCO 收发延迟补偿偏移量(高速时必配)

假设数据段目标速率 2 Mbps,系统时钟 80 MHz:

  • 取 8 TQ/bit
  • DBRP = 80 MHz / (2 Mbps × 8) = 5
  • DTSEG1 = 5 TQ,DTSEG2 = 2 TQ,DSJW = 1 TQ

代码实现:

/* 配置 Data Bit Timing */
fdcan->DBTP = (4 << FDCAN_DBTP_DBRP_Pos)   /* DBRP = 5 - 1 */
            | (4 << FDCAN_DBTP_DTSEG1_Pos) /* DTSEG1 = 5 - 1 */
            | (1 << FDCAN_DBTP_DTSEG2_Pos) /* DTSEG2 = 2 - 1 */
            | (0 << FDCAN_DBTP_DSJW_Pos);  /* DSJW = 1 - 1 */

/* 配置收发延迟补偿(数据速率 > 1 Mbps 时建议开启) */
fdcan->TDCR = (30 << FDCAN_TDCR_TDCO_Pos); /* TDC Offset 根据实测调整 */

注意:数据速率超过 1 Mbps 时,收发延迟补偿(TDC)必须配置。否则信号在总线上的往返延迟会导致采样点偏移,出现位错误。我遇到过一例:2 Mbps 下没配 TDC,总线偶尔丢帧,查了三天才发现是 TDC 没开。

配置 FDCAN 全局滤波器

全局滤波器决定哪些报文能进入 FDCAN 的接收 FIFO。说白了,就是一道「安检门」。

FDCAN 有 2 个全局滤波器:

  • Global Standard Filter:过滤标准帧(11 位 ID)
  • Global Extended Filter:过滤扩展帧(29 位 ID)

每个滤波器可以配置为:

模式 说明
接受所有报文 不过滤,全部放行
拒绝所有报文 全部拦截
匹配指定 ID 只放行与滤波器 ID 匹配的报文

最简单的配置——接受所有报文:

/* 配置全局标准滤波器:接受所有标准帧 */
fdcan->GFC = (0 << FDCAN_GFC_RRFS_Pos)   /* 拒绝远程帧请求:不过滤 */
            | (0 << FDCAN_GFC_RRFE_Pos)   /* 拒绝扩展帧远程帧:不过滤 */
            | (0 << FDCAN_GFC_ANFS_Pos)   /* 标准帧未匹配时:存入 FIFO 0 */
            | (0 << FDCAN_GFC_ANFE_Pos);  /* 扩展帧未匹配时:存入 FIFO 0 */

我的习惯:调试阶段先用「接受所有报文」,等通信稳定了再细化滤波器配置。这样能快速排除滤波器配置错误导致的通信问题。你想想看,如果滤波器配错了,报文进不来,你可能会怀疑时序、怀疑硬件,就是想不到滤波器——我吃过这个亏。

完整的初始化函数

把上面几步串起来,就是一个完整的初始化函数:

void FDCAN_Init(FDCAN_HandleTypeDef *hfdcan)
{
    /* 1. 进入初始化模式 */
    hfdcan->Instance->CCCR |= FDCAN_CCCR_INIT;
    while (!(hfdcan->Instance->CCCR & FDCAN_CCCR_INIT));

    /* 2. 配置 Nominal Bit Timing (500 kbps) */
    hfdcan->Instance->NBTP = (9 << FDCAN_NBTP_NBRP_Pos)
                            | (12 << FDCAN_NBTP_NTSEG1_Pos)
                            | (1 << FDCAN_NBTP_NTSEG2_Pos)
                            | (0 << FDCAN_NBTP_NSJW_Pos);

    /* 3. 配置 Data Bit Timing (2 Mbps) */
    hfdcan->Instance->DBTP = (4 << FDCAN_DBTP_DBRP_Pos)
                            | (4 << FDCAN_DBTP_DTSEG1_Pos)
                            | (1 << FDCAN_DBTP_DTSEG2_Pos)
                            | (0 << FDCAN_DBTP_DSJW_Pos);

    /* 4. 配置收发延迟补偿 */
    hfdcan->Instance->TDCR = (30 << FDCAN_TDCR_TDCO_Pos);

    /* 5. 配置全局滤波器:接受所有报文 */
    hfdcan->Instance->GFC = 0;

    /* 6. 退出初始化模式 */
    hfdcan->Instance->CCCR &= ~FDCAN_CCCR_INIT;
    while (hfdcan->Instance->CCCR & FDCAN_CCCR_INIT);
}

嗯,到这里 FDCAN 的初始化就完成了。你可能会问:为什么没有配置中断和 FIFO?别急,那些是后面章节的内容。初始化只负责让 FDCAN 能正常收发报文,剩下的功能咱们一步步来。