# STM32H7 零 CPU 干预 ADC 连续采样:MDMA 与双缓冲的深度配置指南 ## 为什么需要 MDMA + 双缓冲? STM32H7 的 ADC 支持高达 3.6 Msps 的采样率,若使用普通 DMA 将数据搬运至单一缓冲区,CPU 仍需在每次缓冲区满时处理数据,期间无法执行其他任务。而 MDMA 具备内存到内存的搬运能力,且支持双缓冲(即乒乓缓冲),可让 ADC 数据持续写入一个缓冲区,同时 MDMA 将另一个已满的缓冲区数据搬运至用户指定区域,整个过程无需 CPU 介入,极大提升系统实时性。 ## 核心原理:MDMA 与双缓冲的协同 - **MDMA 特点**:支持 8/16/32 位数据宽度,可配置突发传输,拥有独立的 FIFO,能实现内存到内存、外设到内存的搬运,且不占用 CPU 周期。 - **双缓冲机制**:ADC 通过 DMA 请求将数据写入缓冲区 A,当 A 写满后自动切换至缓冲区 B,同时触发 MDMA 将 A 中的数据搬运至目标内存。如此交替,实现数据无缝流转。 - **零 CPU 干预**:MDMA 搬运完成后可产生中断,但 CPU 仅在需要处理数据时被唤醒,而非每次采样都触发中断。 ## 配置步骤(基于 STM32H743 + CubeMX) ### 1. ADC 配置 - 开启 ADC1,选择连续转换模式,通道采样时间根据信号源阻抗设置(如 2.5 周期)。 - 在 DMA 设置中,添加 DMA 请求,模式选择 **Circular**,数据宽度设为 **Half Word**(16 位),内存地址递增。 - 注意:ADC 的 DMA 请求必须连接到 MDMA,而非普通 DMA。在 CubeMX 中,将 DMA 请求映射到 MDMA 通道。 ### 2. MDMA 配置 - 在 MDMA 设置中,添加一个通道,请求源选择 **ADC1**(或对应 DMA 请求)。 - 设置源地址为 ADC 数据寄存器地址(如 `ADC1->DR`),目标地址为双缓冲区数组首地址。 - 传输模式:**Double Buffer**,缓冲区大小设为 ADC 采样点数(如 1024)。 - 数据宽度:源和目标均为 **Half Word**,突发大小建议设为 4 或 8,以提升效率。 - 使能 **Transfer Complete 中断**,用于通知 CPU 处理数据。 ### 3. 双缓冲区内存定义 在链接脚本或代码中定义两个缓冲区,需对齐到 32 字节(MDMA 要求)。 ```c // 双缓冲区,每个 1024 个 16 位数据 __attribute__((aligned(32))) uint16_t buffer_a[1024]; __attribute__((aligned(32))) uint16_t buffer_b[1024]; ``` ## 完整代码示例 以下代码基于 HAL 库,展示初始化与中断处理。 ```c // 初始化函数 void ADC_MDMA_Init(void) { // 1. 启动 ADC DMA 请求(使用 MDMA 通道) HAL_ADC_Start_DMA(&hadc1, (uint32_t*)buffer_a, 1024); // 2. 配置 MDMA 双缓冲 MDMA_HandleTypeDef hmdma; hmdma.Instance = MDMA_Channel0; hmdma.Init.Request = MDMA_REQUEST_ADC1; // 根据实际映射 hmdma.Init.TransferTriggerMode = MDMA_BUFFER_TRANSFER; hmdma.Init.SourceInc = MDMA_SRC_INC_HALFWORD; hmdma.Init.DestInc = MDMA_DEST_INC_HALFWORD; hmdma.Init.SourceDataSize = MDMA_SRC_DATASIZE_HALFWORD; hmdma.Init.DestDataSize = MDMA_DEST_DATASIZE_HALFWORD; hmdma.Init.BufferTransferLength = 1024; hmdma.Init.DoubleBufferMode = MDMA_DOUBLE_BUFFER_MODE_ENABLE; hmdma.Init.DoubleBufferBaseAddress = (uint32_t)buffer_a; hmdma.Init.DoubleBufferCurrentBuffer = 0; // 0 表示使用 buffer_a hmdma.Init.SourceBurst = MDMA_SOURCE_BURST_4BEATS; hmdma.Init.DestBurst = MDMA_DEST_BURST_4BEATS; hmdma.Init.SourceAddress = (uint32_t)&hadc1.Instance->DR; hmdma.Init.DestAddress = (uint32_t)buffer_a; HAL_MDMA_Init(&hmdma); // 3. 使能 MDMA 中断 HAL_NVIC_SetPriority(MDMA_IRQn, 0, 0); HAL_NVIC_EnableIRQ(MDMA_IRQn); // 4. 启动 MDMA HAL_MDMA_Start(&hmdma, (uint32_t)&hadc1.Instance->DR, (uint32_t)buffer_a, 1024); } // MDMA 中断回调 void HAL_MDMA_TC_Callback(MDMA_HandleTypeDef *hmdma) { // 判断当前缓冲区,处理数据 if (hmdma->Init.DoubleBufferCurrentBuffer == 0) { // buffer_a 已填满,处理 buffer_a 数据 ProcessData(buffer_a, 1024); } else { ProcessData(buffer_b, 1024); } } // 注意:MDMA 中断处理函数中需调用 HAL_MDMA_IRQHandler void MDMA_IRQHandler(void) { HAL_MDMA_IRQHandler(&hmdma); } ``` ## 关键细节与注意事项 - **缓冲区对齐**:MDMA 要求缓冲区地址按 32 字节对齐,否则可能触发总线错误。 - **数据宽度匹配**:ADC 数据寄存器为 16 位,MDMA 源和目标数据宽度必须一致,否则数据错位。 - **中断优先级**:MDMA 中断优先级应高于 ADC 中断,但低于实时性要求更高的任务中断,避免数据覆盖。 - **启动顺序**:务必先启动 ADC DMA,再启动 MDMA,否则 MDMA 可能因无请求而挂起。 - **双缓冲切换**:MDMA 在传输完成时自动切换缓冲区,但需在回调中确认当前缓冲区,避免处理旧数据。 - **缓存一致性**:若使用 D-Cache,需在 MDMA 搬运前执行 `SCB_CleanDCache`,搬运后执行 `SCB_InvalidateDCache`,否则可能读到脏数据。 - **调试技巧**:使用逻辑分析仪观察 MDMA 请求信号,确认 ADC 触发频率与 MDMA 传输速率匹配。 ## 总结 通过 MDMA 与双缓冲的结合,STM32H7 能实现 ADC 连续采样数据的零 CPU 干预搬运,显著降低系统负载。本文的配置细节和代码示例可直接应用于实际项目,但需根据具体硬件调整参数。掌握这一技术,你将能轻松应对高速数据采集场景,为后续信号处理留出充足 CPU 资源。