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 稳定。

// 使用 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,并启用硬件时间戳。

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 时间戳。

// 假设 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;
    // 处理数据...
}

四、完整代码示例

以下是一个简单的发送和接收示例,展示如何利用时间戳实现同步。

#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 协议栈。