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比较寄存器

不过说实话,我在实际项目中很少用外设到外设模式。原因有两个:

  1. 很多MTK芯片的外设到外设DMA支持有限,需要查数据手册确认
  2. 调试起来比较麻烦,两个外设的时序要完全匹配

我的建议:除非性能要求极高,否则外设到外设的传输,还是走内存中转一下更稳妥。虽然多了一次拷贝,但调试和维护成本低很多。

3.4 四种模式对比总结

模式 源地址 目标地址 地址自增 使用频率
内存→内存 内存 内存 两端都自增
内存→外设 内存 外设寄存器 源自增,目标固定 很高
外设→内存 外设寄存器 内存 源固定,目标自增 很高
外设→外设 外设寄存器 外设寄存器 两端都固定

最后说一句:DMA不是万能的。小数据量、低频次的数据传输,用中断反而更简单。我一般以64字节为分界线——小于64字节用中断,大于64字节用DMA。当然,这个阈值你可以根据实际项目调整。

下一节,咱们深入MTK平台的DMA控制器,看看寄存器怎么配、中断怎么处理。到时候我会拿一个真实的UART DMA接收案例,手把手带大家走一遍。