# 引言 在工业自动化、机器人控制等场景中,FDCAN(Flexible Data-rate CAN)与 TSN(Time-Sensitive Networking)以太网常被用于实时通信。两者协同工作时,时间同步精度是决定系统性能的关键。STM32H7 系列以 480MHz 主频和丰富的外设资源,为高精度时间同步提供了硬件基础。然而,仅靠软件打时间戳往往受中断延迟、总线竞争等因素影响,产生微秒级抖动。本文聚焦于 STM32H7 的硬件级抖动补偿,通过 FDCAN 和 TSN 的硬件时间戳单元,实现纳秒级同步。 # 原理讲解 ## 1. 时间同步的核心:硬件时间戳 传统软件时间戳在报文接收/发送时,由 CPU 读取定时器值,但中断响应时间不确定,导致抖动。STM32H7 的 FDCAN 和 TSN 外设内置硬件时间戳单元,在报文帧起始(SOF)或帧结束(EOF)时刻自动捕获定时器值,无需 CPU 干预,从而消除软件延迟。 - **FDCAN 时间戳**:每个 FDCAN 内核包含一个 16 位或 32 位自由运行定时器,可配置为在报文接收或发送时捕获当前值,并存入消息 RAM 的特定字段。 - **TSN 时间戳**:STM32H7 的 TSN 以太网 MAC 支持 IEEE 1588v2,硬件在 PTP 报文(如 Sync、Delay_Req)的特定位置打时间戳,精度可达纳秒级。 ## 2. 抖动来源与硬件补偿 即使有硬件时间戳,仍存在残余抖动,主要来源包括: - **FDCAN 位时序误差**:FDCAN 的波特率由预分频器和位段配置决定,若时钟源精度不足,会导致位时间偏移。 - **TSN 时钟同步偏差**:TSN 使用 PTP 协议同步时钟,但 PTP 报文在 MAC 和 PHY 之间的传输延迟会引入抖动。 - **跨域同步**:FDCAN 和 TSN 各自使用独立定时器,需统一到同一时间基准。 STM32H7 的硬件级补偿策略: - **FDCAN 自动重同步**:FDCAN 支持 CAN 协议中的硬同步和重同步,通过调整相位缓冲段来吸收位流中的相位误差。 - **TSN 时钟滤波**:TSN 外设内置时钟伺服(Clock Servo),通过 PI 控制器对 PTP 时间戳进行滤波,平滑时钟调整。 - **共享时间基准**:利用 STM32H7 的 TIM 定时器作为全局时间源,FDCAN 和 TSN 的时间戳均基于该定时器,实现跨域统一。 # 配置步骤 ## 1. 系统时钟配置 首先,确保 STM32H7 运行在 480MHz。使用 PLL1 将 HSE(如 25MHz)倍频至 480MHz,并配置 AHB、APB 时钟分频。 ```c // 使用 HAL 库配置时钟 RCC_OscInitTypeDef RCC_OscInitStruct = {0}; RCC_ClkInitTypeDef RCC_ClkInitStruct = {0}; RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE; RCC_OscInitStruct.HSEState = RCC_HSE_ON; RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON; RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE; RCC_OscInitStruct.PLL.PLLM = 25; // 25MHz / 25 = 1MHz RCC_OscInitStruct.PLL.PLLN = 480; // 1MHz * 480 = 480MHz RCC_OscInitStruct.PLL.PLLP = 2; // 480MHz / 2 = 240MHz (用于 SYS) RCC_OscInitStruct.PLL.PLLQ = 4; // 用于 USB等 RCC_OscInitStruct.PLL.PLLR = 2; // 用于 FDCAN 等 HAL_RCC_OscConfig(&RCC_OscInitStruct); RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK | RCC_CLOCKTYPE_SYSCLK | RCC_CLOCKTYPE_PCLK1 | RCC_CLOCKTYPE_PCLK2; RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK; RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1; RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2; // 120MHz RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV2; // 120MHz HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_4); ``` ## 2. FDCAN 硬件时间戳配置 FDCAN 使用独立时钟源(如 PLL1Q),并启用时间戳功能。 ```c FDCAN_HandleTypeDef hfdcan1; FDCAN_FilterTypeDef sFilterConfig; hfdcan1.Instance = FDCAN1; hfdcan1.Init.ClockDivider = FDCAN_CLOCK_DIV1; hfdcan1.Init.FrameFormat = FDCAN_FRAME_FD_BRS; hfdcan1.Init.Mode = FDCAN_MODE_NORMAL; hfdcan1.Init.AutoRetransmission = ENABLE; // 其他参数... // 启用时间戳:使用内部定时器,64位计数器(由TIM2提供) hfdcan1.Init.TimestampConfig = FDCAN_TIMESTAMP_INTERNAL; hfdcan1.Init.TimestampPrescaler = 1; // 1MHz 时基 HAL_FDCAN_Init(&hfdcan1); // 配置全局时间源(TIM2) __HAL_RCC_TIM2_CLK_ENABLE(); TIM_HandleTypeDef htim2; htim2.Instance = TIM2; htim2.Init.Prescaler = 480 - 1; // 480MHz / 480 = 1MHz htim2.Init.CounterMode = TIM_COUNTERMODE_UP; htim2.Init.Period = 0xFFFFFFFF; // 32位 HAL_TIM_Base_Init(&htim2); HAL_TIM_Base_Start(&htim2); // 将TIM2的计数器映射到FDCAN时间戳 HAL_FDCAN_EnableTimestamp(&hfdcan1, TIM2); ``` ## 3. TSN 以太网时间戳配置 STM32H7 的 TSN 外设支持 IEEE 1588v2,需配置 PTP 时钟和报文过滤。 ```c ETH_HandleTypeDef heth; ETH_MACConfigTypeDef MACConf; // 初始化 ETH 外设(略) // 启用 PTP 硬件时间戳 HAL_ETH_PTP_Init(&heth); // 配置 PTP 时钟:使用 TIM1 作为时间源,频率 1MHz heth.PTP_InitStruct.PTP_TimeStampMode = ETH_PTP_TIMESTAMP_MODE_FINE; heth.PTP_InitStruct.PTP_TimeStampUpdate = ETH_PTP_TIMESTAMP_UPDATE_DIGITAL; heth.PTP_InitStruct.PTP_TimeStampPrescaler = 480 - 1; // 1MHz HAL_ETH_PTP_Config(&heth); // 设置 PTP 报文过滤(如 Sync 报文) HAL_ETH_PTP_SetMessageFilter(&heth, ETH_PTP_MESSAGE_SYNC | ETH_PTP_MESSAGE_DELAY_REQ); ``` ## 4. 跨域时间同步补偿 由于 FDCAN 和 TSN 均基于 TIM2 作为时间基准,需确保两者时间戳读取一致。在应用层,可通过读取 TIM2 计数器并转换为纳秒,实现统一时间轴。 ```c uint64_t GetGlobalTimeNs(void) { uint32_t ticks = __HAL_TIM_GET_COUNTER(&htim2); return (uint64_t)ticks * 1000; // 1MHz -> 1us 精度,可扩展为纳秒 } ``` # 完整代码示例 以下示例演示如何配置 FDCAN 和 TSN 时间戳,并在中断中读取时间戳进行同步。 ```c // 初始化全局时间基准 void GlobalTime_Init(void) { // 配置 TIM2 为 1MHz 自由运行计数器 __HAL_RCC_TIM2_CLK_ENABLE(); htim2.Instance = TIM2; htim2.Init.Prescaler = 480 - 1; htim2.Init.CounterMode = TIM_COUNTERMODE_UP; htim2.Init.Period = 0xFFFFFFFF; HAL_TIM_Base_Init(&htim2); HAL_TIM_Base_Start(&htim2); } // FDCAN 接收中断回调 void HAL_FDCAN_RxFifo0MsgPendingCallback(FDCAN_HandleTypeDef *hfdcan) { FDCAN_RxHeaderTypeDef rxHeader; uint8_t data[8]; HAL_FDCAN_GetRxMessage(hfdcan, FDCAN_RX_FIFO0, &rxHeader, data); // 获取硬件时间戳(单位:1MHz tick) uint32_t ts = rxHeader.Timestamp; uint64_t global_ns = GetGlobalTimeNs(); // 当前全局时间 // 计算偏移并进行补偿(示例:简单打印) printf("FDCAN Rx TS: %u, Global: %llu ns\n", ts, global_ns); } // TSN PTP 事件回调 void HAL_ETH_PTP_EventCallback(ETH_HandleTypeDef *heth, uint32_t event) { if (event & ETH_PTP_EVENT_SYNC) { uint32_t ts = HAL_ETH_PTP_GetTimestamp(heth); printf("TSN Sync TS: %u\n", ts); } } int main(void) { HAL_Init(); SystemClock_Config(); // 480MHz GlobalTime_Init(); FDCAN_Init(); ETH_Init(); // 启动 FDCAN 接收中断 HAL_FDCAN_Start(&hfdcan1); HAL_FDCAN_ActivateNotification(&hfdcan1, FDCAN_IT_RX_FIFO0_MSG_PENDING, 0); while (1) { // 主循环 } } ``` # 注意事项 - **时钟源精度**:FDCAN 和 TSN 时间戳的精度依赖于时钟源。建议使用外部高精度晶振(如 25MHz ±20ppm),并启用 STM32H7 的时钟校准功能。 - **时间戳溢出**:32 位时间戳在 1MHz 下约 71 分钟溢出一次,需在应用层处理溢出,如使用 64 位计数器扩展。 - **FDCAN 位时序**:在 480MHz 下,FDCAN 的位时序需仔细计算,确保采样点位置合理,避免因位同步误差导致时间戳偏移。 - **TSN 时钟伺服**:PTP 同步过程中,时钟伺服参数(如 PI 系数)需根据网络环境调整,过大可能导致振荡,过小则收敛慢。 - **调试技巧**:使用逻辑分析仪同时抓取 FDCAN 和 TSN 报文,对比时间戳,可直观评估补偿效果。 # 结语 通过 STM32H7 的硬件时间戳单元,FDCAN 与 TSN 的时间同步精度可达到纳秒级,有效消除软件抖动。本文提供的配置方法和代码示例,可帮助开发者快速实现高精度同步。在实际项目中,还需根据具体应用场景优化时钟伺服参数和补偿算法,以发挥硬件最大潜力。