STM32单片机——DMA数据传输

STM32单片机——DMA数据传输

  • DMA相关概述
  • 实验一:内存到内存搬运
    • Cubemx工程配置
    • Hal库程序设计及实现
    • 固件库程序设计及实现
  • 实验二:内存到外设(DMA串口发送)
    • Cubemx工程配置
    • Hal库程序设计及实现
    • 固件库程序设计
  • 实验三:外设到内存(DMA串口接收)
    • DMA接收Cubemx工程配置
    • 串口DMA接收相关函数解析
    • DMA串口接收Hal库代码实现
    • DMA串口接收固件库程序设计
  • 写在最后

DMA相关概述

  • 参考文章:STM32 DMA详解

  • 1. 什么是DMA
    令人头秃的描述
      DMA(Direct Memory Access,直接存储器访问) 提供在外设与内存、存储器和存储器、外设与外设之间的高速数据传输使用。它允许不同速度的硬件装置来沟通,而不需要依赖于CPU,在这个时间中,CPU对于内存的工作来说就无法使用。
    简单描述:
      就是一个数据搬运工!!

  • 2. DMA的意义
    代替 CPU 搬运数据,为 CPU 减负。
    (1)数据搬运的工作比较耗时间;
    (2) 数据搬运工作时效要求高(有数据来就要搬走);
    (3) 没啥技术含量(CPU 节约出来的时间可以处理更重要的事)。

  • 3. 搬运什么数据
      存储器: spi、usart、iic、adc 等基于APB1 、APB2或AHB时钟的外设
      外设: 自身的闪存(flash)或者内存(SRAM)以及外设的存储设备都可以作为访问地源或者目的
    三种搬运方式:

    搬运方式 用途(案例)
    存储器→存储器 复制某特别大的数据buf
    存储器→外设 将某数据buf写入串口TDR寄存器
    外设→存储器 将串口RDR寄存器写入某数据buf

存储器→存储器
STM32单片机——DMA数据传输_第1张图片
   存储器→外设
STM32单片机——DMA数据传输_第2张图片
   外设→存储器
STM32单片机——DMA数据传输_第3张图片

  • 4. DMA 控制器
      STM32F103有2个 DMA 控制器,DMA1有7个通道,DMA2有5个通道。
      一个通道每次只能搬运一个外设的数据!! 如果同时有多个外设的 DMA 请求,则按照优先级进行响应。
      DMA1有7个通道:
       STM32单片机——DMA数据传输_第4张图片
      DMA2有5个通道:
         STM32单片机——DMA数据传输_第5张图片
  • 5. DMA及通道的优先级
    (1)软件: 每个通道的优先级可以在DMA_CCRx寄存器中设置,有4个等级最高级>高级>中级>低级
    (2)硬件: 如果2个请求,它们的软件优先级相同,则较低编号的通道比较高编号的通道有较高的优先权
  • 6. DMA传输方式
    模式 传输方式
    DMA_Mode_Normal(正常模式) 一次DMA数据传输完后,停止DMA传送 ,也就是只传输一次
    DMA_Mode_Circular(循环传输模式) 当传输结束时,硬件自动会将传输数据量寄存器进行重装,进行下一轮的数据传输。 也就是多次传输模式
  • 7. 指针递增模式
      外设和存储器指针在每次传输后可以自动向后递增或保持常量。当设置为增量模式时,下一个要传输的地址将是前一个地址加上增量值。
    STM32单片机——DMA数据传输_第6张图片

实验一:内存到内存搬运

Cubemx工程配置

  • 时钟配置
    1. 采用外部高速晶振
    2. 时钟树配置
      STM32单片机——DMA数据传输_第7张图片
  • DMA配置
    STM32单片机——DMA数据传输_第8张图片
  • 打开串口

