STM32 DMA配置

作个搬运工,以下内容均来自:
https://blog.csdn.net/gdjason/article/details/51019219
https://blog.csdn.net/u010280307/article/details/53334985
https://blog.csdn.net/faihung/article/details/78748033
https://blog.csdn.net/weixin_40709185/article/details/79867003
仅供个人学习使用,并无他意

首先来自:## https://blog.csdn.net/weixin_40709185/article/details/79867003

**DMA涉及概念讲解:

**
①:DMA即Direct Memory Access(直接存储器存取),是STM32特有的外设。大容量STM32产品集成了两个DMA,分别是DMA1和DMA2,。其中DMA1有7个通道,DMA2有5个通道,具体每个通道连接的外设可以参考STM32芯片的数据手册。
②:通过DMA可以将数据在两个不同的地址之间进行传递,如存储器到外设寄存器,外设寄存器到存储器,也可以从存储器到存储器之间。
③:当两个数据在不同的地址之间传递时,需要在程序配置中确定每次传输的字节数,确定是字节、半字还是字。
④:DMA的每个通道优先级是可变的。以DMA1为例,它有7个通道,可以配置每个通道的优先级为很高、高、中,低四种中的一种。若两个通道的优先级一样,当两个通道同时有DMA请求时,通道号小的优先级则高。
⑤:DMA每次传输的数据量是可变的,DMA中有一个专门的寄存器用于存储这个数据量值。这个寄存器是32位的,但高16位全部保留为0,实际上起作用的是低16位,所以每次传输的最大数据量值是65536。
⑥:⑤中所示,比如设置数据量值为100,若DMA传输设置为循环模式,则100个数据传输完成后,将自动进行下一轮传输。若设置成非循环模式,则需要先关闭DMA,再设置数据量值,再开启DMA,才能进行下一轮传输。
⑦:DMA在传输过程中,常用的有3种标志位–传输完成一半、传输全部完成,传输过程发生错误。可以在程序中设置开启对应标志位的中断,当标志位到来时,会执行中断服务程序。也可不开启相应标志位的中断。
⑧:确定好要传输的外设和存储器地址之后,需要在程序中设置传输方向,即传输方向是从外设到寄存器,还是从寄存器到外设。
⑨:DMA一般用来在外设和存储器之间进行数据传输,所以还要设置外设地址及存储器地址是否递增。例如定义一个数组,char data[100],外设地址为&UART->TX,若将数组中的100个数据传输到UART->TX中,则存储器地址需要每次递增,而外设地址不需要递增。

DMA配置过程:

①:确定传输数据的外设和寄存器地址
②:确定传输方向
③:确定每次传输的数据量值
④:确定传输数据的字节数
⑤:配置通道优先级
⑥:确定传输是循环模式还是非循环模式
⑦:如若需要开启中断,则开启响应位中断
注意:DMA也可以从存储器到存储器,但存储器到存储器过程只能为非循环模式。

然后是来自https://blog.csdn.net/gdjason/article/details/51019219的形象解读:

一、如何理解DMA
对于DMA,打个比方就很好理解:
角色预设: 淘宝店主 —- STM32 MCU
快递员 —- 外设(如UART,SPI)
发货室 —- DMA
1、首先你是一个淘宝店主,如果每次发货收货都要跟快递沟通交涉会很浪费时间和精力。
2、然后你就自己建了一个发货室,发货室里有好多个货柜箱子,每个箱子上都写着快递名字(如果申通快递,顺丰快递等)。
3、每次发什么快递,你就找到对应的货柜箱子,把货物放进去即可,然后跟快递通知一声。
4、快递取走快件。
5、如果是收货,快递直接把快件放到对应的柜子,然后通知你一下。
6、你过来提取货物。

通过上面的方式,你可以不需要直接跟快递打交道,就可以轻松发货成功,DMA处理方式跟上面例子是一样的。如下图:
在这里插入图片描述

