6-stm32外设DMA学习

前言


         本系列,将stm32常用片上外设进行整理,包括大致原理和代码,主要是熟悉如何根据手册去编写代码。便于以后需要做实验时,能够快速编写基本的驱动,然后编写应用代码。

        本系列基于“标准库”整理,开发板用的是正点原子精英版V1.5,单片机是STM32F103ZET6。开发工具KEIL。主要参考资料有《STM32F103xCDE中文参考手册》《STM32F103xCDE中文数据手册》、《CM3权威指南》、《STM32F103xCDE闪存编程手册》、《精英版原理图V1.5》。

》《STM32F1xx Cortex-M3编程手册-英文版》。

 

一、DMA介绍

        DMA(Direct Memory Access),即直接存储器访问。支持三种模式,分别是:内存<->内存、内存<->外设、外设<->内存。DMA就是实现搬运数据的功能,从而将CPU空出来,使CPU可以去干其它事情,例如用串口进行大量数据接受,串口最多8位或者9位,来一个字节的数据就得使用中断去读取,当来1000个字节时就得进入1千次中断,这时候CPU都去处理串口接受数据了,那么其它任务就被搁置了,所以DMA就来帮CPU搬运串口数据,CPU可以正常干其他事情。从而提高系统响应程度。

        STM32F1系列有DMA1支持7个独立通道,大容量还要DMA2支持5个独立通道,所以一共12个独立通道。如果全部用来接受外设数据的话,可以接12个。

 

二、DMA框图

        DMA框图由于框图比较简单,所以和系统框图十分相似。此图在《STM32F103xCDE中文参考手册》10.2的最后,如下图所示:

7319a9c361724d3abeb8a9910539a804.png

        系统架构如下图所示:

bea970bf2bde430998d774adad76f35a.png

        从图中可以看出DMA多个通道同时发起请求时,是需要仲裁器来选择到底先响应那一通道请求的。

三、DMA源端宽度和目标宽度不一致时的对齐方式

        在项目中遇到过这种情况,通过串口发送一串数据,通过DMA来搬运。此时源端是内存,目标是串口DR寄存器。如果将源端宽度设置位16位,串口的DR寄存器是8位的。此时接收端只会收到低字节,而丢掉高字节。如发送uint16-t  array= {0x01,0x02,x03,0x04,0x05,0x06},使用DMA搬运,通过串口发送给串口调试助手,如果源大小16位,目标8位,此时串口调试助手只会收到0x01,0x03,0x05。此映射表在《STM32F103xCDE中文参考手册》的10.3.4,如下图所示:

b22bb70b2cd041e6ada2c25a0db4f5d0.png

四、DMA请求映射图

        经常在配置代码时,不知道去哪找,串口1的tx到底是映射到DMA的那一路通道。其实此映射表在《STM32F103xCDE中文参考手册》的10.3.7 DMA请求映射,有图也有表,如下图所示。

d3a5f51e332549f18c2d7391426b9f77.png

5d80d4a71ae24ddfb2158b3120056941.png

五、串口使用DMA搬运数据配置

        在标准库中配置DMA搬运串口数据,发送使用DMA,接受使用空闲中断和DMA,便于读者查看代码,全部在main函数里。以下代码实现,开机向串口助手发送hello,接受到串口助手发送的数据然后回发给串口助手。


#include "stm32f10x.h"

void usart1_dma_init(void);
void com_usart_write_byte(uint8_t * psrc,uint32_t unit_num);
void com_usart_start_recv(void);

 int main(void)
 {		
	  
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//设置中断优先级分组为组2:2位抢占优先级,2位响应优先级
	usart1_dma_init();
	 com_usart_write_byte("hello",7);
	 com_usart_start_recv();//开启dma接受数据  
 	while(1)
	{
	  	   
	}   
}

#define SEND_BUF_SIZE 256
#define RECV_BUF_SIZE 256

uint8_t g_sendbuff[SEND_BUF_SIZE];	//发送数据缓冲区
uint8_t g_recvbuff[RECV_BUF_SIZE];	//发送数据缓冲区

