# 引言 在高速数据采集系统中,ADC 采样率动辄数 MSPS,数据量巨大。STM32H7 系列内置了强大的 MDMA 和 LPDMA 控制器,合理利用它们可以显著降低 CPU 负载,实现数据流的无缝处理。本文将聚焦于一种延迟隐藏策略:使用 MDMA 将 ADC 数据从外设 SRAM 搬运到主 RAM,同时用 LPDMA 管理双缓冲切换,从而让 CPU 在后台处理数据时,搬运操作并行进行,彻底隐藏搬运延迟。 # 原理剖析 ## 1. 双缓冲(Ping-Pong)机制 双缓冲使用两个缓冲区(Buffer A 和 Buffer B)。当 ADC 正在填充 Buffer A 时,CPU 或 DMA 可以处理 Buffer B 中的数据;反之亦然。这种交替使用避免了数据覆盖和等待,是实现延迟隐藏的基础。 ## 2. MDMA 与 LPDMA 的分工 - **MDMA**:专为存储器到存储器传输设计,支持 8/16/32 位数据宽度,可配置突发传输,适合大块数据搬运。它拥有独立的 FIFO 和通道,可编程的传输地址和长度,非常适合将 ADC 采样数据从外设 SRAM(如 D1 域 SRAM)搬到主 RAM(D2 域)。 - **LPDMA**:低功耗 DMA,支持外设到存储器、存储器到外设等传输,功耗更低,适合管理外设事件。在本策略中,LPDMA 用于监听 ADC 转换完成事件,并触发 MDMA 启动搬运,同时切换双缓冲指针。 ## 3. 延迟隐藏的核心 当 ADC 完成一次转换并产生 DMA 请求时,LPDMA 响应此请求,立即将当前缓冲区的地址和长度信息配置给 MDMA,并启动 MDMA 传输。在 MDMA 搬运数据期间,ADC 继续向另一个缓冲区写入新数据。CPU 则可以在主 RAM 中处理已搬运完成的数据块。由于搬运和采集并行,CPU 无需等待,延迟被有效隐藏。 # 硬件与软件准备 - 硬件:STM32H743 开发板(或类似 H7 系列) - 软件:STM32CubeIDE(含 HAL 库) - 外设:ADC1(注入组或规则组,使用 DMA 循环模式) # 配置步骤(基于 CubeMX) ## 1. 配置 ADC - 开启 ADC1,选择扫描模式,使能 DMA 循环请求。 - 设置采样时间,确保满足信号带宽。 - 在 DMA 设置中,添加 DMA 请求,选择 LPDMA1 作为 DMA 控制器(注意:ADC 的 DMA 请求可以连接到 LPDMA)。 ## 2. 配置 LPDMA - 在 LPDMA1 中,添加一个通道,模式设为外设到存储器,外设地址为 ADC 的数据寄存器地址,存储器地址为双缓冲区的基地址(可设置为循环模式)。 - 数据宽度:半字(16 位,因为 ADC 通常 12/16 位)。 - 使能循环模式,并设置传输完成中断(用于切换缓冲)。 ## 3. 配置 MDMA - 在 MDMA 中,添加一个通道,模式设为存储器到存储器。 - 源地址为 LPDMA 搬运的目标缓冲区(即外设 SRAM 中的缓冲),目的地址为主 RAM 中的处理缓冲区。 - 数据宽度:32 位(可提高带宽),突发传输大小设为 4 或 8。 - 传输完成中断用于通知 CPU 数据就绪。 ## 4. 连接与优先级 - 在 NVIC 中启用 LPDMA 和 MDMA 中断,并设置优先级(建议 LPDMA 高于 MDMA,以保证及时响应 ADC 事件)。 - 确保 LPDMA 的传输完成事件能触发 MDMA 启动(可通过软件触发,或使用硬件事件链接,但 H7 中通常用软件触发)。 # 代码实现 以下代码基于 HAL 库,演示了核心逻辑。 ```c // 缓冲区定义(双缓冲) #define ADC_BUF_SIZE 1024 uint16_t adc_buf[2][ADC_BUF_SIZE]; // 外设 SRAM 中的缓冲(实际需放置到 D1 域) uint32_t proc_buf[ADC_BUF_SIZE/2]; // 主 RAM 中的处理缓冲 // 标志位 volatile uint8_t buf_index = 0; volatile uint8_t data_ready = 0; // LPDMA 传输完成回调(切换缓冲) void HAL_LPDMA_TC_Callback(LPDMA_HandleTypeDef *hldma) { // 切换缓冲索引 buf_index ^= 1; // 触发 MDMA 搬运已满的缓冲 HAL_MDMA_Start_IT(&hmdma, (uint32_t)&adc_buf[buf_index], (uint32_t)proc_buf, ADC_BUF_SIZE/2); } // MDMA 传输完成回调(通知 CPU 处理) void HAL_MDMA_TC_Callback(MDMA_HandleTypeDef *hmdma) { data_ready = 1; } // 主函数初始化 void init_adc_dma() { // 配置 ADC 和 LPDMA(略,见 CubeMX 生成代码) // 启动 LPDMA 循环传输 HAL_LPDMA_Start_IT(&hldma, (uint32_t)&hadc1.Instance->DR, (uint32_t)&adc_buf[0], ADC_BUF_SIZE); } // 主循环 while(1) { if(data_ready) { // 处理 proc_buf 中的数据 process_data(proc_buf, ADC_BUF_SIZE/2); data_ready = 0; } } ``` # 注意事项 - **内存域**:STM32H7 有多个 RAM 域(D1、D2、D3),MDMA 访问主 RAM(D2)和外设 SRAM(D1)时,需确保时钟使能,且地址映射正确。建议将 adc_buf 放在 D1 域(如 AXI SRAM),proc_buf 放在 D2 域(如 SRAM1)。 - **数据一致性**:MDMA 和 CPU 访问同一内存时,注意缓存一致性。若使用带缓存的内存,需在 MDMA 传输前执行 `SCB_CleanDCache()`,传输后执行 `SCB_InvalidateDCache()`。 - **中断优先级**:LPDMA 中断应高于 MDMA,避免 ADC 数据丢失。 - **缓冲大小**:ADC 缓冲大小需与 LPDMA 配置的传输长度一致,且为 MDMA 数据宽度的整数倍。 - **触发方式**:本例中 MDMA 由软件触发,也可使用硬件事件(如 LPDMA 的完成事件连接到 MDMA 的请求),但需查阅参考手册确认连接。 # 总结 通过 MDMA 与 LPDMA 的协同,STM32H7 能够高效处理高速 ADC 数据流,双缓冲机制有效隐藏了搬运延迟,使 CPU 专注于核心算法。该策略在电机控制、振动分析、软件无线电等领域具有广泛应用价值。开发者可根据实际需求调整缓冲大小和传输宽度,以平衡吞吐量与资源占用。