那么DMA在STM32上是具体怎么实现的呢? 我们先了解一下STM32关于DMA的相关配置。
1、两个DMA控制器有12个通道(DMA1有7个通道,DMA2有5个通道)
ps:对应我们例子,就是有两个大的发货室,一个有7个货柜,另个有5个货柜。
2、在同一个DMA模块上,多个请求间的优先权可以通过软件编程设置(共有四级:很高、高、中等和低),优先权设置相等时由硬件决定(请求0优先于请求1,依此类推)
ps: 店主可以跟每个快递公司签订协议,可以在货柜前贴上加急(很高),很急(高),急(中),一般(低), 如果同时有几个快递员过来取货,优先根据上面的优先级先取件。
3、独立数据源和目标数据区的传输宽度(字节、半字、全字),模拟打包和拆包的过程。源和目标地址必须按数据传输宽度对齐。
ps: 指的是货件大小
4、支持循环的缓冲器管理(会把原来的数据覆盖)
5、每个通道都有3个事件标志(DMA半传输、DMA传输完成和DMA传输出错),这3个事件标志逻辑或成为一个单独的中断请求。
ps: 送快递出现的异常情况(送到了一半,送完,快递出错)
解释到这里,不知道大家能不能理解呢。后面是具体的配置。
1、DMA 对应通道如下图
DMA1:
这里写图片描述


DMA2:
这里写图片描述

2、DMA配置
1)数据传输的目的地和来源
这里写图片描述
对应我的例子,就是送快递还是取快递。

2)定义DMA通道的DMA缓存的大小
ps: 即货柜大小,能存多少个快件

3)外设地址寄存器递增与否
这里写图片描述

4)内存地址寄存器递增与否
这里写图片描述

5)设定了外设数据宽度
这里写图片描述

6)设定了内存数据宽度
这里写图片描述

7)设置了DMA的工作模式
这里写图片描述

8)DMA通道的软件优先级
这里写图片描述

9)使能或关闭DMA通道的内存到内存传输
这里写图片描述

2)初始化DMA

u8 sendbuf[1024];
u8 receivebuf[1024];
static void _uart1_dma_configuration()
{
  DMA_InitTypeDef DMA_InitStructure;

  /* DMA1 Channel6 (triggered by USART1 Rx event) Config */
  RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1 ,
                        ENABLE);

  /* DMA1 Channel5 (triggered by USART1 Rx event) Config */
  DMA_DeInit(DMA1_Channel5);
  DMA_InitStructure.DMA_PeripheralBaseAddr = USART1_DR_Base;// 初始化外设地址,相当于“哪家快递”  
  DMA_InitStructure.DMA_MemoryBaseAddr =(u32)receivebuf;// 内存地址,相当于几号柜
  DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;//外设作为数据来源,即为收快递
  DMA_InitStructure.DMA_BufferSize = DMASIZE ;// 缓存容量,即柜子大小
  DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable; // 外设地址不递增,即柜子对应的快递不变
  DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;// 内存递增
  DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte; //外设字节宽度,即快递运输快件大小度量(按重量算,还是按体积算) 
  DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;// 内存字节宽度,即店主封装快递的度量(按重量,还是按体质进行封装)
  DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;// 正常模式,即满了就不在接收了,而不是循环存储
  DMA_InitStructure.DMA_Priority = DMA_Priority_VeryHigh;// 优先级很高,对应快递就是加急
  DMA_InitStructure.DMA_M2M = DMA_M2M_Disable; // 内存与外设通信,而非内存到内存 
  DMA_Init(DMA1_Channel5, &DMA_InitStructure);// 把参数初始化,即拟好与快递公司的协议

  DMA_Cmd(DMA1_Channel5, ENABLE);// 启动DMA,即与快递公司签订合同,正式生效

  /* DMA1 Channel4 (triggered by USART1 Tx event) Config */
  DMA_DeInit(DMA1_Channel4);
  DMA_InitStructure.DMA_PeripheralBaseAddr = USART1_DR_Base;  // 外设地址,串口1, 即发件的快递
  DMA_InitStructure.DMA_MemoryBaseAddr =(u32)sendbuf;// 发送内存地址
  DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST;// 外设为传送数据目的地,即发送数据,即快递是发件
  DMA_InitStructure.DMA_BufferSize = 0;  //发送长度为0,即未有快递需要发送
  DMA_Init(DMA1_Channel4, &DMA_InitStructure);//初始化

  USART_ITConfig(USART1, USART_IT_TC, ENABLE);// 使能串口发送完成中断
  USART_DMACmd(USART1, USART_DMAReq_Tx|USART_DMAReq_Rx, ENABLE);// 使能DMA串口发送和接受请求

