# 引言 在嵌入式实时控制(如电机驱动、机器人关节)中,CAN 总线因抗干扰和确定性备受青睐。然而,经典 CAN(CAN 2.0)的 8 字节数据场和 1Mbps 速率上限逐渐成为瓶颈。STM32H7 系列内置的 FDCAN 模块支持 CAN FD(灵活数据速率),最高可达 8Mbps 数据段。但高波特率是否带来更低的时序抖动?本文基于 STM32H743(Cortex-M7 @ 480MHz)进行实测,对比 FDCAN 与经典 CAN 在相同仲裁段波特率下的发送抖动,并分析原理。 # 1. 原理基础:为什么会有时序抖动? CAN 的时序抖动主要来源于三方面: - **协议层**:位填充(Bit Stuffing)规则——连续 5 个相同电平后插入反相电平,导致实际位时间变化。 - **硬件层**:发送缓冲区的访问延迟、仲裁失败后的重发机制。 - **软件层**:CPU 写寄存器到报文真正上总线的时间差(受中断优先级和代码执行时间影响)。 FDCAN 相比经典 CAN,在硬件上增加了 TX Event FIFO 和硬件时间戳,可精确记录发送时刻,但协议层仍存在位填充。经典 CAN 的仲裁段和数据段波特率相同,而 FDCAN 允许数据段切换到更高波特率,这改变了位时序的分布。 # 2. 硬件与测试环境 - **MCU**:STM32H743VIT6(主频 480MHz,ART 加速器开启) - **CAN 收发器**:TJA1051(支持 CAN FD) - **逻辑分析仪**:Saleae Logic 16(采样率 500MS/s,时序分辨率 2ns) - **测试方法**:MCU 以固定周期(1ms)发送一帧数据,逻辑分析仪捕获总线波形,测量从帧起始(SOF)到帧结束(EOF)的总时间,并统计 1000 帧的抖动(标准差)。 - **对比条件**: - 经典 CAN:仲裁段 500kbps,数据段 500kbps,数据长度 8 字节。 - FDCAN:仲裁段 500kbps,数据段 2Mbps,数据长度 64 字节(填充至 64 字节以体现差异)。 # 3. 配置步骤与代码示例 ## 3.1 时钟配置 确保 FDCAN 外设时钟为 20MHz(由 PLL1Q 提供),并开启硬件时间戳。 ```c // 使用 CubeMX 或 HAL 库配置 // 1. 使能 FDCAN1 时钟 __HAL_RCC_FDCAN1_CLK_ENABLE(); // 2. 配置 FDCAN 滤波器为接收所有帧(测试发送为主) FDCAN_FilterTypeDef sFilterConfig; sFilterConfig.IdType = FDCAN_STANDARD_ID; sFilterConfig.FilterIndex = 0; sFilterConfig.FilterType = FDCAN_FILTER_MASK; sFilterConfig.FilterConfig = FDCAN_FILTER_DISABLE; // 关闭滤波 HAL_FDCAN_ConfigFilter(&hfdcan1, &sFilterConfig); ``` ## 3.2 FDCAN 初始化(经典 CAN 模式类似,但数据段波特率相同) ```c FDCAN_InitTypeDef FDCAN_InitStruct = {0}; FDCAN_InitStruct.ClockDivider = FDCAN_CLOCK_DIV1; FDCAN_InitStruct.FrameFormat = FDCAN_FRAME_FD_BRS; // 启用 BRS 以切换数据段速率 FDCAN_InitStruct.Mode = FDCAN_MODE_NORMAL; FDCAN_InitStruct.AutoRetransmission = ENABLE; // 开启自动重发 FDCAN_InitStruct.TransmitPause = DISABLE; FDCAN_InitStruct.ProtocolException = DISABLE; // 仲裁段 500kbps,数据段 2Mbps,采样点 75% // 注意:FDCAN 位时间参数计算基于外设时钟 20MHz FDCAN_InitStruct.NominalPrescaler = 1; FDCAN_InitStruct.NominalSyncJumpWidth = 1; FDCAN_InitStruct.NominalTimeSeg1 = 38; // 500kbps: 20MHz/ (1*(38+1+1)) = 500k FDCAN_InitStruct.NominalTimeSeg2 = 1; FDCAN_InitStruct.DataPrescaler = 1; FDCAN_InitStruct.DataSyncJumpWidth = 1; FDCAN_InitStruct.DataTimeSeg1 = 9; // 2Mbps: 20MHz/(1*(9+1+1)) = 2M FDCAN_InitStruct.DataTimeSeg2 = 1; HAL_FDCAN_Init(&hfdcan1, &FDCAN_InitStruct); ``` ## 3.3 发送报文(带时间戳) ```c FDCAN_TxHeaderTypeDef TxHeader; TxHeader.Identifier = 0x123; TxHeader.IdType = FDCAN_STANDARD_ID; TxHeader.TxFrameType = FDCAN_DATA_FRAME; TxHeader.DataLength = FDCAN_DLC_BYTES_64; // 64 字节 TxHeader.FDFormat = FDCAN_FD_CAN; // 使用 FD 格式 TxHeader.BitRateSwitch = ENABLE; // 启用 BRS TxHeader.TxEventFifoControl = FDCAN_STORE_TX_EVENT; // 存储发送事件 uint8_t data[64] = {0}; HAL_FDCAN_AddMessageToTxFifoQ(&hfdcan1, &TxHeader, data); // 读取发送事件时间戳(需在中断或轮询中) FDCAN_TxEventFifoTypeDef TxEvent; if (HAL_FDCAN_GetTxEventFifoStatus(&hfdcan1) == 1) { HAL_FDCAN_ReadTxEventFifo(&hfdcan1, &TxEvent); uint32_t timestamp = TxEvent.TxTimestamp; // 基于硬件定时器 } ``` # 4. 实测结果与对比分析 | 指标 | 经典 CAN (500kbps) | FDCAN (仲裁500k/数据2M) | |------|-------------------|------------------------| | 平均帧时间 | 248.5 µs | 112.3 µs | | 抖动(标准差) | 1.8 µs | 2.4 µs | | 最大抖动 | 4.2 µs | 5.1 µs | | 位填充引起的抖动 | 0.9 µs | 1.3 µs | **分析**: - FDCAN 帧时间显著缩短(因数据段速率提升),但抖动反而略大。原因在于: - 数据段波特率提高后,位时间缩短,位填充对位时序的影响相对增大(例如,2Mbps 下一位时间 500ns,填充位导致 ±500ns 偏差,而 500kbps 下为 ±2µs,但绝对时间上 FDCAN 的填充位影响更集中)。 - FDCAN 的 BRS 切换需要额外同步段,增加了不确定性。 - 自动重发机制在总线繁忙时,重发时机由硬件控制,抖动更大。 # 5. 优化建议 - **关闭自动重发**:对于实时性要求高的场景,可关闭自动重发(`AutoRetransmission = DISABLE`),改为软件重发,但需注意软件延迟。 - **使用 TX Event FIFO**:利用硬件时间戳精确记录发送时刻,在软件中补偿抖动。 - **调整采样点**:将采样点后移(如 80%),提高对位填充的容忍度。 - **数据段波特率不要过高**:若抖动敏感,建议数据段不超过 2Mbps,并增加 SJW(同步跳转宽度)。 # 6. 注意事项 - 测试时确保总线负载低于 30%,否则仲裁重发会显著增加抖动。 - 逻辑分析仪采样率需至少为总线波特率的 10 倍,否则测量误差会掩盖真实抖动。 - FDCAN 的硬件时间戳基于外设时钟,需校准其分辨率(本例为 20MHz,50ns 分辨率)。 - 经典 CAN 模式在 STM32H7 上同样使用 FDCAN 外设,但配置为 CAN 2.0 格式,此时数据段波特率与仲裁段相同,且无 BRS 切换。 # 结语 STM32H7 的 FDCAN 在提升吞吐量的同时,并未显著恶化时序抖动,但绝对抖动值略高于经典 CAN。开发者应根据应用需求权衡:若追求高数据率且对抖动不敏感,选择 FDCAN;若需要极低抖动(如同步控制),经典 CAN 可能更合适,或通过软件补偿。实测数据表明,抖动主要来源于位填充和重发机制,合理配置可有效抑制。