DMA —— 让 CPU “偷懒”的数据搬运工

1. 为什么需要 DMA?

想象一下:你有一个串口每 100μs 收到一个字节,需要用 CPU 把这个字节从串口数据寄存器搬到内存的缓冲区里。

  • 没有 DMA 时:每次接收完成,串口中断触发 → CPU 读数据 → 存到数组 → 退出中断。如果数据速率很高(比如 2Mbps),CPU 大部分时间都在"搬砖",根本没空做其他任务。
  • 有 DMA 时 :配置 DMA 通道自动把串口数据搬运到内存,完全不需要 CPU 插手,只在搬完一整个数据块后才产生一次中断通知 CPU。

DMA(Direct Memory Access,直接存储器访问) 就是这样一个"硬件搬运工":它可以在 不占用 CPU 的情况下,在内存与外设、内存与内存之间快速传输数据。

2. DMA 的核心工作原理

DMA 控制器本身是一个精简的"小 CPU",它有:

  • 源地址:从哪读数据(可以是外设数据寄存器,也可以是内存数组)。
  • 目标地址:写到哪去。
  • 传输计数器:还要传多少字节。
  • 传输模式:单次、循环、增量/固定地址等。

基本流程

  1. CPU 配置 DMA 通道(源地址、目标地址、数据长度、触发源等)。
  2. 使能 DMA。
  3. 当触发事件发生(比如串口收到一个字节),DMA 自动执行一次搬运:读源地址 → 写目标地址 → 计数器减 1。
  4. 计数器减到 0 时,DMA 产生传输完成中断(可选),通知 CPU 处理。

3. 在 STM32 中的应用场景

场景 作用 效果
串口(UART)接收 DMA 将串口数据自动存入环形缓冲区 CPU 只在收到一帧完整数据时才处理
ADC 多通道扫描 DMA 将 ADC 结果连续存入数组 可实现高频采样(几十 kHz)而 CPU 几乎零负担
I2C/SPI 通信 DMA 自动发送/接收数据块 配合 RTOS,通信任务可以阻塞直到 DMA 完成
内存拷贝 两个内存区域之间高速拷贝 memcpy 快,且不占用 CPU
定时器 PWM 更新 用 DMA 自动修改多个 CCR 值 实现复杂的 LED 呼吸灯模式或波形输出

4. 关键概念:循环模式 vs 普通模式

  • 普通模式:传输计数器减到 0 后停止,需要 CPU 重新配置才能再次启动。适合固定长度的数据块(比如发送一个 512 字节的传感器数据包)。
  • 循环模式 :计数器减到 0 后自动重装初始值,继续从头搬运。适合环形缓冲区(比如连续 ADC 采样)。STM32 的 DMA 循环模式需要配合 FIFO双缓冲区 来防止数据覆盖。

5. 双缓冲区(Double Buffer)技巧

某些 STM32 的 DMA 支持双缓冲区(如 DMA2)。两个缓冲区交替使用:

  • 当 DMA 正在填充缓冲区 0 时,CPU 可以处理缓冲区 1。
  • 缓冲区满后,DMA 自动切换目标到另一个缓冲区,并触发中断通知 CPU 处理已满的那个。

这是实现 无锁、零拷贝 高吞吐数据流的标准方案。

6. 使用 DMA 的注意事项

  1. 数据一致性 :如果 DMA 和 CPU 同时访问同一块内存,必须用 __DSB() 屏障或关中断保护,防止 CPU 读到缓存中的旧数据(尤其在 Cortex-M7 带数据缓存时)。
  2. 对齐要求:某些 DMA 传输要求源/目标地址按字(4 字节)对齐,否则可能出错或降低效率。
  3. 通道冲突:多个外设可能共享同一个 DMA 通道(如 USART1_TX 和 SPI1_RX),需要合理分配优先级和仲裁。
  4. 中断优先级:DMA 传输完成中断通常设较低优先级,避免影响实时任务。
  5. 调试困难:DMA 在后台"偷偷"搬运,一旦配置错(如地址写反),现象很诡异(数据全零或错乱),需要仔细检查。

7. 代码示例(STM32 HAL)

c 复制代码
// 配置 ADC1 用 DMA 连续采样 100 个值到数组
#define ADC_BUFFER_SIZE 100
uint32_t adc_buffer[ADC_BUFFER_SIZE];

ADC_HandleTypeDef hadc1;
DMA_HandleTypeDef hdma_adc1;

void ADC_DMA_Init(void) {
    __HAL_RCC_ADC1_CLK_ENABLE();
    __HAL_RCC_DMA1_CLK_ENABLE();

    // 配置 DMA 通道
    hdma_adc1.Instance = DMA1_Channel1;
    hdma_adc1.Init.Direction = DMA_PERIPH_TO_MEMORY;
    hdma_adc1.Init.PeriphInc = DMA_PINC_DISABLE;   // 外设地址固定(ADC_DR)
    hdma_adc1.Init.MemInc = DMA_MINC_ENABLE;       // 内存地址递增
    hdma_adc1.Init.PeriphDataAlignment = DMA_PDATAALIGN_WORD;
    hdma_adc1.Init.MemDataAlignment = DMA_MDATAALIGN_WORD;
    hdma_adc1.Init.Mode = DMA_CIRCULAR;            // 循环模式
    hdma_adc1.Init.Priority = DMA_PRIORITY_HIGH;
    HAL_DMA_Init(&hdma_adc1);

    __HAL_LINKDMA(&hadc1, DMA_Handle, hdma_adc1);

    // 启动 ADC 并开始 DMA 传输
    HAL_ADC_Start_DMA(&hadc1, adc_buffer, ADC_BUFFER_SIZE);
}

之后,adc_buffer 中的值会不断被更新,CPU 可以在主循环中读取最新数据,完全无感知。

总结

DMA 是嵌入式系统实现 高吞吐、低 CPU 负载 的关键技术。学会 DMA,你的程序就能从"忙得团团转"变成"悠然自得"。

相关推荐
xiangw@GZ2 小时前
EMC原理:CS传导抗扰度测试总结
单片机·嵌入式硬件
椰羊~王小美2 小时前
STM32加密步骤简述
stm32
三佛科技-134163842122 小时前
PL3325CS/CD/CH/CE 与PL3325BE 之间的对比与联系(应用功率与典型应用电路)
单片机·嵌入式硬件·物联网·智能家居·pcb工艺
blevoice2 小时前
杰理智能蓝牙音响开发板AC696N:文件系统操作API精讲
单片机·物联网·杰理蓝牙芯片·ac6966b蓝牙音响方案·杰理智能音箱开发·杰理ac6965e蓝牙音频开发
lularible2 小时前
PTP协议精讲(2.18):遵循规则的艺术——Profile与一致性要求深度解析
网络·网络协议·开源·嵌入式·ptp
恶魔泡泡糖3 小时前
stm32F103C8T6标准库流水灯1——输出模式
stm32·单片机·嵌入式硬件
三佛科技-134163842123 小时前
FT838NB1-RT_5W(5V1A)原边反馈(5级能效)典型应用电路分析
单片机·嵌入式硬件·物联网·智能家居·pcb工艺
森利威尔电子-5 小时前
森利威尔SL3075 脚位完全兼容 TPS54560 65V降压恒压芯片5A电流能力
单片机·嵌入式硬件·集成电路·芯片·电源芯片
神一样的老师6 小时前
【兆易创新GD32VW553开发板试用】 BSP 从 GitHub 下载与编译指南
单片机·github·rt-thread