STM32H7 零中断搬运:MDMA 与定时器联动实现外设到内存的高效传输
👁 2 阅读 · 2026-08-27 · 嵌入式
在 STM32H7 高性能 MCU 上,传统中断驱动的外设数据搬运常因频繁上下文切换而消耗 CPU 资源。本文深入解析 MDMA(Master DMA)与定时器联动机制,实现外设到内存的零中断搬运,彻底解放 CPU。通过原理讲解、CubeMX 配置、完整代码示例及关键注意事项,帮助开发者掌握这一高效数据传输方案,适用于 ADC 连续采样、高速通信等场景。
# 引言
在嵌入式系统中,外设数据(如 ADC 采样值、UART 接收帧)通常需要搬运到内存进行处理。传统方法依赖中断服务函数(ISR)逐次读取数据,但高频中断会严重占用 CPU 时间,尤其在 STM32H7 这类高性能 MCU 上,浪费了宝贵的处理能力。MDMA(Master DMA)是 STM32H7 特有的增强型 DMA,支持内存到内存、外设到内存的突发传输,并可独立于 CPU 运行。本文将介绍如何利用定时器触发 MDMA,实现外设到内存的零中断搬运,让 CPU 专注于核心算法。
# 原理剖析
## MDMA 与常规 DMA 的区别
- 常规 DMA(如 DMA1/DMA2)主要面向外设与内存之间的传输,但每次传输需要 CPU 配置,且传输完成后通过中断通知 CPU。
- MDMA 是更高层次的 DMA 控制器,支持内存到内存、外设到内存,具有 16 字节突发传输、双缓冲、链表模式等高级功能,可独立完成复杂传输任务,且不占用 CPU 中断资源。
## 定时器触发机制
STM32H7 的定时器(如 TIM1/TIM8)可以产生触发输出(TRGO),连接到 MDMA 的请求输入。当定时器计数达到设定值(如更新事件),会生成触发信号,MDMA 收到后自动启动一次传输。这种硬件联动无需 CPU 干预,实现了真正的零中断搬运。
## 传输流程
1. 定时器配置为 PWM 模式或更新事件,产生固定频率的触发信号。
2. MDMA 配置为外设到内存模式,外设地址指向 ADC 数据寄存器,内存地址指向缓冲区。
3. 每次触发,MDMA 将外设数据搬运到内存,并自动递增内存地址。
4. 当传输计数完成后,MDMA 可配置为循环模式,持续搬运。
# 硬件与软件准备
- 硬件:STM32H743 开发板(如 NUCLEO-H743ZI2),外设使用 ADC1 的通道 0(PA0)模拟输入。
- 软件:STM32CubeIDE 1.13+,STM32CubeH7 固件库 V1.11+。
# 配置步骤(基于 CubeMX)
## 1. 定时器配置
- 选择 TIM1,时钟源为内部时钟(如 100MHz)。
- 设置预分频器(PSC)和自动重载寄存器(ARR)以产生 1kHz 触发频率:
- PSC = 99(100MHz/100 = 1MHz)
- ARR = 999(1MHz/1000 = 1kHz)
- 在“Trigger Output (TRGO)”中选择“Update Event”。
## 2. ADC 配置
- 启用 ADC1,选择通道 0,采样时间设为 32.5 周期。
- 将 ADC 设置为“Discontinuous Conversion Mode”或“Single Conversion”,并启用 DMA 请求(但实际由 MDMA 触发,ADC 的 DMA 请求可关闭)。
- 注意:ADC 的触发源选择“Timer 1 Trigger Out”,确保 ADC 与定时器同步。
## 3. MDMA 配置
- 在“Connectivity”中找到 MDMA,添加一个 MDMA 通道。
- 请求源选择“TIM1_TRIG”,方向为“Peripheral to Memory”。
- 外设地址:&ADC1->DR,内存地址:自定义缓冲区(如 adc_buffer[256])。
- 传输大小:半字(16位,ADC 数据为 12 位)。
- 突发大小:4 或 8 字节,提高效率。
- 循环模式:启用,以便连续搬运。
- 传输计数:256(缓冲区大小)。
## 4. 生成代码
在 CubeMX 中生成初始化代码,然后添加用户逻辑。
# 完整代码示例
以下代码基于生成的工程,在 main.c 中添加关键部分。
```c
/* 缓冲区定义 */
#define BUFFER_SIZE 256
uint16_t adc_buffer[BUFFER_SIZE];
/* 用户代码区(在 main 函数中) */
int main(void)
{
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
MX_ADC1_Init();
MX_TIM1_Init();
MX_MDMA_Init();
/* 启动 ADC 和定时器 */
HAL_ADC_Start(&hadc1);
HAL_TIM_Base_Start(&htim1);
/* 启动 MDMA 传输,循环模式 */
HAL_MDMA_Start(&hmdma_mdma_channel0, (uint32_t)&ADC1->DR, (uint32_t)adc_buffer, BUFFER_SIZE);
while (1)
{
// 主循环无需处理数据搬运,可执行其他任务
// 当需要处理数据时,检查缓冲区是否更新(例如通过标志)
// 此处简单演示:每 100ms 打印缓冲区前 10 个值
HAL_Delay(100);
for (int i = 0; i < 10; i++) {
printf("%d ", adc_buffer[i]);
}
printf("\n");
}
}
```
MDMA 初始化代码(由 CubeMX 生成,但需确认参数):
```c
void MX_MDMA_Init(void)
{
hmdma_mdma_channel0.Instance = MDMA_Channel0;
hmdma_mdma_channel0.Init.Request = MDMA_REQUEST_TIM1_TRIG;
hmdma_mdma_channel0.Init.TransferTriggerMode = MDMA_REPEAT_BLOCK_TRIGGER; // 每次触发传输一个块
hmdma_mdma_channel0.Init.Priority = MDMA_PRIORITY_HIGH;
hmdma_mdma_channel0.Init.Endianness = MDMA_LITTLE_ENDIAN;
hmdma_mdma_channel0.Init.SourceInc = MDMA_SRC_INC_FIXED; // 外设地址固定
hmdma_mdma_channel0.Init.DestinationInc = MDMA_DEST_INC_WORD; // 内存地址递增
hmdma_mdma_channel0.Init.SourceDataSize = MDMA_SRC_DATASIZE_HALFWORD;
hmdma_mdma_channel0.Init.DestDataSize = MDMA_DEST_DATASIZE_HALFWORD;
hmdma_mdma_channel0.Init.DataAlignment = MDMA_DATAALIGN_PACK;
hmdma_mdma_channel0.Init.BufferTransferLength = BUFFER_SIZE;
hmdma_mdma_channel0.Init.SourceBurst = MDMA_SOURCE_BURST_4BEATS;
hmdma_mdma_channel0.Init.DestBurst = MDMA_DEST_BURST_4BEATS;
hmdma_mdma_channel0.Init.SourceBlockAddressOffset = 0;
hmdma_mdma_channel0.Init.DestBlockAddressOffset = 0;
HAL_MDMA_Init(&hmdma_mdma_channel0);
/* 关联 MDMA 到 ADC 和定时器 */
HAL_MDMA_ConfigTransfer(&hmdma_mdma_channel0, &sConfig);
}
```
注意:实际代码中,`sConfig` 需要根据需求配置,但 CubeMX 会生成完整结构。
# 注意事项
- **内存一致性**:MDMA 写入内存后,CPU 读取时需确保缓存一致性。在 STM32H7 上,若缓冲区位于可缓存区域(如 DTCM),需使用 `__DMB()` 或配置 MPU 为非缓存。推荐将缓冲区放在 AXI SRAM 并配置为强序。
- **触发频率与传输时间**:确保定时器触发频率低于 MDMA 完成一次传输的时间,否则数据可能丢失。例如,传输 256 个半字,突发 4 次,约需 64 个周期,远低于 1ms。
- **ADC 与定时器同步**:ADC 的触发源必须与 MDMA 的触发源一致,否则数据可能错位。本例中,ADC 由 TIM1_TRGO 触发,MDMA 也由 TIM1_TRGO 触发,但需注意时序:ADC 转换完成后,数据寄存器更新,MDMA 触发时读取到的是最新值。
- **循环模式下的缓冲区管理**:当缓冲区写满后,MDMA 自动回卷,可能覆盖旧数据。开发者需在合适时机处理数据,如使用双缓冲或半传输中断(但中断会破坏零中断特性,可考虑使用 DMA 的传输完成标志轮询)。
- **调试建议**:使用逻辑分析仪观察定时器触发信号和 MDMA 请求,确保时序正确。
# 总结
通过定时器与 MDMA 联动,STM32H7 实现了外设到内存的零中断搬运,极大降低了 CPU 负载。本文提供的配置和代码可直接应用于 ADC 连续采样等场景。开发者可根据实际需求调整触发源、缓冲区大小和传输模式,充分发挥 MDMA 的高效性能。