第1章 DMA传输概述
大家好,我是老李,搞嵌入式系统十几年了。今天咱们开篇聊聊DMA——直接存储器访问。这玩意儿在嵌入式系统里几乎无处不在,但很多人对它又爱又恨。爱的是它能解放CPU,恨的是调试起来容易让人抓狂。尤其是缓存一致性问题,一不留神就掉坑里。
这一章我们先把基础打牢。搞清楚DMA是什么、DMA跟CPU怎么配合工作。后面那些调试技巧才能用得上。
什么是DMA?
DMA全称Direct Memory Access,直接存储器访问。说白了,就是一块硬件,能在不占用CPU的情况下,自己把数据从A搬到B。
我记得刚入行那会儿,第一块板子上跑数据采集,CPU每隔200微秒就得去读一次ADC结果,再写到内存。程序一跑,主循环卡得要死。后来老工程师甩给我一句话:“用DMA啊,CPU只负责点火,搬砖交给DMA去做。”换完之后,CPU占用率直接降了90%。
DMA传输的核心流程其实很简单:
- CPU配置好源地址、目的地址、传输长度
- 启动DMA,然后CPU该干嘛干嘛
- DMA自己走总线,在源和目的之间搬数据
- 搬完触发中断,通知CPU“活干完了”
你想想看,如果CPU每搬一个字节都要取指令、执行指令,那效率多低。DMA是硬连线逻辑,一次传输只需要占用总线几个时钟周期。尤其在大量数据重复搬运的场景下,DMA简直是神器。
DMA与CPU的协同工作
很多人以为DMA和CPU是抢关系的。其实不然。DMA和CPU共用系统总线,但有一套仲裁机制。谁有更高优先级,谁先用总线。常用的策略有:
- 轮询仲裁:CPU和DMA轮流用总线,公平但不高效
- 优先级仲裁:通常CPU优先级高,DMA发请求时,CPU会在空闲周期放行
- 突发传输:DMA连续搬多个字再释放总线,减少切换开销
我个人习惯在工程设计里,把DMA优先级设得比CPU高,但只在传输期间。这样CPU做计算时,DMA搬运数据,互不干扰。但要注意:如果DMA长时间霸占总线,CPU中断响应会被延迟。所以很多芯片支持DMA在每次传输后自动让出总线。
下面是DMA与CPU协同工作的一个典型流程:
- CPU配置DMA:写源地址寄存器、目的地址寄存器、传输计数值
- CPU启动DMA:置位使能位,DMA开始工作
- DMA请求总线:DMA控制器向总线仲裁器发请求
- 总线仲裁授予:CPU当前指令执行完,释放总线,仲裁器把总线让给DMA
- DMA执行传输:从源地址读数据,写到目的地址,每次传输后地址加1,计数减1
- 传输完成:计数到0,DMA发中断给CPU
- CPU响应中断:进入中断服务程序,处理传输完的数据
这个流程里有个隐藏的坑——缓存一致性。后面我们会专门讲,这里先提一嘴:如果CPU先写了内存的数据,然后让DMA把这个数据搬到外设。但CPU写的数据还在Cache里,没回写到内存。DMA直接从内存读,读到的是旧数据。这就是经典的一致性问题。
CPU搬运 vs DMA搬运
为了更直观对比,我整理了一个常用表格:
| 对比维度 | CPU搬运 | DMA搬运 |
|---|---|---|
| 数据传输方式 | 逐字读-写-循环 | 硬件自动批量搬移 |
| CPU利用率 | 传输期间CPU被占用 | 传输期间CPU可执行其他任务 |
| 传输速度 | 受CPU频率和指令流水线限制 | 纯总线速度,通常比CPU快2~3倍 |
| 适用场景 | 小数据量、非实时或低频率操作 | 大数据量、周期性传输(如ADC采样、显示刷新) |
| 代码复杂度 | 简单循环 | 需要配置寄存器、处理中断 |
| 缓存问题 | CPU直接操作,天然一致 | 容易产生缓存一致性问题 |
选哪个,看你的场景。我个人习惯:单次传输少于50字节且不频繁,直接用CPU搬。超过256字节且需要周期性执行,必上DMA。
图形化理解DMA工作流程
下面这张图总结了DMA从配置到完成的完整路径。你一眼就能看明白各个角色怎么配合。
小技巧: 配置DMA时,建议把传输长度寄存器先清零再赋值。我遇到过芯片复位后该寄存器保留随机值的情况,直接写低16位会出错。养成好习惯,先清零后赋值,少踩坑。
小结
这一章我们捋清了DMA的基本概念和与CPU的协同机制。DMA本质上是个“硬件搬运工”,CPU只管交任务、收结果。两者通过总线仲裁和中断机制配合工作。
重点记住三点:
- DMA传输不占CPU时间,但会占总线带宽
- DMA配置必须保证源/目的地址和长度准确
- 缓存一致性是调试中最容易忽略的雷——CPU写内存后,DMA读之前,一定要clean Cache;DMA写内存后,CPU读之前,一定要invalid Cache
后面的章节,我会结合真实调试案例,详细拆解缓存一致性问题怎么定位、怎么修复。咱们慢慢聊。
一句话总结: DMA让CPU从重复搬砖中解放出来,但解放的前提是——你得跟缓存搞好关系。