3、DMA基础概念:什么是DMA?为什么需要DMA?
各位同学,咱们今天聊一个嵌入式开发里绕不开的话题——DMA。
我记得刚入行那会儿,第一次接触MTK平台,看到数据手册里DMA那一章密密麻麻的寄存器,说实话,头有点大。后来真正在项目里调通了第一版DMA传输,才恍然大悟:原来这东西就是帮CPU干苦力活的。
3.1 什么是DMA?
DMA,全称Direct Memory Access,直接存储器访问。
说白了,就是一块硬件,专门负责搬数据。CPU只需要告诉它:从哪搬、搬到哪、搬多少,然后就可以撒手不管了。DMA自己吭哧吭哧把活干完,最后给CPU发个中断说一声:搞定了。
核心思想:DMA让CPU从数据搬运这种机械劳动中解放出来,专心做计算和控制。
你想想看,如果没有DMA,CPU每次要搬数据都得亲自下场——读寄存器、写内存、循环判断……这得多浪费算力?
3.2 为什么需要DMA?
这个问题我在课上经常问学生。答案其实就三个字:效率。
我给大家算笔账:
| 场景 | CPU亲自搬 | DMA搬 |
|---|---|---|
| CPU占用率 | 100%(搬完才能干别的) | 0%(启动后就去干别的) |
| 传输速度 | 受限于CPU指令周期 | 硬件直接操作总线,更快 |
| 功耗 | CPU高频运行,功耗高 | DMA低功耗硬件,省电 |
| 实时性 | 容易被中断打断 | 硬件独立完成,稳定 |
我在MTK平台上做过一个音频项目,需要把I2S接口进来的音频数据实时搬到内存。如果让CPU逐字节搬,系统直接卡死,连触摸屏都动不了。换成DMA后,CPU占用率从95%降到了5%。嗯,这就是差距。
我的经验:只要数据量超过几十个字节,或者传输频率超过1KHz,就别让CPU亲自干了。用DMA,省心省力。
3.3 DMA传输的四种基本模式
DMA传输模式,说白了就是看数据从哪来、到哪去。一共四种组合,我一个个讲。
3.3.1 内存到内存(Memory to Memory)
这个最简单。就是把内存A区域的数据,搬到内存B区域。
典型场景:图像数据从缓冲区搬到显存、协议栈数据从接收缓冲区搬到应用缓冲区。
// 伪代码示例:内存到内存DMA传输
DMA_Channel_Config ch;
ch.src_addr = 0x80000000; // 源地址:内存A
ch.dst_addr = 0x90000000; // 目标地址:内存B
ch.transfer_size = 1024; // 传输1024字节
ch.mode = DMA_MEM_TO_MEM; // 内存到内存模式
DMA_Start(&ch); // 启动DMA,CPU可以去做别的事了
我曾经在调试一个摄像头预览花屏的问题时,发现就是内存到内存的DMA传输地址没对齐,导致数据错位。后来强制4字节对齐,问题解决。这个坑,大家以后注意。
3.3.2 内存到外设(Memory to Peripheral)
这个模式很常用。CPU把数据准备好放在内存里,DMA负责把数据送到外设的寄存器。
典型场景:
- UART发送:把要发的数据从内存搬到UART的发送寄存器
- SPI发送:把显示数据从内存搬到SPI的发送FIFO
- I2C发送:把控制命令从内存搬到I2C的发送缓冲区
我建议你在配置这种模式时,特别注意外设寄存器的地址是否支持DMA访问。有些外设的寄存器是只读的,你往那写数据,DMA会报错。
注意:内存到外设模式,外设端地址通常是固定的寄存器地址,不会自增。而内存端地址会自增。这个细节搞反了,数据就全乱了。
3.3.3 外设到内存(Peripheral to Memory)
这个模式,说白了就是DMA帮CPU收数据。外设有数据来了,DMA自动把它搬到内存里。
典型场景:
- UART接收:串口收到一字节,DMA自动存到内存缓冲区
- ADC采样:ADC转换完成,DMA把结果搬到内存数组
- 摄像头数据:摄像头输出一帧图像,DMA搬到内存
我记得在MTK平台上调一个GPS模块时,NMEA数据每秒输出一次,每次几百字节。如果用中断方式接收,CPU每秒被中断几百次,系统响应都变慢了。换成DMA接收后,CPU只在数据接收完成时处理一次,轻松多了。
3.3.4 外设到外设(Peripheral to Peripheral)
这个模式用得最少,但某些场景下非常有用。
典型场景:
- 两个UART之间数据转发
- SPI从设备数据直接送到I2S输出
- 定时器捕获值直接送到PWM比较寄存器
不过说实话,我在实际项目中很少用外设到外设模式。原因有两个:
- 很多MTK芯片的外设到外设DMA支持有限,需要查数据手册确认
- 调试起来比较麻烦,两个外设的时序要完全匹配
我的建议:除非性能要求极高,否则外设到外设的传输,还是走内存中转一下更稳妥。虽然多了一次拷贝,但调试和维护成本低很多。
3.4 四种模式对比总结
| 模式 | 源地址 | 目标地址 | 地址自增 | 使用频率 |
|---|---|---|---|---|
| 内存→内存 | 内存 | 内存 | 两端都自增 | 高 |
| 内存→外设 | 内存 | 外设寄存器 | 源自增,目标固定 | 很高 |
| 外设→内存 | 外设寄存器 | 内存 | 源固定,目标自增 | 很高 |
| 外设→外设 | 外设寄存器 | 外设寄存器 | 两端都固定 | 低 |
最后说一句:DMA不是万能的。小数据量、低频次的数据传输,用中断反而更简单。我一般以64字节为分界线——小于64字节用中断,大于64字节用DMA。当然,这个阈值你可以根据实际项目调整。
下一节,咱们深入MTK平台的DMA控制器,看看寄存器怎么配、中断怎么处理。到时候我会拿一个真实的UART DMA接收案例,手把手带大家走一遍。