【STM32】DMA原理,配置步骤超详细,一文搞懂DMA

目录

DMA(Direct Memory Access)简介

DMA传输方式

DMA功能框图

DMA请求映像

DMA1控制器

DMA2控制器

通道

仲裁器

DMA主要特性

DMA处理

DMA数据配置

从哪里来到哪里去

外设到存储器

存储器到外设

存储器到存储器

要传多少,单位是什么

什么时候传输完成

DMA配置部分

DMA初始化结构体详解

DMA_InitTypeDef初始化结构体

DMA存储器到存储器模式实验

编程要点

DMA宏定义及相关变量定义

DMA初始化配置

存储数据对比

存储器到存储器模式主函数


DMA(Direct Memory Access)简介

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传输方式

DMA的作用就是实现数据的直接传输,而去掉了传统数据传输需要CPU寄存器参与的环节(如R1,R2寄存器等),主要涉及三种情况的数据传输,但本质上是一样的,都是从内存中的某一区域传输到内存的另一区域(外设的数据寄存器本质上就是内存的一个存储单元)。三种情况的数据传输如下:

  • 外设到内存(例如:ADC)
  • 内存到外设(例如:串口发送)
  • 内存到内存(例如:直接给一变量赋值一个常量型变量的值)

PS:这里讲下,对于SRAM动态内存存储的主要是变量等数据,FLASH存储的主要是常量以及code(代码)。

DMA功能框图

DMA控制器独立于内核,属于一个单独的外设,结构比较简单,从编程的角度来看,我们只需要掌握功能框图中的三部分内容即可,具体见DMA框图;DMA控制器的框图。

DMA请求映像

如果外设想要通过DMA来传输数据,必须先给DMA控制器发送DMA请求,DMA收到请求信号之后,控制器会给外设一个应答信号,当外设应答后且DMA控制器接收到应答信号之后,就会启动DMA传输,直到传输完毕

DMA有DMA1和DMA2两个控制器,DMA1有7个通道,DMA2有5个通道,不同的DMA控制器的通道对应着不同的外设请求,这决定了我们在软件编程上面该怎么设置,具体见DMA请求映像表。

DMA1控制器

从外设(TIMx[x=1、2、3、4]、ADC1、SPI1、SPI/I2S2、I2Cx[x=1、2]和USARTx[x=1、2、3])

产生的7个请求、通过逻辑或输入到DMA1控制器,这意味着同时只能有一个请求有效。参见下图的DMA1请求映像

外设的DMA请求,可以通过设置相应外省寄存器中的控制位,被独立的开启或关闭

å¨è¿éæå¥å¾çæè¿°

å¨è¿éæå¥å¾çæè¿°

DMA2控制器

å¨è¿éæå¥å¾çæè¿°

å¨è¿éæå¥å¾çæè¿°

PS: 其中ADC3、SDIO和TIM8的DMA请求只在大容量产品中存在,这个在具体项目时要注意。

通道

DMA具有12个独立可编程的通道,其中DMA1有7个通道,DMA2有5个通道,每个通道对应不同的外设的DMA请求。虽然每个通道可以接收多个外设的请求,但是同一时间只能接收一个,不能同时接收多个。

仲裁器

当发生多个通道请求时,就意味着有先后响应处理的顺序问题,这就是由仲裁器来管理的。仲裁器管理DMA通道请求分为两个阶段。第一阶段属于软件阶段,可以在DMA_CCRx寄存器中设置,有4个等级:非常高、高、中和低四个优先级。第二阶段属于硬件阶段,如果两个或以上的DMA通道请求优先级一样的话,则它们优先级取决于通道编号,编号越低,优先级越高。比如通道0高于通道1,在大容量产品中DMA1控制器拥有高于DMA2控制器的优先级。

DMA主要特性

  • 12个独立的可配置的通道(请求):DMA1有7个通道,DMA2有5个通道
  • 每个通道都直接连接专用的硬件DMA请求,每个通道都同样支持软件触发。这些功能由软件来配置。
  • 在同一个DMA模块上,多个请求间的优先级可以通过软件编程设置(共有四级:非常高、高、中和低),优先权设置相等时由硬件决定(请求0优于请求1,依此类推)
  • 独立数据源和目标数据区的传输宽度(字节、半字、全字),模拟打包和拆包的过程。源和目标地址必须按数据传输宽度对齐。
  • 支持循环的缓冲器管理
  • 每个通道都有3个事件标志(DMA半传输、DMA传输完成和DMA传输出错),这三个事件标志逻辑或成为一个单独的中断请求。
  • 存储器和存储器之间的传输
  • 外设和存储器、存储器和外设之间的传输
  • 内存、SRAM、外设的SRAM、APB1、APB2和AHB外设均可作为访问的源和目标
  • 可编程的数据传输数目:最大为65535

DMA处理

在发生一个事件之后,外设向DMA控制器发送一个请求信号,DMA控制器根据通道的优先级处理请求。当DMA控制器开始访问发出请求的外设时,DMA控制器立即发送给它一个应答信号。当从DMA控制器得到应答信号时,外设立即释放它的请求。一旦外设释放了这个请求,DMA控制器同时撤销应答信号。如果有更多的请求时,外设可以启动下一个周期。

总之,每次DMA传送由3个操作组成:

  • 从外设数据寄存器或者从当前外设/存储器地址寄存器指示的存储器地址取数据,第一次传输时的开始地址时DMA_CPARx或DMA_CMARx寄存器指定的外设基地址或存储单元。
  • 存数据到外设数据寄存器或者当前外设/存储器地址寄存器指示的存储器地址,第一次传输时的开始地址是DMA_CPARx或DMA_CMARx寄存器指定的外设基地址或存储器单元。
  • 执行一次DMA_CNDTRx寄存器的递减操作,该寄存器包含未完成的操作数目。

DMA数据配置

使用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循环模式位控制。

DMA配置部分

DMA初始化结构体详解

标准库函数对每个外设都建立了一个初始化结构体xxx_InitTypeDef(xxx位外设名称),结构体成员用于设置外设工作参数,并由标准库函数xxx_Init()调用这些设定参数进入设置外设相应的寄存器,达到配置外设的工作环境的目的。

PS:以上的就是固件库编程的总体思路,并不只是这一个实验,固件库编程的“套路”都是这样的。

DMA_InitTypeDef初始化结构体

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;

DMA存储器到存储器模式实验

编程要点

  1. 使能DMA时钟(凡是外设,第一步永远是开时钟)
  2. 配置DMA数据参数
  3. 使能DMA,进行传输
  4. 等待传输完成,并对源数据和目标地址数据进行比较

DMA宏定义及相关变量定义

#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);    //比较数据函数声明

DMA初始化配置

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实验的资料。

本图文内容来源于网友网络收集整理提供,作为学习参考使用,版权属于原作者。
THE END
分享
二维码
< <上一篇
下一篇>>