嵌入式开发—浅析DMA

1、什么是DMA

DMA全程Direct Memory Access,即直接存储器访问。简单来讲,它的功能是把数据从一个地址搬运到另一个地址。通常有三个传输方向,分别是内存到内存,内存到外设和外设到内存。

DMA传输方向示意图

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;
    }
}
  • 2
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

二土电子

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值