1.DMA的简介
-
首先先跟大家说明一下什么叫DMA,英文名称Direct Memory Access,简称DMA,翻译成中文就是直接存储器访问,是一种让硬件设备直接直接与内存直间接进行传输数据的技术,这种机制可以大大减轻CPU的负载
那么,我们为什么要,设置DMA呢,因为我们知道CPU有转移数据、计算、控制程序转移等很多功能,而且CPU无时无刻的在处理着大量任务,但是有些事情就不那么重要,比如说数据的复制和转移,如果我们把这部分CPU资源拿出来,去处理其他更重要的任务,这样就可以提高CPU的利用率,由此DMA产生了
下面用ADC采集数据发送到SRAM中
如下图所示,当数据量过大时,会严重占用CPU资源
下面就是采用DMA则,不会占用CPU资源,当数据量过大时,也对CPU没有影响,可以大大提高CPU利用率
简单介绍一下他的工作流程
DMA传输时外设对DMA控制器发出请求。 DMA控制器收到请求,触发DMA工作。 DMA控制器从AHB外设获取ADC采集的数据,存储到DMA通道中 DMA控制器的DMA总线与总线矩阵协调,使用AHB把外设ADC采集的数据经由DMA通道存放到SRAM中,这个数据的传输过程中,完全不需要内核的参与,也就是不需要CPU的参与,
这样一来,就解决了大量数据转移过度浪费CPU资源的问题,使CPU更专注于更加实用的操作–计算、控制等。
仲裁器
仲裁器的作用是当有多个申请时,确定其优先级,
分为两个阶段
第一阶段
软件:每个通道的优先权可以在DMA_CCRx寄存器中设置,有4个等级:
-
最高优先级
-
高优先级
-
中等优先级
-
低优先级;
第二阶段
硬件:如果2个请求有相同的软件优先级,则较低编号的通道比较高编号的通道有较高的优先权。比如:如果软件优先级相同,通道2优先于通道4。
注意: 在大容量产品和互联型产品中,DMA1控制器拥有高于DMA2控制器的优先级。
DMA数据流(仅存在于STM32F4 /M4 内核上)
DMA的传输方式
因为DMA就是两个区域的数据传输,所以说,DMA就有四个传输方式
-
内 ->内
-
内->外
-
外->内
-
外->外
可编程的数据传输宽度的问题
假如源端想要发送两个八个字节的数据,接收端有两个16个字节的数据,那么他会怎么接收呢
他们回传输到两个不同的位置,而不会填满一个
假如我发送端是一个16字节数据,接受端是8字节数据,那么只能接受8个字节
2 .DMA在STM32中的使用
上面用画图的方法给大家做了DMA的简介,以及他的优点,下面给大家介绍一下DMA在STM32中的使用
DMA控制器介绍
在STM32F1系列微控制器中,有两个DMA控制器:DMA1 和 DMA2。每个DMA控制器都包含若干个通道,具体如下:
-
DMA1 包含 7 个通道(DMA1_Channel1 到 DMA1_Channel7)。
-
DMA2 包含 5 个通道(DMA2_Channel1 到 DMA2_Channel5)。
因此,在STM32F1系列中,总共有 12 个DMA通道。
下图给大家介绍了DMA1和DMA2各个通道的请求,根据下图来配置不同的外设
根据上图和你想实现的功能,来配置通道,
DMA在USART1传输代码
首先呢,给大家书写了一份中文结构体注释,这样有利于大家的理解
{ uint32_t DMA_PeripheralBaseAddr; /*!< 指定DMAy通道x的外设基地址。 */ uint32_t DMA_MemoryBaseAddr; /*!< 指定DMAy通道x的内存基地址。 */ uint32_t DMA_DIR; /*!< 指定外设是源地址还是目标地址。 此参数可以是@ref DMA_data_transfer_direction 中的一个值。*/ uint32_t DMA_BufferSize; /*!< 指定指定通道的数据缓冲区大小,以数据单元为单位。 数据单元等于在DMA_PeripheralDataSize或DMA_MemoryDataSize成员中设置的配置, 取决于传输方向。*/ uint32_t DMA_PeripheralInc; /*!< 指定是否使能外设地址寄存器的递增功能。 此参数可以是@ref DMA_peripheral_incremented_mode 中的一个值。*/ uint32_t DMA_MemoryInc; /*!< 指定是否使能内存地址寄存器的递增功能。 此参数可以是@ref DMA_memory_incremented_mode 中的一个值。*/ uint32_t DMA_PeripheralDataSize; /*!< 指定外设数据宽度。 此参数可以是@ref DMA_peripheral_data_size 中的一个值。*/ uint32_t DMA_MemoryDataSize; /*!< 指定内存数据宽度。 此参数可以是@ref DMA_memory_data_size 中的一个值。*/ uint32_t DMA_Mode; /*!< 指定DMAy通道x的操作模式。 此参数可以是@ref DMA_circular_normal_mode 中的一个值。 注意:如果配置了所选通道的内存到内存数据传输,则不能使用循环缓冲区模式。*/ uint32_t DMA_Priority; /*!< 指定DMAy通道x的软件优先级。 此参数可以是@ref DMA_priority_level 中的一个值。*/ uint32_t DMA_M2M; /*!< 指定DMAy通道x是否用于内存到内存的传输。 此参数可以是@ref DMA_memory_to_memory 中的一个值。*/ }DMA_InitTypeDef;
DAM初始化函数,为了加大大家对初始化函数的理解,给初始化分成了三个部分,
1.数据的输入与输出,
//数据输入与输出 //1外设地址 2内部储存器地址 3传输方向 DMA_InitStruc.DMA_PeripheralBaseAddr = (uint32_t)SRC_Buffer; //外设地址,就是串口的DR寄存器的地址 DMA_InitStruc.DMA_MemoryBaseAddr = (uint32_t)DST_Buffer; //内部储存器地址 DMA_InitStruc.DMA_DIR = DMA_DIR_PeripheralSRC; //传输方向,外设作为传输源
2.数据大小以及单位
DMA_InitStruc.DMA_BufferSize = BUFFER_SIZE;//传输数据大小 DMA_InitStruc.DMA_PeripheralInc = DMA_PeripheralInc_Enable; //内存地址增量使能,因为是数组 DMA_InitStruc.DMA_MemoryInc = DMA_MemoryInc_Enable;//外设地址增量使能,因为串口的位置是固定的 DMA_InitStruc.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Word; //外设数据宽度 DMA_InitStruc.DMA_MemoryDataSize = DMA_MemoryDataSize_Word; //内存的数据宽度
3.传输模式选择
DMA_InitStruc.DMA_Mode = DMA_Mode_Normal; //选择普通股模式 DMA_InitStruc.DMA_Priority = DMA_Priority_High; //选择高的优先级,因为就一个,所以说选择那个都可 DMA_InitStruc.DMA_M2M = DMA_M2M_Disable; //内存到内存功能使能
根据以上整理出完整版
void USART_DMA() { RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1,ENABLE); DMA_InitTypeDef DMA_InitStruc; //数据输入与输出 //1外设地址 2内部储存器地址 3传输方向 DMA_InitStruc.DMA_PeripheralBaseAddr = (uint32_t)USART_DR_ADD; //外设地址 DMA_InitStruc.DMA_MemoryBaseAddr = (uint32_t)Send_Buff; //内部储存器地址 DMA_InitStruc.DMA_DIR = DMA_DIR_PeripheralDST; //传输方向,内部作为传输源 //数据的大小 DMA_InitStruc.DMA_BufferSize = 500;//传输数据大小 DMA_InitStruc.DMA_PeripheralInc = DMA_PeripheralInc_Disable; //外设地址增量 DMA_InitStruc.DMA_MemoryInc = DMA_MemoryInc_Enable;//内存地址增量 DMA_InitStruc.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte; //外设数据宽度 DMA_InitStruc.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte; //内存的数据宽度 DMA_InitStruc.DMA_Mode = DMA_Mode_Normal; DMA_InitStruc.DMA_Priority = DMA_Priority_High; DMA_InitStruc.DMA_M2M = DMA_M2M_Disable; DMA_Init(DMA1_Channel4 , &DMA_InitStruc); DMA_ClearFlag(DMA1_FLAG_TC4); DMA_Cmd(DMA1_Channel4,ENABLE); }
.h文件
#ifndef __DAM_H #define __DAM_H #include "stm32f10x.h" #define BUFFER_SIZE 16 #define USART_DR_ADD (USART1_BASE+0x04) //串口DR寄存器加上便宜量 void DMA_Init_Srtuct(); uint8_t Buffcrmp(const uint32_t *Buff1,uint32_t*Buff2,uint32_t size); void USART_DMA(); #endif
main函数
#include "stm32f10x.h" #include "main.h" #include "LED.h" #include "Bear.h" #include "key.h" #include "Realy.h" #include "usart.h" #include "shake.h" #include "exti.h" #include "time.h" #include "pwm.h" #include "HC-SR04.h" #include "Systick.h" #include "OLED.h" #include "DHT11.h" #include "SPI.h" #include "NRF24L01.h" #include "DAM.h" extern const uint32_t SRC_Buffer[16]; extern uint32_t DST_Buffer[BUFFER_SIZE]; extern uint8_t Send_Buff[500]; int main(void) { uint32_t i = 0; for(i = 0;i<500;i++) { Send_Buff[i] = '0'; } Usart_Init(); USART_DMA(); USART_DMACmd(USART1,USART_DMAReq_Tx,ENABLE); while(1) { } }
结果是在串口上看见500个0;