STM32H7 串口 DMA 空闲中断接收不定长数据的缓存对齐与丢包排查
1. 背景与问题现象
STM32H7 主频高达 480MHz,带 D-Cache,串口波特率常达 921600 甚至更高。使用 DMA + 空闲中断 接收不定长数据是高效方案,但很多开发者会遇到:
- 数据偶尔丢失前几个字节
- 接收长度正确但内容错乱
- 高速连续发送时丢包严重
根本原因往往不是串口配置,而是 DMA 缓存对齐 与 Cache 一致性 问题。
2. 原理讲解:Cache 与 DMA 的冲突
2.1 D-Cache 的工作机制
Cortex-M7 的 D-Cache 按 Cache Line(32 字节) 操作。当 CPU 访问数据时,若未命中,会从内存加载整个 Cache Line 到缓存。
2.2 DMA 直接访问内存
DMA 绕过 Cache 直接读写物理内存。若 CPU 之前访问过该区域,Cache 中可能有旧数据;DMA 写入新数据后,CPU 读到的仍是 Cache 中的旧值。
2.3 空闲中断的陷阱
空闲中断触发后,CPU 读取 DMA 剩余计数器(NDTR)计算长度,然后处理缓冲区。若缓冲区未做 Cache 无效化(Invalidate),或缓冲区未按 32 字节对齐,就会读到脏数据或触发总线错误。
关键结论:
- DMA 接收缓冲区必须 32 字节对齐
- 读取数据前必须 Invalidate D-Cache
- 缓冲区大小最好是 32 的整数倍
3. CubeMX 配置步骤
- 串口配置:USART1 异步模式,波特率 921600,开启 DMA 接收(Normal 模式)。
- DMA 配置:USART1_RX 通道,优先级 High,数据宽度 Byte。
- NVIC 配置:使能 USART1 全局中断,使能 DMA 通道中断(可选)。
- Cache 配置:默认开启 D-Cache 和 I-Cache。
4. 完整代码示例
4.1 定义对齐的缓冲区
#include "main.h"
#define RX_BUFFER_SIZE 256
/* 强制 32 字节对齐 */
aligned(32) uint8_t rx_dma_buffer[RX_BUFFER_SIZE];
volatile uint16_t rx_len = 0;
volatile uint8_t rx_flag = 0;
4.2 启动 DMA 接收
void UART_StartReceive(void)
{
HAL_UART_Receive_DMA(&huart1, rx_dma_buffer, RX_BUFFER_SIZE);
__HAL_UART_ENABLE_IT(&huart1, UART_IT_IDLE);
}
4.3 空闲中断处理
void USART1_IRQHandler(void)
{
if (__HAL_UART_GET_FLAG(&huart1, UART_FLAG_IDLE))
{
__HAL_UART_CLEAR_IDLEFLAG(&huart1);
/* 停止 DMA 以获取准确长度 */
HAL_UART_DMAStop(&huart1);
uint16_t remain = __HAL_DMA_GET_COUNTER(huart1.hdmarx);
rx_len = RX_BUFFER_SIZE - remain;
/* 无效化 Cache,确保 CPU 读到 DMA 写入的最新数据 */
SCB_InvalidateDCache_by_Addr((uint32_t*)rx_dma_buffer, RX_BUFFER_SIZE);
rx_flag = 1;
/* 重新启动接收 */
HAL_UART_Receive_DMA(&huart1, rx_dma_buffer, RX_BUFFER_SIZE);
}
HAL_UART_IRQHandler(&huart1);
}
4.4 主循环处理数据
while (1)
{
if (rx_flag)
{
rx_flag = 0;
/* 处理 rx_dma_buffer 中前 rx_len 字节 */
ProcessData(rx_dma_buffer, rx_len);
}
}
5. 丢包排查清单
-
检查对齐:
rx_dma_buffer是否 32 字节对齐?用__attribute__((aligned(32)))或aligned(32)。 -
检查 Cache 操作:读取前是否调用
SCB_InvalidateDCache_by_Addr?注意长度需覆盖整个缓冲区,且地址对齐。 - 检查 DMA 模式:使用 Normal 模式,每次空闲后重启;若用 Circular 模式,需处理 NDTR 回绕。
- 检查中断优先级:空闲中断优先级不宜过高,避免打断 DMA 传输。
- 检查缓冲区大小:建议为 32 的整数倍,且大于最大帧长。
- 检查时钟与波特率:H7 串口时钟源需正确,避免波特率误差累积。
- 使用示波器:观察 RX 引脚波形,确认数据是否完整到达。
6. 注意事项
-
SCB_InvalidateDCache_by_Addr的地址和长度必须 32 字节对齐,否则触发 HardFault。 - 若使用
HAL_UARTEx_ReceiveToIdle_DMA(新版 HAL),内部已处理空闲中断,但仍需手动 Invalidate Cache。 - 避免在中断中处理大量数据,建议置标志位由主循环处理。
- 若数据量极大,可考虑使用 MDMA 或 BDMA 减轻 CPU 负担。
- 调试时关闭 D-Cache 可快速定位是否为 Cache 问题。
7. 总结
STM32H7 的 Cache 与 DMA 协同工作必须谨慎处理对齐和一致性。遵循 32 字节对齐 + 读取前 Invalidate 原则,可解决绝大多数丢包问题。结合空闲中断,即可实现高效、稳定的不定长数据接收。