引言

在工业 4.0 与车载域控架构中,TSN(时间敏感网络)成为确定性通信的基石,而 FDCAN(Flexible Data-rate CAN)则广泛存在于现有车载网络。当两者共存时,跨网络的时间同步至关重要。STM32H7 系列最高运行于 400MHz,内置 FDCAN 控制器支持硬件时间戳,但默认情况下,其时间基准与外部 TSN 时钟(如 802.1AS)无关联。本文介绍一种利用硬件触发(HRTIM 或通用定时器)将 FDCAN 时间戳与 TSN 时钟对齐的工程方法,实现亚微秒级同步误差。

原理概述

FDCAN 时间戳机制

STM32H7 的 FDCAN 内核(Bosch M_CAN)提供 16 位或 32 位时间戳计数器,时钟源可选为 FDCAN 内核时钟(如 20MHz)或外部时钟。每次接收/发送帧时,硬件自动捕获当前计数值到消息 RAM 的 Rx/Tx Timestamp 字段。但该计数器独立运行,无法直接感知 TSN 的全局时间。

TSN 时间同步(gPTP)

TSN 基于 IEEE 802.1AS(gPTP)实现主从时钟同步,从设备通过 Sync/Follow_Up 报文调整本地时钟。通常,TSN 交换机(如 Microchip LAN9662)提供硬件时间戳接口,并可通过 GPIO 输出同步脉冲(如 1PPS)或触发信号。

硬件触发对齐策略

核心思路:利用 STM32H7 的定时器(如 TIM1/TIM8 或 HRTIM)产生一个与 TSN 时钟同步的触发事件,该事件触发 FDCAN 时间戳计数器复位或锁存,同时记录 TSN 全局时间,从而建立映射关系。具体实现分两步:

  1. 同步脉冲捕获:将 TSN 的 1PPS 信号连接到 STM32H7 的输入捕获引脚,捕获定时器计数值,并记录 TSN 全局时间(通过 SPI/并行读取 TSN 交换机寄存器)。
  2. 触发对齐:在捕获到同步脉冲后,通过软件或硬件自动调整 FDCAN 时间戳计数器,使其与 TSN 时间对齐。更优方案是使用定时器的 TRGO 直接触发 FDCAN 的 timestamp 复位(需检查 FDCAN 是否支持外部复位,若不支持,则用中断快速写寄存器)。

硬件配置

时钟树设计

  • 主频:400MHz(HSE=25MHz,PLL1 倍频)。
  • FDCAN 内核时钟:选择 PLL2 输出 20MHz,确保时间戳分辨率 50ns。
  • TIM1 时钟:挂在 APB2 上,预分频后得到 1MHz 计数频率(1μs 分辨率)。
  • 外部 1PPS 信号:连接到 PA0(TIM2_CH1)用于捕获,同时连接到 EXTI 用于快速中断。

引脚分配

| 功能 | 引脚 | 复用 | |------|------|------| | FDCAN1_TX | PD1 | AF9 | | FDCAN1_RX | PD0 | AF9 | | 1PPS_IN | PA0 | TIM2_CH1 | | TSN_SYNC_OUT | PB0 | TIM1_CH1 (TRGO) |

软件实现

初始化代码(HAL 库)

// 系统时钟配置:400MHz,FDCAN 时钟 20MHz
void SystemClock_Config(void) {
    RCC_OscInitTypeDef RCC_OscInit = {0};
    RCC_ClkInitTypeDef RCC_ClkInit = {0};
    RCC_PeriphCLKInitTypeDef PeriphClkInit = {0};

    // 省略标准配置,关键设置:
    PeriphClkInit.PeriphClockSelection = RCC_PERIPHCLK_FDCAN;
    PeriphClkInit.Fdcan1ClockSelection = RCC_FDCANCLKSOURCE_PLL2;
    HAL_RCCEx_PeriphCLKConfig(&PeriphClkInit);
}

// TIM2 输入捕获初始化,用于捕获 1PPS
void TIM2_IC_Init(void) {
    TIM_IC_InitTypeDef sICConfig = {0};
    htim2.Instance = TIM2;
    htim2.Init.Prescaler = 400 - 1; // 400MHz/400 = 1MHz
    htim2.Init.CounterMode = TIM_COUNTERMODE_UP;
    htim2.Init.Period = 0xFFFFFFFF;
    HAL_TIM_IC_Init(&htim2);

    sICConfig.ICPolarity = TIM_INPUTCHANNELPOLARITY_RISING;
    sICConfig.ICSelection = TIM_IC_SELECTION_DIRECTTI;
    sICConfig.ICPrescaler = TIM_ICPSC_DIV1;
    sICConfig.ICFilter = 0;
    HAL_TIM_IC_ConfigChannel(&htim2, &sICConfig, TIM_CHANNEL_1);
    HAL_TIM_IC_Start_IT(&htim2, TIM_CHANNEL_1);
}

// FDCAN 初始化,使能硬件时间戳
void FDCAN1_Init(void) {
    hfdcan1.Instance = FDCAN1;
    hfdcan1.Init.ClockDivider = FDCAN_CLOCK_DIV1;
    hfdcan1.Init.FrameFormat = FDCAN_FRAME_FD_BRS;
    // ... 其他参数
    hfdcan1.Init.TimestampConfig = FDCAN_TIMESTAMP_ENABLE;
    hfdcan1.Init.TimestampPrescaler = 1; // 20MHz 计数
    HAL_FDCAN_Init(&hfdcan1);
}

