引言
在嵌入式实时控制(如电机驱动、机器人关节)中,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 提供),并开启硬件时间戳。
// 使用 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 模式类似,但数据段波特率相同)
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 发送报文(带时间戳)
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 可能更合适,或通过软件补偿。实测数据表明,抖动主要来源于位填充和重发机制,合理配置可有效抑制。