DMA叫做直接存储器访问,用于在外设与存储器之间与存储器之间提供高速数据传输。可以在无需任何CPU操作的情况下通过DMA快速移动数据。这样节省的CPU资源可供其他操作使用。从硬件层次上来说,DMA 控制器是独立于 Cortex-M4内核的,有点类似 GPIO、USART 外设一般,只是 DMA的功能是可以快速移动内存数据。
要实现把外设数据写入内存,或把内存数据送至外设,一般由CPU控制完成,可采用标记查询或中断的方式。利用中断传送数据,可大大提高CPU的利用率。但当批量传输数据时,由于进出中断需保护现场和断点,无形之中增加了CPU的中断负载。另外,CPU的核心工作是算法处理,借用CPU进行大量数据传输,会降低CPU处理算法的效率。引入DMA技术,在一定程度上解放了CPU,提高了CPU的处理能力。
STM32F4xx的两个 DMA 控制器总共有 16 个数据流(每个控制器 8 个),每一个 DMA控制器都用于管理一个或多个外设的存储器访问请求。每个数据流总共可以有多达 8 个通道(或称请求)。每个通道都有一个仲裁器,用于处理 DMA 请求间的优先级。
DMA控制器结构框图
DMA 控制器执行直接存储器传输:因为采用 AHB 主总线,它可以控制 AHB 总线矩阵来启动 AHB 事务。
它可以执行下列事务:
● 外设到存储器的传输
● 存储器到外设的传输
● 存储器到存储器的传输(仅DMA2数据流能实现存储器到存储器的传输)
通道选择
每个数据流可连接8个通道,某一时刻具体使用数据流的哪个通道,需通过DMA_SxCR寄存器中的CHSEL[2:0]位控制。通道选择如下图所示:
两个DMA控制器的通道(请求)与数据流的映射关系如下所示:
a.DMA1通道选择(请求映射)
b.DMA2通道选择(请求映射)
由表可知,STM32F4XX的每个DMA控制器有8个数据流,每个数据流有多达8个通道。
仲裁器
当有多个数据流同时请求数据传输时,由仲裁器裁决数据流的传输顺序。
仲裁器为两个 AHB 主端口(存储器和外设端口)提供基于请求优先级的 8 个 DMA 数据流请求管理,并启动外设/存储器访问序列。
优先级管理分为两个阶段:
● ** 软件** :每个数据流优先级都可以在 DMA_SxCR 寄存器中配置。分为四个级别:
— 非常高优先级
— 高优先级
— 中优先级
— 低优先级
● 硬件 :如果两个请求具有相同的软件优先级,则编号低的数据流优先于编号高的数据流。例如,数据流 2 的优先级高于数据流 4。
配置好优先级,还需要指定数据传输的方向、指针递增方式和循环模式。
DMA的核心设置集中在配置寄存器、指定内存地址、外设地址及数据项目。下面描述DMA的相关寄存器。
DMA 低中断状态寄存器 (DMA_LISR)
DMA 高中断状态寄存器 (DMA_HISR)
DMA控制器使用状态寄存器LISR和HISR,来描述8个数据流的状态标记。LISR描述低4个数据流,而HISR描述高4个数据流。每个流对应5个状态,下面以数据流x来进行说明。
FCIFx :数据流x传输完成中断标记;
HTIFx :数据流x半传输完成中断标记;
TEIFx :数据流x传输错误中断标记;
DMEIFx:数据流x直接传输错误中断标记;
FEIFx :数据流xFIFO错误中断标记。
DMA 低中断标志清零寄存器 (DMA_LIFCR)
DMA 高中断标志清零寄存器 (DMA_HIFCR)
由DMA_LIFR/DMA_HIFR寄存器可知,DMA的标记是只读的,不能直接擦除。DMA控制器含有中断标记清零寄存器,用户通过向中断标记清零寄存器对应位写1,可清除对应的中断标记。
DMA 数据流 x 配置寄存器 (DMA_SxCR) (x = 0..7)
位 27:25 CHSEL[2:0]:通道选择 (Channel selection)
这些位将由软件置 1 和清零。
000:选择通道 0
001:选择通道 1
010:选择通道 2
011:选择通道 3
100:选择通道 4
101:选择通道 5
110:选择通道 6
111:选择通道 7
这些位受到保护,只有 EN 为“0”时才可以写入
位 24:23 MBURST :存储器突发传输配置(Memory burst transfer configuration)
这些位将由软件置 1 和清零。
00:单次传输
01:INCR4(4 个节拍的增量突发传输)
10:INCR8(8 个节拍的增量突发传输)
11:INCR16(16 个节拍的增量突发传输)
这些位受到保护,只有 EN 为“0”时才可以写入
在直接模式中,当位 EN =“1”时,这些位由硬件强制置为 0x0。
位 22:21 PBURST[1:0]:外设突发传输配置 (Peripheral burst transfer configuration)
这些位将由软件置 1 和清零。
00:单次传输
01:INCR4(4 个节拍的增量突发传输)
10:INCR8(8 个节拍的增量突发传输)
11:INCR16(16 个节拍的增量突发传输)
这些位受到保护,只有 EN 为“0”时才可以写入
在直接模式下,这些位由硬件强制置为 0x0。
位 19 CT:当前目标(仅在双缓冲区模式下)(Current target (only in double buffer mode))
此位由硬件置 1 和清零,也可由软件写入。
0:当前目标存储器为存储器 0(使用 DMA_SxM0AR 指针寻址)
1:当前目标存储器为存储器 1(使用 DMA_SxM1AR 指针寻址)
只有 EN 为“0”时,此位才可以写入,以指示第一次传输的目标存储区。在使能数据流后,此位相当于一个状态标志,用于指示作为当前目标的存储区。
位 18 DBM:双缓冲区模式 (Double buffer mode)
此位由软件置 1 和清零。
0:传输结束时不切换缓冲区
1:DMA 传输结束时切换目标存储区
此位受到保护,只有 EN 为“0”时才可以写入。
位 17:16 PL[1:0]:优先级 (Priority level)
这些位将由软件置 1 和清零。
00:低
01:中
10:高
11:非常高
这些位受到保护,只有 EN 为“0”时才可以写入。
位 15 PINCOS:外设增量偏移量 (Peripheral increment offset size)
此位由软件置 1 和清零
0:用于计算外设地址的偏移量与 PSIZE 相关
1:用于计算外设地址的偏移量固定为 4(32 位对齐)。
如果位 PINC =“0”,则此位没有意义。
此位受到保护,只有 EN 为“0”时才可以写入。
如果选择直接模式或者 PBURST 不等于“00”,则当使能数据流(位 EN =“1”)时,此位由硬件强制置为低电平。
位 14:13 MSIZE[1:0]:存储器数据大小 (Memory data size)
这些位将由软件置 1 和清零。
00:字节(8 位)
01:半字(16 位)
10:字(32 位)
11:保留
这些位受到保护,只有 EN 为“0”时才可以写入。
在直接模式下,当位 EN =“1”时,MSIZE 位由硬件强制置为与 PSIZE 相同的值。
位 12:11 PSIZE[1:0]:外设数据大小 (Peripheral data size)
这些位将由软件置 1 和清零。
00:字节(8 位)
01:半字(16 位)
10:字(32 位)
11:保留
这些位受到保护,只有 EN 为“0”时才可以写入
位 10 MINC:存储器递增模式 (Memory increment mode)
此位由软件置 1 和清零。
0:存储器地址指针固定
1:每次数据传输后,存储器地址指针递增(增量为 MSIZE 值)
此位受到保护,只有 EN 为“0”时才可以写入。
位 9 PINC:外设递增模式 (Peripheral increment mode)
此位由软件置 1 和清零。
0:外设地址指针固定
1:每次数据传输后,外设地址指针递增(增量为 PSIZE 值)
此位受到保护,只有 EN 为“0”时才可以写入。
位 8 CIRC:循环模式 (Circular mode)
此位由软件置 1 和清零,并可由硬件清零。
0:禁止循环模式
1:使能循环模式
如果外设为流控制器(位 PFCTRL=1)且使能数据流(位 EN=1),此位由硬件自动强制清零。
如果 DBM 位置 1,当使能数据流(位 EN =“1”)时,此位由硬件自动强制置 1。
位 7:6 DIR[1:0]:数据传输方向 (Data transfer direction)
这些位将由软件置 1 和清零。
00:外设到存储器
01:存储器到外设
10:存储器到存储器
11:保留
这些位受到保护,只有 EN 为“0”时才可以写入。
位 5 PFCTRL:外设流控制器 (Peripheral flow controller)
此位由软件置 1 和清零。
0:DMA 是流控制器
1:外设是流控制器
此位受到保护,只有 EN 为“0”时才可以写入。
选择存储器到存储器模式(位 DIR[1:0]=10)后,此位由硬件自动强制清零。
位 4 TCIE:传输完成中断使能 (Transfer complete interrupt enable)
此位由软件置 1 和清零。
0:禁止 TC 中断
1:使能 TC 中断
位 3 HTIE:半传输中断使能 (Half transfer interrupt enable)
此位由软件置 1 和清零。
0:禁止 HT 中断
1:使能 HT 中断
位 2 TEIE:传输错误中断使能 (Transfer error interrupt enable)
此位由软件置 1 和清零。
0:禁止 TE 中断
1:使能 TE 中断
位 1 DMEIE:直接模式错误中断使能 (Direct mode error interrupt enable)
此位由软件置 1 和清零。
0:禁止 DME 中断
1:使能 DME 中断
位 0 EN:数据流使能/读作低电平时数据流就绪标志 (Stream enable / flag stream ready when read low)
此位由软件置 1 和清零。
0:禁止数据流
1:使能数据流
以下情况下,此位可由硬件清零:
— DMA 传输结束时(准备好配置数据流)
— AHB 主总线出现传输错误时
— 存储器 AHB 端口上的 FIFO 阈值与突发大小不兼容时
此位读作 0 时,软件可以对配置和 FIFO 位寄存器编程。EN 位读作 1 时,禁止向这些寄存器执行写操作。
另外,在EN位置1(启动流前),与数据流相对应的事件标记都要清0。
DMA 数据流 x 数据项数寄存器 (DMA_SxNDTR) (x = 0..7)
位 15:0 NDT[15:0]:要传输的数据项数目 (Number of data items to transfer)
要传输的数据项数目(0 到 65535)。只有在禁止数据流时,才能向此寄存器执行写操作。
使能数据流后,此寄存器为只读,用于指示要传输的剩余数据项数。每次 DMA 传输后,此寄存器将递减。
传输完成后,此寄存器保持为零(数据流处于正常模式时),或者在以下情况下自动以先前编程的值重载:
— 以循环模式配置数据流时。
— 通过将 EN 位置“1”来重新使能数据流时
如果该寄存器的值为零,则即使使能数据流,也无法完成任何事务。
DMA 数据流 x 外设地址寄存器 (DMA_SxPAR) (x = 0..7)
位 31:0 PAR[31:0]:外设地址 (Peripheral address)
读/写数据的外设数据寄存器的基址。
这些位受到写保护,只有 DMA_SxCR 寄存器中的 EN 为“0”时才可以写入。
DMA相当于CPU的助理,通过软件对DMA控制器的以上寄存器进行合理配置,就可以启动DMA来传输数据了。此时CPU就可以专注于运算或其它事务的处理了。
这里以串口DMA为例编写程序
void DMA2_Uart1_TX_Init()
{
//1. 开外设时钟(DMA2)
RCC- >AHB1ENR |= 1< < 22;
//2.配置DMA控制器
//a. 先禁止DMA,然后再设置
//清状态标记(写1清除)
DMA2- >LIFCR = 0X0F7D0F7D;
DMA2- >HIFCR = 0X0F7D0F7D;
//禁止EN
DMA2_Stream7- >CR = 0;
while((DMA2_Stream7- >CR & (1< < 0)) != 0); //等待DMA停止
//b. 配置DMA/采用单次模式
DMA2_Stream7- >CR |= 4< < 25; //通道4
DMA2_Stream7- >CR |= 2< < 16; //优先级:2
//c. PINCOS不偏移(不固定4字节对齐)、内存/外设大小为一个字节
//内存递增、外设不递增
DMA2_Stream7- >CR |= 1< < 10;
//不采用循环模式、方向:内存到外设
DMA2_Stream7- >CR |= 1< < 6;
}
/*
传输设置函数
paddr:外设地址
maddr:内存地址
cnt:数据项数目
*/
void DMA2_TransConfig(u32 paddr,u32 maddr,u16 cnt)
{
//清状态标记(写1清除)
DMA2- >LIFCR = 0X0F7D0F7D;
DMA2- >HIFCR = 0X0F7D0F7D;
DMA2_Stream7- >CR &=~(1< < 0);
while((DMA2_Stream7- >CR & (1< < 0)) != 0); //等待DMA停止
DMA2_Stream7- >PAR = paddr;
DMA2_Stream7- >M0AR = maddr;
DMA2_Stream7- >NDTR = cnt;
DMA2_Stream7- >CR |= 1< < 0; //启动传输
}
u8 DMA2_GetFlag() //获取传输完成标记
{
if(DMA2- >HISR &(1< < 27)) //判断是否传输完成
return 1;
else
return 0;
}
u8 DMA2_ClearFlag()
{
DMA2- >HIFCR = 1< < 27; //清传输完成标记
}
除了以上DMA控制器的配置外,还要开启串口的DMA功能。
void Usart1_dma_config()
{
USART1- >CR3 |= 1< < 7; //使能串口DMA发送功能
}
接着编写主函数进行测试:
#include "stm32f4xx.h"
#include "usart.h"
#include "DMA.h"
#include "delay.h"
u8 str[]="hello,world!rn";
int main()
{
u32 i=0;
Usart1_Init(460800);
Usart1_dma_config(); //开启串口发送DMA功能
DMA2_Uart1_TX_Init(); //DMA控制器初始化
DMA2_TransConfig((u32)&USART1- >DR,(u32)str,sizeof(str)); //开始传输
while(1)
{
while(DMA2_GetFlag() == 0); //判断传输是否完成,完成时退出循环
DMA2_ClearFlag(); //清传输完成标记
DMA2_TransConfig((u32)&USART1- >DR,(u32)str,sizeof(str)); //再次开启传输
Delay_ms(500);
}
}
由于DMA的传输速度较快,所以把串口的波特率调到最高460800,但是速度还是太快,串口接收的速度跟不上,如下图所示:
因此在大循环的最后加上500ms的延时,就可以看到传输的数据被完整地打印出来了。可见DMA传输速度之快。
串口DMA发送数据测试成功。串口DMA发送数据是属于内存到外设的,对于外设到内存和内存到内存的应用同样只要根据以上寄存器的描述进行配置,就可以实现DMA的数据传输了。