数据传输还用 CPU?不如交给 DMA 吧!

https://mp.weixin.qq.com/s/CQQSV26Xvmt2xuAPFnh-YQ

鱼鹰  鱼鹰谈单片机  3月3日

预计阅读时间: 9 分钟

数据传输耗时又耗力?交给 DMA 去干吧!

 

参考:

stm32f2 技术培训_直接存储器访问_dma http://www.doc88.com/p-7952436689372.html

AN4031 应用笔记:使用 STM32F2 和 STM32F4 DMA 控制器

AN2548 应用笔记:使用 STM32F101xx 和 STM32F103xx DMA 控制器

STM32中文参考手册

看完这些就差不多了。

首先大概介绍一下功能吧,看笔记对于初学者可能有些吃力,在这里我将按我的理解进行说明。

DMA 的作用是在没有 Cortex-M3 核心的干预下,在后台完成数据传输。在传输数据的过程中,主处理器可以执行其它任务,只有在整个数据块传输结束后,需要处理这些数据时才会中断主处理器的操作。它可以在对系统性能产生较小影响的情况下,实现大量数据的传输。

以上是官方话。怎么理解呢?

假如现在你是 CPU,然后你的一个伙伴是 DMA,你的伙伴和你还有些不同,你可以做很多事情,比如判断、计算、存储、I/O 操作等等,但你的伙伴就比较笨了,他只能干一件事情,就是存储功能,但正因为他的专一性,所以干活的速度特别快,同样是干存储的活,他干的就比你快。所以你就会想,既然他能干存储的活,我干嘛还干,交给他就行了啊!所以当有人(用户)让你去干存储的活时,你就让你的伙伴 DMA 干了,你首先告诉你的伙伴从哪往哪搬数据、数据大小是多少、数据宽度是多少,把这些基本的信息告诉他之后,你就可以忙你自己的事情了,对了,记得提醒他一声,让他干完活就过来通知你(中断),好让你知道他已经干完了活,是继续干其他活还是去休息了。如果继续干活,就要继续把新的基本信息告诉他;如果是休息,就让他休息就好了。这样你就能节省不少时间忙你的其他工作,数据传输量少的时候可能区别不是很大,毕竟在小数据量的时候也要每次通知 DMA 告诉他基本情况,然后他干完活后又得通知你,有这时间,你自己就把活干了。但是在数据量大的时候就不一样了,你会发现 DMA 还是很有用的,你会发现你俩干活的差距是很大的。还有在你分身乏术的时候,也可以叫他。比如有些外设数据(SPI、UART、I2C 等)可能来的比较突然,就需要 DMA 始终监控这些数据,一旦有新数据来了,就开始搬运数据,这样就不怕新数据覆盖旧数据了。

这些就是大方向上的理解了,但是只是懂这些东西还是没用的,如果你不能从细节上把握,你还是不能很好的使用 DMA,细节上的东西就交给上面的文档了,下面是读文档之后的一些总结性内容,你可以在看完这些文档之后再回过头来看这些。

看完之后你会发现 STM32F4 的 DMA 和 STM32F1 的 DMA 有很大差别。

第一个区别就是,STM32F1 的通道和 STM32F4 的通道意义不同,F1 的通道指的是传输通道,如下所示:

而 F4 的通道是这样的:

你会发现 F1 通道意义类似与 F4 的数据流,而 F1 请求信号和 F4 的通道号类似的,只是以前直接使用或门,现在换成了通道选择开关,避免了一些干扰。他们的关系如下:

然后通过表格来了解一下他们之间的关系:

这是 F1 的

这是 F4 的

是不是发现他们很接近啊。但是如果你看了之前的文档,你就会发现她们的差异是很大的。

最大的区别就是 FIFO 了,这也是很多人(包括我)用过 F1 的 DMA,但是发现 F4 的 DMA 不好用,原因就在这个 FIFO 里面。关于 FIFO 原理可以参考另一篇文章,不过那个是讲软件实现,这个是硬件 FIFO。

FIFO 的存在是为了优化带宽,但是也增加了操作的复杂性,但是也可以禁用 FIFO,这样就类似 F1 的 DMA 了。但是明明有这么好的东西却不用,就有些可惜了。

FIFO 很大的一个特点,就是可以暂存数据,这样如果一次性有很多数据进来了,但你的出口端没有获得传输的条件,那么就可以暂存在 FIFO 里面了,一旦获得了总线仲裁,那么就可以开始传输了,这样数据就不容易丢失。

