【嵌入式原理系列-第七篇】DMA:从原理到配置全解析
目录
引言
一.源地址和目的地址
二.传输方向
三.外设位宽和存储器位宽
四.地址增量
五.传输计数
六.DMA和外设的处理通知
七.数据块传输和硬件FIFO
八.循环模式和普通模式
九.传输完成通知
十.DMA通道及优先级
引言
DMA(Direct Memory Access,直接存储器访问)是 MCU 中的重要外设,它的出现主要是为了提高数据拷贝的效率,通过一定的设计使得DMA可以代替CPU进行数据拷贝,从而将CPU从这种重复又耗时的工作中解放出来。
一.源地址和目的地址
DMA既然需要做数据拷贝,那么就需要指定源地址和目的地址,即从哪拷贝到哪。那么可以指定哪些目标?
可指定的目标分为两类:
- 存储器:我们知道MCU的存储主要分为易失性存储器和非易失性存储器。DMA一般来说是无法对非易失性存储器进行操作的(用于存放数据的Data Flash由于只能按块读写,需要软件进行参与管理)。因此我们的目标也就是我们经常说的RAM(这里不区分SRAM和DRAM,统称RAM)。因此指定的存储器地址其实就是我们声明的全局变量的首地址。
- 外设:这里指定的外设一般是通信类外设,比如UART。指定的外设地址实际上是外设提供的硬件缓冲区的地址,具体地址需要查询芯片手册。
二.传输方向
指定了源地址和目的地址后,我们还需要指定传输方向。即以下三选一(DMA不提供外设到外设的传输方向,一般来说这个选项没有实际意义):
- 存储器(源)到外设(目的):使用较多,属于本文介绍的主要内容
- 外设(源)到存储器(目的):使用较多,属于本文介绍的主要内容
- 存储器(源)到存储器(目的):加速大数据块的拷贝传输(一般用的较少)
为什么指定了地址后还需要指定方向呢?DMA不能根据所指定的地址自行判断源和目的类型么?
我们需要知道MCU提供的地址有可能是虚拟地址,对于外设来说其缓冲区地址可能经过复杂的映射。并不像直接访问RAM那样方便。如果由用户随意指定地址交由DMA判断类型以及合法性,无疑会大大提供设计上的复杂度以及安全隐患。
通过简单的指定传输方向可以很大程度减少DMA设计复杂度,属于看起来冗余但是有用的设计。
三.外设位宽和存储器位宽
接下来我们需要配置外设位宽和存储器位宽。我们知道外设/CPU/存储器是通过总线来进行互通的,数据的通信效率收到总线位宽的限制。同时DMA的传输效率也收到源和目的地址能够处理的位宽最大值限制。
即我们每次DMA传输能发送的数据大小收到三个方面限制:
- 源地址每次操作位宽
- 目的地址每次接收位宽
- 总线位宽
为了提高DMA的效率,一般源地址和目的地址位宽设置遵循以下规则:
- 两者位宽一般设置相同值
- 位宽满足MIN(源位宽,目的位宽,总线位宽):当用于外设和存储器之间通信时,一般实际都是受外设最大处理位宽限制。
- 存储器设置优化:存储器的首地址和缓冲大小设置成4的倍数,这样可以减少拷贝过程中的对齐消耗。
四.地址增量
我们现在已经配置好了传输地址/传输方向/位宽。但是我们现在指定的地址是一个固定地址,假设传输方向是存储器到外设,位宽1字节,那么每次都是从固定地址取1字节,这样无疑非常低效。
因此,DMA提供了增量模式。即当设置后以上参数后,每次DMA传输会在位宽及相关参数的基础上偏移一个地址,这样就可以进行连续传输,比如将一个256字节的RAM数组从头到尾传输到UART外设。
存储器一般都需要配置为增量模式以提高传输效率,外设看具体芯片支持情况,大部分芯片的外设不支持外设地址增量(因为外设的缓冲区一般不大)。
五.传输计数
增量模式下,DMA并不知道你的缓冲区边界在哪里,因此需要显式指明传输计数。只要配置了增量模式,则必须配置传输计数,否则可能存在非法越界访问的风险。传输计数根据单次传输数据量以及缓冲区总大小计算。
六.DMA和外设的处理通知
刚才说过了DMA是按照位宽设置及相关参数来搬运数据,单次搬运数据量有限。对于存储器到存储器的搬运连续传输即可。
对于外设到存储器或者存储器到外设,由于外设需要一定的处理时间,因此需要有通知机制保证数据传输的正确性。即当外设收到数据或者数据处理完成需要通知DMA进行下一步的数据搬运。
这个外设和DMA之间的通知机制是依赖硬件来执行的,也是DMA的核心。如果没有这个硬件机制,则每一步的传输确认都需要软件执行,也就失去了DMA释放CPU工作量的初心。
七.数据块传输和硬件FIFO
前面介绍了外设每次处理完成需要通知DMA,DMA才能进行下一步的数据拷贝操作,为了提供效率(如果芯片支持)我们可以在中间设置一个硬件FIFO缓冲区,DMA每次拷贝若干个数据单元到FIFO缓冲区,外设按照FIFO次序顺序处理缓冲区的数据,这样可以减少DMA拷贝的次数。这个模式就叫做数据块传输。
需要注意的是,数据块传输必须配置DMA FIFO,否则无法生效,同时FIFO的深度需要大于数据块每次传输的最大数据量。
八.循环模式和普通模式
当按照传输计数和位宽传输完成了传输,理论上应该干什么?
当然是从头开始传输(循环模式)或者停止传输(普通模式)。DMA也提供了循环传输,当传输模式配置成循环+增量时,完成一轮传输后会从首地址重新进行传输。
循环模式下若CPU没来得及处理或者填充数据,重新开始传输会不会导致发送无效/重复数据(内存到外设)或者数据被覆盖(外设到内存)?
有可能出现此情况,因此在对安全性要求较高的汽车电子领域,一般采取以下做法。每次循环模式结束时,DMA会触发传输完成通知,CPU收到通知会立刻关闭循环模式,并根据传输方向进行数据填充或者数据处理,之后再打开循环模式。
既然如此为什么不直接配置普通模式,反正每次都是CPU关闭循环模式进行数据处理后再打开循环模式?
其实还是有意义的,因为循环模式下,许多参数不需要重新配置,如果是使用普通模式需要重新配置。此外循环模式对于一些对数据丢失不敏感的场景依然有很好的发挥。
车载领域适不适合使用双缓冲结构,即DMA往一个缓冲区写入/读取数据,CPU在另外一个缓冲区写入/读取数据?
安全性要求极高的车载领域,通常不推荐使用双缓冲结构。因为双缓冲机制要求CPU的处理速度必须始终高于DMA的数据吞吐速度,否则会导致缓冲区覆盖和数据丢失。这种依赖时序的假设在复杂的车载环境中难以得到绝对保证,因此引入了非确定性风险。更常见的做法是采用上述的‘半传输中断+循环模式’并结合安全机制(如看门狗、数据校验)来确保可靠性。
九.传输完成通知
DMA的传输完成通知一般使用中断形式,常用中断通知类型如下
• 传输完成中断:全部数据搬完后(由传输计数判断)再进行通知,一般用于传输方向内存到外设。若外设到内存使用此方法,当CPU处理数据时,新的数据可能会丢失,因此外设到内存一般建议使用半传输中断,此时不推荐使用传输完成中断。
• 半传输中断:
即搬运到缓冲区一半时触发(由传输计数判断),主要用于传输方向为外设到内存时,传输一半就进行中断通知,之前CPU处理前一半数据,DMA继续搬运后一半数据。
传输方向为内存到外设时,使用此中断可以使数据处理处理时间曲线更平滑,减少数据堆积时间,不强制使用。
• 传输错误中断:外设未就绪、总线出错、非法地址等
注意!
当传输方向是外设到内存时,DMA中断场景覆盖有缺失,即当数据量达不到传输完成或者半传输完成的要求,但是又没有后续数据。这时候一般外设中断(不如UART的Idle中断)或者CPU轮询(不是主流)。
十.DMA通道及优先级
DMA控制器一般提供不止一个通道,每个通道可以选择一个源地址和目的地址,理论上各个通道是独立并行,但实际上一般的芯片设计不同DMA通道实际上是共用总线,因此需要设定不同通道之间的处理优先级。
想了解更多嵌入式技术知识,请点击阅读我的其他文章
烟花的文章链接集合-CSDN博客
如果你觉得内容对您有帮助,别忘了点赞、收藏和分享支持下哦