# 引言 STM32H7 系列凭借 Cortex-M7 内核和双 FDCAN 控制器,成为车载和工业通信的首选。但在 400MHz 主频下,FDCAN 以 8Mbps 速率连续发送时,若 DMA 缓冲设计不当,极易出现帧丢失。本文将从底层原理剖析丢帧原因,并给出可落地的 DMA 缓冲深度设计方案。 # FDCAN 与 DMA 的协作机制 ## FDCAN 硬件架构 FDCAN 控制器内部有独立的发送邮箱(Tx Buffer)和接收 FIFO(Rx FIFO)。当启用 DMA 时,FDCAN 通过 DMA 请求信号触发数据搬运,无需 CPU 逐字节干预。但 DMA 的搬运能力受限于总线带宽和缓冲深度。 ## 丢帧根因分析 - **缓冲溢出**:DMA 缓冲深度不足,导致 FIFO 满时新帧被丢弃。 - **中断延迟**:CPU 在 400MHz 下虽快,但若中断优先级设置不当,DMA 传输完成中断被其他中断阻塞,缓冲无法及时释放。 - **总线竞争**:DMA 与 CPU 同时访问 SRAM,若未使用紧耦合内存(如 DTCM),可能因总线仲裁延迟导致 DMA 来不及搬运。 # DMA 缓冲深度设计原理 ## 缓冲深度计算公式 设 FDCAN 波特率为 B(bit/s),每帧最大位数为 L(包括填充位,通常 128 位数据帧约 1500 位),则每帧传输时间 T_frame = L / B。若 DMA 每次传输一帧,则缓冲深度 N 需满足: N ≥ (T_frame * f_DMA) / 帧大小 + 1 其中 f_DMA 为 DMA 时钟频率。实际工程中,建议 N 取 8 或 16,并配合中断批处理。 ## 环形缓冲 vs 线性缓冲 - 线性缓冲:简单但需频繁重置指针,易产生碎片。 - 环形缓冲:利用 DMA 的循环模式,自动回绕,减少 CPU 干预,是高负载场景首选。 # 配置步骤(以 STM32H743 为例) ## 1. 时钟与引脚配置 使用 CubeMX 配置 FDCAN1 时钟为 20MHz,波特率 8Mbps,引脚 PB8/PB9。 ## 2. DMA 配置 - 选择 DMA1 Stream0,通道 FDCAN1_TX。 - 模式:循环(Circular)。 - 数据宽度:字(32位)。 - 优先级:非常高。 ## 3. 缓冲数组定义 ```c #define FDCAN_TX_BUFFER_SIZE 16 uint32_t tx_buffer[FDCAN_TX_BUFFER_SIZE][8]; // 每帧最多 8 个 32 位字 ``` ## 4. 初始化代码 ```c 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 = 1; 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.TxFifoQueueMode = FDCAN_TX_FIFO_OPERATION; HAL_FDCAN_Init(&hfdcan1); // 配置 DMA hdma_fdcan1_tx.Instance = DMA1_Stream0; hdma_fdcan1_tx.Init.Request = DMA_REQUEST_FDCAN1_TX; hdma_fdcan1_tx.Init.Direction = DMA_MEMORY_TO_PERIPH; hdma_fdcan1_tx.Init.PeriphInc = DMA_PINC_DISABLE; hdma_fdcan1_tx.Init.MemInc = DMA_MINC_ENABLE; hdma_fdcan1_tx.Init.PeriphDataAlignment = DMA_PDATAALIGN_WORD; hdma_fdcan1_tx.Init.MemDataAlignment = DMA_MDATAALIGN_WORD; hdma_fdcan1_tx.Init.Mode = DMA_CIRCULAR; hdma_fdcan1_tx.Init.Priority = DMA_PRIORITY_HIGH; HAL_DMA_Init(&hdma_fdcan1_tx); __HAL_LINKDMA(&hfdcan1, hdmaTx, hdma_fdcan1_tx); // 启动 FDCAN HAL_FDCAN_Start(&hfdcan1); HAL_FDCAN_ActivateNotification(&hfdcan1, FDCAN_IT_TX_FIFO_EMPTY, 0); } ``` ## 5. 发送数据函数 ```c void FDCAN_SendBuffer(uint32_t *data, uint32_t len) { static uint8_t idx = 0; // 将数据复制到当前缓冲槽 memcpy(tx_buffer[idx], data, len * 4); // 配置 DMA 传输(假设每次传输一帧) HAL_FDCAN_AddMessageToTxFifoQ(&hfdcan1, &tx_header, tx_buffer[idx]); idx = (idx + 1) % FDCAN_TX_BUFFER_SIZE; } ``` # 帧丢失排查实战 ## 排查步骤 1. **检查 FIFO 状态**:读取 FDCAN 的 TXFQS 寄存器,确认是否有溢出标志。 2. **监控 DMA 中断**:在 DMA 传输完成中断中设置断点,观察是否频繁触发。 3. **测量 CPU 负载**:使用逻辑分析仪测量中断响应时间,若超过 100μs,需优化。 ## 优化技巧 - 将 DMA 缓冲放在 DTCM 区域,减少总线冲突。 - 使用中断批处理:在 DMA 完成中断中一次性处理多帧,而非每帧中断。 - 调整 DMA 优先级为最高,并确保 FDCAN 中断优先级低于 DMA。 # 注意事项 - **缓冲深度不是越大越好**:过大会浪费内存,且增加延迟。 - **FDCAN 自动重传**:若开启自动重传,丢帧可能被掩盖,排查时应关闭。 - **DMA 循环模式**:务必确保缓冲大小是帧大小的整数倍,否则回绕时数据错位。 # 总结 STM32H7 在 400MHz 下 FDCAN 丢帧问题,本质是 DMA 缓冲深度与中断处理机制不匹配。通过合理设计环形缓冲深度、优化中断优先级和内存布局,可彻底解决。建议开发者结合具体波特率和帧长,使用公式计算缓冲深度,并利用调试工具验证。