STM32H7 MDMA与LPDMA协同搬运双缓冲音频流的时序对齐技巧
👁 1 阅读 · 2026-08-27 · 嵌入式
在STM32H7高性能MCU上,音频流处理常面临多通道DMA协同的时序挑战。本文深入解析MDMA与LPDMA在双缓冲音频搬运中的角色分工,揭示两者协同工作的核心时序对齐原理,并给出基于HAL库的完整配置代码与调试技巧,帮助开发者实现零丢包、低延迟的音频传输。
# STM32H7 MDMA与LPDMA协同搬运双缓冲音频流的时序对齐技巧
## 1. 为什么需要MDMA与LPDMA协同?
STM32H7系列内置了两种DMA控制器:
- **LPDMA**(Low-Power DMA):低功耗、低延迟,适合外设到内存或内存到外设的常规搬运,支持双缓冲(Double Buffer)模式。
- **MDMA**(Master DMA):高带宽、高灵活性,可访问全部内存空间,支持链表模式(Linked List),适合大块数据搬运或内存到内存的快速拷贝。
在音频应用中,典型场景是:ADC/DAC通过LPDMA将数据搬运到RAM缓冲区,同时需要将缓冲区数据快速拷贝到另一个处理缓冲区(如音频算法处理),或从Flash加载音频样本到RAM。此时,若仅用LPDMA,会占用其通道资源且带宽受限;若仅用MDMA,则无法直接与外设握手。因此,两者协同成为最佳方案:**LPDMA负责与外设交互,MDMA负责内存间的大块数据搬运**。
## 2. 双缓冲机制与时序对齐核心
双缓冲(Ping-Pong Buffer)是音频流处理的标准做法:
- 当LPDMA正在填充缓冲区A时,CPU或MDMA处理缓冲区B;
- 当A填满后,LPDMA切换至B,同时处理A。
**时序对齐的关键**在于:确保MDMA的搬运操作与LPDMA的缓冲区切换严格同步,避免数据覆盖或读取未就绪的数据。
### 2.1 对齐策略
- **基于中断同步**:LPDMA在缓冲区切换时产生传输完成中断(TC),在中断服务函数中触发MDMA搬运已满缓冲区。
- **基于硬件握手**:利用LPDMA的请求信号(如DMA request)直接触发MDMA,但需注意信号极性及延迟。
- **基于定时器**:使用定时器触发MDMA,以固定周期搬运,但需确保周期与LPDMA缓冲区填充时间匹配。
推荐使用**中断同步**,因其简单可靠,且易于调试。
## 3. 配置步骤(基于STM32CubeH7 HAL库)
### 3.1 硬件资源
- 音频外设:如SAI或I2S,配置为DMA模式,使用LPDMA通道。
- MDMA:配置为内存到内存模式,使用链表或普通模式。
### 3.2 代码示例
```c
/* 定义双缓冲结构 */
#define AUDIO_BUF_SIZE 1024
uint32_t bufA[AUDIO_BUF_SIZE];
uint32_t bufB[AUDIO_BUF_SIZE];
volatile uint8_t current_buf = 0; // 0表示A,1表示B
/* LPDMA句柄 */
DMA_HandleTypeDef hdma_lpdm; // 假设已配置
/* MDMA句柄 */
MDMA_HandleTypeDef hmdma_mdma;
/* LPDMA传输完成中断回调 */
void HAL_DMA_TC_Callback(DMA_HandleTypeDef *hdma) {
if (hdma == &hdma_lpdm) {
// 切换当前缓冲区索引
current_buf = (current_buf == 0) ? 1 : 0;
// 启动MDMA搬运:将刚填满的缓冲区数据拷贝到处理缓冲区
uint32_t *src = (current_buf == 0) ? bufB : bufA; // 注意:刚填满的是另一个缓冲区
uint32_t *dst = (current_buf == 0) ? bufA : bufB; // 这里假设处理缓冲区就是另一个缓冲区,实际可指向其他内存
// 配置MDMA传输
hmdma_mdma.Init.Request = MDMA_REQUEST_SW; // 软件触发
hmdma_mdma.Init.TransferTriggerMode = MDMA_BUFFER_TRANSFER;
hmdma_mdma.Init.SourceInc = MDMA_SRC_INC_WORD;
hmdma_mdma.Init.DestInc = MDMA_DEST_INC_WORD;
hmdma_mdma.Init.SourceDataSize = MDMA_SRC_DATASIZE_WORD;
hmdma_mdma.Init.DestDataSize = MDMA_DEST_DATASIZE_WORD;
hmdma_mdma.Init.DataAlignment = MDMA_DATAALIGN_PACKONLY;
hmdma_mdma.Init.BufferTransferLength = AUDIO_BUF_SIZE;
HAL_MDMA_Init(&hmdma_mdma);
HAL_MDMA_Start_IT(&hmdma_mdma, (uint32_t)src, (uint32_t)dst, AUDIO_BUF_SIZE);
}
}
/* MDMA传输完成回调(可选) */
void HAL_MDMA_TC_Callback(MDMA_HandleTypeDef *hmdma) {
// 可在此处通知音频算法处理
}
/* 主函数初始化 */
void Audio_Init(void) {
// 配置LPDMA为双缓冲模式,链接bufA和bufB
// 配置MDMA(如上)
// 启动LPDMA接收
HAL_DMA_Start_IT(&hdma_lpdm, (uint32_t)&SAI_RX, (uint32_t)bufA, AUDIO_BUF_SIZE);
// 注意:双缓冲模式下,需调用HAL_DMAEx_MultiBufferStart_IT
}
```
### 3.3 关键点说明
- **缓冲区切换逻辑**:在LPDMA的TC回调中,`current_buf`表示当前正在被LPDMA填充的缓冲区。因此,刚填满的是另一个缓冲区,MDMA应搬运那个缓冲区。
- **MDMA配置**:使用软件触发,每次搬运固定长度。注意`BufferTransferLength`需与缓冲区大小一致。
- **中断优先级**:LPDMA的TC中断优先级应高于MDMA的TC中断,以确保及时触发搬运。
## 4. 时序对齐的进阶技巧
### 4.1 使用MDMA链表模式
若需连续搬运多个缓冲区(如多声道),可配置MDMA链表,每个节点对应一个缓冲区,减少中断开销。
```c
MDMA_LinkNodeTypeDef Node1, Node2;
// 初始化节点,配置源地址、目的地址、长度等
Node1.NextNode = &Node2;
Node2.NextNode = NULL;
HAL_MDMA_Start_IT(&hmdma_mdma, (uint32_t)src1, (uint32_t)dst1, AUDIO_BUF_SIZE, &Node1);
```
### 4.2 避免缓存一致性问题
STM32H7的D-Cache可能导致数据不一致。在MDMA搬运前,需调用`SCB_InvalidateDCache_by_Addr`或`SCB_CleanDCache_by_Addr`。
```c
SCB_InvalidateDCache_by_Addr((uint32_t*)dst, AUDIO_BUF_SIZE * sizeof(uint32_t));
```
### 4.3 测量时序裕量
使用定时器或`DWT->CYCCNT`测量LPDMA填充一个缓冲区的时间与MDMA搬运时间,确保搬运时间远小于填充时间,否则需优化MDMA配置(如增加突发传输)。
## 5. 注意事项
- **MDMA时钟使能**:在`HAL_MDMA_Init`前,需使能MDMA时钟(`__HAL_RCC_MDMA_CLK_ENABLE()`)。
- **内存对齐**:源和目的地址建议4字节对齐,以匹配字传输。
- **中断服务函数耗时**:在TC回调中避免耗时操作,仅触发MDMA,实际处理放在主循环或低优先级任务。
- **调试建议**:使用逻辑分析仪观察LPDMA的TC信号与MDMA的启动信号,验证时序。
## 6. 总结
通过LPDMA与MDMA的协同,STM32H7可高效处理双缓冲音频流,实现零拷贝、低延迟。关键在于利用LPDMA的中断触发MDMA,并注意缓存一致性。本文提供的配置代码和技巧可直接应用于实际项目,帮助开发者快速实现稳定可靠的音频系统。