# 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,并注意缓存一致性。本文提供的配置代码和技巧可直接应用于实际项目,帮助开发者快速实现稳定可靠的音频系统。