DMA基础回顾:工作原理、传输方向与模式

做嵌入式开发的朋友,对DMA应该都不陌生。但说实话,很多人用了好几年DMA,遇到复杂场景还是容易翻车。我刚开始做STM32项目时也是这样——觉得DMA不就是把数据从A搬到B嘛,能有多难?直到有一次在高速数据采集项目里,DMA和CPU抢总线导致采集丢帧,我才真正开始认真研究它的底层机制。

这一章,咱们把DMA的基础重新捋一遍。不是为了凑篇幅,而是后面讲优化和调试时,很多坑的根源就在这些基础概念里。

DMA工作原理:谁在帮你搬数据?

DMA的全称是Direct Memory Access,直接存储器访问。它的核心作用就一句话:在不占用CPU的情况下,完成外设与存储器之间的数据传输。

你想想看,如果没有DMA,每次ADC转换完成、UART收到一个字节,CPU都要停下来去处理中断、搬运数据。对于高速外设(比如ADC每秒采样几百万次),CPU根本忙不过来。

DMA控制器本质上是一个独立的总线主设备。它有自己的地址寄存器、数据计数器和传输控制逻辑。当外设触发DMA请求时,DMA控制器会向总线仲裁器申请总线控制权,拿到总线后直接执行传输,传输完成后再释放总线。

关键点:DMA传输是硬件自动完成的,不需要CPU逐字节干预。但DMA本身也是总线上的一个设备,它会和CPU、其他DMA通道竞争总线带宽。

我在一个多通道同步采样项目中就遇到过这个问题:两个DMA通道同时请求传输,结果总线仲裁导致其中一个通道的采样数据间隔不均匀。后来通过调整通道优先级和FIFO阈值才解决。

传输方向:数据往哪流?

STM32的DMA支持三种传输方向,说白了就是数据从哪来到哪去:

传输方向 描述 典型应用场景
外设→存储器 从外设数据寄存器读取数据,写入存储器 ADC采集、UART接收、SPI接收
存储器→外设 从存储器读取数据,写入外设数据寄存器 DAC输出、UART发送、SPI发送
存储器→存储器 从一个存储器区域复制到另一个区域 数据搬移、内存拷贝加速

这里有个容易忽略的细节:存储器到存储器传输在STM32上需要特别注意。它不像外设传输那样由硬件请求触发,而是由软件启动后连续执行。我记得有一次用DMA做内存拷贝,没注意源地址和目标地址的字节对齐,结果传输速度反而比CPU memcpy还慢。

我的习惯:存储器到存储器传输尽量用32位对齐的地址,并且一次传输的数据量最好是4的倍数。这样DMA可以充分发挥总线带宽,速度能提升30%以上。

传输模式:普通、循环、双缓冲

STM32的DMA支持三种传输模式,每种模式对应不同的应用场景。选错了模式,轻则浪费资源,重则数据错乱。

普通模式(Normal)

这是最基础的传输模式。DMA收到触发请求后,开始传输数据,直到设定的传输次数(数据项数量)完成后停止。传输完成后,如果需要再次传输,必须由软件重新配置。

适合一次性数据传输,比如把一段音频数据从Flash搬到DAC。我早期做的一个单次波形发生器就用这个模式,简单可靠。

循环模式(Circular)

传输完成后,DMA自动将地址和计数器重置为初始值,然后继续等待下一次触发。数据会像环形缓冲区一样不断循环。

这个模式在连续数据采集和输出中非常常用。比如ADC连续采样,数据不断写入一个缓冲区,CPU可以在后台处理已经填满的数据块。

我曾经踩过的坑:循环模式下,如果CPU读取缓冲区的速度跟不上DMA写入速度,就会出现数据覆盖。一定要配合双缓冲或乒乓缓冲机制来避免这个问题。

双缓冲模式(Double Buffer)

这是STM32比较高级的特性。DMA有两个存储器地址指针,可以交替使用。当DMA正在往缓冲区A写入数据时,CPU可以安全地处理缓冲区B的数据。传输完成后,DMA自动切换到缓冲区B,CPU处理缓冲区A。

说白了,这就是硬件实现的乒乓缓冲。不需要软件干预切换,效率非常高。

模式 自动重载 适用场景 注意事项
普通模式 否 单次传输、一次性数据搬移 每次需软件重新配置
循环模式 是 连续采样、连续输出 注意数据覆盖问题
双缓冲模式 是(交替) 高速连续数据流 两个缓冲区需连续地址空间

FIFO与突发传输:提升效率的关键

很多初学者不理解FIFO和突发传输的作用。我简单解释一下:

FIFO是DMA内部的一个小缓存。外设的数据先存到FIFO里,攒够一定数量后,再一次性通过总线写入存储器。这样做的好处是减少总线访问次数。

突发传输(Burst Transfer)则是DMA在一次总线仲裁中连续传输多个数据项,而不是每传一个字节就释放一次总线。

这两个机制配合使用,可以大幅提升传输效率。但配置不当也会出问题。

核心原则:FIFO阈值和突发长度要匹配外设的数据产生速率。阈值设得太小,突发传输的优势发挥不出来;设得太大,外设可能因为FIFO满而等待,导致数据丢失。

我做过一个音频采集项目,采样率96kHz,16位立体声。一开始FIFO阈值设成1/4,突发长度4,结果发现偶尔有数据丢失。后来把阈值改成1/2,突发长度8,问题就解决了。嗯,这里要注意,不同STM32系列的FIFO深度不一样,配置前一定要查手册。

知识体系总览

下面这张图把本章的核心知识点串起来了。你可以把它当作DMA基础知识的速查地图:

DMA基础知识体系 DMA控制器 传输方向 传输模式 FIFO与突发 外设→存储器 存储器→外设 存储器→存储器 普通模式 循环模式 双缓冲模式 FIFO阈值 突发长度 匹配配置 核心原则:DMA配置必须与外设特性、总线带宽、数据速率匹配 否则再高的理论性能也无法在实际项目中发挥出来

这张图把DMA的核心知识点分成了三个分支:传输方向、传输模式和FIFO与突发。每个分支下面又有具体的子项。你可以把它当作一个速查框架,后面讲到优化和调试时,我们会反复回到这些基础概念上。

我的建议:刚开始学DMA时,不要急着看高级特性。先把普通模式和循环模式用熟,搞清楚FIFO阈值和突发长度的关系。这些基础打牢了,后面遇到复杂问题才能快速定位根因。