Hal库程序设计及实现

  • 常用库函数解析

    1. 开启DMA传输
      HAL_StatusTypeDef HAL_DMA_Start(DMA_HandleTypeDef *hdma, uint32_t SrcAddress, uint32_t DstAddress, uint32_t DataLength)
      
      参数解析
      参数 解析
      返回值 HAL_StatusTypeDef,HAL状态(OK,busy,ERROR,TIMEOUT
      DMA_HandleTypeDef *hdma DMA通道句柄
      uint32_t SrcAddress 源内存地址
      uint32_t DstAddress 目标内存地址
      uint32_t DataLength 传输数据长度。注意:需要乘以sizeof(uint32_t)
    2. 获取DMA Flag的值
      #define __HAL_DMA_GET_FLAG(__HANDLE__, __FLAG__)   (DMA1->ISR & (__FLAG__))
      
      参数解析
      参数 解析
      返回值 FLAG的值(SET/RESET
      __HANDLE__ DMA通道句柄
      __FLAG__ 数据传输标志。DMA_FLAG_TCx表示数据传输完成标志,x表通道数
  • Hal库程序设计

    // 源数组  放在flash内部
    const uint32_t srcBuf[16] = {
    		0x00000000,0x11111111,0x22222222,0x33333333,
    		0x44444444,0x55555555,0x66666666,0x77777777,
    		0x88888888,0x99999999,0xAAAAAAAA,0xBBBBBBBB,
    		0xCCCCCCCC,0xDDDDDDDD,0xEEEEEEEE,0xFFFFFFFF
    };
    // 目标数组	放置在SRAM内
    uint32_t desBuf[16];
    
    
    //开启数据传输
     HAL_DMA_Start(&hdma_memtomem_dma1_channel1, (uint32_t)srcBuf, (uint32_t)desBuf, 16 * sizeof(uint32_t));
    
    //等待数据传输完成
     while( __HAL_DMA_GET_FLAG(&hdma_memtomem_dma1_channel1, DMA_FLAG_TC1) == RESET );
     
     //打印目标数组内容
     HAL_UART_Transmit(&huart1,(const uint8_t *)desBuf, 16 * sizeof(uint32_t), 200);
    
  • 串口打印目标数组
    STM32单片机——DMA数据传输_第9张图片

固件库程序设计及实现

  • dma.c存储器和外设变量定义
    #defien BUF_SIZE 16
    //源数组  const表常量类型,数组buf放在flash中
    const uint32_t src_buf[BUF_SIZE]={
    		0x00000000,0x11111111,0x22222222,0x33333333,
    		0x44444444,0x55555555,0x66666666,0x77777777,
    		0x88888888,0x99999999,0xAAAAAAAA,0xBBBBBBBB,
    		0xCCCCCCCC,0xDDDDDDDD,0xEEEEEEEE,0xFFFFFFFF
    };
    //目标数组   放置在SRAM中
    uint32_t des_buf[BUF_SIZE];
    
  • M2M结构体配置
    void DMA_MTM_Init(void)
    {
    	DMA_InitTypeDef DMA_InitStructure;
    	
    	RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);	//使能DMA时钟
    	
    	DMA_InitStructure.DMA_PeripheralBaseAddr =	(uint32_t)src_buf;		//外设地址
    	DMA_InitStructure.DMA_MemoryBaseAddr 	 = (uint32_t)des_buf;		//内存地址
    	DMA_InitStructure.DMA_DIR 				 = DMA_DIR_PeripheralSRC;	//传输方向
    	
    	DMA_InitStructure.DMA_BufferSize 		= BUF_SIZE  * 4;				//传输数量
    	DMA_InitStructure.DMA_PeripheralInc 	= DMA_PeripheralInc_Enable;		//外设地址增量
    	DMA_InitStructure.DMA_MemoryInc 		= DMA_MemoryInc_Enable;			//存储器地址递增
    	DMA_InitStructure.DMA_PeripheralDataSize= DMA_PeripheralDataSize_Byte;	//外设数据宽度 1字节 
    	DMA_InitStructure.DMA_MemoryDataSize 	= DMA_MemoryDataSize_Byte;		//存储器数据宽度 1字节
    	
    	DMA_InitStructure.DMA_Mode 				= DMA_Mode_Normal;			//正常模式,发送一次
    	DMA_InitStructure.DMA_Priority 			= DMA_Priority_Low;			//优先级 低
    	DMA_InitStructure.DMA_M2M 				= DMA_M2M_Enable;			//使能MTM
    	
    	DMA_Init(DMA1_Channel1, &DMA_InitStructure);	//结构体初始化
    	DMA_ClearFlag(DMA1_FLAG_TC1);	 				//清除对应标志位
    	DMA_Cmd(DMA1_Channel1, ENABLE);					//使能DMA1 通道1
    }
    
  • M2M传输函数
    uint8_t Buffercmp(const uint32_t *pBuffer1, uint32_t *pBuffer2, uint32_t BufferLen )
    {
    	while(BufferLen--)
    	{
    		if( *pBuffer1 !=  *pBuffer2)	//对应的两个源数组不相等,返回0
    			return 0;
    		pBuffer1++;						//递增两个数据源的地址指针
    		pBuffer2++;
    	}
    	return 1;							//完成判断 并且对应数据相对
    }	
    
  • 主函数程序设计
    //外部源数组、目标数组变量定义
    extern const uint32_t src_buf[BUF_SIZE];
    extern uint32_t des_buf[BUF_SIZE];
    int main(void)
    {	
    	
    	LED_Init();
    	Usart1_Init(115200);		//串口初始化
    	DMA_MTM_Init();				//M2M初始化																
    	Buffercmp(src_buf, des_buf, BUF_SIZE * 4);	//M2M传输数据
    	//串口打印目标数组数据
    	Usart_SendString(USART1,(unsigned char *)des_buf, BUF_SIZE * 4);
    	while(1)
    	{
    	
    	}
    }
    

实验二:内存到外设(DMA串口发送)

Cubemx工程配置

  • 时钟及串口配置(同上)
  • 串口DMA配置
    STM32单片机——DMA数据传输_第10张图片

Hal库程序设计及实现

  • 串口DMA发送函数解析

    HAL_StatusTypeDef HAL_UART_Transmit_DMA(UART_HandleTypeDef *huart, const uint8_t *pData, uint16_t Size)
    

    参数解析

    参数 解析
    返回值 HAL_StatusTypeDef,HAL状态(OK,busy,ERROR,TIMEOUT
    UART_HandleTypeDef *huart 串口句柄
    const uint8_t *pData 待发送数据首地址
    uint16_t Size 发送数据长度
  • DMA串口发送程序设计

    #include 
    
    //发送缓存
    unsigned char sendBuf[128] = "send by DMA\r\n";
    unsigned char sendBuf[128] = "send by DMA\r\n";
    
    //DMA发送数据,此过程不消耗CPU
    HAL_UART_Transmit_DMA(&huart1, sendBuf, strlen((const char *)sendBuf));
    

固件库程序设计

  • 参考博文:STM32—DMA功能讲解串口发送
  • dma.c dma结构体配置
    #define SEND_SIZE	16							//USART_DMA
    #define USART1_DR_ADDR 	(USART1_BASE + 0x04)	//串口发送寄存器
    
    //发送数据变量定义
    uint8_t send_buf[SEND_SIZE] = "0123456789ABCDEF";
    
    void USART1_DMATx_Init(void)
    {
    	DMA_InitTypeDef DMA_InitStructure;
    	
    	RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);	//使能DMA时钟
    	
    	DMA_InitStructure.DMA_MemoryBaseAddr 	= (uint32_t)send_buf;			//内存地址
    	DMA_InitStructure.DMA_PeripheralBaseAddr= (uint32_t)USART1_DR_ADDR;		//外设地址
    	DMA_InitStructure.DMA_DIR 				= DMA_DIR_PeripheralDST;		//传输方向
    	
    	DMA_InitStructure.DMA_BufferSize 		= SEND_SIZE ;					//传输数量
    	DMA_InitStructure.DMA_PeripheralInc 	= DMA_PeripheralInc_Disable;	//外设地址不变
    	DMA_InitStructure.DMA_MemoryInc 		= DMA_MemoryInc_Enable;			//存储器地址递增
    	DMA_InitStructure.DMA_PeripheralDataSize= DMA_PeripheralDataSize_Byte;	//外设数据宽度 1字节 
    	DMA_InitStructure.DMA_MemoryDataSize 	=	DMA_MemoryDataSize_Byte;	//存储器数据宽度 1字节
    	
    	DMA_InitStructure.DMA_Mode 				= DMA_Mode_Normal;				//正常模式,发送一次
    	DMA_InitStructure.DMA_Priority 			= DMA_Priority_Low;				//优先级 低
    	DMA_InitStructure.DMA_M2M 				= DMA_M2M_Disable;				//失能MTM
    	
    	DMA_Init(DMA1_Channel4, &DMA_InitStructure);	//结构体初始化
    	DMA_ClearFlag(DMA1_FLAG_TC4);	 				//清除对应标志位
    	DMA_Cmd(DMA1_Channel4, ENABLE);					//使能DMA1 通道4
    	
    }
    
  • main.c 使能串口DMA数据传输
    USART1_DMATx_Init();					//串口DMA发送初始化
    USART_DMACmd(USART1, USART_DMAReq_Tx , ENABLE );	//使能串口DMA发送请求
    

实验三:外设到内存(DMA串口接收)

DMA接收Cubemx工程配置

  • 时钟及串口配置(同上)
  • 串口DMA配置
    STM32单片机——DMA数据传输_第11张图片
  • 开启串口中断
    STM32单片机——DMA数据传输_第12张图片

串口DMA接收相关函数解析

  • 1使能IDLE空闲中断

    #define __HAL_UART_ENABLE_IT(__HANDLE__, __INTERRUPT__)   ............
    

    参数解析

    参数 解析
    __HANDLE__ 串口句柄
    __INTERRUPT__ 需要使能的中断
    返回值
  • 串口DMA接收函数

    HAL_StatusTypeDef HAL_UART_Receive_DMA(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size)
    

    参数解析

    参数 解析
    返回值 HAL_StatusTypeDef,HAL状态(OK,busy,ERROR,TIMEOUT
    UART_HandleTypeDef *huart 串口句柄
    uint8_t *pData 接收缓存首地址
    uint16_t Size 接收缓存长度
  • 获取串口 Flag的值

    #define __HAL_UART_GET_FLAG(__HANDLE__, __FLAG__) (((__HANDLE__)->Instance->SR & (__FLAG__)) == (__FLAG__))
    

    参数解析

    参数 解析
    返回值 FLAG的值(SET/RESET
    __HANDLE__ 串口句柄
    __FLAG__ 需要查看的FLAG
  • 清除串口IDLE标识

    #define __HAL_UART_CLEAR_IDLEFLAG(__HANDLE__) __HAL_UART_CLEAR_PEFLAG(__HANDLE__)
    
    参数 解析
    __HANDLE__ 串口句柄
    返回值
  • 关闭串口DMA

    HAL_StatusTypeDef HAL_UART_DMAStop(UART_HandleTypeDef *huart)
    
    参数 解析
    UART_HandleTypeDef *huart 串口句柄
    返回值 HAL_StatusTypeDef,HAL状态(OK,busy,ERROR,TIMEOUT
  • 获取未传输数据大小

    #define __HAL_DMA_GET_COUNTER(__HANDLE__) ((__HANDLE__)->Instance->CNDTR)
    
    参数 解析
    __HANDLE__ 串口句柄
    返回值 未传输数据大小

DMA串口接收Hal库代码实现

如何判断串口接收是否完成?如何知道串口收到数据的长度?
使用串口空闲中断(IDLE)!

  • 串口空闲时,触发空闲中断;
  • 空闲中断标志位由硬件置1,软件清零

利用串口空闲中断,可以用如下流程实现DMA控制的任意长数据接收:

  1. 使能IDLE空闲中断;
  2. 使能DMA接收中断;
  3. 收到串口接收中断,DMA不断传输数据到缓冲区;
  4. 一帧数据接收完毕,串口暂时空闲,触发串口空闲中断;
  5. 在中断服务函数中,清除中断标志位,关闭DMA传输(防止干扰);
  6. 计算刚才收到了多少个字节的数据。
  7. 处理缓冲区数据,开启DMA传输,开始下一帧接收
#define BUF_SIZE 128

//DMA接收缓存变量定义
unsigned char rcvBuf[BUF_SIZE] = {0};
unsigned char rcvLen = 0;		//接收字节数

main.c初始化
__HAL_UART_ENABLE_IT(&huart1, UART_IT_IDLE);			//使能IDLE空闲中断
HAL_UART_Receive_DMA(&huart1, rcvBuf, BUF_SIZE);		//使能DMA接收中断	

//中断服务函数中,清除中断标志位,关闭DMA传输,计算数据接收长度并操作数据
void USART1_IRQHandler(void)
{
  /* USER CODE BEGIN USART1_IRQn 0 */

  /* USER CODE END USART1_IRQn 0 */
  HAL_UART_IRQHandler(&huart1);
  /* USER CODE BEGIN USART1_IRQn 1 */
	if( __HAL_UART_GET_FLAG(&huart1, UART_FLAG_IDLE)  == SET )	// 判断IDLE标志位是否被置位)
  {
		__HAL_UART_CLEAR_IDLEFLAG(&huart1);			//清除中断标志位
		HAL_UART_DMAStop(&huart1);					//关闭DMA传输
		rcvLen = BUF_SIZE - __HAL_DMA_GET_COUNTER(&hdma_usart1_rx);		//计算收到的字节数
		
		//处理缓冲区数据 若程序复杂可立flag 在 while(1) 中处理
		HAL_UART_Transmit_DMA(&huart1,rcvBuf,rcvLen);	
		if(strstr((const char *)rcvBuf,"open") != NULL)
			HAL_GPIO_WritePin(LED1_GPIO_Port,LED1_Pin,GPIO_PIN_RESET);
		if(strstr((const char *)rcvBuf,"close") != NULL)
			HAL_GPIO_WritePin(LED1_GPIO_Port,LED1_Pin,GPIO_PIN_SET);
		
		HAL_UART_Receive_DMA(&huart1, rcvBuf, BUF_SIZE);	//开启DMA传输
	}	
  /* USER CODE END USART1_IRQn 1 */
}

DMA串口接收固件库程序设计

  • 参考博文1:STM32_串口的DMA接收
  • 参考博文2:STM32实现DMA接收串口数据
  • dma结构体配置
    //USART_DMA_R 接收缓存
    uint8_t receive_buf[16] ={'\0'};
    
    //USART1_DMA_Receive
    void USART1_DMARx_Init(void)
    {
    	DMA_InitTypeDef DMA_InitStructure;
    	
    	RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);	//使能DMA时钟
    	
    	DMA_InitStructure.DMA_MemoryBaseAddr 	 = (uint32_t)receive_buf;	//内存地址
    	DMA_InitStructure.DMA_PeripheralBaseAddr =(uint32_t)USART1_DR_ADDR;	//外设地址
    	DMA_InitStructure.DMA_DIR 				 = DMA_DIR_PeripheralSRC;	//传输方向
    	
    	DMA_InitStructure.DMA_BufferSize 		 = 16;						   //传输数量
    	DMA_InitStructure.DMA_PeripheralInc 	 = DMA_PeripheralInc_Disable;  //外设地址不变
    	DMA_InitStructure.DMA_MemoryInc 		 = DMA_MemoryInc_Enable;	   //存储器地址递增
    	DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;//外设数据宽度 1字节 
    	DMA_InitStructure.DMA_MemoryDataSize 	 = DMA_MemoryDataSize_Byte;	  //存储器数据宽度 1字节
    	
    	DMA_InitStructure.DMA_Mode 			= DMA_Mode_Normal;		//循环接收
    	DMA_InitStructure.DMA_Priority 		= DMA_Priority_High;	//优先级 高
    	DMA_InitStructure.DMA_M2M 			= DMA_M2M_Disable;		//失能MTM
    	
    	DMA_Init(DMA1_Channel5, &DMA_InitStructure);	//结构体初始化
    	DMA_ClearFlag(DMA1_FLAG_TC5);	 				//清除对应标志位
    	DMA_Cmd(DMA1_Channel5, ENABLE);					//使能DMA1 通道5
    }
    
  • 主函数程序设计
    uint8_t DMARx_flag = 0;					//中断标志
    extern uint8_t receive_buf[128];		//外部USART_DMA 接收缓存
    int main()
    {
    	Usart1_Init(115200);				//串口初始化 115200
    	//USRAT_DMA
    	USART1_DMARx_Init();				//初始化DMA接收
    	USART_DMACmd(USART1, USART_DMAReq_Rx , 	ENABLE);//使能串口DMA接收请求
    	USART_ITConfig(USART1, USART_IT_IDLE,  ENABLE);	//使能空闲中断
    	while(1)
    	{
    		
    	}
    }
    
  • 串口中断程序设计
    void USART1_IRQHandler ( void ) 
    {
        u16 Usart1_Rec_Cnt = 0;
    
        if ( USART_GetITStatus ( USART1, USART_IT_IDLE ) != RESET )	//检查是否发生空闲中断
    	{
            USART1->SR;
            USART1->DR;					// 清USART_IT_IDLE标志
    		USART_ClearITPendingBit ( USART1, USART_IT_IDLE ); 	// 清除中断标志
            DMA_Cmd ( DMA1_Channel5, DISABLE );		//关闭DMA传输,进行数据处理,数据处理完再开启
     
            // 计算接收到的数据帧长度
            Usart1_Rec_Cnt = 16 - DMA_GetCurrDataCounter ( DMA1_Channel5);
            printf ( "The length is %d\r\n", Usart1_Rec_Cnt );
            printf ( "The data is %s", receive_buf );
    		Usart1_Rec_Cnt = 0;
    		memset(receive_buf,'\0',16);
    			
            DMA_SetCurrDataCounter( DMA1_Channel5, 16 );	//清除counter计数器
            DMA_Cmd ( DMA1_Channel5, ENABLE );
    			
    		//		DMARx_flag = 1;							//中断标志位
        }
    }
    

写在最后

  • 以上就是关于STM32单片机——DMA数据传输的相关理论及例程验证,欢迎评论区友好交流批评指正!!!
    STM32单片机——DMA数据传输_第13张图片

你可能感兴趣的:(stm32单片机,单片机,stm32,嵌入式硬件)