下面代码是一个标准DMA设置,当然实际应用中可根据实际情况进行裁减,来自:

## https://blog.csdn.net/faihung/article/details/78748033

```
DMA_DeInit(DMA_Channel1);
```
上面这句是给DMA配置通道,根据ST提供的资料,STM3210Fx中DMA包含7个通道(CH1~CH7),也就是说可以为外设或memory提供7座“桥梁”
```
DMA_InitStructure.DMA_PeripheralBaseAddr = ADC1_DR_Address;
```
上面语句中的DMA_InitStructure是一个DMA结构体,在库中有声明了,当然使用时就要先定义 了;DMA_PeripheralBaseAddr是该结构体中一个数据成员,给DMA一个起始地址,好比是一个buffer起始地址,数据流程是:外设 寄存器à DMA_PeripheralBaseAddàmemory中变量空间(或flash中数据空间等),ADC1_DR_Address是我定义的一个地址 变量;
```
DMA_InitStructure.DMA_MemoryBaseAddr = (u32)ADC_ConvertedValue;
```
上面这句很显然是DMA要连接在Memory中变量的地址,ADC_ConvertedValue是我自己在memory中定义的一个变量;

     DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;

上面的这句是设置DMA的传输方向,就如前面我所说的,DMA可以双向传输,也可以单向传输,这里设置的是单向传输,如果需要双向传输:把DMA_DIR_PeripheralSRC改成DMA_DIR_PeripheralDST即可。

    DMA_InitStructure.DMA_BufferSize = 2;

上面的这句是设置DMA在传输时缓冲区的长度,前面有定义过了buffer的起始地址:ADC1_DR_Address ,为了安全性和可靠性,一般需要给buffer定义一个储存片区,这个参数的单位有三种类型:Byte、HalfWord、word,我设置的2个 half-word(见下面的设置);32位的MCU中1个half-word占16 bits。

    DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;

上面的这句是设置DMA的外设递增模式,如果DMA选用的通道(CHx)有多个外设连接,需要使用外设递增模式:DMA_PeripheralInc_Enable;我的例子里DMA只与ADC1建立了联系,所以选用DMA_PeripheralInc_Disable

    DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;

上面的这句是设置DMA的内存递增模式,DMA访问多个内存参数时,需要使用DMA_MemoryInc_Enable,当DMA只访问一个内存参数时,可设置成:DMA_MemoryInc_Disable。

    DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;

上面的这句是设置DMA在访问时每次操作的数据长度。有三种数据长度类型,前面已经讲过了,这里不在叙述。

    DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;

与上面雷同。在此不再说明。

    DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;

上面的这句是设置DMA的传输模式:连续不断的循环模式,若只想访问一次后就不要访问了(或按指令操作来反问,也就是想要它访问的时候就访问,不要它访问的时候就停止),可以设置成通用模式:DMA_Mode_Normal

    DMA_InitStructure.DMA_Priority = DMA_Priority_High;

上面的这句是设置DMA的优先级别:可以分为4级:VeryHigh,High,Medium,Low.

    DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;

上面的这句是设置DMA的2个memory中的变量互相访问的

    DMA_Init(DMA_Channel1,&DMA_InitStructure);

前面那些都是对DMA结构体成员的设置,在次再统一对DMA整个模块做一次初始化,使得DMA各成员与上面的参数一致。

    DMA_Cmd(DMA_Channel1,ENABLE);

哈哈哈!这一句我想我就不罗嗦了,大家一看就明白。

接下来,我们去STM32的程序中来分析下DMA配置的详细过程:
我们主要详细的讲解下两个配置函数:DMA_Configuration()和DMA_Init()这两个函数,来自:
## https://blog.csdn.net/u010280307/article/details/53334985

