1. 什么是DMA?说白了就是让数据自己跑

大家好,我是你们的嵌入式系统讲师。今天咱们聊聊DMA——直接存储器访问。

这名字听着挺唬人,对吧?其实说白了,DMA就是一块硬件,专门负责搬数据。CPU呢,就可以腾出手来干正事。

我刚开始做嵌入式开发那会儿,总觉得DMA是个高大上的东西,碰都不敢碰。后来被逼急了——项目里数据量太大,CPU忙不过来,系统卡得跟幻灯片似的。没办法,硬着头皮学DMA。结果发现,嗯,这东西其实没那么玄乎。

1.1 没有DMA的时候,CPU在干嘛?

先看个最简单的场景:你要从ADC(模数转换器)读一批数据,存到内存里。

没有DMA的时候,代码大概是这样的:

// 伪代码:CPU亲自搬数据
for (i = 0; i < 1000; i++) {
    while(!(ADC->SR & ADC_FLAG_EOC));  // 等ADC转换完成
    buffer[i] = ADC->DR;               // CPU亲自读数据,再写到内存
}

你想想看,CPU每读一个数据,就要等ADC转换完。这期间CPU啥也干不了,就干等着。1000个数据,CPU就傻等1000次。

我见过一个项目,工程师用这种轮询方式读ADC,CPU占用率直接飙到80%以上。系统其他任务全被拖垮了。这就是典型的「CPU被数据搬运累死」的案例。

1.2 DMA来了:数据自己走

DMA的工作原理,其实特别简单。我画个图你就明白了:

DMA工作原理示意图 CPU 中央处理器 DMA控制器 直接存储器访问 存储器 SRAM / Flash 外设 ADC / UART / SPI 配置 数据搬运 请求 CPU只需配置DMA,之后数据搬运由DMA独立完成

你看这个图,CPU只需要配置一下DMA,告诉它「从哪搬、搬到哪、搬多少」。然后DMA就自己干活了。CPU呢?该干嘛干嘛去。

数据流是这样的:

  1. 外设触发:ADC转换完成,给DMA发个信号
  2. DMA响应:DMA收到信号,从外设寄存器读数据
  3. DMA写入:直接把数据写到内存里
  4. 通知CPU:全部搬完了,给CPU发个中断,「活干完了,你来处理吧」

核心要点:DMA搬运数据时,CPU完全不用参与。数据从外设到内存,或者从内存到外设,都是DMA自己搞定。

1.3 DMA到底怎么「绕过」CPU的?

这个问题我经常被问到。其实关键在于总线仲裁

系统总线上挂着好几个设备:CPU、DMA、外设、内存。谁想用总线,得先申请。CPU和DMA都有这个资格。

DMA工作的时候,会向总线仲裁器申请「总线控制权」。仲裁器一看,CPU这会儿没在用总线(比如在执行内部运算),就把总线交给DMA。DMA拿到总线,直接在外设和内存之间传数据。

整个过程,CPU压根不知道总线上发生了什么。它还在那算它的数据呢。

我的经验:我曾经在一个项目中,用DMA搬运UART接收的数据。CPU只需要在DMA完成中断里处理数据,平时完全不用管。结果CPU占用率从70%降到了15%。那感觉,就像给系统装了个涡轮增压。

1.4 DMA的三种搬运模式

DMA不是只会一种搬法。根据场景不同,有三种模式:

模式 怎么搬 典型场景
内存→内存 从一块内存搬到另一块内存 数据拷贝、缓冲区移动
外设→内存 从外设寄存器搬到内存 ADC采集、UART接收
内存→外设 从内存搬到外设寄存器 DAC输出、SPI发送

我个人用得最多的是「外设→内存」模式。比如采集音频数据,ADC每转换完一个样本,DMA就自动搬到缓冲区。等缓冲区满了,CPU一次性处理。效率极高。

1.5 避坑指南:DMA不是万能的

我曾经踩过的坑:有一次做高速数据采集,DMA配置好了,结果数据老是丢。查了两天才发现——DMA传输速度太快,内存带宽不够,导致数据覆盖。

用DMA要注意几点:

  • 内存带宽有限:DMA和CPU共用总线,DMA搬得太猛,CPU反而被拖慢
  • 缓存一致性问题:DMA直接操作内存,CPU的缓存里可能是旧数据。需要手动刷新缓存
  • DMA通道有限:芯片里的DMA通道就那么几个,别指望所有外设都用DMA

嗯,这些坑后面章节会详细讲。今天先把概念理清楚。

1.6 小结

DMA说白了就是:让硬件帮你搬数据,CPU专心做计算

我刚开始学的时候,总觉得DMA配置复杂,不如自己写循环。后来发现,数据量一上来,CPU根本扛不住。DMA不是锦上添花,而是雪中送炭。

下一节,咱们会深入DMA的寄存器配置,看看怎么在代码里把DMA用起来。到时候我会拿STM32的DMA控制器做例子,手把手教你配。

一句话记住DMA:CPU只管「算」,DMA只管「搬」。各司其职,效率翻倍。

专注资料整理