串口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传输

开发环境:Keil 、 CubeMX
Vesion : 5.28 、 5.4.0
开发语言:C

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通道?如何知道接收到数据的长度?答案便是使用串口空闲中断。

  1. 串口空闲中断,对应事件标志为IDLE。
  2. 检测到串口空闲线路时,该位由硬件置 1。如果USART_CR1寄存器中 IDLEIE=1,则会生成中断。
  3. 该位由软件序列清零(读入 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)实现将受到的不定长数据发送出去,测试结果如下:

在这里插入图片描述

五.示例源码

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

六.同系列博客

  1. GPIO相关函数解析(HAL库)
  2. Stm32延时与计时方法(HAL库)
  3. 串口通讯知识梳理及在Stm32上的应用(HAL库)
  4. 串口DMA知识梳理以及在Stm32的应用(HAL库)
  5. CAN通信知识梳理及在Stm32上的应用(HAL库)
### STM32 HAL DMA 串口接收 示例代码及教程 #### 使用CubeMX配置相关设置 为了简化开发过程并确保硬件初始化正确无误,推荐使用STM32CubeMX工具完成初步配置。对于DMA与UART接口的连接,在图形界面中指定相应的参数即可自动生成基础框架文件。 - **系统与时钟配置** - 启用SWD调试模式以便于程序烧录和在线调试。 - 如果项目需求允许的话,可以启用外部高速晶振(HSE)作为系统的主时钟源以获得更稳定的性能表现[^1]。 - **USART/DMA模块使能** - 打开所需使用的USART端口(如USART1, USART2等), 设置合适的波特率和其他通信属性。 - 对应地激活关联的DMA控制器及其对应的传输通道用于支持异步数据交换操作[^4]。 #### BSP层驱动设计 针对具体应用场景编写BSP(Board Support Package)级别的API函数能够提高软件可移植性和维护效率。下面是一个简单的`bsp_usart_dma.c`实现片段: ```c #include "bsp_usart_dma.h" // 初始化USART以及其DMA资源 void USART_DMA_Init(void){ /* Initialize the UART peripheral */ MX_USARTx_UART_Init(); /* Configure and enable the reception completion callback function */ huart->RxState = HAL_UART_STATE_READY; __HAL_LINKDMA(huart, hdmarx, hdma_rx); } // 发送字符串到指定的串行端口 uint8_t usart_send_str(const char* str){ uint16_t len = strlen(str); if (HAL_OK != HAL_UART_Transmit(&huart, (uint8_t*)str, len, 0xFFFF)){ return ERROR; } return SUCCESS; } ``` 上述代码展示了如何创建一个基于HAL的通用发送功能,并且设置了回调机制准备处理接收到的数据流事件。 #### 接收逻辑处理 当利用DMA方式进行连续读取时,通常会结合IDLE线状态变化触发中断通知CPU当前帧结束从而启动后续流程。这里给出一段典型的ISR内部调用链路示意: ```c /* Called when RXNE flag is set or IDLE event occurs during Reception phase*/ void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart){ static uint8_t buffer[BUFFER_SIZE]; size_t received_length; // 获取实际接收到的有效字节数量 received_length = BUFFER_SIZE - __HAL_DMA_GET_COUNTER(hdma_rx); // 将新到达的信息复制出来供应用程序进一步解析 memcpy(received_data_buffer, buffer, received_length); // 准备下一轮循环接收任务 HAL_UART_Receive_DMA(huart, buffer, sizeof(buffer)); } ``` 这段伪码解释了每当一次完整的消息被捕捉后应当采取的动作序列——更新全局变量保存最新内容的同时重新安排新一轮监听等待新的输入到来[^2]。
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

冬瓜~

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值