引言

在工业控制、车载网关等场景中,STM32H7 常需同时处理 FDCAN(灵活数据速率 CAN)和以太网通信。当主频提升至 400MHz 时,虽然 CPU 算力充足,但外设并发访问共享资源(如 AHB 总线、SRAM、DMA 控制器)会引发时序冲突,导致 FDCAN 报文丢失或以太网帧延迟超时。本文从硬件架构出发,剖析冲突根源,并提供可落地的优化方案。

1. 冲突根源分析

1.1 总线矩阵竞争

STM32H7 采用多主多从总线矩阵,CPU、DMA1/2、以太网 MAC、FDCAN 等主设备可并行访问不同从设备,但访问同一 SRAM 或 AHB 外设时需仲裁。FDCAN 和以太网 DMA 均可能同时访问 AHB SRAM(如 DTCM 或 AXI SRAM),导致等待周期增加。

  • 关键路径:以太网 DMA 突发传输(Burst)长度可达 8 字,占用总线时间较长;FDCAN 每次传输仅 1-2 字,但频率高,两者竞争时 FDCAN 易被饿死。
  • 实测现象:当以太网流量达 90% 带宽时,FDCAN 发送延迟从 20μs 增至 200μs,甚至触发 TEC(发送错误计数)增加。

1.2 中断优先级与嵌套

FDCAN 和以太网中断默认优先级相同,若同时触发,CPU 需按硬件编号顺序响应,造成不确定延迟。此外,若中断处理函数中执行耗时操作(如内存拷贝),会阻塞另一外设的中断服务。

1.3 缓存一致性问题

STM32H7 的 CPU 有 L1-Cache,而 DMA 不经过 Cache。当 CPU 修改发送缓冲区后,若未执行 Clean 操作,DMA 可能读到旧数据;反之,DMA 写入接收缓冲区后,CPU 读取时可能命中脏 Cache。

2. 优化策略

2.1 中断优先级分组与抢占

将 FDCAN 中断设为最高优先级(抢占优先级 0),以太网设为次高(抢占优先级 1),并确保中断处理函数短小精悍。使用 NVIC 优先级分组 3(4 位抢占,0 位子优先级)。

// 配置 NVIC 优先级分组
HAL_NVIC_SetPriorityGrouping(NVIC_PRIORITYGROUP_3);

// FDCAN1 中断:抢占优先级 0,子优先级 0
HAL_NVIC_SetPriority(FDCAN1_IT0_IRQn, 0, 0);
HAL_NVIC_EnableIRQ(FDCAN1_IT0_IRQn);

// 以太网中断:抢占优先级 1,子优先级 0
HAL_NVIC_SetPriority(ETH_IRQn, 1, 0);
HAL_NVIC_EnableIRQ(ETH_IRQn);

2.2 DMA 通道分离与双缓冲

FDCAN 使用 DMA1 通道,以太网使用 DMA2 通道,避免同一 DMA 控制器的内部仲裁。同时,为 FDCAN 接收配置双缓冲区(Ping-Pong),防止 DMA 写入时 CPU 读取冲突。

// FDCAN 接收双缓冲配置(基于 HAL 库)
FDCAN_RxHeaderTypeDef RxHeader;
uint8_t RxData[2][8];  // 双缓冲
uint8_t ActiveBuf = 0;

void FDCAN_RxCallback(FDCAN_HandleTypeDef *hfdcan, uint32_t RxFifo) {
    if (HAL_FDCAN_GetRxMessage(hfdcan, RxFifo, &RxHeader, RxData[ActiveBuf]) == HAL_OK) {
        // 处理数据
        ProcessRxData(RxData[ActiveBuf]);
        ActiveBuf ^= 1;  // 切换缓冲区
    }
}

2.3 内存屏障与 Cache 维护

在 DMA 传输前后使用 __DMB()SCB_CleanDCache_by_Addr() 确保数据一致性。

// 发送前:确保 CPU 写入可见
SCB_CleanDCache_by_Addr((uint32_t*)TxData, sizeof(TxData));
__DMB();
HAL_FDCAN_AddMessageToTxFifoQ(&hfdcan1, &TxHeader, TxData);

// 接收后:使 Cache 失效,读取最新 DMA 数据
SCB_InvalidateDCache_by_Addr((uint32_t*)RxData[ActiveBuf], sizeof(RxData[ActiveBuf]));
__DMB();

2.4 时序调度:时间片轮转

