引言

在工业自动化与智能驾驶场景中,TSN(IEEE 802.1AS)提供亚微秒级网络同步,而 CAN FD 仍是车身与底盘控制的核心总线。当两者共存于同一网关时,必须实现时间基准对齐,否则数据融合将产生不可接受的延迟误差。STM32H7 系列凭借 400MHz Cortex-M7 内核、双 FDCAN 控制器以及灵活的定时器/外部信号接口,为硬件级时间同步提供了可能。

1. 硬件时序对齐原理

1.1 时间同步的核心挑战

软件同步(如读取系统 tick 后打时间戳)会引入中断延迟、缓存等待等不确定性,通常误差在微秒级。而 TSN 要求同步精度优于 1μs,FDCAN 的硬件时间戳分辨率可达 20ns(50MHz 时钟)。因此,必须利用硬件外设直接捕获/生成时间事件。

1.2 STM32H7 的硬件资源

  • FDCAN 控制器:每个 FDCAN 内核内置 16 位/32 位时间戳计数器(TSCC),可配置为基于内部定时器或外部时钟源递增。
  • 定时器(TIM2/TIM5):32 位高级定时器,支持外部时钟模式,可作为全局时间基准(如 10ns 分辨率)。
  • 外部中断/事件控制器(EXTI):可将 TSN 的同步脉冲(如 PPS)映射到定时器触发输入。
  • DMA:用于自动传输时间戳数据,减少 CPU 干预。

1.3 对齐策略

采用主-从时间同步:TSN 主时钟通过 PPS 信号(每秒一个脉冲)输出到 STM32H7 的定时器输入捕获通道,定时器记录本地时间并校准偏移;同时,FDCAN 的硬件时间戳计数器与定时器共享同一时钟源(如 PLL2 输出 100MHz),从而保证 FDCAN 时间戳与 TSN 时间基准严格对齐。

2. 配置步骤

2.1 时钟树设计

  • 使用 HSE(25MHz)经 PLL1 生成 400MHz 系统时钟。
  • 配置 PLL2 输出 100MHz 作为 FDCAN 内核时钟(FDCANCLK),同时作为 TIM2 的时钟源(通过 MUX 选择)。
  • 确保 FDCAN 时间戳时钟(TSCC)选择为 FDCANCLK 分频,例如 50MHz(分频 2)。

2.2 FDCAN 硬件时间戳配置

  1. 使能 FDCAN 时钟(RCC->APB1ENR1 |= RCC_APB1ENR1_FDCAN1EN)。
  2. 配置 FDCAN 位时序(数据段 2Mbps,仲裁段 500kbps)。
  3. 设置时间戳单元:
    • 选择内部时钟源(FDCANCLK)。
    • 设置分频系数(例如 2,得到 50MHz 计数频率)。
    • 使能时间戳捕获(在接收/发送帧时自动打戳)。

2.3 TSN 同步脉冲接入

  • 将 TSN 的 PPS 信号连接到 TIM2_CH1(PA0)。
  • 配置 TIM2 为输入捕获模式,上升沿触发,并记录捕获值。
  • 通过比较捕获值与本地时间基准,计算偏移量并写入 FDCAN 时间戳偏移寄存器(TSOFF)。

2.4 中断与 DMA

  • 使用 TIM2 捕获中断,在中断中更新偏移量(注意中断延迟,但仅需微秒级,可接受)。
  • 对于 FDCAN 消息,使用 FIFO 的硬件时间戳,通过 DMA 读取到内存,避免 CPU 读取延迟。

3. 完整代码示例

以下基于 STM32H743 和 HAL 库,演示核心配置(省略 GPIO 初始化)。

