目录
DMA(Direct Memory Access)简介
DMA传输方式
DMA功能框图
DMA请求映像
DMA1控制器
DMA2控制器
通道
仲裁器
DMA主要特性
DMA处理
DMA数据配置
从哪里来到哪里去
外设到存储器
存储器到外设
存储器到存储器
要传多少,单位是什么
什么时候传输完成
DMA配置部分
DMA初始化结构体详解
DMA_InitTypeDef初始化结构体
DMA存储器到存储器模式实验
编程要点
DMA宏定义及相关变量定义
DMA初始化配置
存储数据对比
存储器到存储器模式主函数
DMA(Direct Memory Access)——直接存储器访问,是单片机的一个外设,它的主要功能是用来搬数据,但是不需要占用CPU,即在传输数据的时候,CPU可以干其他的事情,好像是多线程一样。数据传输支持从外设到存储器或者存储器到存储器,这里的存储器可以是SRAM或者是FLASH。DMA控制器包含了DMA1和DMA2,其中DMA1有7个通道,DMA2有5个通道,这里的通道可以理解为传输数据的一种管道。要注意的是DMA2只存在于大容量的单片机中(这里的大容量是指FLASH的大小,规定FLASH在256-512KB的视为大容量)。
DMA传输数据从一个地址空间复制到另一个地址空间,提供在外设和存储器或者存储器和存储器之间的高速数据传输。
我们知道CPU有转移数据,计算,控制程序转移等很多功能,系统运作的核心就是CPU,CPU无时无刻都在处理着大量的事务,但有些事却没有那么重要,比如说数据的复制和存储数据,如果我们把这部分CPU的资源拿出来,让CPU去处理其他的复杂事务,就能够更好的利用CPU的资源。
因此:转移数据(尤其是大量数据)是可以不需要CPU参与的,比如希望外设A的数据拷贝到外设B,只要给外设提供一条数据通道,直接让数据由A拷贝到B不经过CPU的处理。
DMA就是基于以上思想设计的,它的作用就是解决大量数据转移过度消耗CPU资源的问题,有了DMA使得CPU可以更加专注的实用的的操作——计算、控制等。
DMA定义:DMA用来提供在外设和存储器之间后者存储器和存储器之间的高速数据传输。无须CPU的干预,通过DMA数据可以快速的移动,这就节省了CPU的资源来做其他操作。
DMA的作用就是实现数据的直接传输,而去掉了传统数据传输需要CPU寄存器参与的环节(如R1,R2寄存器等),主要涉及三种情况的数据传输,但本质上是一样的,都是从内存中的某一区域传输到内存的另一区域(外设的数据寄存器本质上就是内存的一个存储单元)。三种情况的数据传输如下:
PS:这里讲下,对于SRAM动态内存存储的主要是变量等数据,FLASH存储的主要是常量以及code(代码)。
DMA控制器独立于内核,属于一个单独的外设,结构比较简单,从编程的角度来看,我们只需要掌握功能框图中的三部分内容即可,具体见DMA框图;DMA控制器的框图。
如果外设想要通过DMA来传输数据,必须先给DMA控制器发送DMA请求,DMA收到请求信号之后,控制器会给外设一个应答信号,当外设应答后且DMA控制器接收到应答信号之后,就会启动DMA传输,直到传输完毕
DMA有DMA1和DMA2两个控制器,DMA1有7个通道,DMA2有5个通道,不同的DMA控制器的通道对应着不同的外设请求,这决定了我们在软件编程上面该怎么设置,具体见DMA请求映像表。
从外设(TIMx[x=1、2、3、4]、ADC1、SPI1、SPI/I2S2、I2Cx[x=1、2]和USARTx[x=1、2、3])
产生的7个请求、通过逻辑或输入到DMA1控制器,这意味着同时只能有一个请求有效。参见下图的DMA1请求映像
外设的DMA请求,可以通过设置相应外省寄存器中的控制位,被独立的开启或关闭
PS: 其中ADC3、SDIO和TIM8的DMA请求只在大容量产品中存在,这个在具体项目时要注意。
DMA具有12个独立可编程的通道,其中DMA1有7个通道,DMA2有5个通道,每个通道对应不同的外设的DMA请求。虽然每个通道可以接收多个外设的请求,但是同一时间只能接收一个,不能同时接收多个。
当发生多个通道请求时,就意味着有先后响应处理的顺序问题,这就是由仲裁器来管理的。仲裁器管理DMA通道请求分为两个阶段。第一阶段属于软件阶段,可以在DMA_CCRx寄存器中设置,有4个等级:非常高、高、中和低四个优先级。第二阶段属于硬件阶段,如果两个或以上的DMA通道请求优先级一样的话,则它们优先级取决于通道编号,编号越低,优先级越高。比如通道0高于通道1,在大容量产品中DMA1控制器拥有高于DMA2控制器的优先级。
在发生一个事件之后,外设向DMA控制器发送一个请求信号,DMA控制器根据通道的优先级处理请求。当DMA控制器开始访问发出请求的外设时,DMA控制器立即发送给它一个应答信号。当从DMA控制器得到应答信号时,外设立即释放它的请求。一旦外设释放了这个请求,DMA控制器同时撤销应答信号。如果有更多的请求时,外设可以启动下一个周期。
总之,每次DMA传送由3个操作组成:
使用DMA,最核心的就是配置要传输的数据,包括数据从哪里来,要到哪里去,传输的数据的单位是什么,要传多少数据,是一次传输还是循环传输(即传输方式)
我们知道DMA传输数据的方向有3个:从外设到存储器,从存储器到外设,从存储器到存储器。具体的方向DMA_CCR位DIR配置:0代表从外设到存储器,1表示从存储器到外设。这里面涉及到的外设地址由DMA_CPAR配置,存储器地址由DMA_CMAR配置。
当我们使用从外设到存储器传输时,以ADC采集为例。DMA外设寄存器的地址对应的就是ADC数据寄存器的地址,DMA存储器的地址就是我们自定义变量(用来接收存储AD采集的数据)的地址。方向我们设置外设为源地址。
当我们使用从存储器到外设传输时,以串口向电脑发送数据为例。DMA外设寄存器的地址对应就是串口数据寄存器地址,DMA存储器的地址是我们自定义的变量(相当于一个缓冲区,用来存储通过串口发送到电脑的数据)的地址。方向我们设置为外设为目标地址
当我们使用从存储器到存储器传输时,以内部FLASH向内部SRAM复制数据为例。DMA外设寄存器的地址对应的就是内部FLASH(我们这里把内部FLASH当作一个外设来看)的地址,DMA存储器的地址就是我们自定义的变量(相当于一个缓冲区,用来存储来自内部FLASH的数据)的地址。方向我们设置外设(即内部FLASH)为源地址。跟上面两个不一样的是,这里需要把DMA_CRR位14:MEM2MEM:存储器到存储器模式配置为1,启动M2M模式。
当我们配置好数据要从哪里来到哪里去之后,我们还需要知道我们要传输的数据是多少,数据的单位是什么。
以串口向电脑发送数据为例,我们可以一次性给电脑发送很多数据,具体多少由DMA_CNDTR
配置,这是一个32位的寄存器,一次最多能传输65535个数据。
要想传输正确,源和目标地址存储的数据宽度还必须一致,串口数据寄存器是8位的,所以我们定义的要发送的数据也必须是8位的。外设的数据宽度由DMA_CCRx的PSIZE[1:0]配置,可以是8/16/32位,存储器的数据宽度由DMA_CCRx的MSIZE[1:0]配置,可以是8/16/32位。
在DMA控制器的控制下,数据想要有条不紊的从一个地方搬移到另一个地方,还必须正确设置两边数据指针的增量模式。外设的地址指针由DMA_CCRx的PINC配置,存储器的地址指针由MINC配置。以串口向电脑发送数据为例,要发送的数据很多,每发送完一个,那么存储器的地址指针就应该加1,而串口数据寄存器只有一个,那么外设的地址指针就固定不变。具体的数据指针的增量模式由实际情况决定。
数据什么时候传输完成,我们可以通过查询标志位后者通过中断的方式来鉴别。每个DMA通道在DMA传输过半、传输完成、传输出错时都会有相应的标志位,如果使能了该类型的中断后,则会产生中断。有关各标志位的详细描述可参考相应手册的寄存器部分。
传输完成还分为两种模式,是一次传输还是循环传输,一次传输很好理解,即使传输一次之后就停止,要想再传输的话,必须关闭DMA使能之后在重新配置后才能继续传输。循环传输则是一次传输完成之后又恢复第一次传输时的配置循环传输,不断地重复。具体的由DMA_CRRx寄存器的CIRC循环模式位控制。
标准库函数对每个外设都建立了一个初始化结构体xxx_InitTypeDef(xxx位外设名称),结构体成员用于设置外设工作参数,并由标准库函数xxx_Init()调用这些设定参数进入设置外设相应的寄存器,达到配置外设的工作环境的目的。
PS:以上的就是固件库编程的总体思路,并不只是这一个实验,固件库编程的“套路”都是这样的。
typedef struct
{
uint32_t DMA_PeripheralBaseAddr; //外设地址
uint32_t DMA_MemoryBaseAddr; //存储器地址
uint32_t DMA_DIR; //传输方向
uint32_t DMA_BufferSize; /传输数目
uint32_t DMA_PeripheralInc; //外设地址增量模式
uint32_t DMA_MemoryInc; //存储器地址增量模式
uint32_t DMA_PeripheralDataSize; //外设数据宽度
uint32_t DMA_MemoryDataSize; //存储器数据宽度
uint32_t DMA_Mode; //模式选择
uint32_t DMA_Priority; //通道优先级
uint32_t DMA_M2M; //存储器到存储器模式
}DMA_InitTypeDef;
#define BUFFER_SIZE 32 //定义传输的数据数量
#define DMA_MTM_CLK RCC_AHBPeriph_DMA1 //定义DAM的外设时钟
#define DMA_MTM_CHANNEL DMA1_Channel5 //定义DAM的通道
void DMA_MTM_Config(void); //DMA初始化函数声明
uint8_t Buffercmp(const uint32_t* pBuffer,
uint32_t* pBuffer1, uint16_t BufferLength); //比较数据函数声明
void DMA_MTM_Config(void)
{
/*定义DMA初始化结构体*/
DMA_InitTypeDef DMA_InitStruct;
/*打开DMA外设时钟,DMA挂载在AHB总线时钟上*/
RCC_AHBPeriphClockCmd(DMA_MTM_CLK, ENABLE);
/*设置外设基地址,即源地址*/
DMA_InitStruct.DMA_PeripheralBaseAddr = (uint32_t) aSRC_Const_Buffer;
/*设置目标存储器地址,即目标地址*/
DMA_InitStruct.DMA_MemoryBaseAddr = (uint32_t) aDST_Buffer;
/*配置传输方向,这里为止解决的是从哪里来,到哪里去*/
DMA_InitStruct.DMA_DIR = DMA_DIR_PeripheralSRC;
/*设置要传输的数量*/
DMA_InitStruct.DMA_BufferSize = BUFFER_SIZE;
/*设置外设的字宽*/
DMA_InitStruct.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Word;
/*设置外设地址增量*/
DMA_InitStruct.DMA_PeripheralInc = DMA_PeripheralInc_Enable;
/*设置存储器地址增量*/
DMA_InitStruct.DMA_MemoryInc = DMA_MemoryInc_Enable;
/*设置存储器的宽度*/
DMA_InitStruct.DMA_MemoryDataSize = DMA_MemoryDataSize_Word;
/*设置传输模式是一次性传输,还是循环传输*/
DMA_InitStruct.DMA_Mode = DMA_Mode_Normal;
/*设置优先级*/
DMA_InitStruct.DMA_Priority = DMA_Priority_High;
/*开启存储器到存储器传输*/
DMA_InitStruct.DMA_M2M = DMA_M2M_Enable;
/*DMA初始化*/
DMA_Init(DMA_MTM_CHANNEL, &DMA_InitStruct);
/*打开DMA通道*/
DMA_Cmd(DMA_MTM_CHANNEL, ENABLE);
**
* 判断指定长度的两个数据源是否完全相等,
* 如果完全相等返回1,只要其中一对数据不相等返回0
*/
uint8_t Buffercmp(const uint32_t* pBuffer,
uint32_t* pBuffer1, uint16_t BufferLength)
{
/* 数据长度递减 */
while(BufferLength--)
{
/* 判断两个数据源是否对应相等 */
if(*pBuffer != *pBuffer1)
{
/* 对应数据源不相等马上退出函数,并返回0 */
return 0;
}
/* 递增两个数据源的地址指针 */
pBuffer++;
pBuffer1++;
}
/* 完成判断并且对应数据相对 */
return 1;
}
extern const uint32_t aSRC_Const_Buffer[BUFFER_SIZE];
extern uint32_t aDST_Buffer[BUFFER_SIZE];
void delay(uint32_t count)
{
for(;count!=0;count--);
}
int main(void)
{
uint8_t status=0;
LED_GPIO_Config();
LED_YELLOW;
delay(0xFFFFFF);
DMA_MTM_Config();
while(DMA_GetFlagStatus(DMA1_FLAG_TC5) == RESET);
status = Buffercmp(aSRC_Const_Buffer,
aDST_Buffer, BUFFER_SIZE);
if(status == 1)
{
LED_GREEN;
}
else
{
LED_RED;
}
while(1)
{
}
}
这里最后在说明一点,关于存储器到外设的比如串口发送数据的例子和这个是大同小异的,主要就是DMA的配置,有关具体的可以看我资源里面的关于DMA实验的资料。