串口DMA知识梳理以及在Stm32的应用(HAL库)

一.关于DMA

1.什么是DMA?
答:DMA(Direct Memory Access,直接存储器访问) 是所有现代电脑的重要特色,它允许不同速度的硬件装置来沟通,而不需要依赖于CPU的大量中断负载。否则,CPU需要从来源把每一片段的资料复制到暂存器,然后把它们再次写回到新的地方。在这个时间中,CPU对于其他的工作来说就无法使用。

2.DMA的意义是什么?
答:简单的来说,能控制主存内部读写,这样有利于减轻CPU负担,加快读取速度。

3.串口使用DMA与不使用DMA有什么区别?
答:区别可大了。通俗的讲:在没有DMA之前,串口每次发送数据时都要由CPU将源地址上的数据拷贝到串口发送的相关寄存器上;串口每次接收数据时都要由CPU将发送来的数据拷贝到主存上。而加了DMA后,只需要告诉DMA源地址和目标地址,DMA通道就能够自动进行数据的转移,即CPU只需要告诉DMA:串口需要发送的数据在哪里,串口接收到的数据应该存在哪里,运输的工作则交由DMA去做,运输期间CPU就可以去处理别的事情,这就大大提高了CPU的运行效率。

二.DMA的应用场景

DMA用在只需要传输数据,不需要处理数据的地方,有三种传输方式:

  • 外设→存储器 (例如:将串口RDR寄存器写入某数据buf)
  • 存储器→外设 (例如:将某数据buf写入串口TDR寄存器)
  • 存储器→存储器(例如:复制某特别大的数据buf)

三.DMA控制器结构

Stm32F4最多有:2个DMA控制器,各8个数据流,每个数据流有8个通道(或请求),每个通道有一个仲裁器,用于处理请求的优先级。

四.Stm32实现串口DMA传输

开发环境:CubeMX   Vesion 5.4.0
     Keil      Vesion 5.28

1.CubeMX配置串口DMA

在这里插入图片描述
打开串口一,同时打开串口接收中断、DMA发送、DMA接收。

2.DMA串口数据发送

 /* @brief  DMA串口发送函数(非阻塞)
  * @param  huart       串口句柄
  * @param  pData       发送的数据指针
  * @param  Size        数据量(数据的字节数)
  * @retval HAL status  HAL_OK、HAL_ERROR、HAL_BUSY、HAL_TIMEOUT
  */
HAL_StatusTypeDef HAL_UART_Transmit_DMA(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size)

在需要的地方调用HAL_UART_Transmit_DMA(······)即可完成数据发送,例如:

uint8_t data_16[4]={0x11,0x22,0x33,0x44};
uint8_t data_character[]="hello! I am 马云";
HAL_UART_Transmit_DMA(&huart1,data_16,4);
HAL_Delay(1);//等待上一次发送完毕后再开启下一次发送
HAL_UART_Transmit_DMA(&huart1,data_character, sizeof(data_character));

测试结果:
在这里插入图片描述

3.DMA串口数据接收

 /* @brief  串口DMA接收函数
  * @param  huart  串口句柄
  * @param  pData  数据指针
  * @param  Size   数据量(数据字节数)
  * @retval HAL status   HAL_OK、HAL_ERROR、HAL_BUSY、HAL_TIMEOUT
  */
HAL_StatusTypeDef HAL_UART_Receive_DMA(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size)

4.串口空闲中断(IDLE)

当DMA串口接收开始后,DMA通道会不断的将发送来的数据转移到主存,那么问题来了,该如何判断串口接收是否完成从而及时关闭DMA通道?如何知道接收到数据的长度?答案便是使用串口空闲中断。

  • 串口空闲中断,对应事件标志为IDLE
  • 检测到串口空闲线路时,该位由硬件置 1。如果 USART_CR1寄存器中 IDLEIE=1,则会生成中断。
  • 该位由软件序列清零(读入 USART_SR 寄存器,然后读入 USART_DR 寄存器)

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

  1.开启串口DMA接收

  2.串口收到数据,DMA不断传输数据到存储buf

  3.一帧数据发送完毕,串口暂时空闲,触发串口空闲中断

  4.在中断服务函数中,可以计算刚才收到了多少个字节的数据

  5.解码存储buf,清除标志位,开始下一帧接收

举个例子,如果要实现串口DMA不定长接收:

/*1.首先定义3个全局变量*/
uint8_t rx_buffer[100];//接收数组
volatile uint8_t rx_len = 0; //接收到的数据长度
volatile uint8_t recv_end_flag = 0; //接收结束标志位
/*2.在main中开启IDLE中断以及串口DMA接收*/
__HAL_UART_ENABLE_IT(&huart1, UART_IT_IDLE);    
HAL_UART_Receive_DMA(&huart1,rx_buffer,100);	
/*在CubeMX生成的 UART1中断服务函数中判断接收是否结束,如果结束就计算出接收到的数据长度*/
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 */
 uint8_t tmp_flag =__HAL_UART_GET_FLAG(&huart1,UART_FLAG_IDLE); //获取IDLE标志位
 if((tmp_flag != RESET))//通过标志位判断接收是否结束
   { 
      recv_end_flag = 1; //置1表明接收结束
      __HAL_UART_CLEAR_IDLEFLAG(&huart1);//清除标志位
      HAL_UART_DMAStop(&huart1); 
      uint8_t temp=__HAL_DMA_GET_COUNTER(&hdma_usart1_rx);                 
      rx_len =100-temp; //计算出数据长度
	  HAL_UART_Transmit_DMA(&huart1, rx_buffer,rx_len);//将受到的数据发送出去
      HAL_UART_Receive_DMA(&huart1,rx_buffer,100);//开启DMA接收,方便下一次接收数据
   }
  /* USER CODE END USART1_IRQn 1 */
}

在中断中加上HAL_UART_Transmit_DMA(&huart1, rx_buffer,rx_len)实现将受到的不定长数据发送出去,测试结果如下:

在这里插入图片描述

5.示例源码

1. HAL库+CubeMX+Stm32F405实现串口DMA不定长收发

猜你喜欢

转载自blog.csdn.net/weixin_44793491/article/details/107564370