# 引言 在工业 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 库) ```c // 系统时钟配置: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); } ``` ## 同步中断处理 ```c // 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 为更新事件。 ```c // 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)。 # 完整代码示例(关键部分) ```c // 主循环中,周期性检查同步状态 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 支持的增强,此类设计将更加简化。