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的核心知识点分成了三个分支:传输方向、传输模式和FIFO与突发。每个分支下面又有具体的子项。你可以把它当作一个速查框架,后面讲到优化和调试时,我们会反复回到这些基础概念上。
我的建议:刚开始学DMA时,不要急着看高级特性。先把普通模式和循环模式用熟,搞清楚FIFO阈值和突发长度的关系。这些基础打牢了,后面遇到复杂问题才能快速定位根因。