作个搬运工,以下内容均来自:
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;
```