# STM32H7 480MHz 下 FDCAN 与 TSN 结合的低延迟同步方案 ## 一、为什么需要 FDCAN + TSN? 传统 CAN 总线带宽低、延迟不确定,难以满足工业现场对实时性的苛刻要求。TSN(时间敏感网络)基于以太网提供纳秒级时钟同步和确定性传输,而 FDCAN(Flexible Data-rate CAN)在 CAN 2.0 基础上提升了速率(最高 8Mbps)和帧负载(64 字节)。将两者结合,可以利用 TSN 的全局时钟同步能力,为 FDCAN 网络提供统一时间基准,实现跨网段的精确事件同步。STM32H7 系列内置 FDCAN 控制器和以太网 MAC(支持 TSN 部分特性),在 480MHz 主频下可轻松处理协议栈,降低延迟。 ## 二、硬件架构与同步原理 ### 2.1 系统框图 ``` +-------------------+ +-------------------+ | STM32H7 (480MHz)| | TSN Switch | | | | | | +-------------+ | | +-------------+ | | | FDCAN1 |<-+------>| | TSN Endpoint| | | +-------------+ | | +-------------+ | | +-------------+ | | | | | Ethernet |<-+------>| | | +-------------+ | +-------------------+ +-------------------+ ``` STM32H7 的 FDCAN 和以太网控制器共享同一系统时钟,通过内部 PTP(精确时间协议)硬件单元,可以捕获帧收发时间戳。TSN 网络中的 gPTP(IEEE 802.1AS)同步主时钟,STM32H7 作为从节点,通过以太网接收同步报文,校准本地时钟,然后利用该时钟为 FDCAN 报文打时间戳。 ### 2.2 低延迟关键点 - **硬件时间戳**:FDCAN 和以太网控制器均支持硬件时间戳捕获,避免软件中断延迟。 - **DMA 传输**:使用 DMA 搬运数据,减少 CPU 干预。 - **中断优先级**:将 FDCAN 接收中断设为最高优先级,确保及时处理。 - **时钟同步精度**:通过 PTP 同步,本地时钟偏差可控制在 100ns 以内。 ## 三、FDCAN 配置步骤 ### 3.1 时钟配置 在 STM32H7 上,FDCAN 内核时钟来自 FDCAN 时钟(通常为 20MHz),通过 PLL 分频得到。确保系统时钟 480MHz 稳定。 ```c // 使用 HAL 库配置时钟 void SystemClock_Config(void) { RCC_OscInitTypeDef RCC_OscInitStruct = {0}; RCC_ClkInitTypeDef RCC_ClkInitStruct = {0}; // 配置 HSE 和 PLL,使 SYSCLK = 480MHz 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 = 2; RCC_OscInitStruct.PLL.PLLN = 60; RCC_OscInitStruct.PLL.PLLP = 2; // 480MHz RCC_OscInitStruct.PLL.PLLQ = 4; RCC_OscInitStruct.PLL.PLLR = 2; HAL_RCC_OscConfig(&RCC_OscInitStruct); RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_SYSCLK | RCC_CLOCKTYPE_HCLK | RCC_CLOCKTYPE_PCLK1 | RCC_CLOCKTYPE_PCLK2; RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK; RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1; RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2; RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV2; HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_4); } ``` ### 3.2 FDCAN 初始化 配置 FDCAN 为外部时钟模式,数据位速率 5Mbps,仲裁位速率 1Mbps,并启用硬件时间戳。 ```c FDCAN_HandleTypeDef hfdcan1; void FDCAN_Init(void) { 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 = DISABLE; hfdcan1.Init.TransmitPause = DISABLE; hfdcan1.Init.ProtocolException = DISABLE; hfdcan1.Init.NominalPrescaler = 10; // 1Mbps (20MHz/10/2) hfdcan1.Init.NominalSyncJumpWidth = 1; hfdcan1.Init.NominalTimeSeg1 = 13; hfdcan1.Init.NominalTimeSeg2 = 2; hfdcan1.Init.DataPrescaler = 2; // 5Mbps (20MHz/2/2) hfdcan1.Init.DataSyncJumpWidth = 1; hfdcan1.Init.DataTimeSeg1 = 13; hfdcan1.Init.DataTimeSeg2 = 2; hfdcan1.Init.StdFiltersNbr = 1; hfdcan1.Init.ExtFiltersNbr = 0; hfdcan1.Init.TxEventsNbr = 1; hfdcan1.Init.RxFifo0ElmtsNbr = 1; hfdcan1.Init.RxFifo0ElmtsSize = FDCAN_DATA_BYTES_MAX; hfdcan1.Init.TxEventsElmtsSize = FDCAN_DATA_BYTES_MAX; HAL_FDCAN_Init(&hfdcan1); // 配置时间戳单元,使用 TSU 时钟 HAL_FDCAN_EnableTimestamp(&hfdcan1, FDCAN_TIMESTAMP_UNIT_TSU); } ``` ### 3.3 时间戳同步 利用 TSN 的 gPTP 同步本地时钟,然后通过 HAL 函数获取 FDCAN 时间戳。 ```c // 假设 gPTP 已同步,本地时间变量 local_time_ns uint64_t local_time_ns; // 在 FDCAN 接收中断中获取时间戳 void HAL_FDCAN_RxFifo0MsgPendingCallback(FDCAN_HandleTypeDef *hfdcan) { FDCAN_RxHeaderTypeDef rxHeader; uint8_t data[64]; HAL_FDCAN_GetRxMessage(hfdcan, FDCAN_RX_FIFO0, &rxHeader, data); // 获取硬件时间戳(单位:TSU 时钟周期) uint32_t ts = rxHeader.Timestamp; // 转换为纳秒,假设 TSU 时钟为 20MHz,则每个周期 50ns uint64_t timestamp_ns = (uint64_t)ts * 50; // 与本地同步时间结合,得到全局时间 uint64_t global_time = local_time_ns + timestamp_ns; // 处理数据... } ``` ## 四、完整代码示例 以下是一个简单的发送和接收示例,展示如何利用时间戳实现同步。 ```c #include "main.h" FDCAN_HandleTypeDef hfdcan1; void FDCAN_SendSyncMessage(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 = len << 16; // 设置 DLC txHeader.BitRateSwitch = FDCAN_BRS_ON; txHeader.FDFormat = FDCAN_FD_CAN; txHeader.TxEventFifoControl = FDCAN_STORE_TX_EVENT; // 存储发送事件 txHeader.MessageMarker = 0; HAL_FDCAN_AddMessageToTxFifoQ(&hfdcan1, &txHeader, data); // 发送后,可在 TxEventFifo 回调中获取时间戳 } void HAL_FDCAN_TxEventFifoCallback(FDCAN_HandleTypeDef *hfdcan, uint32_t TxEventFifoITs) { FDCAN_TxEventFifoTypeDef txEvent; HAL_FDCAN_GetTxEventFifo(hfdcan, &txEvent); uint32_t ts = txEvent.Timestamp; // 记录发送时间戳 } int main(void) { HAL_Init(); SystemClock_Config(); FDCAN_Init(); // 启动 FDCAN HAL_FDCAN_Start(&hfdcan1); HAL_FDCAN_ActivateNotification(&hfdcan1, FDCAN_IT_RX_FIFO0_NEW_MESSAGE | FDCAN_IT_TX_EVENT_FIFO, 0); uint8_t msg[] = "Sync"; while (1) { FDCAN_SendSyncMessage(0x123, msg, 4); HAL_Delay(100); } } ``` ## 五、注意事项 - **时钟同步精度**:确保 PTP 同步周期足够短(如 1s),并校准晶振漂移。 - **中断优先级**:将 FDCAN 中断优先级设为最高(如 0),避免被其他中断延迟。 - **DMA 使用**:在高速率下,建议使用 DMA 传输 FDCAN 数据,减少 CPU 负载。 - **时间戳溢出**:32 位时间戳在 20MHz 下约 214 秒溢出,需在软件中处理扩展。 - **硬件设计**:FDCAN 和以太网走线应短且等长,减少信号延迟。 - **协议栈开销**:TSN 协议栈(如 gPTP)需占用 CPU,建议使用 RTOS 或硬件加速。 ## 六、总结 通过 STM32H7 的硬件时间戳和 TSN 的全局同步,FDCAN 网络可以实现微秒级的事件同步,满足工业控制对低延迟和高确定性的需求。本文提供的配置和代码可作为基础,开发者可根据具体场景调整参数,并集成更完整的 TSN 协议栈。