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/(500e316)=50,实际位时间 = 5016/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 模式下的收发。

/* 初始化 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 主频下稳定通信。