同步中断处理

// 1PPS 捕获中断回调
void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim) {
    if (htim->Instance == TIM2) {
        uint32_t capture_val = HAL_TIM_ReadCapturedValue(&htim2, TIM_CHANNEL_1);
        // 记录捕获时刻的 TSN 全局时间(通过 SPI 读取 TSN 交换机寄存器)
        uint64_t tsn_time = ReadTSNTime();
        // 计算映射:FDCAN 时间戳 = 捕获值 * 50ns + 偏移
        // 此处简化,实际需考虑中断延迟补偿
        fdcan_offset = tsn_time - (capture_val * 50); // 单位 ns
        // 可选:立即调整 FDCAN 时间戳计数器(需在 FDCAN 空闲时)
        HAL_FDCAN_SetTimestamp(&hfdcan1, capture_val); // 假设有该函数,实际需操作寄存器
    }
}

硬件触发对齐(高级方案)

若 FDCAN 支持外部触发复位(部分型号支持),可使用 TIM1 的 TRGO 连接 FDCAN 的 timestamp reset。配置 TIM1 为 PWM 模式,输出同步脉冲(与 1PPS 对齐),并设置 TRGO 为更新事件。

// TIM1 配置为 PWM,输出 1Hz 同步脉冲,TRGO 触发 FDCAN 复位
void TIM1_PWM_Init(void) {
    TIM_OC_InitTypeDef sConfigOC = {0};
    htim1.Instance = TIM1;
    htim1.Init.Prescaler = 40000 - 1; // 400MHz/40000 = 10kHz
    htim1.Init.Period = 10000 - 1;    // 10kHz/10000 = 1Hz
    htim1.Init.CounterMode = TIM_COUNTERMODE_UP;
    HAL_TIM_PWM_Init(&htim1);

    sConfigOC.OCMode = TIM_OCMODE_PWM1;
    sConfigOC.Pulse = 5000; // 50% 占空比
    sConfigOC.OCPolarity = TIM_OCPOLARITY_HIGH;
    HAL_TIM_PWM_ConfigChannel(&htim1, &sConfigOC, TIM_CHANNEL_1);

    TIM_MasterConfigTypeDef sMasterConfig = {0};
    sMasterConfig.MasterOutputTrigger = TIM_TRGO_UPDATE;
    sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
    HAL_TIMEx_MasterConfigSynchronization(&htim1, &sMasterConfig);
    HAL_TIM_PWM_Start(&htim1, TIM_CHANNEL_1);
}

注意:FDCAN 的 timestamp 复位触发需查阅参考手册(RM0433)中 FDCAN 的 TIMESTAMP 控制寄存器(TSCC),确认是否有外部触发位。若无,则只能采用软件中断方式,但需注意中断延迟抖动(通常 1-2μs)。

完整代码示例(关键部分)

// 主循环中,周期性检查同步状态
int main(void) {
    HAL_Init();
    SystemClock_Config();
    MX_GPIO_Init();
    TIM2_IC_Init();
    TIM1_PWM_Init();
    FDCAN1_Init();
    HAL_FDCAN_Start(&hfdcan1);

    while (1) {
        // 应用层发送 FDCAN 消息,时间戳自动捕获
        FDCAN_TxHeaderTypeDef txHeader = {0};
        txHeader.Identifier = 0x123;
        txHeader.Timestamp = 0; // 硬件自动填充
        uint8_t data[8] = {0};
        HAL_FDCAN_AddMessageToTxFifoQ(&hfdcan1, &txHeader, data);
        HAL_Delay(1000);
    }
}

// 读取 TSN 时间(示例通过 SPI)
uint64_t ReadTSNTime(void) {
    uint8_t buf[8];
    // SPI 读取 TSN 交换机寄存器,解析为纳秒时间
    // 此处省略具体实现
    return 0x123456789ULL;
}

注意事项

  • 中断延迟补偿:软件对齐方案中,从 1PPS 上升沿到中断处理存在延迟(约 1-2μs),需测量并补偿。可使用 DWT 或 TIM 捕获中断入口时间,计算精确延迟。
  • 时钟源一致性:FDCAN 时间戳时钟必须与定时器时钟同源(或已知比例),否则映射关系漂移。建议使用同一 PLL 输出。
  • FDCAN 时间戳溢出:32 位计数器在 20MHz 下约 214 秒溢出,需软件处理溢出中断,维护扩展时间。
  • 硬件触发限制:并非所有 STM32H7 型号支持 FDCAN 外部复位时间戳,务必查阅参考手册。若不支持,可考虑使用外部逻辑门将 1PPS 与 FDCAN 的 TTCAN 模式结合。
  • 调试建议:使用逻辑分析仪同时抓取 1PPS 和 FDCAN TX 信号,验证时间戳误差。

总结

通过硬件触发与软件补偿相结合,STM32H7 在 400MHz 下可实现 FDCAN 与 TSN 的亚微秒级时间同步。本文提供的方案兼顾灵活性与精度,开发者可根据实际硬件资源选择纯中断或硬件触发方式。未来,随着 STM32H7 系列对 TSN 支持的增强,此类设计将更加简化。