DMA的基本介绍
DMA,全称Direct Memory Access,即直接存储器访问。
DMA传输:在没有CPU的任何干预下,将数据从一个地址空间复制到另一个地址空间,提供在外设和存储器之间或者存储器和存储器之间的高速数据传输。
我们知道CPU有转移数据、计算、控制程序转移等很多功能,系统运作的核心就是CPU,CPU无时不刻的在处理着大量的事务,比方说数据的复制和存储数据,如果我们把这部分的CPU资源拿出来,让CPU去处理其他的复杂计算事务,是不是能够更好的利用CPU的资源呢?因此:转移数据(尤其是转移大量数据)我们可以不需要CPU参与。比如希望外设A的数据拷贝到外设B,只要给两种外设提供一条数据通路,直接让数据由外设A拷贝外设B 或者 让数据由外设A搬运到存储器(FLASH/RAM)再从存储器(FLASH/RAM)搬运到外设B,这样的话,数据就不用经过CPU的处理,直接从外设A传递到外设B。
它的作用就是解决大量数据转移过度消耗CPU资源的问题。有了DMA使CPU更专注于更加实用的操作–计算、控制等。
DMA传输参数
我们知道,数据传输,首先需要的是1 数据的源地址、2 数据传输位置的目标地址 、3 传输宽度,4 传输多少字节,5 传输模式。DMA所需要的核心参数,便是这5个。
当用户将参数设置好,主要涉及源地址、目标地址、传输数据量这三个,DMA控制器就会启动数据传输,当剩余传输数据量为0时 达到传输终点,结束DMA传输 ,当然,DMA 还有循环传输模式 当到达传输终点时会重新启动DMA传输。也就是说只要剩余传输数据量不是0,而且DMA是启动状态,那么就会发生数据传输。
数据的源:我们通过串口将数据传输到MCU。我们先在栈中(即RAM)定义一个数组,然后将接收到的数据通过DMA搬运到数组(RAM)中,此时这个串口外设就是数据的源,即数据的来源。而这个数组就是目的数据区。(这是外设到内存的传输)
如果是一个数组的数据传输到另外一个数组,那么就使用DMA通道连接两个数组,将一个数组的数据通过DMA搬运到另外一个数组。那么第一个数组就是数据来源,另外一个数组就是目的数据区。(这是内存到内存的传输)
目标数据区:FLASH/RAM 存储器中的地址。
传输宽度:一次传输数据的字节数。分为 字节(8bit)、半字(16bit)、全字节(32bit)
注意:数据源和目标数据区设置的传输宽度必须保持一致(数据对齐)
我们可以看到,如果我们设置数据源的传输宽度为8位(一个字节),存储器的传输宽度也设置为8位(一个字节)。那么数据源传输一个字节数据0xB0给存储器,存储器就存储一个字节数据0x B0。
如果我们设置数据源的传输宽度为8位(一个字节),存储器的传输宽度却设置为16位(2个字节)。那么数据源传输一个字节数据0xB0给存储器,存储器就存储两个字节数据0x 00 B0。
同理,如果我们设置数据源的传输宽度为8位(一个字节),存储器的传输宽度却设置为32位(4个字节)。那么数据源传输一个字节数据0xB0给存储器,存储器就存储四个字节数据0x 00 00 00 B0。
如果我们设置数据源的传输宽度为16位(两个字节),存储器的传输宽度却设置为8位(一个字节)。那么数据源传输两个字节数据0x B1 B0给存储器,存储器就存储一个字节数据0x B0。
如果我们设置数据源的传输宽度为16位(两个字节),存储器的传输宽度却设置为16位(两个字节)。那么数据源传输两个字节数据0x B1 B0给存储器,存储器就存储两个字节数据0x B1 B0。
同理,可由上述推理得出。
所以,我们使用DMA传输数据的时候,要保证外设和内存的数据传输宽度对齐。
传输模式:
循环的缓冲器管理:就是循环传输模式,从数组的第一个数据搬运,直到搬运完最后一个数据(传输多少字节数据由我们自己设置)。又重新来搬运数组的第一个数据,直到搬运到最后一个数据,以此往复。
正常模式:从数组的第一个数据搬运,直到搬运完最后一个数据。,然后就不再传输数据了。
DMA传输方式
DMA的作用就是实现数据的直接传输,而去掉了传统数据传输需要CPU寄存器参与的环节,主要涉及四种情况的数据传输,但本质上是一样的,都是从内存的某一区域传输到内存的另一区域(外设的数据寄存器本质上就是内存的一个存储单元)。四种情况的数据传输如下:
外设到内存 (如:UART的数据搬运到FLASH/RAM存储器)(注意:此时需要将DMA中“存储器到存储器”这项设置失能)
内存到外设 (如:FLASH/RAM存储器的数据搬运到UART)
内存到内存 (如:两个数组之间搬运数据或者两个变量之间搬运数据)
外设到外设 (注意:F103在这里不能实现一个外设的数据直接搬运到另一个外设(因为规格书中没有写 外设到外设,如下图),所以此时我们想要实现外设到外设,就让数据由外设A搬运到存储器(或者数组),然后再从存储器(或者数组)搬运到外设B)
外设的数据到DMA
DMA的数据到FLSAH/RAM
各个通道的DMA1功能
DMA1有7个独立通道,不同的通道,其功能不一样。
比如通道1,它就只能搬运ADC1、TIM2_CH3、TIM4_CH1这个3个外设的数据。
配置DMA库函数
设置外设,是作为数据的来源,还是作为数据传输的目的地
设置DMA一共需要传输多少个数据(一个数据是多少字节,由传输数据宽度来决定)
外设地址递增就是,发送完一个数据,外设地址自动增加,下一次发送数据时,发送的时增加后的地址中的数据。
外设地址不变就是,一直发送的都是一个地址中的数据。
内存地址递增就是,保存完一个数据,地址自动增加,下一次接收数据的时候,把数据保存到增加后的内存地址。
内存地址不变就是,一直将数据保存到内存中的同一个地址中。
设置外设的传输数据宽度,即一次传输多少位数据(字节 半字 全字)
设置内存的传输数据宽度,即一次接收多少位数据(字节 半字 全字)
当我们使用的是外设到内存的时候,我们需要将“内存到内存”这项设置为失能
【STM32】 DMA原理,步骤超细详解,一文看懂DMA_Z小旋的博客-CSDN博客_dma是什么意思DMA的基本介绍什么是DMA (DMA的基本定义)DMA,全称Direct Memory Access,即直接存储器访问。DMA传输将数据从一个地址空间复制到另一个地址空间,提供在外设和存储器之间或者存储器和存储器之间的高速数据传输。我们知道CPU有转移数据、计算、控制程序转移等很多功能,系统运作的核心就是CPU,CPU无时不刻的在处理着大量的事务,但有些事情却没有那么重要,比方说数据的...https://blog.csdn.net/as480133937/article/details/104927922?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522165594756316782389474485%2522%252C%2522scm%2522%253A%252220140713.130102334..%2522%257D&request_id=165594756316782389474485&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~all~top_positive~default-1-104927922-null-null.142%5Ev20%5Econtrol,157%5Ev15%5Enew_3&utm_term=DMA&spm=1018.2226.3001.4187
编程步骤
#define data_size 30
uint32_t SRC[data_size]={
0x11111111,0x12111111,0x11211111,0x11114111,0x11111113,
0x21111111,0x13111111,0x11311111,0x11115111,0x11111114,
0x31111111,0x14111111,0x11411111,0x11116111,0x11111115,
0x41111111,0x15111111,0x11511111,0x11117111,0x11111116,
0x51111111,0x16111111,0x11611111,0x11118111,0x11111117,
0x61111111,0x17111111,0x11711111,0x11119111,0x11111118
};
uint32_t DRC[data_size]={0};
编程步骤:(内存到内存)(将SRC数组的数据通过DMA搬运到DRC数组中去)
1,打开时钟---DMA1
2,初始化DMA
----外设基地址----SRC(数组)
----内存基地址----DRC (数组)
----方向----外设作为数据来源
----外设地址是否递增---是
----内存地址是否递增---是
----外设数据传输的宽度---全字
----内存数据传输的宽度---全字
----传输的数目----30
----优先级---中等
----模式---正常模式
----存储器到存储器是否使能---是
3,使能DMA1通道2
4,验证
等待传输完成
清除标志位
判断SRC和DRC中的数据是否一致
编写程序
方法一:(轮询方法)
void DMA1CH2_Config(uint32_t *src,uint32_t *drc,uint32_t size)
{
DMA_InitTypeDef DMA_InitStruct;
// 1,打开时钟---DMA1
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1,ENABLE);
// 2,初始化DMA
DMA_InitStruct.DMA_PeripheralBaseAddr =(uint32_t )src;
DMA_InitStruct.DMA_MemoryBaseAddr =(uint32_t )drc;
DMA_InitStruct.DMA_DIR =DMA_DIR_PeripheralSRC;
DMA_InitStruct.DMA_PeripheralInc =DMA_PeripheralInc_Enable;
DMA_InitStruct.DMA_MemoryInc =DMA_MemoryInc_Enable;
DMA_InitStruct.DMA_PeripheralDataSize =DMA_PeripheralDataSize_Word;
DMA_InitStruct.DMA_MemoryDataSize =DMA_MemoryDataSize_Word;
DMA_InitStruct.DMA_BufferSize =size;
DMA_InitStruct.DMA_M2M =DMA_M2M_Enable;
DMA_InitStruct.DMA_Mode =DMA_Mode_Normal;
DMA_InitStruct.DMA_Priority =DMA_Priority_Medium;
DMA_Init(DMA1_Channel2,&DMA_InitStruct);
// 3,使能DMA1通道2
DMA_Cmd(DMA1_Channel2,ENABLE);
}
uint8_t SRC_DRC_compare(uint32_t *src,uint32_t *drc,uint32_t size) //检查两个数组中的数据是否完全相同
{
uint8_t ret=0;
uint8_t i;
for(i=0;i<size;i++){
if(src[i] != drc[i]){
ret=1;
return ret;
}
}
return ret;
}
#define data_size 30
uint32_t SRC[data_size]={
0x11111111,0x12111111,0x11211111,0x11114111,0x11111113,
0x21111111,0x13111111,0x11311111,0x11115111,0x11111114,
0x31111111,0x14111111,0x11411111,0x11116111,0x11111115,
0x41111111,0x15111111,0x11511111,0x11117111,0x11111116,
0x51111111,0x16111111,0x11611111,0x11118111,0x11111117,
0x61111111,0x17111111,0x11711111,0x11119111,0x11111118
};
uint32_t DRC[data_size]={0};
uint8_t ledflag=0;
int main(void)
{
RCC_ConfigTo72M();//将系统时钟配置成72MHZ
Systick_Config(72);
DMA1CH2_Config(SRC,DRC,data_size); //一共传输30个数据
while(SET!=DMA_GetFlagStatus(DMA1_FLAG_TC2)) { ; } //DMA1通道2传输数据完成时,该标志位置1
DMA_ClearFlag(DMA1_FLAG_TC2); //清除DMA1通道2传输数据完成标志位
if(0==SRC_DRC_compare(SRC,DRC,data_size)){ //传输数据完成,如果两个数组内容相当,则通过串口助手打印dma is ok!
printf("dma is ok!\n");
}
while(1) {;}
}
方法二:(中断方法)
void DMA1CH2_Config(uint32_t *src,uint32_t *drc,uint32_t size)
{
DMA_InitTypeDef DMA_InitStruct;
NVIC_InitTypeDef NVIC_InitStruct;
// 1,打开时钟---DMA1
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1,ENABLE);
// 2,初始化DMA
DMA_InitStruct.DMA_PeripheralBaseAddr =(uint32_t )src;
DMA_InitStruct.DMA_MemoryBaseAddr =(uint32_t )drc;
DMA_InitStruct.DMA_DIR =DMA_DIR_PeripheralSRC;
DMA_InitStruct.DMA_PeripheralInc =DMA_PeripheralInc_Enable;
DMA_InitStruct.DMA_MemoryInc =DMA_MemoryInc_Enable;
DMA_InitStruct.DMA_PeripheralDataSize =DMA_PeripheralDataSize_Word;
DMA_InitStruct.DMA_MemoryDataSize =DMA_MemoryDataSize_Word;
DMA_InitStruct.DMA_BufferSize =size;
DMA_InitStruct.DMA_M2M =DMA_M2M_Enable;
DMA_InitStruct.DMA_Mode =DMA_Mode_Normal;
DMA_InitStruct.DMA_Priority =DMA_Priority_Medium;
DMA_Init(DMA1_Channel2,&DMA_InitStruct);
//打开DMA中断
DMA_ITConfig(DMA1_Channel2, DMA_IT_TC, ENABLE);
//配置中断优先级
NVIC_InitStruct.NVIC_IRQChannel =DMA1_Channel2_IRQn;
NVIC_InitStruct.NVIC_IRQChannelCmd =ENABLE;
NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority =0;
NVIC_InitStruct.NVIC_IRQChannelSubPriority =3;
NVIC_Init(&NVIC_InitStruct);
// 3,使能DMA1通道2
DMA_Cmd(DMA1_Channel2,ENABLE);
}
uint8_t SRC_DRC_compare(uint32_t *src,uint32_t *drc,uint32_t size)
{
uint8_t ret=0;
uint8_t i;
for(i=0;i<size;i++){
if(src[i]!=drc[i]){
ret=1;
return ret;
}
}
return ret;
}
//DMA中断服务函数
extern uint8_t dmaflag;
void DMA1_Channel2_IRQHandler(void)
{
if(SET==DMA_GetITStatus(DMA1_IT_TC2)){ /*当DMA传输数据完成后就会触发DMA中断,并且该中断标志位会置1。注意:除了中断的时候需要CPU,在整个DMA传输数据的过程中,CPU都不会去干预*/
DMA_ClearITPendingBit(DMA1_IT_TC2);
dmaflag=1;
}
}
#define data_size 30
uint32_t SRC[data_size]={
0x11111111,0x12111111,0x11211111,0x11114111,0x11111113,
0x21111111,0x13111111,0x11311111,0x11115111,0x11111114,
0x31111111,0x14111111,0x11411111,0x11116111,0x11111115,
0x41111111,0x15111111,0x11511111,0x11117111,0x11111116,
0x51111111,0x16111111,0x11611111,0x11118111,0x11111117,
0x61111111,0x17111111,0x11711111,0x11119111,0x11111118
};
uint32_t DRC[data_size]={0};
uint8_t dmaflag=0;
int main(void)
{
RCC_ConfigTo72M();//将系统时钟配置成72MHZ
Systick_Config(72);
// 4,如果是程序的第一个中断,需要设置优先级分钟
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1);
DMA1CH2_Config(SRC,DRC,data_size);
while(1){
if(1== dmaflag){
dmaflag = 0;
if(0==SRC_DRC_compare(SRC,DRC,data_size)){
printf("dma is ok!\n");
}
}
}
}
轮询的方法与中断的方法区别在于,轮询需要CPU一直循环的去检测标志位。而中断的方法不占用CPU资源。所以,我们常用中断的方法去使用DMA。