// 1. 初始化 FDCAN 时间戳
void FDCAN_TS_Init(FDCAN_HandleTypeDef *hfdcan) {
    hfdcan->Init.ClockDivider = FDCAN_CLOCK_DIV2;  // 100MHz -> 50MHz
    hfdcan->Init.FrameFormat = FDCAN_FRAME_FD_BRS;
    hfdcan->Init.Mode = FDCAN_MODE_NORMAL;
    hfdcan->Init.AutoRetransmission = DISABLE;
    hfdcan->Init.TransmitPause = DISABLE;
    hfdcan->Init.ProtocolException = DISABLE;
    hfdcan->Init.NominalPrescaler = 2;  // 500kbps
    hfdcan->Init.NominalSyncJumpWidth = 1;
    hfdcan->Init.NominalTimeSeg1 = 13;
    hfdcan->Init.NominalTimeSeg2 = 2;
    hfdcan->Init.DataPrescaler = 1;     // 2Mbps
    hfdcan->Init.DataSyncJumpWidth = 1;
    hfdcan->Init.DataTimeSeg1 = 13;
    hfdcan->Init.DataTimeSeg2 = 2;
    hfdcan->Init.StdFiltersNbr = 1;
    hfdcan->Init.ExtFiltersNbr = 0;
    hfdcan->Init.TxEventsNbr = 0;
    hfdcan->Init.RxFifo0ElmtsNbr = 8;
    hfdcan->Init.RxFifo0ElmtsSize = FDCAN_DATA_BYTES_8;
    hfdcan->Init.RxFifo1ElmtsNbr = 0;
    hfdcan->Init.RxFifo1ElmtsSize = FDCAN_DATA_BYTES_8;
    hfdcan->Init.TxEventsNbr = 0;
    hfdcan->Init.TxBuffersNbr = 0;
    HAL_FDCAN_Init(hfdcan);

    // 配置时间戳单元
    FDCAN_TimestampConfigTypeDef tsConfig;
    tsConfig.TimestampPrescaler = 1;  // 50MHz/1 = 50MHz
    tsConfig.TimestampGlobal = FDCAN_TIMESTAMP_GLOBAL;  // 全局时间戳
    tsConfig.TimestampInternal = FDCAN_TIMESTAMP_INTERNAL; // 使用内部时钟
    HAL_FDCAN_ConfigTimestamp(hfdcan, &tsConfig);

    // 使能接收时间戳捕获
    HAL_FDCAN_ActivateTimestamp(hfdcan, FDCAN_TIMESTAMP_RX);
}

// 2. TIM2 输入捕获初始化(PPS 信号)
void TIM2_PPS_Init(void) {
    __HAL_RCC_TIM2_CLK_ENABLE();
    TIM_HandleTypeDef htim2 = {0};
    htim2.Instance = TIM2;
    htim2.Init.Prescaler = 0;  // 100MHz 计数
    htim2.Init.CounterMode = TIM_COUNTERMODE_UP;
    htim2.Init.Period = 0xFFFFFFFF;  // 32 位自由运行
    htim2.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
    HAL_TIM_IC_Init(&htim2);

    TIM_IC_InitTypeDef sConfigIC = {0};
    sConfigIC.ICPolarity = TIM_INPUTCHANNELPOLARITY_RISING;
    sConfigIC.ICSelection = TIM_ICSELECTION_DIRECTTI;
    sConfigIC.ICPrescaler = TIM_ICPSC_DIV1;
    sConfigIC.ICFilter = 0;
    HAL_TIM_IC_ConfigChannel(&htim2, &sConfigIC, TIM_CHANNEL_1);
    HAL_TIM_IC_Start_IT(&htim2, TIM_CHANNEL_1);
}

// 3. 捕获中断回调(更新 FDCAN 偏移)
void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim) {
    if (htim->Instance == TIM2) {
        uint32_t pps_capture = HAL_TIM_ReadCapturedValue(htim, TIM_CHANNEL_1);
        // 假设 TSN 主时钟的 PPS 对应全局时间 T_global(例如 0 或已知值)
        // 计算偏移:offset = T_global - pps_capture(需考虑溢出)
        uint32_t offset = 0;  // 根据实际同步协议计算
        // 写入 FDCAN 时间戳偏移寄存器(通过 HAL 或直接寄存器操作)
        FDCAN1->TSOFF = offset;
    }
}

// 4. 接收消息时读取时间戳(示例)
void FDCAN_RxCallback(FDCAN_HandleTypeDef *hfdcan, uint32_t fifo) {
    FDCAN_RxHeaderTypeDef rxHeader;
    uint8_t data[8];
    HAL_FDCAN_GetRxMessage(hfdcan, fifo, &rxHeader, data);
    uint32_t timestamp = rxHeader.Timestamp;  // 硬件时间戳,已与 TSN 对齐
    // 进一步处理...
}

4. 注意事项

  • 时钟同步精度:确保 FDCAN 时间戳时钟与 TIM2 时钟同源(如均来自 PLL2),否则存在相位漂移。建议使用 PLL2 的专用输出,并避免分频不一致。
  • 溢出处理:32 位时间戳在 50MHz 下约 85.9 秒回绕,需在应用层实现扩展(如记录秒数)。
  • 中断优先级:TIM2 捕获中断应设为最高优先级,但中断处理需极短(仅计算偏移),避免影响 FDCAN 接收中断。
  • 硬件连接:PPS 信号应使用高速数字隔离器(如 ISO7721)以降低噪声,且走线尽量短。
  • 调试技巧:使用逻辑分析仪同时抓取 PPS 和 FDCAN 发送的 SYNC 帧,验证对齐误差是否在 ±100ns 内。

5. 总结

通过 STM32H7 的硬件时间戳单元和外部同步接口,可以实现 FDCAN 与 TSN 的亚微秒级时间对齐,无需软件干预。本文提供的配置方法和代码可直接应用于网关设计。实际项目中,还需结合具体 TSN 协议栈(如 gPTP)进行偏移计算,但硬件基础已就绪。