DMA简介:
在硬件系统中,主要由CPU(内核),外设,内存(SRAM),总线等结构组成,数据就经常要在内存与外设之间传输转移,或者是从外设A转移到外设B.
DMA(Direct Memory Access)直接存储器存储,是一种可以大大减轻CPU工作量的数据存储方式.
数据转移的一般方式:
例如当CPU需要处理由ADC外设采集回来的数据时,CPU首先要把数据从ADC外设的寄存器读取到内存中(变量),然后进行运算处理.
(但是,因为在转移数据的过程中会占用CPU十分宝贵的资源,所以希望CPU更多地被用在数据运算或响应中断之中,而数据转移的工作交由其它部件完成。)
DMA的方式:
DMA可以为CPU分担了数据转移的工作。因为DMA的存在,CPU被解放出来,它可以在DMA转移数据的过程中同时进行数据运算,响应中断,大大提高效率.
DMA的工作:
在STM32中文手册可以找到STM32的系统结构图,可以很清晰的看到STM32内核,存储器,外设以及DMA的连接
所有这些硬件结构最终都通过各种各样的线连接到总线矩阵之中,硬件结构之间的数据转移都经过总线矩阵的协调,使各个外设都能够和谐地使用总线来传输数据.
例如:
在不使用DMA的情况下,内核通过DCode经过总线矩阵协调,使用AHB把外设ADC采集的数据读取到内核,然后内核DCode再通过总线矩阵协调,把数据存放到内存SRAM中。
而在使用DMA之后,由DMA控制器的DMA总线与总线矩阵协调,使用AHB把外设ADC的数据经由DMA通道存放到内存SRAM。在这个数据传输的过程中,不需要内核的全程参与,所以内核可以同时进行数据运算,而且DMA的方式是点到点的数据转移,而不使用DMA的方式还要以内核来作为中转站,显然是DMA的传输方式的效率更高。(所以”直接“是很强的东西!!)
DMA的控制参数:
要使用DMA,需要确定一系列的控制参数:
如外设数据的地址,内存地址,传输方向等,在开启DMA传输前还要先发出DMA请求。
DMA实例main函数:
main函数功能:
实际上是利用DMA把数据(数组)从内存转移到外设(串口)。外设工作的时候,除了转移数据,实质是不需要内核干预的,而数据转移的工作现在交给了DMA,所以在串口发送数据的时候,内核同时还可以进行其它操作,比如点亮LED灯(类似一个线程动作)。
头文件忽略………..
extern uint8_t SendBuff[SENDBUFF_SIZE];
uint16_t i;
int main(void)
{
/USART1_Config 115200 8-N-1/
USART1_Config();
DMA_Config();
LED_GPIO_Config();
r(i=0;i { SendBuff[i] = 0xff; } /*串口向DMA发出请求 */ USART_DMACmd(USART1, USART_DMAReq_Tx, ENABLE);//在DMA传送未完成时,CPU会继续执行main函数中的代码 LED1(ON);//先点亮LED,而同时DMA在向串口运送数据,当DMA发送完成时,在中断函数关闭LED while(1); } main函数里面配置好了串口1,DMA,以及LED外设,使能DMA的发送请求.其中串口配置以及LEDGPIO的配置这里就不讲解了,前面的博客都有说,配置的内容都一样. DMA的配置: void DMA_Config(void) { DMA_InitTypeDef DMA_InitStructure; RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);//开启 DMA 时钟 NVIC_Config(); //配置 DMA 中断 /*设置 DMA 源:内存地址&串口数据寄存器地址*/ DMA_InitStructure.DMA_PeripheralBaseAddr = USART1_DR_Base; /*内存地址(要传输的变量的指针)*/ DMA_InitStructure.DMA_MemoryBaseAddr = (u32)SendBuff; /*方向:从内存到外设*/ DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST; /*传输大小 DMA_BufferSize=SENDBUFF_SIZE*/ DMA_InitStructure.DMA_BufferSize = SENDBUFF_SIZE; /*外设地址不增*/ DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable; /*内存地址自增*/ DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; /*外设数据单位*/ DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte; /*内存数据单位 8bit*/ DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte; /*DMA 模式:一次传输,循环*/ DMA_InitStructure.DMA_Mode = DMA_Mode_Normal; /*优先级:中*/ DMA_InitStructure.DMA_Priority = DMA_Priority_Medium; /*禁止内存到内存的传输 */ DMA_InitStructure.DMA_M2M = DMA_M2M_Disable; /*配置 DMA1 的 4 通道*/ DMA_Init(DMA1_Channel4, &DMA_InitStructure); DMA_Cmd (DMA1_Channel4,ENABLE); //使能 DMA_ITConfig(DMA1_Channel4,DMA_IT_TC,ENABLE); //配置 DMA 发送完之后产生中断 } DMA_InitStructure.DMA_PeripheralBaseAddr = USART1_DR_Base; ps:外设数据寄存器的地址可以在《STM32参考手册》中找到一个存储器映射表(部分)(图截的不是很好看!!!) DMA_InitStructure.DMA_MemoryBaseAddr = (u32)SendBuff; DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST; DMA_InitStructure.DMA_BufferSize = SENDBUFF_SIZE; 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_Medium; DMA_InitStructure.DMA_M2M = DMA_M2M_Disable; DMA_Init(DMA1_Channel4, &DMA_InitStructure); 填充好结构体后,就使能DMA了,这里用到DMA1_Channel4(DMA1的通道4),这个通道不是随便选择的,是根据DMA的请求映射来选择的,在《STM32参考手册》可以找到。 DMA_ITConfig(DMA1_Channel4,DMA_IT_TC,ENABLE); DMA的中断: NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1); //配置DMA通道的优先级组1 NVIC_InitStructure.NVIC_IRQChannel = DMA1_Channel4_IRQn;//中断通道 NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1; NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1; NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; NVIC_Init(&NVIC_InitStructure); } 这里的是调用了NVIC_PriorityGroupConfig()DMA的中断优先级为组1,中断通道配置为DMA1_Channel4_IRQn. 中断服务函数: void DMA1_Channel4_IRQHandler(void) { if(DMA_GetFlagStatus(DMA1_FLAG_TC4)==SET)//判断是否为 DMA 发送完成中断 { LED1(OFF);//LED 关闭 DMA_ClearFlag(DMA1_FLAG_TC4); //清除DMA中断标志位 } } 这个中断服务函数名在启动文件startup_stm32f10x_hd.s中找到(以前说过,这里再说一次),这里就是检查中断标志位,关闭LED灯,清除中断标志位 在main函数里USART_DMACmd(USART1, USART_DMAReq_Tx, ENABLE); 使能或者关闭串口的DMA接口,这里配置的是串口1的USART_DMAReq_Tx(串口发送请求),也可以是USART_DMAReq_Rx(串口接收请求). 调用这个库函数允许串口外设向 DMA 发出请求,请求DMA传输数据。调用了这个函数之后,DMA开始响应串口的请求,根据DMA配置,把数组中的数据一个个地转移到串口数据寄存器,并由串口向外发送这些数据。在调用了USART_DMACmd()函数之后,接下来在main函数就把LED点亮了. PS: 实际上,在DMA还没传输完成数据的时候,因为内核并不参与DMA数据传输的全过程,所以内核在这个时候执行了点亮LED1的代码。而当DMA传输完成时,进入了中断,再把LED1关闭.
保存外设数据寄存器的基地址,这个地址作为传输的源或目标(DMA具有地址自增的功能,地址自增可以方便地读取连续的单元)
这里面用的USART1_DR_Base宏实际是#define USART1_DR_Base 0x40013804,从《 STM32 参考手册》可知,串口外设会自动地把数据寄存器中的数据,送入它的移位寄存器,然后由硬件按照串口协议把该数据发送出去。
在这个实例中,把数据寄存器的地址作为外设的地址,那么由DMA通道转移过来的内存数据就会被保护到这个寄存器中,然后串口就会自动进行发送了.
可以看到,USART1(串口1)的外设基地址为0x40013800,同时我们在查找《STM32数据手册》手册可以找到USART的数据寄存器,了解到偏移量是0x04,那么USART1的外设基地址加上数据寄存器的地址偏移就是在DMA传输中需要的目标地址了。
0x4001 3804 = 0x4001 3800 + 0x04;
保存了内存的基地址,这个地址也可以作为传输源或目标.在使用时通常会给这个成员赋值为某个数组的基地址,然后利用DMA的地址自增功能把数组一个个地填满.
(在C语言中数组名就是该数组的基地址,而数组(变量)是被保存到内存(SRAM)上的,所以我们实质上给.DMA_MemoryBaseAddr 这个结构体成员赋予了一个内存地址。)
DMA数据传输方向,可以选择是外设到内存还是内存到外设。DMA_DIR_PeripheralDST是内存到外设
DMA要传输的数据总大小, DMA_BufferSize=SENDBUFF_SIZE=5000,这里要传输5000个数据
外设地址不增,因为用的是外设地址是固定的.
内存地址自增,数组自增把数据一个个都传到数据寄存器.
外设传输数据单元大小,可以为字节,半字节,字.
内存传输数据单元大小,可以为字节,半字节,字,这里是8bit.
DMA的模式,可以为循环模式或者正常模式,在循环模式下传输完一轮数据之后再重新传输,适合ADC不断采集数据等场合,这里是正常模式,也就是一次.
配置DMA通道的优先级,总线矩阵根据DMA通道的优先级进行总线协调分配,这里配置为DMA_Priority_Medium(中等优先级).
(在使用1个DMA通道时,配置任何优先级都没有区别)
使能内存到内存的DMA传输。DMA传输可以在外设与内存,外设与外设,还可以在内存与内存之间进行传输。
这里用的是内存到外设 ,使用DMA_M2M_Disable(禁止内存到内存的传输).
DMA_Cmd (DMA1_Channel4,ENABLE);
(DMA请求是指外设在需要使用DMA前需要向DMA控制器发送请求信息,DMA在接收到请求后才会根据DMA配置进行数据转移。)
PS:
从图中可以看到即使同样是外设串口1,串口1的发送数据 DMA 请求和串口1的接收数据DMA请求通道都是不一样的,分别为DMA1通道4和DMA1的通道5。
把DMA配置成DMA_IT_TC(DMA发送完成标志)中断.DMA_ITConfig是用于外设中断的函数.
中断配置:
在DMA_Config()中调用了NVIC_Config()进行DMA中断配置
static void NVIC_Config(void)
{
NVIC_InitTypeDef NVIC_InitStructure;
上一篇:STM32之SPI_FLASH
下一篇:STM32之USART(串口通信)
推荐阅读最新更新时间:2024-03-16 16:09