引言

STM32H7 系列(如 STM32H743/750)凭借 Cortex-M7 内核的 480MHz 主频,以及内置的 FDCAN 控制器和 TSN 以太网 MAC,成为工业 IoT 和车载网关的理想平台。然而,当 FDCAN(CAN FD)与 TSN(时间敏感网络)以太网需要协同工作时,实时调度成为关键挑战:如何确保不同协议的数据在共享 CPU 资源下满足各自的截止时间?本文将从硬件机制、时间同步和软件调度三个层面展开。

硬件资源与实时性基础

1. FDCAN 特性

  • 数据速率:最高 8Mbps(数据段),支持 64 字节数据场。
  • 硬件 FIFO:每个 FDCAN 实例有 3 个 TX 邮箱和 2 个 RX FIFO,减少 CPU 中断负担。
  • 时间戳:内置 16 位定时器,可配合外部时间同步。

2. TSN 以太网特性

  • IEEE 802.1Qbv:时间感知整形器(TAS),通过门控列表控制队列发送。
  • IEEE 802.1AS:gPTP 精确时间协议,实现微秒级同步。
  • 多队列:STM32H7 的 MAC 支持 8 个 TX 队列,可映射不同优先级。

3. 协同难点

  • 资源竞争:CPU 需同时处理 FDCAN 中断和以太网 DMA 中断。
  • 时间基准:FDCAN 和 TSN 需要统一的时间源,否则调度失序。
  • 优先级反转:低优先级任务可能阻塞高优先级实时帧。

时间同步机制

1. 统一时间基准

利用 STM32H7 的 TIM2 作为全局时间主时钟(64 位),通过以下方式同步:

  • FDCAN:将 TIM2 映射到 FDCAN 的 Timestamp Counter(需配置为外部时钟模式)。
  • TSN:使用 802.1AS 的 gPTP 同步,将 TIM2 作为本地时钟源。

配置示例(HAL 库):

// 初始化 TIM2 为 1MHz 计数
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);

// FDCAN 时间戳配置(使用外部时钟)
FDCAN_HandleTypeDef hfdcan1;
hfdcan1.Init.TimestampConfig = FDCAN_TIMESTAMP_INTERNAL; // 改为外部
hfdcan1.Init.TimestampPrescaler = 1;
// 注意:需将 TIM2 输出连接到 FDCAN 的 Timestamp 引脚(如 PA8)

2. gPTP 同步流程

  • 启用以太网 MAC 的 IEEE 1588 功能,配置 PTP 时钟源为 TIM2。
  • 通过 gPTP 协议从主时钟获取偏移,并调整 TIM2 的计数值(使用 HAL_TIM_OC_ConfigChannel 产生校正脉冲)。

实时调度策略

1. 优先级映射

  • FDCAN 高优先级帧(如紧急控制消息)映射到 TSN 的队列 7(最高优先级)。
  • FDCAN 普通帧映射到队列 3。
  • TSN 音视频流映射到队列 0-2,利用 Qbv 门控。

2. 中断与轮询结合

  • FDCAN 中断:仅对高优先级消息启用 FIFO 满中断,普通消息使用轮询(在空闲时隙)。
  • 以太网中断:利用 DMA 完成中断,但将处理函数放入低优先级任务。

3. 时间片轮转

在 RTOS(如 FreeRTOS)中,为 FDCAN 和 TSN 分配独立任务,并设置优先级:

  • FDCAN_Task:优先级 5,周期 1ms。
  • TSN_Task:优先级 3,周期 5ms。
  • 使用消息队列传递数据,避免共享内存冲突。

完整代码示例

以下示例展示如何配置 FDCAN 发送高优先级消息,并通过 TSN 以太网发送同步帧(基于 STM32H743 和 HAL 库):

