# STM32H7 400MHz 下 FDCAN 与普通 CAN 的时序兼容性实测与配置陷阱 ## 引言 STM32H7 系列凭借 400MHz 的 Cortex-M7 内核和双 FDCAN 控制器,在车载、工业控制等领域应用广泛。FDCAN 支持经典 CAN(2.0A/B)和 CAN FD 两种模式,但许多开发者直接沿用低主频 MCU 的 CAN 配置,导致在 H7 上出现采样点偏移、同步失败等问题。本文通过实测分析 FDCAN 在经典 CAN 模式下的位时序机制,揭示配置陷阱,并提供可复用的解决方案。 ## 一、FDCAN 与经典 CAN 的时序基础 ### 1.1 位时序结构 经典 CAN 的位时间由四段组成: - **同步段 (SYNC_SEG)**:固定 1 个时间量子 (tq),用于同步总线上的节点。 - **传播段 (PROP_SEG)**:补偿信号传播延迟。 - **相位缓冲段 1 (PHASE_SEG1)**:用于补偿上升沿相位误差。 - **相位缓冲段 2 (PHASE_SEG2)**:用于补偿下降沿相位误差。 采样点位于 PHASE_SEG1 与 PHASE_SEG2 之间,理想采样点位置通常在 75%-85% 处。 ### 1.2 FDCAN 的位时序寄存器 FDCAN 控制器使用 **NBTP (Nominal Bit Timing & Prescaler) 寄存器** 配置经典 CAN 模式下的时序,其字段包括: - `BRP` (Baud Rate Prescaler):预分频器,范围 1-512。 - `TSEG1` (Time Segment 1):包含 PROP_SEG 和 PHASE_SEG1,范围 1-256。 - `TSEG2` (Time Segment 2):即 PHASE_SEG2,范围 1-128。 - `SJW` (Sync Jump Width):同步跳转宽度,范围 1-128。 注意:FDCAN 的 TSEG1 和 TSEG2 与经典 CAN 控制器(如 bxCAN)的命名不同,且没有独立的 PROP_SEG 字段,需将传播段合并到 TSEG1 中。 ## 二、实测:400MHz 主频下的时序陷阱 ### 2.1 陷阱一:预分频器计算错误 STM32H7 的 FDCAN 内核时钟来自 APB1 外设时钟(通常为 100MHz 或 200MHz,取决于 RCC 配置)。若直接使用 400MHz 作为计算基准,会导致位时间量子过小,超出 FDCAN 的时序范围。 **实测数据**: - 目标波特率:500kbps - APB1 时钟:100MHz(H7 默认配置) - 若误用 400MHz 计算:BRP = 400MHz / (500k * (1+TSEG1+TSEG2)),假设 TSEG1=13, TSEG2=2,则 BRP=400e6/(500e3*16)=50,实际位时间 = 50*16/100e6 = 8us,波特率变为 125kbps,完全错误。 **正确计算**: - 使用 APB1 时钟 100MHz,BRP = 100e6 / (500e3 * 16) = 12.5,取整为 12 或 13,再微调 TSEG 以满足采样点。 ### 2.2 陷阱二:采样点偏移导致同步失败 经典 CAN 要求所有节点的采样点一致,否则在总线竞争时可能因相位误差累积而错误。FDCAN 的默认复位值 TSEG1=13, TSEG2=2,采样点 = (1+13)/(1+13+2) = 87.5%,这通常偏高,且未考虑传播延迟。 **实测场景**: - 两个 STM32H7 节点,一个使用 FDCAN 经典模式,一个使用传统 CAN 控制器(如 MCP2515),波特率 500kbps。 - 默认配置下,通信成功率仅 60%,且偶发总线错误。 - 调整采样点至 80%(TSEG1=12, TSEG2=3),通信成功率提升至 100%。 ### 2.3 陷阱三:SJW 设置不当 SJW 决定了重同步时的最大补偿步长。在高速主频下,若 SJW 过小,无法补偿时钟漂移;若过大,则可能引入噪声。实测推荐 SJW 设为 1-4 个 tq,且不大于 TSEG2。 ## 三、CubeMX 配置步骤(以 STM32H743 为例) ### 3.1 时钟配置 1. 打开 STM32CubeMX,选择 STM32H743ZI 芯片。 2. 在 **Clock Configuration** 中,设置 HSE 为 25MHz,PLL 倍频至 400MHz(CPU 时钟)。 3. 确认 APB1 时钟为 100MHz(APB1 分频系数为 4,因为 APB1 最大 100MHz)。 ### 3.2 FDCAN 配置 1. 在 **Connectivity** 中选择 FDCAN1,勾选 **Activate**。 2. 在 **Parameter Settings** 中: - **Mode**:选择 "Classic CAN"(经典 CAN 模式)。 - **Bit Timings Parameters**: - **Baud Rate Prescaler (BRP)**:根据计算填入(如 12)。 - **Time Segment 1 (TSEG1)**:填入 12(包含传播段和相位段1)。 - **Time Segment 2 (TSEG2)**:填入 3。 - **Sync Jump Width (SJW)**:填入 1。 - **Global Filter**:配置接收过滤器,例如屏蔽模式,接收所有 ID。 3. 生成代码,并在 main.c 中初始化 FDCAN。 ## 四、完整代码示例 以下代码基于 STM32H7 HAL 库,实现 FDCAN 经典 CAN 模式下的收发。 ```c /* 初始化 FDCAN */ void FDCAN_Init(void) { FDCAN_FilterTypeDef sFilterConfig; /* 配置 FDCAN 句柄 */ hfdcan1.Instance = FDCAN1; hfdcan1.Init.ClockDivider = FDCAN_CLOCK_DIV1; // 时钟不分频 hfdcan1.Init.FrameFormat = FDCAN_FRAME_CLASSIC; // 经典 CAN 模式 hfdcan1.Init.Mode = FDCAN_MODE_NORMAL; // 正常模式 hfdcan1.Init.AutoRetransmission = ENABLE; // 自动重传 hfdcan1.Init.TransmitPause = DISABLE; hfdcan1.Init.ProtocolException = DISABLE; hfdcan1.Init.NominalPrescaler = 12; // BRP = 12 hfdcan1.Init.NominalSyncJumpWidth = 1; // SJW = 1 hfdcan1.Init.NominalTimeSeg1 = 12; // TSEG1 = 12 hfdcan1.Init.NominalTimeSeg2 = 3; // TSEG2 = 3 hfdcan1.Init.DataPrescaler = 1; // 数据段预分频(经典模式不使用) hfdcan1.Init.DataSyncJumpWidth = 1; hfdcan1.Init.DataTimeSeg1 = 1; hfdcan1.Init.DataTimeSeg2 = 1; hfdcan1.Init.MessageRAMOffset = 0; // 消息 RAM 偏移 hfdcan1.Init.StdFiltersNbr = 1; // 标准过滤器数量 hfdcan1.Init.ExtFiltersNbr = 1; // 扩展过滤器数量 hfdcan1.Init.RxFifo0ElmtsNbr = 8; // RX FIFO0 元素数 hfdcan1.Init.RxFifo0ElmtSize = FDCAN_DATA_BYTES_8; hfdcan1.Init.RxFifo1ElmtsNbr = 0; hfdcan1.Init.RxBuffersNbr = 0; hfdcan1.Init.TxEventsNbr = 0; hfdcan1.Init.TxBuffersNbr = 0; hfdcan1.Init.TxFifoQueueElmtsNbr = 8; // TX FIFO 元素数 hfdcan1.Init.TxFifoQueueMode = FDCAN_TX_FIFO_OPERATION; hfdcan1.Init.TxElmtSize = FDCAN_DATA_BYTES_8; if (HAL_FDCAN_Init(&hfdcan1) != HAL_OK) { Error_Handler(); } /* 配置过滤器:接收所有 ID */ sFilterConfig.IdType = FDCAN_STANDARD_ID; sFilterConfig.FilterIndex = 0; sFilterConfig.FilterType = FDCAN_FILTER_MASK; sFilterConfig.FilterConfig = FDCAN_FILTER_TO_RXFIFO0; sFilterConfig.FilterID1 = 0x000; sFilterConfig.FilterID2 = 0x7FF; // 掩码全 1,接收所有 if (HAL_FDCAN_ConfigFilter(&hfdcan1, &sFilterConfig) != HAL_OK) { Error_Handler(); } /* 启动 FDCAN */ if (HAL_FDCAN_Start(&hfdcan1) != HAL_OK) { Error_Handler(); } /* 激活接收中断 */ HAL_FDCAN_ActivateNotification(&hfdcan1, FDCAN_IT_RX_FIFO0_NEW_MESSAGE, 0); } /* 发送消息 */ void FDCAN_Send(uint32_t id, uint8_t *data, uint8_t len) { FDCAN_TxHeaderTypeDef txHeader; txHeader.Identifier = id; txHeader.IdType = FDCAN_STANDARD_ID; txHeader.TxFrameType = FDCAN_DATA_FRAME; txHeader.DataLength = FDCAN_DLC_BYTES_8; // 固定 8 字节,经典 CAN 模式 txHeader.ErrorStateIndicator = FDCAN_ESI_ACTIVE; txHeader.BitRateSwitch = FDCAN_BRS_OFF; txHeader.FDFormat = FDCAN_CLASSIC_CAN; txHeader.TxEventFifoControl = FDCAN_NO_TX_EVENTS; txHeader.MessageMarker = 0; if (HAL_FDCAN_AddMessageToTxFifoQ(&hfdcan1, &txHeader, data) != HAL_OK) { // 发送失败处理 } } /* 接收回调 */ void HAL_FDCAN_RxFifo0Callback(FDCAN_HandleTypeDef *hfdcan, uint32_t RxFifo0ITs) { FDCAN_RxHeaderTypeDef rxHeader; uint8_t rxData[8]; if ((RxFifo0ITs & FDCAN_IT_RX_FIFO0_NEW_MESSAGE) != 0) { if (HAL_FDCAN_GetRxMessage(hfdcan, FDCAN_RX_FIFO0, &rxHeader, rxData) == HAL_OK) { // 处理接收数据 } } } ``` ## 五、注意事项 - **时钟源确认**:FDCAN 的时钟源可以是 APB1 或 PLL2,务必在 RCC 配置中确认,避免误用 400MHz。 - **采样点计算**:建议采样点设置在 75%-85% 之间,可通过公式 `(1+TSEG1)/(1+TSEG1+TSEG2)` 计算。 - **总线长度**:传播段需覆盖总线双向延迟,若总线较长,应增加 TSEG1 值。 - **兼容性测试**:与不同厂商的 CAN 控制器混用时,需用示波器测量实际波形,确保位时间误差在 ±0.5% 以内。 - **中断优先级**:FDCAN 中断优先级应高于其他非实时任务,避免接收溢出。 ## 六、总结 STM32H7 的 FDCAN 在经典 CAN 模式下,只要正确配置位时序,即可与普通 CAN 控制器无缝兼容。本文通过实测揭示了预分频器误算、采样点偏移等陷阱,并提供了完整的配置步骤和代码。开发者应始终基于 APB1 时钟计算,并验证采样点,以确保在 400MHz 主频下稳定通信。