Kinetis/RT 中的DMA

DMA全称Direct Memory Access, DMA是SOC系统中非常关键的一个部分,它可以在无CPU介入的情况下将数据从一个地方搬运到另一个地方,可以用于不同速率模块之间的接口。DMA可以有效的降低CPU的负载,从而使CPU可以完成更多运算相关的操作。

DMA一般的运行流程是:

  1. CPU初始化DMA传输的各种参数
  2. DMA开始数据传输,或者等待触发信号开始传输
  3. DMA数据传输完成之后产生中断,通知CPU完成数据传输

比如SOC上的串口在工作于DMA模式的时候可以自动将接收到的数据搬运至内存,或者将内存中的数据搬运到UART模块发送出去,这样就可以在无CPU介入的情况下完成数据传输。

一般的DMA传输,我们需要配置数据的源地址和目的地址,每次搬运数据的大小,小循环中搬运数据的数目和小循环的次数。如果需要类似串口那样的外部触发,还需要配置DMA_MUX模块,实现模块自动触发DMA操作。

当然DMA的数据传输一般不会这么简单,这里就把涉及到的其它一些知识也说明一下:

  1. 源地址和目的地址:这个是DMA传输的基础,代表数据从哪里来,到哪里去
  2. 源地址和目的地址的偏移:这个是代表每次搬移之后源地址和目的地址变化的一个增量。
  3. 小循环数据传输的次数:这个代表DMA一次Trigger之后,数据传输的数据。
  4. 大循环次数,这个代表DMA总共有多少次Trigger。
  5. 其它特殊配置,比如地址模数,DMA级联等等

源地址目的地址的偏移

在DMA设计中,为了应对各种不同的传输请求,设计了源地址和目的地址的偏移,这个可以控制DMA传输时候地址的变化。一般地址偏移有以下几种情况:

源地址不变,目的地址递增(或者递减)

这种情况的典型应用场合是数据接收,比如一个串口模块,接收到的数据放在一个FIFO中,数据通过DATA寄存器读出。这个时候DMA的源地址就可以直接设置为DATA寄存器的地址,而目的地址一般为SRAM中的一个数组,这样DMA就可以将收到的数据自动搬运到RAM中,CPU就可以在收到大量数据之后集中处理了。

这里DMA的设置中源地址的偏移应该设置为0, 因为源地址每次都不会变化,目的地址根据需要,比如数据是8比特的字符型,偏移地址就可以设置为1,数据传输的单位设置为8Bit。如果是16比特的数据,那么偏移地址为2, 数据传输单位为16Bit。

目的地址不变,源地址递增(或者递减)

这种情况一般对应数据发送的场合,比如通过SPI发送数据,这时候CPU先把要发送的数据放到RAM中,然后配置DMA的源地址为RAM中待发送数据数组的首地址,并配置源地址偏移为数据大小(Byte为单位),目的地址配置为SPI发送DATA寄存器地址,地址偏移为0.

DMA和FIFO的WaterMArk

现在一般的数据传输模块都会有一个FIFO,FIFO中都会有个WaterMark的配置参数,实际上这个参数和DMA关系比较密切的,比如接收FIFO的watermark配置为6,含义是当接收FIFO中有6个数据之后产生DMA请求。这个时候DMA可以直接读走6个数据,而不是每次读走1个数据,这样可以降低DMA触发的次数,如果MCU处于lower power模式,则可以降低唤醒次数。DMA直接读走6个数据就是通过小循环次数决定的。同理发送FIFO也有类似的配置,这个在程序编写的时候一定要注意充分利用片上的各种资源。

DMA地址模数

这个属于DMA的比较特殊的应用了,其实这个功能也是我决定好好研究DMA这个功能的原因。先说说我遇到的一个问题吧。

在接收数字麦克风数据的时候,因为数字麦克风有多个通道,每个通道都有自己的DATA寄存器,这些寄存器地址是连续有规律的,那么有没有可能让DMA循环传输这些数据呢?

这个时候我发现DMA有个地址模数的功能,这个功能就是为了传输这种循环数据的,比如DMA可以循环传输0到8地址的数据,地址从0到8之后再继续回到0传输。注意这个模数(SMOD, DMOD)的定义是按照2的幂次方地址定义的,比如SMOD=5,它代表当地址递增到2^5=32的时候,地址重新回到0。它的实现方式就是将当前地址对32进行取模之后作为新地址,所以才形成了地址循环。

这种地址取模方式有一个要求就是数据传输的首地址必须是模对齐的,即使刚开始地址不对齐,在传输过程中,地址经过取模之后还是会到一个模对齐的地址。比如我在数字麦克风中就遇到这样的问题,它的地址是0x24开始的,当DMA传输的时候,第八次传输的时候地址会退回到0x20,这样就会导致数据传输失败。

DMA级联模式

因为DMA地址模数的功能无法满足我的传输需求,最后我只能通过DMA级联的方式接收来自数字麦克风的数据。

DMA级联实际的功能和字面上的功能类似,就是可以将多个通道的数据连起来,比如0通道传完之后自动Trigger 1通道,并以此类推。在数字麦克风模块中,因为所有通道公用一个Trigger,所以采用了DMA级联的方式接收数据。

比如0-7通道的DMA分别对应0-7通道的数字麦克风,那么数据流如下:

Trigger -> CH0 -> CH1 -> CH2 -> CH3 -> CH4 -> CH5 -> CH6 -> CH7

DMA级联也分大循环和小循环,含义就是DMA往下传递的时候时发生在大循环结束还是小循环结束,这里我们用到的是小循环结束自动触发下一个通道,它的配置比较简单,就是打开级联开关,设定好下一个通道的通道数就好了。

DMA Minor Loop offset

DMA还有一个Minor Loop Offset的参数,这个参数的实际含义和字面意义差不多,基本上就是指DMA每次Minor Loop完成之后地址会经过一个Offset的修正。

我们再回到之前遇到的数字麦克风的问题,因为这个数字麦克风的数据寄存器地址是连续的,但是首地址又是不对齐的,所以不能直接通过取模来实现DMA传输。这里我们就可以通过Minor Loop Offset的功能实现地址修正,从而实现一个环状的数据传输。

比如我们依然把首地址设置为0x24,数据传输地址变化如下:

0x24->0x28->0x2c->0x30->0x34->0x38->0x3c->0x40 -> offset -0x1c -> 0x24

这里我们将Minor Loop设置为8,Minor Loop Offset设置为-0x1c,这样地址累加到0x40的时候Minor Loop结束,地址经过修正之后减去0x1c,再次回到0x24开始传输。

不过这样配置也有一个问题,因为Minor Loop是基于通道数目的,每次数据传输每个通道只能传输一个数据,这样麦克风模块内部的FIFO就发挥不了作用了。并且DMA Trigger的次数也会增加若干倍,此时如果系统是处于Low Power模式,将会导致系统唤醒次数增加,整体耗电增加。

综上,在配置数字麦克风DMA的时候还是要根据DMA剩余资源进行合理配置。

发表新评论