如果启用 FIFO,这里面就有一个阈值需要考虑。我们知道外设数据的传输条件是通过请求的,比如说串口接收数据过程。当串口接收到一个字节数据之后,串口就向 DMA 控制器发出请求,就会将数据传输至 FIFO 中暂存,如果关闭 FIFO 的情况下,这个数据是会马上传输到目的地的,但是现在你启用了 FIFO,那么这个数据就要在这里停留一段时间,那么这段时间是多少呢?不确定。不确定怎么玩?别急,慢慢听我说。

有三种条件会将 FIFO 数据传输到目的地中:

  1. 关闭 DMA。

在你启用了数据流的情况下,又关闭了数据流,如果 FIFO 里面有数据,那么就会将 FIFO 里面的数据传输到目的地中去,这个称之为刷新,但是 FIFO 的数据量你是不确定的,你怎么知道有多少个数据传输到了目的地呢?这个就要通过计数器反推了。比如说你设置数据流接收 10 个字节数据,现在计数器等于 3,那就说明你已经接收了 7 个字节,因为没接收一次数据,计数器都会减一,这样你就知道目的地到底存了多少个有效数据。再说一点,通过这种方式,再打开串口空闲中断,就可以接收不定长的串口数据了,不必像一些协议一样,通过一些特殊数据来判断帧头帧尾的方式来接收不定长数据,这样简单而高效。

注意:关闭 DMA 的时候一定要确定一已经关闭了,因为如果 DMA 当前还有工作在做,比如正在传输一个数据,这个时候 DMA 其实还没有关闭,只有在传输完成之后才会关闭。所以一定要在进行下一次 DMA 操作之前确保已经关闭了 DMA,在此之前必须等待,等待的条件可以是传输完成标志,也可以是 DMA 关闭位 EN。

  1. 接收完成。

还是串口接收过程,还是接收 10 个字节,当接收完 10 个字节之后,因为计数器已经是 0 了,所以硬件自动进行刷新工作,并且关闭数据流,如果下次要接收的话,就要重新打开了。不过这里有一个双缓冲模式,启用他的话,它就不会关闭数据流,而是继续工作,不过这次工作的目的地换了一个地方了。

  1. 阈值

通过设置阈值,也可以触发 FIFO 的传输工作。FIFO 的大小为 4 个字,一个字为 4 个字节,所以就有 16 个字节的空间大小。如果你设置的阈值为 1/2,那么当数据传输了 8 个字节的时候,就会触发条件,将 FIFO 中的数据传输到目的地当中去,当然了如果在传输的过程中串口又来了数据,那也是没问题的,毕竟 FIFO 就是用来缓存的啊,后来的数据在后面排队就行。但是如果你设置的阈值是满,那么可能就会丢失数据了,毕竟现在 FIFO 里已经没有空间了,不过不太可能的,因为串口传输数据实在是太慢了,当你触发满的条件开始传输时,在下一个字节数据来之前肯定最少能转移出一个字节数据,给新数据留个位置的。所以不用担心。

这里还有一点就是,假如你设置接收 10 个字节数据,设置的阈值是 1/4,也就是 4 字节,你现在接收了 5 个字节,前面四个字节因为触发了阈值条件已经将数据传输到目的地了,但是还有 1 个字节数据还滞留在 FIFO 里面没动,怎么办?就是通过关闭 DMA 的方式来将 FIFO 的数据转移至目的地。

使用 DMA 的时候要特别注意操作顺序。比如我做的一个项目,需要定时器触发 DMA,然后将内存中的数据转移至 PSC(实现实时改变频率的功能),一开始不懂,首先开启了定时器的请求,然后才配置 DMA,开启 DMA。这样导致的后果是,打开 DMA 失败,最后查找原因发现是出现了错误(通过错误状态寄存器观察),虽然通过清除错误的方式成功启动了 DMA,但还是不知道为什么会发生这种错,后面才知道原因:如果一开始就开始定时器请求的话,因为 DMA 中还没有数据,无法进行传输工作,所以就导致了错误。后面换了操作顺序就没有这种错误出现了。

最后在总结几点:

  1. F4 的 DMA 只有 DMA2 可以进行内存到内存的传输模式,并且必须开启 FIFO。

  2. 突发配置的限制

  1. 不同于 F1,F4 的不同数据长度进行 DMA 传输时必须开启 FIFO

  2. 停止外设的流程,不能只关闭 DMA,而不断开外设连接请求。

------------------------------------------------------------------------------------2018/12/05  Osprey

posted @ 2019-08-18 16:39  wdliming  阅读(646)  评论(0编辑  收藏  举报