void usart1_dma_init(void)
{

	//GPIO端口设置
	GPIO_InitTypeDef GPIO_InitStructure;
	DMA_InitTypeDef DMA_InitStructure;
	USART_InitTypeDef USART_InitStructure;
	NVIC_InitTypeDef NVIC_InitStructure;
	 
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);	//使能GPIOA时钟

	//USART1_TX   GPIOA.9
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9; //PA.9
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;	//复用推挽输出
	GPIO_Init(GPIOA, &GPIO_InitStructure);//初始化GPIOA.9

	//USART1_RX	  GPIOA.10初始化
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;//PA10
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;//浮空输入
	GPIO_Init(GPIOA, &GPIO_InitStructure);//初始化GPIOA.10  

	
	RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);//使能DMA1时钟
	//根据手册映射,USART1 TX 映射到DMA1的通道4
	DMA_DeInit(DMA1_Channel4);   //将DMA的通道1寄存器重设为缺省值
	DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&USART1->DR;  //DMA外设基地址
	DMA_InitStructure.DMA_MemoryBaseAddr = 0;  //DMA内存基地址,初始化时不配置,留给发送函数
	DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST;  //数据传输方向,从内存读取发送到外设
	DMA_InitStructure.DMA_BufferSize = 0;  //DMA通道的DMA缓存的大小,初始化时不配置,留给发送函数
	DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;  //外设地址寄存器不变
	DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;  //内存地址寄存器递增
	DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;  //数据宽度为8位
	DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte; //数据宽度为8位
	DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;  //工作在正常缓存模式
	DMA_InitStructure.DMA_Priority = DMA_Priority_Medium; //DMA通道 x拥有中优先级 
	DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;  //DMA通道x没有设置为内存到内存传输
	DMA_Init(DMA1_Channel4, &DMA_InitStructure);  //DMA1_Channel4
	
	//根据手册映射,USART1 RX 映射到DMA1的通道5
	DMA_DeInit(DMA1_Channel5);   //将DMA的通道1寄存器重设为缺省值
	DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&USART1->DR;  //DMA外设基地址
	DMA_InitStructure.DMA_MemoryBaseAddr = 0;  //DMA内存基地址,初始化时不配置,留给接受函数
	DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;  //数据传输方向,从外设到内存
	DMA_InitStructure.DMA_BufferSize = 0;  //DMA通道的DMA缓存的大小,初始化时不配置,留给接受函数
	DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;  //外设地址寄存器不变
	DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;  //内存地址寄存器递增
	DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;  //数据宽度为8位
	DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte; //数据宽度为8位
	DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;  //工作在正常缓存模式
	DMA_InitStructure.DMA_Priority = DMA_Priority_Medium; //DMA通道 x拥有中优先级 
	DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;  //DMA通道x没有设置为内存到内存传输
	DMA_Init(DMA1_Channel5, &DMA_InitStructure);  //DMA1_Channel4
	
	//Usart1 NVIC 配置
	NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=3 ;//抢占优先级3
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3;		//子优先级3
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;			//IRQ通道使能
	NVIC_Init(&NVIC_InitStructure);	//根据指定的参数初始化VIC寄存器

	//USART 初始化设置
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);	//使能USART1时钟
	
	USART_InitStructure.USART_BaudRate = 115200;//串口波特率
	USART_InitStructure.USART_WordLength = USART_WordLength_8b;//字长为8位数据格式
	USART_InitStructure.USART_StopBits = USART_StopBits_1;//一个停止位
	USART_InitStructure.USART_Parity = USART_Parity_No;//无奇偶校验位
	USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;//无硬件数据流控制
	USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;	//收发模式

	USART_Init(USART1, &USART_InitStructure); //初始化串口1
	USART_ITConfig(USART1, USART_IT_IDLE, ENABLE);//开启串口空闲中断
	
	USART_DMACmd(USART1,USART_DMAReq_Tx,ENABLE); //使能串口1的DMA发送      
	USART_DMACmd(USART1,USART_DMAReq_Rx,ENABLE); //使能串口1的DMA接受  
	
	USART_Cmd(USART1, ENABLE);                    //使能串口1 

}

//使用USART1 TX发送数据时调用
void com_usart_write_byte(uint8_t * psrc,uint32_t unit_num)
{
	 uint16_t i;

	 if( unit_num > SEND_BUF_SIZE )
	 {
	   return;
	 }

	 for(i = 0; i< unit_num; i++)
	 {
		g_sendbuff[i]  =  psrc[i];
	 }

	DMA_Cmd(DMA1_Channel4, DISABLE );  //关闭USART1 TX DMA1 所指示的通道
	
 	DMA_SetCurrDataCounter(DMA1_Channel4,unit_num);//DMA通道的DMA缓存的大小
	DMA1_Channel4->CMAR = (uint32_t) (&g_sendbuff[0]);//因为没有相关库函数,所以使用寄存器配置
 	DMA_Cmd(DMA1_Channel4, ENABLE);  //使能USART1 TX DMA1 所指示的通道 
  
}

//开启USART1 RX接受数据,有数据来时,DMA会将数据搬运到RecvBuff中,配置串口空闲中断,空闲中断产生时,RecvBuff中就会有数据
void com_usart_start_recv(void)
{
      DMA_Cmd(DMA1_Channel5, DISABLE );  //关闭USART1 RX DMA1 所指示的通道
      DMA_SetCurrDataCounter(DMA1_Channel5,RECV_BUF_SIZE);//DMA通道的DMA缓存的大小为RECV_BUF_SIZE
      DMA1_Channel5->CMAR = (uint32_t)(&g_recvbuff[0]);//因为没有相关库函数,所以使用寄存器配置
      DMA_Cmd(DMA1_Channel5, ENABLE);  //使能USART1 RX DMA1 所指示的通道 

}

void USART1_IRQHandler(void)
{
	if(USART_GetITStatus(USART1,USART_IT_IDLE) == SET)
	{	
			//根据手册串口寄存器SR的bit4介绍,先读SR再读DR,清除IDLE位
			USART1->SR;
			USART1->DR;
			printf("recv:%s",g_recvbuff);
			com_usart_start_recv();//开始从头接收数据
	
	}
	


}

236a9e1f54e44d30a587e3d4c02ff9da.png

六、总结

     熟读一遍手册中DMA介绍和DMA寄存器,加上有标准库,配置起来还是比较容易,DMA很好用,需要注意的是源大小和目标大小要一致(见二),第二就是外设和DMA通道的映射图(见三)。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值