若应用允许,可采用时间片轮转,将 FDCAN 发送集中在以太网空闲窗口。例如,使用定时器中断触发 FDCAN 批量发送,避免与以太网 DMA 突发重叠。

// 定时器回调:每 1ms 触发一次 FDCAN 发送
void TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) {
    if (htim->Instance == TIM6) {
        // 检查以太网 DMA 是否忙碌
        if (HAL_ETH_GetDMAFlagStatus(&heth, ETH_DMA_FLAG_BUSY) == RESET) {
            SendPendingFDCANMessages();
        }
    }
}

3. 完整代码示例

以下为综合配置片段,展示 FDCAN 和以太网初始化及中断处理。

// 初始化 FDCAN1
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 = ENABLE;
    hfdcan1.Init.TransmitPause = DISABLE;
    hfdcan1.Init.ProtocolException = DISABLE;
    hfdcan1.Init.NominalPrescaler = 2;
    hfdcan1.Init.NominalSyncJumpWidth = 1;
    hfdcan1.Init.NominalTimeSeg1 = 13;
    hfdcan1.Init.NominalTimeSeg2 = 2;
    hfdcan1.Init.DataPrescaler = 1;
    hfdcan1.Init.DataSyncJumpWidth = 1;
    hfdcan1.Init.DataTimeSeg1 = 13;
    hfdcan1.Init.DataTimeSeg2 = 2;
    hfdcan1.Init.StdFiltersNbr = 1;
    hfdcan1.Init.ExtFiltersNbr = 0;
    hfdcan1.Init.RxFifo0ElmtsNbr = 8;
    hfdcan1.Init.RxFifo0ElmtSize = FDCAN_DATA_BYTES_MAX;
    hfdcan1.Init.RxFifo1ElmtsNbr = 0;
    hfdcan1.Init.RxFifo1ElmtSize = FDCAN_DATA_BYTES_MAX;
    hfdcan1.Init.TxEventsNbr = 0;
    hfdcan1.Init.TxBuffersNbr = 4;
    hfdcan1.Init.TxEventFifoElmtsNbr = 0;
    HAL_FDCAN_Init(&hfdcan1);
    HAL_FDCAN_Start(&hfdcan1);
    HAL_FDCAN_ActivateNotification(&hfdcan1, FDCAN_IT_RX_FIFO0_NEW_MESSAGE, 0);
}

// 初始化以太网(使用 DMA)
ETH_HandleTypeDef heth;
void ETH_Init(void) {
    heth.Instance = ETH;
    heth.Init.MACAddr[0] = 0x00;
    heth.Init.MACAddr[1] = 0x11;
    heth.Init.MACAddr[2] = 0x22;
    heth.Init.MACAddr[3] = 0x33;
    heth.Init.MACAddr[4] = 0x44;
    heth.Init.MACAddr[5] = 0x55;
    heth.Init.MediaInterface = ETH_MEDIA_INTERFACE_RMII;
    heth.Init.TxDesc = TxDesc;
    heth.Init.RxDesc = RxDesc;
    heth.Init.RxBuffLen = 1524;
    HAL_ETH_Init(&heth);
    HAL_ETH_Start(&heth);
}

// 中断处理:FDCAN 优先
void FDCAN1_IT0_IRQHandler(void) {
    HAL_FDCAN_IRQHandler(&hfdcan1);
}

void ETH_IRQHandler(void) {
    HAL_ETH_IRQHandler(&heth);
}

4. 注意事项

  • 时钟配置:确保 FDCAN 内核时钟和以太网 MAC 时钟来自不同 PLL,避免频率耦合。
  • DMA 描述符:以太网 DMA 描述符应放在 AXI SRAM 且 32 字节对齐,FDCAN 缓冲区放在 DTCM 以减少总线冲突。
  • 调试技巧:使用逻辑分析仪抓取 FDCAN TX 和以太网 TX 信号,观察冲突窗口;或利用 STM32 的调试计数器(DWT)测量中断延迟。
  • 实时性验证:在最大负载下运行 24 小时,监控 FDCAN 错误计数和以太网丢包率,确保优化有效。

结语

通过中断优先级调整、DMA 通道分离、缓存维护和时序调度,STM32H7 在 400MHz 下可稳定支持 FDCAN 与以太网并发传输。实际项目中需根据具体流量模型微调参数,建议结合 RTOS 的优先级翻转机制进一步优化。希望本文能为你解决类似问题提供参考。