// 初始化 FDCAN1(速率 5Mbps,数据段 8Mbps)
void FDCAN1_Init(void) {
    FDCAN_HandleTypeDef hfdcan1;
    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;
    hfdcan1.Init.TransmitPause = DISABLE;
    hfdcan1.Init.ProtocolException = DISABLE;
    hfdcan1.Init.NominalPrescaler = 4; // 80MHz/4/2 = 10MHz 仲裁段
    hfdcan1.Init.NominalSyncJumpWidth = 1;
    hfdcan1.Init.NominalTimeSeg1 = 13;
    hfdcan1.Init.NominalTimeSeg2 = 2;
    hfdcan1.Init.DataPrescaler = 2; // 数据段 8Mbps
    hfdcan1.Init.DataSyncJumpWidth = 1;
    hfdcan1.Init.DataTimeSeg1 = 9;
    hfdcan1.Init.DataTimeSeg2 = 2;
    hfdcan1.Init.TimestampConfig = FDCAN_TIMESTAMP_EXTERNAL;
    hfdcan1.Init.TimestampPrescaler = 1;
    HAL_FDCAN_Init(&hfdcan1);
    // 配置过滤器(接收所有)
    FDCAN_FilterTypeDef sFilterConfig;
    sFilterConfig.IdType = FDCAN_STANDARD_ID;
    sFilterConfig.FilterIndex = 0;
    sFilterConfig.FilterType = FDCAN_FILTER_MASK;
    sFilterConfig.FilterConfig = FDCAN_FILTER_TO_RXFIFO0;
    sFilterConfig.FilterID1 = 0;
    sFilterConfig.FilterID2 = 0;
    HAL_FDCAN_ConfigFilter(&hfdcan1, &sFilterConfig);
}

// 发送 FDCAN 高优先级消息
void FDCAN_SendHighPriority(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.FDFormat = FDCAN_FD_CAN;
    txHeader.BitRateSwitch = ENABLE;
    txHeader.TxEventFifoControl = DISABLE;
    txHeader.MessageMarker = 0;
    // 使用 TX 邮箱 0(最高优先级)
    HAL_FDCAN_AddMessageToTxBuffer(&hfdcan1, &txHeader, data, FDCAN_TX_MAILBOX0);
}

// TSN 以太网发送(使用队列 7)
void TSN_SendFrame(uint8_t* frame, uint16_t len) {
    ETH_TxPacketConfig txConfig;
    txConfig.Attributes = ETH_TX_PACKETS_FEATURES_TSO | ETH_TX_PACKETS_FEATURES_VLAN;
    txConfig.VlanTag = 0x8100; // 802.1Q VLAN 标签,PCP=7 映射到队列 7
    txConfig.Payload = frame;
    txConfig.Length = len;
    txConfig.Timestamp = 0; // 使用硬件时间戳
    HAL_ETH_Transmit(&heth, &txConfig, 1);
}

// 主循环调度
void RealTimeScheduler(void) {
    uint32_t tick = HAL_GetTick();
    if (tick % 1 == 0) { // 每 1ms 处理 FDCAN
        FDCAN_SendHighPriority(0x100, (uint8_t*)"Hi", 2);
    }
    if (tick % 5 == 0) { // 每 5ms 发送 TSN 同步帧
        uint8_t frame[64];
        // 填充帧内容(略)
        TSN_SendFrame(frame, 64);
    }
}

注意事项

  • 时钟配置:确保 FDCAN 和以太网使用不同的 PLL 输出,避免频率耦合。
  • 内存屏障:在共享数据(如全局时间戳)访问时,使用 __DMB() 指令。
  • 中断优先级:将 FDCAN 中断优先级设为高于以太网,但低于系统节拍。
  • 调试技巧:使用逻辑分析仪同时抓取 FDCAN 和以太网信号,验证时间同步精度。
  • 功耗考虑:480MHz 下全速运行功耗较高,可在空闲时降低 CPU 频率(通过 PLL 切换)。

总结

通过统一时间基准、优先级映射和 RTOS 任务调度,STM32H7 能够有效协同 FDCAN 与 TSN 以太网,满足实时性要求。实际项目中,需根据具体业务调整时间片和队列配置,并利用硬件特性减少 CPU 负载。希望本文能为您的嵌入式实时系统设计提供参考。