FDCAN 初始化流程:从零开始配置你的 CAN FD
好,咱们进入正题。FDCAN 的初始化,说白了就是让硬件从「复位状态」进入「工作状态」的过程。我刚开始接触 STM32H7 的 FDCAN 时,总觉得这步很简单——不就是配几个寄存器嘛?结果第一次调试时,总线死活没反应,折腾了大半天才发现是初始化顺序搞反了。
嗯,这节课我就把完整的初始化流程拆开来讲。你跟着走一遍,以后就不会踩我踩过的坑了。
FDCAN 初始化步骤概览
先看整体流程。FDCAN 初始化分 4 大步:
- 进入初始化模式——让 FDCAN 停止工作,准备配置
- 配置 Nominal Bit Timing——设置仲裁段的位时序
- 配置 Data Bit Timing——设置数据段的位时序(CAN FD 特有)
- 配置全局滤波器——决定哪些报文能通过
这 4 步必须按顺序来。为什么?因为 FDCAN 在正常模式下很多寄存器是写保护的,只有进了初始化模式才能改。我见过有人跳过第一步直接配时序,结果寄存器写不进去,还以为是芯片坏了……
核心要点:初始化模式是 FDCAN 的「配置窗口」。不进初始化模式,所有时序和滤波器配置都无效。
进入初始化模式
这一步其实就干一件事:把 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 能正常收发报文,剩下的功能咱们一步步来。