```
void DMA_Configuration(void)
{
	DMA_InitTypeDef DMA_InitStructure;
	/* DMA channel1 configuration */ 
	DMA_DeInit(DMA1_Channel1);//重置DMA的寄存器的值,配置为缺省值	
	DMA_InitStructure.DMA_PeripheralBaseAddr =(u32)&ADC1->DR;	/*设置 DMA 外设基地址,即为转换结果的寄存器*/
	DMA_InitStructure.DMA_MemoryBaseAddr =(u32)&AD_Value;/*定义内存基地址*/
	DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;	/*定义AD外设作为数据传输的来源*/
	DMA_InitStructure.DMA_BufferSize = N*M;/*指定DMA通道DMA缓存的大小,即需要开辟几个内存空间*/
	DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;	/*寄存器地址国定*/
	DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;	/*设定内存地址递增,即每次DMA都是将*/	
	DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;/* 定义外设和内存的数据宽度*/
	DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;
	DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;/*设定DMA工作再循环缓存模式*/
	DMA_InitStructure.DMA_Priority = DMA_Priority_High;/*设定DMA选定的通道的软件优先级*/
	DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;//关闭内存到内存的传输   
	DMA_Init(DMA1_Channel1, &DMA_InitStructure);
	DMA_Cmd(DMA1_Channel1, ENABLE);/* Enable DMA channel1 */
}

```

```
void DMA_Init(DMA_Channel_TypeDef* DMAy_Channelx, DMA_InitTypeDef* DMA_InitStruct)
{
  uint32_t tmpreg = 0;
/*--------------------------- DMAy Channelx CCR Configuration -----------------*/
  /* Get the DMAy_Channelx CCR value */
  tmpreg = DMAy_Channelx->CCR;
  /* Clear MEM2MEM, PL, MSIZE, PSIZE, MINC, PINC, CIRC and DIR bits */
  tmpreg &= CCR_CLEAR_Mask;
  /* Configure DMAy Channelx: data transfer, data size, priority level and mode */
  /* Set DIR bit according to DMA_DIR value */
  /* Set CIRC bit according to DMA_Mode value */
  /* Set PINC bit according to DMA_PeripheralInc value */
  /* Set MINC bit according to DMA_MemoryInc value */
  /* Set PSIZE bits according to DMA_PeripheralDataSize value */
  /* Set MSIZE bits according to DMA_MemoryDataSize value */
  /* Set PL bits according to DMA_Priority value */
  /* Set the MEM2MEM bit according to DMA_M2M value */
  tmpreg |= DMA_InitStruct->DMA_DIR | DMA_InitStruct->DMA_Mode |
            DMA_InitStruct->DMA_PeripheralInc | DMA_InitStruct->DMA_MemoryInc |
            DMA_InitStruct->DMA_PeripheralDataSize | DMA_InitStruct->DMA_MemoryDataSize |
            DMA_InitStruct->DMA_Priority | DMA_InitStruct->DMA_M2M;
 
  /* Write to DMAy Channelx CCR */
  DMAy_Channelx->CCR = tmpreg;
 
/*--------------------------- DMAy Channelx CNDTR Configuration ---------------*/
  /* Write to DMAy Channelx CNDTR */
  DMAy_Channelx->CNDTR = DMA_InitStruct->DMA_BufferSize;
 
/*--------------------------- DMAy Channelx CPAR Configuration ----------------*/
  /* Write to DMAy Channelx CPAR */
  DMAy_Channelx->CPAR = DMA_InitStruct->DMA_PeripheralBaseAddr;
 
/*--------------------------- DMAy Channelx CMAR Configuration ----------------*/
  /* Write to DMAy Channelx CMAR */
  DMAy_Channelx->CMAR = DMA_InitStruct->DMA_MemoryBaseAddr;
}

```
将上面两个函数比较一下就可以知道,前者函数对于后者来说就相当于是一个中间量的过程,暂时的将需要的配置参数写入一个结构体DMA_InitTypeDef中,后面调用DMA_Init这个函数之后,重新配置物理地址中DMA的寄存器相应的位。下面附录上两个函数中的结构体参数组成

```
typedef struct
{
  __IO uint32_t CCR;
  __IO uint32_t CNDTR;
  __IO uint32_t CPAR;
  __IO uint32_t CMAR;
} DMA_Channel_TypeDef;

```

```
DMA_InitTypeDef  DMA_InitStructure;
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;

```

猜你喜欢

转载自blog.csdn.net/weixin_42341666/article/details/89642272