您的位置:首页 > 其它

STM32之DMA(直接存储器存储)

2016-07-19 22:41 323 查看
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_SIZE;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;

保存外设数据寄存器的基地址,这个地址作为传输的源或目标(DMA具有地址自增的功能,地址自增可以方便地读取连续的单元)

这里面用的USART1_DR_Base宏实际是#define USART1_DR_Base 0x40013804,从《 STM32 参考手册》可知,串口外设会自动地把数据寄存器中的数据,送入它的移位寄存器,然后由硬件按照串口协议把该数据发送出去。

在这个实例中,把数据寄存器的地址作为外设的地址,那么由DMA通道转移过来的内存数据就会被保护到这个寄存器中,然后串口就会自动进行发送了.

ps:外设数据寄存器的地址可以在《STM32参考手册》中找到一个存储器映射表(部分)(图截的不是很好看!!!)





可以看到,USART1(串口1)的外设基地址为0x40013800,同时我们在查找《STM32数据手册》手册可以找到USART的数据寄存器,了解到偏移量是0x04,那么USART1的外设基地址加上数据寄存器的地址偏移就是在DMA传输中需要的目标地址了。

0x4001 3804 = 0x4001 3800 + 0x04;

DMA_InitStructure.DMA_MemoryBaseAddr = (u32)SendBuff;

保存了内存的基地址,这个地址也可以作为传输源或目标.在使用时通常会给这个成员赋值为某个数组的基地址,然后利用DMA的地址自增功能把数组一个个地填满.

(在C语言中数组名就是该数组的基地址,而数组(变量)是被保存到内存(SRAM)上的,所以我们实质上给.DMA_MemoryBaseAddr 这个结构体成员赋予了一个内存地址。)

DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST;

DMA数据传输方向,可以选择是外设到内存还是内存到外设。DMA_DIR_PeripheralDST是内存到外设

DMA_InitStructure.DMA_BufferSize = SENDBUFF_SIZE;

DMA要传输的数据总大小, DMA_BufferSize=SENDBUFF_SIZE=5000,这里要传输5000个数据

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;

内存传输数据单元大小,可以为字节,半字节,字,这里是8bit.

DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;

DMA的模式,可以为循环模式或者正常模式,在循环模式下传输完一轮数据之后再重新传输,适合ADC不断采集数据等场合,这里是正常模式,也就是一次.

DMA_InitStructure.DMA_Priority = DMA_Priority_Medium;

配置DMA通道的优先级,总线矩阵根据DMA通道的优先级进行总线协调分配,这里配置为DMA_Priority_Medium(中等优先级).

(在使用1个DMA通道时,配置任何优先级都没有区别)

DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;

使能内存到内存的DMA传输。DMA传输可以在外设与内存,外设与外设,还可以在内存与内存之间进行传输。

这里用的是内存到外设 ,使用DMA_M2M_Disable(禁止内存到内存的传输).

DMA_Init(DMA1_Channel4, &DMA_InitStructure);

DMA_Cmd (DMA1_Channel4,ENABLE);

填充好结构体后,就使能DMA了,这里用到DMA1_Channel4(DMA1的通道4),这个通道不是随便选择的,是根据DMA的请求映射来选择的,在《STM32参考手册》可以找到。

(DMA请求是指外设在需要使用DMA前需要向DMA控制器发送请求信息,DMA在接收到请求后才会根据DMA配置进行数据转移。)



PS:

从图中可以看到即使同样是外设串口1,串口1的发送数据 DMA 请求和串口1的接收数据DMA请求通道都是不一样的,分别为DMA1通道4和DMA1的通道5。

DMA_ITConfig(DMA1_Channel4,DMA_IT_TC,ENABLE);

把DMA配置成DMA_IT_TC(DMA发送完成标志)中断.DMA_ITConfig是用于外设中断的函数.

DMA的中断:

中断配置:

在DMA_Config()中调用了NVIC_Config()进行DMA中断配置

static void NVIC_Config(void)

{

NVIC_InitTypeDef NVIC_InitStructure;

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关闭.
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: