Bootstrap

串口DMA接收不定长数据

STM32F767—>串口通信接收不定长数据的处理方法_stm32串口超时中断-CSDN博客

STM32-HAL库串口DMA空闲中断的正确使用方式+解析SBUS信号_stm32 hal usart2 dma-CSDN博客

#define USART1_RxBuffSize 100
extern DMA_HandleTypeDef hdma_usart1_rx;	//此处声明的变量在其他地方定义
uint8_t USART1_RxBuffer[USART1_RxBuffSize];		//串口接收缓冲区
uint8_t USART1_RxLen = 0;	//接收到的数据长度
uint8_t data[USART1_RxBuffSize];
volatile uint8_t rxComplete = 0;  // 接收完成标志

// 重定向printf start
//_write函数在syscalls.c中, 使用__weak定义以可以直接在其他文件中定义_write函数
int _write(int file, char *ptr, int len)
{
	 if(HAL_UART_Transmit(&huart1,(uint8_t *)ptr,len,0xffff) != HAL_OK)
	 {
		 Error_Handler();
		 return -1;
	 }
	 return len;
}
// 重定向printf end
  • 串口控制数据收发!DMA仅仅用于传输数据,减轻CPU负担。

    __HAL_LINKDMA(uartHandle,hdmarx,hdma_usart1_rx);
    
    • 将 DMA(Direct Memory Access,直接内存访问)与 UART(通用异步收发传输器)进行绑定的,在usart.cHAL_UART_MspInit硬件初始化中。
    • USART1 的接收操作能够通过 DMA 直接将数据传输到内存中,减少了 CPU 的参与,提高了数据处理的效率。

方法一:串口空闲中断+DMA

/* 开启串口DMA空闲中断接收,内部会使能串口空闲中断,并设置串口接收类型为空闲中断
   * 空闲的定义是总线上在一个字节的时间内没有再接收到数据,即空闲帧 */
  HAL_UARTEx_ReceiveToIdle_DMA(&huart1, USART1_RxBuffer, USART1_RxBuffSize);
/* 串口空闲中断回调函数 */
void HAL_UARTEx_RxEventCallback(UART_HandleTypeDef *huart, uint16_t Size)
{
	if(huart->Instance == USART1)
	{
		/* 获取DMA中已经传输的数据个数
		 * __HAL_DMA_GET_COUNTER访问DMA的NDTR寄存器(只读,用于指示要传输的剩余数据项数,每次DMA传输后,此寄存器将递减) */
		USART1_RxLen = USART1_RxBuffSize - __HAL_DMA_GET_COUNTER(&hdma_usart1_rx);

		/* 内存复制
		 * 数据帧:帧头:0x65(A),帧长:4个字节 */
		if (USART1_RxBuffer[0] == 0x41 && USART1_RxLen == 4)	//接受完一帧数据
		{
			memcpy(data, USART1_RxBuffer, USART1_RxLen);
		}

		HAL_UART_Transmit_DMA(huart, USART1_RxBuffer, USART1_RxLen);
		// 再次开启串口DMA空闲中断,HAL_UARTEx_ReceiveToIdle_DMA → UART_Start_Receive_DMA
		HAL_UARTEx_ReceiveToIdle_DMA(huart, USART1_RxBuffer, USART1_RxBuffSize);
	}
}

方法二:串口接收超时中断+DMA

/* 接收超时中断
   * 波特率:串口通信的速率,即串口通信时每秒钟可以传输多少个二进制位
   * 时钟分频后,传输1bit所需的时钟周期数为1个时钟周期
   * 波特率 = 系统时钟频率 / (过采样倍数(8或16) * 时钟分频值)
   * 数据帧的格式:起始位 + 数据位 + 停止位
   * USART_RTOR的RTO[23:0]:此位域用于提供接收器的超时值(以波特时钟数为单位)
   * 	在标准模式下,如果在接收到最后一个字符后,在RTO值对应的时间内未检测到新的起始位,则RTOF标志置1
   * 	in terms of number of bits
   * 	写入超时多少个位数 */
  __HAL_UART_ENABLE_IT(&huart1, UART_IT_RTO);	//使能接收超时中断
  HAL_UART_ReceiverTimeout_Config(&huart1, 28800);	//设置接收超时时间,比如3*9600=28800,超时3s(波特率9600bit/s)
  HAL_UART_EnableReceiverTimeout(&huart1);	//使能接收超时功能
  HAL_UART_Receive_DMA(&huart1, USART1_RxBuffer, USART1_RxBuffSize);	//此函数检查功能CR2_RTOEN从而开启中断CR1_RTOIE

