基本概念
先讲一讲基本概念和开发思路。只要把概念和思路理清楚了,就能融会贯通。
DMA
直接存储器访问。这个不用多说了。
burst
关于burst这个词,很多文献中都翻译为“突发”。从中文字面上,不太好理解。本文中还是使用burst这个词。
burst是一次DMA传输中的最小触发单元。每当DMA通道触发时进行的数据传送。触发源可以是外设事件或者软件触发。每次传输最多可以达到32个字。这个数量称为burst数量。(说明,为了理解上的方便,这里先不关心寄存器设置值与概念值的偏差。实际上,burst数量=BURST_SIZE+1。)
burst有个传输计数器(BURST_COUNT),用于表示当前还有多少个字没有完成。burst开始时,将burst数量加载到burst计数器。传输过程中每传一个字,计数器减一,一直减到0,代表burst完成。
burst还有一个状态标志(BURST_STS),burst开始时由硬件置1,完成后归0.
在burst传输过程中,每传输一个字,源地址和目的都会增加一个步长(BURST_STEP)。这个步长是个16位的有符号整数,也就是说,步长既可以是正数,也可以是负数,范围为-4096~+4095. 源地址步长和目的地址步长是分别可设的,对应着2个寄存器:SRC_BURST_STEP和DST_BURST_STEP。
transfer
一次完整的DMA数据传输称为transfer,可以包含多个burst. 所包含的burst数量称为transfer数量。
与burst类似,transfer也有传输计数器(TRANSFER_COUNT)、传输状态(TRANSFER_STS)、传输步长(SRC_TRANSFER_STEP和DST_TRANSFER_STEP)。
另外,一个transfer对应着一次完整的DMA传输,可以使能本通道的DMA中断。中断可以发生在transfer的开始,也可以发生在transfer结束。
DMA控制
有2个标志位用于控制DMA通道的传输:RUN和HALT。都是写1有效。当RUN写入1时,启动DMA传输,RUNSTS标志位由硬件置1,表示DMA正在运行。当HALT写入1时,停止DMA传输,RUNSTS标志位清零。
单次模式(ONESHOT)和连续模式(CONTINUOUS)
单次模式针对burst有效。
禁止单次模式时(ONESHOT=0),每一次burst都需要外设事件触发。如果transfer中包含N个burst,则需要N个外设事件的触发脉冲,才能完成一次DMA传输。
使能单次模式时(ONESHOT=1),只要一个外设事件的触发脉冲,就可以完成一次transfer中的N个burst。
连续模式针对transfer有效。
禁止连续模式时(CONTINUOUS=0),当启动DMA传输后,只完成一次transfer。完成后RUNSTS自动清零。要开启下一次transfer,需要再次启动DMA传输(RUN写入1)。
使能连续模式时(CONTINUOUS=1),一旦启动DMA,则RUNSTS一直为1,DMA通道一直在运行(也要等待外设事件触发才会进行数据传输),直到HALT写入1才停止运行。
典型场景
假定现在ADC的转换结果需要由DMA来传送到内存中。
为简单起见,ADC只转换2路。ADC结果分别为ADCResultA和ADCResultB。
简单用法
简单用法:每次DMA传输时,只需要将两个ADC结果输送到内存中。
在此场景中,需要配置的有:
burst数量为2;burst源地址步长SRC_BURST_STEP为1;burst目的地址步长DST_BURST_STEP为1.
burst传输过程分为2步:第1步将ADC结果A保存到内存A中;然后源址加1,指向ADC结果B,目的地址也加1,指向内存B;再将ADC结果B保存到内存B中。
高级用法
为了方便滤波,每次DMA传输时,需要对A和B进行4次采样,并把采样结果(共2*4=8个字)传送到内存里8个字的数组中。
在此场景中,需要配置的有:
burst数量为2;burst源地址步长SRC_BURST_STEP为1;burst目的地址步长DST_BURST_STEP为4. 因为burst传输的第2步中,内存要增加4个字(比如从A1到B1)。
transfer数量为4;
传输过程如下图所示:
第1个burst传输时,把ADC的结果保存到A1和B1;
第2个burst传输时,把ADC的结果保存到A2和B2;
第3个burst传输时,把ADC的结果保存到A3和B3;
第4个burst传输时,把ADC的结果保存到A4和B4。
地址WRAP
问题的提出
在上述的传输过程中,如果仔细分析的话:
首先,在第1个burst传输的第1小步,源地址指向ADCResultA,目的地址指向A1;数据从A保存到A1;
然后,源地址加一,指向ADCResultB;目的地址加4,指向B1;数据从B保存到B1.
第一次burst传输完成。
再然后呢?
再然后就是第二次burst传输了。但是,源地址怎么才能再一次指向A呢?目标地址也应该回到A2啊。怎么办?
方案一:使用transfer步长
配置源地址transfer步长SRC_TRANSFER_STEP为-1(0xFFFF);配置目的地址transfer步长DST_TRANSFER_STEP为 -3(0xFFFD).
方案二:使用地址WRAP
DSP提供了一种“回绕”或者叫做“换行”的机制:Wrap。
源地址和目的地址可以分别配置WRAP_SIZE和WRAP_STEP。
WRAP_SIZE对应的是burst数量。当完成WRAP_SIZE个burst传输时,就发生地址WRAP。此时,在起始地址的基础上增加WRAP_STEP,重新使用该地址作为下一次burst传输的地址。这里的“起始地址”就是第一次burst的地址。
比如,上例中,可以配置源地址和目的地址的wrap_size都为1,也就是每完成一次burst传输都发生一次地址wrap。源地址的wrap_step为0,也就是回到第一次的源地址ADCResultA。目的地址的wrap_step为1,也就是说,在第一次的目的地址A1的基础上加1,变成A2.
地址指针及传输控制
涉及到的地址寄存器有8个。源地址和目的地址各4个。两种地址的处理逻辑完全相同,下面只选一种来讲述。
这4个地址中,有2个是活动地址(当前正在使用的有效地址),另2个是影子地址。又可以分为当前地址和起始地址。它们之间的关系如下:
一共是4个地址和3个步长(burst步长、transfer步长和wrap步长)
影子地址
首先看影子地址。这2个寄存器一般在程序初始化时由软件写入,指向外设或者内存的起始位置(比如,源地址指向ADC_ResultA,目的地址指向内存数组的开头)。在程序运行过程保持不变。
配置这个地址的库函数为:
//
// DMACH1AddrConfig - DMA Channel 1 Address Configuration
//
void DMACH1AddrConfig(volatile Uint16 *DMA_Dest,volatile Uint16 *DMA_Source)
{
EALLOW;
//
// Set up SOURCE address:
//
DmaRegs.CH1.SRC_BEG_ADDR_SHADOW = (Uint32)DMA_Source; // Point to
// beginning of
// source buffer
DmaRegs.CH1.SRC_ADDR_SHADOW = (Uint32)DMA_Source;
//
// Set up DESTINATION address:
//
DmaRegs.CH1.DST_BEG_ADDR_SHADOW = (Uint32)DMA_Dest; // Point to
// beginning of
// destination buffer
DmaRegs.CH1.DST_ADDR_SHADOW = (Uint32)DMA_Dest;
EDIS;
}
可以看到,配置过程中,源地址的影子地址和影子起始地址为同一个数值。目的地址也一样,影子地址和影子起始地址也相同。
加载影子地址
在启动DMA传输(即transfer开始时,图中的“start"),由影子寄存器加载到对应的活动寄存器。
burst步长和transfer步长
前面已经说过了。在burst传输过程中,每完成一个字的传输就增加burst步长;在transfer传输过程中,每完成一个burst传输就增加transfer步长。
wrap事件
在完成wrap_size个burst传输时,发生wrap事件。此时,活动的起始地址会增加wrap_step,并将增加后的值加载到当前活动地址。比如源地址的处理过程为:
SRC_BEG_ADDR_ACTIVE = SRC_BEG_ADDR_ACTIVE + SRC_WRAP_STEP
SRC_ADDR_ACTIVE = SRC_BEG_ADDR_ACTIVE
需要注意的是,当发生wrap时,transfer步长无效。当前活动地址不再是增加transfer步长后的结果,而是起始地址再加上wrap步长的结果。
总结
每一次DMA传输对应一个transfer。
可以在transfer开始或者结束时触发中断。
每个transfer会传输M个burst. 每个burst完成后地址会递增一个burst步长。
每个burst会传输N个字。每个字传输完成后地址会递增一个transfer步长。
Burst是内环,transfer是外环。
完成K个burst后,可以发生地址wrap。新的地址为起始地址+wrap步长。
附上DMA状态图: