🎀 文章作者:二土电子
🐸 期待大家一起学习交流!
1、什么是DMA
DMA全程Direct Memory Access,即直接存储器访问。简单来讲,它的功能是把数据从一个地址搬运到另一个地址。通常有三个传输方向,分别是内存到内存,内存到外设和外设到内存。
DMA是开发过程中常用到的,但从个人角度来讲,觉得DMA不是很好学。倒不是说他有多难,只是有些寄存器位的含义可能初学时了解的不深刻,很容易出现问题,这里仅代表个人看法。
有的DMA也有一些特殊的传输方式,比如回绕传输,这个在第3小节会有详细介绍。
2、DMA有什么用
第1小节说到,DMA是开发过程中常用到的,原因在于利用DMA搬运数据不需要CPU干预,可以减少对CPU的占用,提高整个系统的效率。
比如在串口接收或者发送时可以直接利用DMA将接收内容直接搬运到接收数组。或者利用DMA将准备发送的数据搬运到发送的缓冲区。
再或者利用DMA把数据搬运到特定的地址,或者从特定的地址利用DMA搬运数据出来。
总而言之,在平时的开发过程中,DMA是非常常用的。
3、怎么用DMA
3.1 常规的DMA配置
通常配置一个DMA主要需要配置以下内容
DMA通道数据源地址
搬运数据时的数据源地址的首地址DMA通道数据目的地址
搬运时接收地址的首地址DMA通道数据块最大长度
对于这个的理解容易出现问题,这个寄存器通常是指启动一次DMA传输时,DMA从数据源拿的数据的个数,设置成200,DMA就会从数据源搬运200个数据。注意是DMA从数据源拿的数据的个数,不是DMA传输到目的地址的数据个数。
一个数据宽度受后面的数据源至DMA的传输数据宽度影响。数据源至DMA的传输数据宽度
设定数据源至DMA的数据传输宽度,通常有8位,16位和32位DMA至数据目的的传输数据宽度
设定DMA至数据目的的数据传输宽度
需要注意的是,数据源到DMA的传输数据宽度和DMA到数据目的的传输数据宽度要设置成一样的,否则可能会导致传输数据出现错误。
数据源至DMA控制器的传输数据突发长度
这个可以理解为每次传输多少个数据给数据目的,也就是每次从数据源拿多少个数据。通常可以设置为1个单位,4个单位,8个单位和16个单位。比如上面DMA到数据目的的传输数据宽度设置的是8位,这里突发长度设置的为4个单位,则每次传输到数据目的的数据为8*4位,也就是4个8位数。数据传输方向
配置是内存和内存之间的数据传输还是内存和外设之间的数据传输,是内存传到内存还是内存传到外设或者是外设传到内存。DMA自动重复传输控制
配置是否在传输完设定的数据量之后自动重新再传输一次。数据源地址/目的地址自增
设置数据源地址是否自增,或者数据目的地址是否自增。关联通道
通常如果是内存和外设之间的数据传输时,需要将DMA关联到外设。
3.2 回绕传输
除了上述描述以外,还有一种回绕传输的方式。什么叫回绕传输,可以看一下下面的例子。
比如定义两个数组
uint8 a[8] = {
1,1,1,1,2,2,2,2};
uint8 b[8] = {
0,0,0,0,0,0,0,0};
利用回绕传输时配置好回绕的起始地址和结束地址,这里利用回绕传输将数组a的值传输到数组b。回绕传输时源地址回绕起始地址设置为数组a的首地址,也就是&a[0],源地址回绕结束地址设置为数组a的最后一位地址,也就是&a[6]。目的地址回绕起始地址设置为数组b的起始地址,也就是&b[0],目的地址回绕结束地址设置为数组b的最后一位地址,也就是&b[3]。这是使能DMA,可以看一下结果
画一下图来描述一下整个传输过程
发现传输时b[0]~b[3]传输结束后会再次从b[0]开始传输,这也就是“回绕”的意思。实际数组a也是,a[0]到a[6]传输完成后会再次从a[0]开始传输。
3.3 DMA中断
通常在使用DMA时也会用到DMA中断,比如做乒乓存储时会使用到DMA传输完成中断,在中断中把目的地址切换到另一个存储区。
有意思的是,DMA并不将Flash中的数据搬运出来。
4、DMA的拓展应用
在使用DMA的过程中有许多发挥空间。
1、比如DMA有一个TCNT寄存器。在初始化完成DMA后,我们配置的DMA通道数据块最大长度的数会被传输到TCNT中,DMA每搬运一个数据TCNT的值就会减1。当TCNT的值减到0时可以认为DMA搬运完成,此时如果开启了DMA搬运完成中断,会进入中断。利用这个可以代替DMA中断,在判断到TCNT等于0时切换存储区实现乒乓存储。此外也可以根据这个值来判断当前搬运了多少个数据了。
2、DMA还有一个寄存器可以指示当前数据源地址或者指示当前数据目的地址。在第一条的基础上,不仅拿到了传输个数,也拿到了当前传输的地址,此时可以做的事情就很多了。
这里举一个小例子,比如我需要ADC采集200个数据,但是在采集过程中我需要对每5个数据计算均值进行判断。
DMA使用的是DMA0,传输200个数据。由于使用的芯片并非常见芯片,这里就不再贴出DMA0的配置程序了,记住配置即可。
设计5个点计算程序时定义一个变量gPreTcnt 记录上一个TCNT值,初始值为200。使能DMA0之后TCNT的值就会随着搬运数据递减,当检测到gPreTcnt 于TCNT的差值大于等于5时说明至少已经搬运完成了5个数据,此时首先将当前的TCNT值赋给gPreTcnt ,然后根据DMA当前数据源地址寄存器的值,往前推5个利用指针来提取数据计算均值。当检测到TCNT等于0,也就代表着200个数据搬运完成了,此时可以在重新初始化一次DMA,同时也需要再给gPreTcnt 重新赋值为200。以下是程序设计。
uint8 gPreTcnt = 200; // 记录上一个TCNT值的变量
void Five_Point_Mean_Cal(void)
{
uint32 *pDmaCurAddr = 0; //DMA0的当前目的地址指针
uint32 tempVar = 0; // 提取数据时的循环变量
uint32 meanResult = 0; // 5个点均值计算结果
// 存储满5个点
if (gPreTcnt - DMACH0_TCNT >= 5)
{
gPreTcnt = DMACH0_TCNT; // 更新上一个TCNT值
pDmaCurAddr = (uint32*)DMACH0_DCUR; // 获取DMA0当前目的地址
// 提取0.5ms数据计算均值
for (tempVar = 1;tempVar <= 5;tempVar ++)
{
meanResult = meanResult + *(pDmaCurAddr - tempVar);
}
meanResult = meanResult / 5;
}
if (DMACH0_TCNT == 0)
{
重新初始化DMA0
gPreTcnt = 200;
}
}