while (1)
  {
	  ProcessReceivedData();
  }
/* 串口接收超时中断回调函数 */
void HAL_UART_ErrorCallback(UART_HandleTypeDef *huart)
{
	if(huart->Instance == USART1)
	{
		USART1_RxLen = USART1_RxBuffSize - __HAL_DMA_GET_COUNTER(&hdma_usart1_rx); // 计算接收的数据长度
		rxComplete = 1; //设置接收完成标志
		memcpy(data, USART1_RxBuffer, USART1_RxLen); //复制接收缓冲区到数据
		memset(USART1_RxBuffer, 0, USART1_RxBuffSize); //清空接收缓冲区,避免残留数据干扰下一次接收
		HAL_UART_Receive_DMA(huart, USART1_RxBuffer, USART1_RxBuffSize); //重新开启DMA+串口接收
	}
}
// 在主循环或独立任务中进行协议处理
// 在中断处理程序中,应该尽量减少复杂的处理,避免阻塞系统
// 一般来说,回调函数只用于标记接收数据的完成状态,而实际的数据解析和协议处理可以放在主循环或独立的任务中来执行
//(回调函数里不要调用print打印信息!)
void ProcessReceivedData(void)
{
    if (rxComplete)
    {
        rxComplete = 0; // 清除接收完成标志

        // 协议解析

        printf("ReceivedValidData: \r\n"); //printf必须带\r\n,否则不显示
        HAL_UART_Transmit_DMA(&huart1, data, USART1_RxLen); // 发送响应数据
    }
}
  • USART1_IRQHandlerHAL_UART_IRQHandler

    在代码中,UART 的接收超时(RTO)中断处理流程如下:

    1. 检测 UART 超时中断标志

      if (((isrflags & USART_ISR_RTOF) != 0U) && ((cr1its & USART_CR1_RTOIE) != 0U))
      

      这里 USART_ISR_RTOF 表示接收超时标志位,USART_CR1_RTOIE 表示接收超时中断使能位。如果 USART_ISR_RTOF 被置位且 USART_CR1_RTOIE 已启用,则会进入接收超时中断的处理流程。

    2. 清除 UART 超时标志

      __HAL_UART_CLEAR_FLAG(huart, UART_CLEAR_RTOF);
      

      该行代码清除 UART 接收超时标志,以防止重复触发中断。

    3. 设置错误代码

      huart->ErrorCode |= HAL_UART_ERROR_RTO;
      

      HAL_UART_ERROR_RTO 添加到 huart->ErrorCode 中,记录接收超时错误。

    4. 进入错误处理
      代码随后检查 huart->ErrorCode 是否包含任何错误。如果 ErrorCode 不为 HAL_UART_ERROR_NONE,代码会根据错误类型采取相应的处理措施:

      • 如果错误被视为“阻塞性错误”,例如接收超时(RTO)、溢出错误(ORE)或 DMA 模式下的错误,则会中止接收传输,调用 UART_EndRxTransferHAL_DMA_Abort_IT 函数中止 DMA。
      • 如果错误是非阻塞性错误,会直接调用用户定义的 ErrorCallback
    5. 进入用户回调函数
      当代码检测到错误并将错误处理完成后,进入错误回调:

      #if (USE_HAL_UART_REGISTER_CALLBACKS == 1)
          huart->ErrorCallback(huart);
      #else
          HAL_UART_ErrorCallback(huart);
      #endif
      

      这里调用了 HAL_UART_ErrorCallback 或用户注册的 ErrorCallback

  • HAL_UART_ErrorCallback

    __weak void HAL_UART_ErrorCallback(UART_HandleTypeDef *huart)
    

    重写错误回调(当前为接收超时RTO)函数。

调试debug

  • 串口助手+STM32CubeIDE
  1. 点击调试debug,烧录程序并进入调试模式;

  2. 点击继续,运行程序;

  3. 串口助手选择串口,配置波特率等,以接收超时中断为例,发送字符AC,则大概3秒之后,串口助手窗口会返回AC;

  4. 若要实时查看变量的值,则在现场表达式进行调试;

  5. 若用表达式查看变量的值,需要点击暂挂,暂停程序运行才能查看:

    请添加图片描述

;