STM32H7 400MHz 下 FDCAN 与 TSN 时间同步的硬件时序对齐实现
👁 3 阅读 · 2026-08-27 · 嵌入式
在工业以太网与车载网络中,TSN(时间敏感网络)与 CAN FD 的融合日益普遍。本文深入探讨 STM32H7 在 400MHz 主频下,如何通过硬件机制实现 FDCAN 与 TSN 的时间同步对齐,避免软件抖动。内容涵盖时间戳单元(TSU)、外部时钟同步接口、FDCAN 硬件时间戳捕获及配置步骤,并提供完整代码示例与工程注意事项,帮助开发者构建高精度混合网络同步系统。
# 引言
在工业自动化与智能驾驶场景中,TSN(IEEE 802.1AS)提供亚微秒级网络同步,而 CAN FD 仍是车身与底盘控制的核心总线。当两者共存于同一网关时,必须实现时间基准对齐,否则数据融合将产生不可接受的延迟误差。STM32H7 系列凭借 400MHz Cortex-M7 内核、双 FDCAN 控制器以及灵活的定时器/外部信号接口,为硬件级时间同步提供了可能。
# 1. 硬件时序对齐原理
## 1.1 时间同步的核心挑战
软件同步(如读取系统 tick 后打时间戳)会引入中断延迟、缓存等待等不确定性,通常误差在微秒级。而 TSN 要求同步精度优于 1μs,FDCAN 的硬件时间戳分辨率可达 20ns(50MHz 时钟)。因此,必须利用硬件外设直接捕获/生成时间事件。
## 1.2 STM32H7 的硬件资源
- **FDCAN 控制器**:每个 FDCAN 内核内置 16 位/32 位时间戳计数器(TSCC),可配置为基于内部定时器或外部时钟源递增。
- **定时器(TIM2/TIM5)**:32 位高级定时器,支持外部时钟模式,可作为全局时间基准(如 10ns 分辨率)。
- **外部中断/事件控制器(EXTI)**:可将 TSN 的同步脉冲(如 PPS)映射到定时器触发输入。
- **DMA**:用于自动传输时间戳数据,减少 CPU 干预。
## 1.3 对齐策略
采用**主-从时间同步**:TSN 主时钟通过 PPS 信号(每秒一个脉冲)输出到 STM32H7 的定时器输入捕获通道,定时器记录本地时间并校准偏移;同时,FDCAN 的硬件时间戳计数器与定时器共享同一时钟源(如 PLL2 输出 100MHz),从而保证 FDCAN 时间戳与 TSN 时间基准严格对齐。
# 2. 配置步骤
## 2.1 时钟树设计
- 使用 HSE(25MHz)经 PLL1 生成 400MHz 系统时钟。
- 配置 PLL2 输出 100MHz 作为 FDCAN 内核时钟(FDCANCLK),同时作为 TIM2 的时钟源(通过 MUX 选择)。
- 确保 FDCAN 时间戳时钟(TSCC)选择为 FDCANCLK 分频,例如 50MHz(分频 2)。
## 2.2 FDCAN 硬件时间戳配置
1. 使能 FDCAN 时钟(RCC->APB1ENR1 |= RCC_APB1ENR1_FDCAN1EN)。
2. 配置 FDCAN 位时序(数据段 2Mbps,仲裁段 500kbps)。
3. 设置时间戳单元:
- 选择内部时钟源(FDCANCLK)。
- 设置分频系数(例如 2,得到 50MHz 计数频率)。
- 使能时间戳捕获(在接收/发送帧时自动打戳)。
## 2.3 TSN 同步脉冲接入
- 将 TSN 的 PPS 信号连接到 TIM2_CH1(PA0)。
- 配置 TIM2 为输入捕获模式,上升沿触发,并记录捕获值。
- 通过比较捕获值与本地时间基准,计算偏移量并写入 FDCAN 时间戳偏移寄存器(TSOFF)。
## 2.4 中断与 DMA
- 使用 TIM2 捕获中断,在中断中更新偏移量(注意中断延迟,但仅需微秒级,可接受)。
- 对于 FDCAN 消息,使用 FIFO 的硬件时间戳,通过 DMA 读取到内存,避免 CPU 读取延迟。
# 3. 完整代码示例
以下基于 STM32H743 和 HAL 库,演示核心配置(省略 GPIO 初始化)。
```c
// 1. 初始化 FDCAN 时间戳
void FDCAN_TS_Init(FDCAN_HandleTypeDef *hfdcan) {
hfdcan->Init.ClockDivider = FDCAN_CLOCK_DIV2; // 100MHz -> 50MHz
hfdcan->Init.FrameFormat = FDCAN_FRAME_FD_BRS;
hfdcan->Init.Mode = FDCAN_MODE_NORMAL;
hfdcan->Init.AutoRetransmission = DISABLE;
hfdcan->Init.TransmitPause = DISABLE;
hfdcan->Init.ProtocolException = DISABLE;
hfdcan->Init.NominalPrescaler = 2; // 500kbps
hfdcan->Init.NominalSyncJumpWidth = 1;
hfdcan->Init.NominalTimeSeg1 = 13;
hfdcan->Init.NominalTimeSeg2 = 2;
hfdcan->Init.DataPrescaler = 1; // 2Mbps
hfdcan->Init.DataSyncJumpWidth = 1;
hfdcan->Init.DataTimeSeg1 = 13;
hfdcan->Init.DataTimeSeg2 = 2;
hfdcan->Init.StdFiltersNbr = 1;
hfdcan->Init.ExtFiltersNbr = 0;
hfdcan->Init.TxEventsNbr = 0;
hfdcan->Init.RxFifo0ElmtsNbr = 8;
hfdcan->Init.RxFifo0ElmtsSize = FDCAN_DATA_BYTES_8;
hfdcan->Init.RxFifo1ElmtsNbr = 0;
hfdcan->Init.RxFifo1ElmtsSize = FDCAN_DATA_BYTES_8;
hfdcan->Init.TxEventsNbr = 0;
hfdcan->Init.TxBuffersNbr = 0;
HAL_FDCAN_Init(hfdcan);
// 配置时间戳单元
FDCAN_TimestampConfigTypeDef tsConfig;
tsConfig.TimestampPrescaler = 1; // 50MHz/1 = 50MHz
tsConfig.TimestampGlobal = FDCAN_TIMESTAMP_GLOBAL; // 全局时间戳
tsConfig.TimestampInternal = FDCAN_TIMESTAMP_INTERNAL; // 使用内部时钟
HAL_FDCAN_ConfigTimestamp(hfdcan, &tsConfig);
// 使能接收时间戳捕获
HAL_FDCAN_ActivateTimestamp(hfdcan, FDCAN_TIMESTAMP_RX);
}
// 2. TIM2 输入捕获初始化(PPS 信号)
void TIM2_PPS_Init(void) {
__HAL_RCC_TIM2_CLK_ENABLE();
TIM_HandleTypeDef htim2 = {0};
htim2.Instance = TIM2;
htim2.Init.Prescaler = 0; // 100MHz 计数
htim2.Init.CounterMode = TIM_COUNTERMODE_UP;
htim2.Init.Period = 0xFFFFFFFF; // 32 位自由运行
htim2.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
HAL_TIM_IC_Init(&htim2);
TIM_IC_InitTypeDef sConfigIC = {0};
sConfigIC.ICPolarity = TIM_INPUTCHANNELPOLARITY_RISING;
sConfigIC.ICSelection = TIM_ICSELECTION_DIRECTTI;
sConfigIC.ICPrescaler = TIM_ICPSC_DIV1;
sConfigIC.ICFilter = 0;
HAL_TIM_IC_ConfigChannel(&htim2, &sConfigIC, TIM_CHANNEL_1);
HAL_TIM_IC_Start_IT(&htim2, TIM_CHANNEL_1);
}
// 3. 捕获中断回调(更新 FDCAN 偏移)
void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim) {
if (htim->Instance == TIM2) {
uint32_t pps_capture = HAL_TIM_ReadCapturedValue(htim, TIM_CHANNEL_1);
// 假设 TSN 主时钟的 PPS 对应全局时间 T_global(例如 0 或已知值)
// 计算偏移:offset = T_global - pps_capture(需考虑溢出)
uint32_t offset = 0; // 根据实际同步协议计算
// 写入 FDCAN 时间戳偏移寄存器(通过 HAL 或直接寄存器操作)
FDCAN1->TSOFF = offset;
}
}
// 4. 接收消息时读取时间戳(示例)
void FDCAN_RxCallback(FDCAN_HandleTypeDef *hfdcan, uint32_t fifo) {
FDCAN_RxHeaderTypeDef rxHeader;
uint8_t data[8];
HAL_FDCAN_GetRxMessage(hfdcan, fifo, &rxHeader, data);
uint32_t timestamp = rxHeader.Timestamp; // 硬件时间戳,已与 TSN 对齐
// 进一步处理...
}
```
# 4. 注意事项
- **时钟同步精度**:确保 FDCAN 时间戳时钟与 TIM2 时钟同源(如均来自 PLL2),否则存在相位漂移。建议使用 PLL2 的专用输出,并避免分频不一致。
- **溢出处理**:32 位时间戳在 50MHz 下约 85.9 秒回绕,需在应用层实现扩展(如记录秒数)。
- **中断优先级**:TIM2 捕获中断应设为最高优先级,但中断处理需极短(仅计算偏移),避免影响 FDCAN 接收中断。
- **硬件连接**:PPS 信号应使用高速数字隔离器(如 ISO7721)以降低噪声,且走线尽量短。
- **调试技巧**:使用逻辑分析仪同时抓取 PPS 和 FDCAN 发送的 SYNC 帧,验证对齐误差是否在 ±100ns 内。
# 5. 总结
通过 STM32H7 的硬件时间戳单元和外部同步接口,可以实现 FDCAN 与 TSN 的亚微秒级时间对齐,无需软件干预。本文提供的配置方法和代码可直接应用于网关设计。实际项目中,还需结合具体 TSN 协议栈(如 gPTP)进行偏移计算,但硬件基础已就绪。