【STM32学习笔记】串口技术详解:轮询模式、中断模式、DMA模式及不定长数据接收实践

【STM32学习笔记】串口总结

文章目录

  • 【STM32学习笔记】串口总结
  • 一、串口CubeMX配置
  • 1、CubeMX配置
  • 二、串口轮询模式
  • 1、简介
  • 2、串口发送、接收函数
  • 3、实验一:轮询模式发送,接收数据实验
  • 3.1简单发送
  • 3.2简单接收
  • 二、串口中断模式
  • 1、串口中断简介
  • 2、串口配置步骤
  • 2.1串口时钟使能和GPIO时钟使能
  • 2.2GPIO 初始化设置:要设置模式为复用功能
  • 2.3开启中断
  • 2.4编写中断服务函数
  • 3、实验二:串口中断实验
  • 三、串口DMA模式
  • 1、串口DMA简介
  • 2、实验三:串口DMA实验
  • 四、接收不定长数据
  • 1、空闲中断简介
  • 2、实验四:串口接收不定长数据
  • 一、串口CubeMX配置

    1、CubeMX配置

    image-20250525225304234

    image-20250525225500760

    image-20250525225922718

    image-20250525230225189

    image-20250525230657224

    选LED0,设置为输出模式

    image-20250525231009820

    image-20250525231103401

    image-20250525231433061

    image-20250525231548170

    二、串口轮询模式

    1、简介

    轮询模式:

    STM32每个串口发送都有两个寄存器,发送数据寄存器(TDR)发送移位寄存器。当调用HAL_UART_Transmit发送一段数据时,STM32的CPU会依次将数据移到寄存器中,发送移位寄存器中的数据会按照我们设定的比特率转化成高低电平到输出引脚输出。发送数据寄存器上的数据会在发送移位寄存器发送完成后被移到发送移位寄存器进行下次发送。而在此过程中,CPU需要不断的去查询发送数据寄存器的数据是否已经移送到了发送移位寄存器,移了的话,就赶紧把下个数据放入发送数据寄存器,如果还没有移,那就再接着不停的查询,直到把本次要发送的数据全部发完,或者用时超过我们设定的超时时间。

    轮询模式下的串口接收也是类似,接收数据寄存器(RDR)接收移位寄存器。要用HAL_UART_Receive函数将接收端口接收的高低电平信号依次转换后存入接收移位寄存器,接收移位寄存器每接收完一帧,就将这帧数据移到接收数据寄存器。CPU会一直查询接收数据寄存器中是否有新数据可以读,一旦检测到,就马上把数据从接收数据寄存器移到我们用来接收数据的变量中,直到接收完我们希望接收到的字节数或者时间超时。

    很明显,在轮询模式下。不管是发送还是接收CPU一直处于忙碌状态。一轮一轮地去查询寄存器是否可用,无暇顾及其他的任务。我们一般称这种一直等待使程序暂时无法向下执行的状态为堵塞。

    2、串口发送、接收函数

    HAL_StatusTypeDef HAL_UART_Receive(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size, uint32_t Timeout)
        
        /*
        1、参数huart:串口句柄的指针
        2、参数pData:指向接收缓冲区的指针
        3、参数Size:要接收数据的数量,以字节为单位
        4、参数Timeout:超时时间,以ms为单位
        5、返回值:返回数据传输的结果(成功还是失败)
                typedef enum 
                {
                  HAL_OK       = 0x00U,  	//成功
                  HAL_ERROR    = 0x01U,		//接收出错
                  HAL_BUSY     = 0x02U,		//串口忙
                  HAL_TIMEOUT  = 0x03U		//接收超时
                } HAL_StatusTypeDef;
        
        */
    
    HAL_StatusTypeDef HAL_UART_Transmit(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size, uint32_t Timeout)
    
    	/*
        1、参数huart:串口句柄的指针
        2、参数pData:指向发送数据的指针
        3、参数Size:要发送数据的数量,以字节为单位
        4、参数Timeout:超时时间,以ms为单位
        5、返回值:返回数据传输的结果(成功还是失败)
                typedef enum 
                {
                  HAL_OK       = 0x00U,  	//成功
                  HAL_ERROR    = 0x01U,		//接收出错
                  HAL_BUSY     = 0x02U,		//串口忙
                  HAL_TIMEOUT  = 0x03U		//接收超时
                } HAL_StatusTypeDef;
        
        */
    

    3、实验一:轮询模式发送,接收数据实验

    编写cubemx生成的代码:

    自动生成的main函数:

    /* USER CODE BEGIN Header */
    /**
      ******************************************************************************
      * @file           : main.c
      * @brief          : Main program body
      ******************************************************************************
      * @attention
      *
      * <h2><center>&copy; Copyright (c) 2025 STMicroelectronics.
      * All rights reserved.</center></h2>
      *
      * This software component is licensed by ST under BSD 3-Clause license,
      * the "License"; You may not use this file except in compliance with the
      * License. You may obtain a copy of the License at:
      *                        opensource.org/licenses/BSD-3-Clause
      *
      ******************************************************************************
      */
    /* USER CODE END Header */
    /* Includes ------------------------------------------------------------------*/
    #include "main.h"
    #include "usart.h"
    #include "gpio.h"
    
    /* Private includes ----------------------------------------------------------*/
    /* USER CODE BEGIN Includes */
    
    /* USER CODE END Includes */
    
    /* Private typedef -----------------------------------------------------------*/
    /* USER CODE BEGIN PTD */
    
    /* USER CODE END PTD */
    
    /* Private define ------------------------------------------------------------*/
    /* USER CODE BEGIN PD */
    /* USER CODE END PD */
    
    /* Private macro -------------------------------------------------------------*/
    /* USER CODE BEGIN PM */
    
    /* USER CODE END PM */
    
    /* Private variables ---------------------------------------------------------*/
    
    /* USER CODE BEGIN PV */
    
    /* USER CODE END PV */
    
    /* Private function prototypes -----------------------------------------------*/
    void SystemClock_Config(void);
    /* USER CODE BEGIN PFP */
    
    /* USER CODE END PFP */
    
    /* Private user code ---------------------------------------------------------*/
    /* USER CODE BEGIN 0 */
    
    /* USER CODE END 0 */
    
    /**
      * @brief  The application entry point.
      * @retval int
      */
    int main(void)
    {
      /* USER CODE BEGIN 1 */
    
      /* USER CODE END 1 */
    
      /* MCU Configuration--------------------------------------------------------*/
    
      /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
      HAL_Init();
    
      /* USER CODE BEGIN Init */
    
      /* USER CODE END Init */
    
      /* Configure the system clock */
      SystemClock_Config();
    
      /* USER CODE BEGIN SysInit */
    
      /* USER CODE END SysInit */
    
      /* Initialize all configured peripherals */
      MX_GPIO_Init();
      MX_USART1_UART_Init();
      /* USER CODE BEGIN 2 */
    
      /* USER CODE END 2 */
    
      /* Infinite loop */
      /* USER CODE BEGIN WHILE */
      while (1)
      {
        /* USER CODE END WHILE */
    
        /* USER CODE BEGIN 3 */
      }
      /* USER CODE END 3 */
    }
    
    /**
      * @brief System Clock Configuration
      * @retval None
      */
    void SystemClock_Config(void)
    {
      RCC_OscInitTypeDef RCC_OscInitStruct = {0};
      RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
    
      /** Configure the main internal regulator output voltage
      */
      __HAL_RCC_PWR_CLK_ENABLE();
      __HAL_PWR_VOLTAGESCALING_CONFIG(PWR_REGULATOR_VOLTAGE_SCALE1);
      /** Initializes the RCC Oscillators according to the specified parameters
      * in the RCC_OscInitTypeDef structure.
      */
      RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
      RCC_OscInitStruct.HSEState = RCC_HSE_ON;
      RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
      RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
      RCC_OscInitStruct.PLL.PLLM = 4;
      RCC_OscInitStruct.PLL.PLLN = 168;
      RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV2;
      RCC_OscInitStruct.PLL.PLLQ = 4;
      if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
      {
        Error_Handler();
      }
      /** Initializes the CPU, AHB and APB buses clocks
      */
      RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
                                  |RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
      RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
      RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
      RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV4;
      RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV2;
    
      if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_5) != HAL_OK)
      {
        Error_Handler();
      }
    }
    
    /* USER CODE BEGIN 4 */
    
    /* USER CODE END 4 */
    
    /**
      * @brief  This function is executed in case of error occurrence.
      * @retval None
      */
    void Error_Handler(void)
    {
      /* USER CODE BEGIN Error_Handler_Debug */
      /* User can add his own implementation to report the HAL error return state */
      __disable_irq();
      while (1)
      {
      }
      /* USER CODE END Error_Handler_Debug */
    }
    
    #ifdef  USE_FULL_ASSERT
    /**
      * @brief  Reports the name of the source file and the source line number
      *         where the assert_param error has occurred.
      * @param  file: pointer to the source file name
      * @param  line: assert_param error line source number
      * @retval None
      */
    void assert_failed(uint8_t *file, uint32_t line)
    {
      /* USER CODE BEGIN 6 */
      /* User can add his own implementation to report the file name and line number,
         ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */
      /* USER CODE END 6 */
    }
    #endif /* USE_FULL_ASSERT */
    
    /************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE****/
    
    
    3.1简单发送

    image-20250526215917548

    image-20250526221415262

    	uint8_t byte_number = 0x82;
    	uint8_t byte_Array[] = {1,2,3,5,9};
    	char ch = 'k';
    	char *str = "hello world";
    	
    	//·¢ËÍÒ»¸ö×Ö½Ú
    	HAL_UART_Transmit(&huart1, &byte_number, 1, HAL_MAX_DELAY);
    	//·¢ËÍÒ»¸öÊý×é
    	HAL_UART_Transmit(&huart1, byte_Array, 5, HAL_MAX_DELAY);
    	//·¢ËÍÒ»¸ö×Ö·û
    	HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1, HAL_MAX_DELAY);
    	//·¢ËÍÒ»¸ö×Ö·û´®
    	HAL_UART_Transmit(&huart1, (uint8_t *)str, strlen(str), HAL_MAX_DELAY);
    

    串口16进制接收

    image-20250526221940129

    将发送放到while循环里,将一直向串口助手发数:

    image-20250526221623355

    image-20250526222000840

    3.2简单接收

    效果:接收到1,灯LED0亮;接收到0,灯LED0灭

    image-20250526222848639

    		uint8_t rec;
    		HAL_UART_Receive(&huart1, &rec, 1, HAL_MAX_DELAY);
    		if(rec == 1){
    			HAL_GPIO_WritePin(GPIOF, GPIO_PIN_9, GPIO_PIN_RESET);
    		}
    		else if(rec == 0){
    			HAL_GPIO_WritePin(GPIOF, GPIO_PIN_9, GPIO_PIN_SET);
    		}
    

    二、串口中断模式

    1、串口中断简介

    如何解决轮询模式CPU的堵塞问题?STM32提供了串口的中断模式。

    使用中断模式的串口发送方式式,CPU将数据塞入发送数据寄存器后,就可以继续进行其他任务了,当发送移位寄存器的数据发送出去之后会触发**“发送数据寄存器空”**中断,把CPU叫回来,CPU在中断处理函数中将数据塞入发送数据寄存器后又可以去处理其他代码。如此反复,直到全部发送完成。

    使用中断接收后,每当接收移位寄存器将一帧数据移入接收数据寄存器,就会触发一次**“接收数据寄存器非空”**中断,把正在处理其他事情的CPU叫回来把数据读入变量中,然后CPU再去处理其他事情,直到接收到我们设定的数据长度,完成本次接收。

    具体来说,虽然接收移位寄存器每向接收数据寄存器转移一帧数据都会触发一次中断,但HAL库的中断处理流程为我们做了优化(具体讲解请看下面讲解),只有当接收到我们想要的字节数,也就是接收完成时,才会调用HAL_UART_RxCpltCallback回调函数,所以我们将代码写到HAL_UART_RxCpltCallback中,就能实现在串口接收完的第一时刻对收到的数据进行分析处理。

    2、串口配置步骤

    串口设置一般包括以下步骤:

    1. 串口时钟使能,GPIO 时钟使能。
    2. GPIO 初始化设置:要设置模式为复用功能。
    3. 串口参数初始化:设置波特率,字长,奇偶校验等参数。
    4. 开启中断并且初始化 NVIC,使能中断(如果需要开启中断才需要这个步骤)。
    5. 使能串口。
    6. 编写中断处理函数:函数名格式为 USARTxIRQHandler(x 对应串口号)。
    2.1串口时钟使能和GPIO时钟使能

    串口初始化函数:

    HAL_StatusTypeDef HAL_UART_Init(UART_HandleTypeDef *huart)
        
        /*入口参数huart:串口句柄,为HAL_StatusTypeDef结构体指针类型,如下:*/
    
    typedef struct __UART_HandleTypeDef
    {
      USART_TypeDef                 *Instance;        /*!< UART registers base address        */
    
      UART_InitTypeDef              Init;             /*!< UART communication parameters      */
    
      uint8_t                       *pTxBuffPtr;      /*!< Pointer to UART Tx transfer Buffer */
    
      uint16_t                      TxXferSize;       /*!< UART Tx Transfer size              */
    
      __IO uint16_t                 TxXferCount;      /*!< UART Tx Transfer Counter           */
    
      uint8_t                       *pRxBuffPtr;      /*!< Pointer to UART Rx transfer Buffer */
    
      uint16_t                      RxXferSize;       /*!< UART Rx Transfer size              */
    
      __IO uint16_t                 RxXferCount;      /*!< UART Rx Transfer Counter           */
    
      __IO HAL_UART_RxTypeTypeDef ReceptionType;      /*!< Type of ongoing reception          */
    
      DMA_HandleTypeDef             *hdmatx;          /*!< UART Tx DMA Handle parameters      */
    
      DMA_HandleTypeDef             *hdmarx;          /*!< UART Rx DMA Handle parameters      */
    
      HAL_LockTypeDef               Lock;             /*!< Locking object                     */
    
      __IO HAL_UART_StateTypeDef    gState;           /*!< UART state information related to global Handle management
                                                           and also related to Tx operations.
                                                           This parameter can be a value of @ref HAL_UART_StateTypeDef */
    
      __IO HAL_UART_StateTypeDef    RxState;          /*!< UART state information related to Rx operations.
                                                           This parameter can be a value of @ref HAL_UART_StateTypeDef */
    
      __IO uint32_t                 ErrorCode;        /*!< UART Error code                    */
    
    #if (USE_HAL_UART_REGISTER_CALLBACKS == 1)
      void (* TxHalfCpltCallback)(struct __UART_HandleTypeDef *huart);        /*!< UART Tx Half Complete Callback        */
      void (* TxCpltCallback)(struct __UART_HandleTypeDef *huart);            /*!< UART Tx Complete Callback             */
      void (* RxHalfCpltCallback)(struct __UART_HandleTypeDef *huart);        /*!< UART Rx Half Complete Callback        */
      void (* RxCpltCallback)(struct __UART_HandleTypeDef *huart);            /*!< UART Rx Complete Callback             */
      void (* ErrorCallback)(struct __UART_HandleTypeDef *huart);             /*!< UART Error Callback                   */
      void (* AbortCpltCallback)(struct __UART_HandleTypeDef *huart);         /*!< UART Abort Complete Callback          */
      void (* AbortTransmitCpltCallback)(struct __UART_HandleTypeDef *huart); /*!< UART Abort Transmit Complete Callback */
      void (* AbortReceiveCpltCallback)(struct __UART_HandleTypeDef *huart);  /*!< UART Abort Receive Complete Callback  */
      void (* WakeupCallback)(struct __UART_HandleTypeDef *huart);            /*!< UART Wakeup Callback                  */
      void (* RxEventCallback)(struct __UART_HandleTypeDef *huart, uint16_t Pos); /*!< UART Reception Event Callback     */
    
      void (* MspInitCallback)(struct __UART_HandleTypeDef *huart);           /*!< UART Msp Init callback                */
      void (* MspDeInitCallback)(struct __UART_HandleTypeDef *huart);         /*!< UART Msp DeInit callback              */
    #endif  /* USE_HAL_UART_REGISTER_CALLBACKS */
    
    } UART_HandleTypeDef;
    

    以上结构体成员变量非常多,一般在使用HAL_UART_Init对串口进行初始化时,只需先对Instance 和 Init 两个成员变量赋值:

    Instance 是 USART_TypeDef 结构体指针类型变量,它是执行寄存器基地址,实际上这个基地址 HAL 库已经定义好了,如果是串口 1,取值为 USART1 即可。 Init 是 UART_InitTypeDef 结构体类型变量,用来设置串口的各个参数,包括波特率, 停止位等,它的使用方法非常简单。UART_InitTypeDef 结构体定义如下:

    typedef struct
    {
      uint32_t BaudRate;                  /*!< This member configures the UART communication baud rate.
                                               The baud rate is computed using the following formula:
                                               - IntegerDivider = ((PCLKx) / (8 * (OVR8+1) * (huart->Init.BaudRate)))
                                               - FractionalDivider = ((IntegerDivider - ((uint32_t) IntegerDivider)) * 8 * (OVR8+1)) + 0.5
                                               Where OVR8 is the "oversampling by 8 mode" configuration bit in the CR1 register. */
    
      uint32_t WordLength;                /*!< Specifies the number of data bits transmitted or received in a frame.
                                               This parameter can be a value of @ref UART_Word_Length */
    
      uint32_t StopBits;                  /*!< Specifies the number of stop bits transmitted.
                                               This parameter can be a value of @ref UART_Stop_Bits */
    
      uint32_t Parity;                    /*!< Specifies the parity mode.
                                               This parameter can be a value of @ref UART_Parity
                                               @note When parity is enabled, the computed parity is inserted
                                                     at the MSB position of the transmitted data (9th bit when
                                                     the word length is set to 9 data bits; 8th bit when the
                                                     word length is set to 8 data bits). */
    
      uint32_t Mode;                      /*!< Specifies whether the Receive or Transmit mode is enabled or disabled.
                                               This parameter can be a value of @ref UART_Mode */
    
      uint32_t HwFlowCtl;                 /*!< Specifies whether the hardware flow control mode is enabled or disabled.
                                               This parameter can be a value of @ref UART_Hardware_Flow_Control */
    
      uint32_t OverSampling;              /*!< Specifies whether the Over sampling 8 is enabled or disabled, to achieve higher speed (up to fPCLK/8).
                                               This parameter can be a value of @ref UART_Over_Sampling */
    } UART_InitTypeDef;
    

    在初始化函数HAL_UART_Init中会调用相应的串口使能函数__HAL_UART_ENABLE(huart);同时,在调用的初始化函数 HAL_UART_Init 内 部,会先调用 MSP 初始化回调函数进行 MCU 相关的初始化,函数为:

    HAL_UART_MspInit(UART_HandleTypeDef* uartHandle)
    

    重写该函数,使能时钟、配置IO以及中断配置

    2.2GPIO 初始化设置:要设置模式为复用功能

    在HAL_UART_MspInit中配置:

        /**USART1 GPIO Configuration
        PA9     ------> USART1_TX
        PA10     ------> USART1_RX
        */
        GPIO_InitStruct.Pin = GPIO_PIN_9|GPIO_PIN_10;
        GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
        GPIO_InitStruct.Pull = GPIO_NOPULL;
        GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
        GPIO_InitStruct.Alternate = GPIO_AF7_USART1;
        HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
    
    2.3开启中断

    使能串口中断的函数:(以接收完成中断为例)

      /* Enable the UART Data Register not empty Interrupt */
      __HAL_UART_ENABLE_IT(huart, UART_IT_RXNE);
    

    输入的第一个参数是串口句柄,第二个参数是串口中断类型。

    关闭中断:

     /* Disable the UART Data Register not empty Interrupt */
          __HAL_UART_DISABLE_IT(huart, UART_IT_RXNE);
    
    2.4编写中断服务函数

    当发生中断时,在串口中断服务函数中编写相应的逻辑代码即可

    void USART1_IRQHandler(void)
    

    串口接收中断的一般逻辑为:(此图由《STM32F4开发指南》截得)

    image-20250528223508516

    HAL库一共提供了5个中断处理回调函数:

    void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart);//发送完成回调函数
    void HAL_UART_TxHalfCpltCallback(UART_HandleTypeDef *huart);//发送完成过半回调函数
    void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart);//接收完成回调函数
    void HAL_UART_RxHalfCpltCallback(UART_HandleTypeDef *huart);//接收完成过半回调函数
    void HAL_UART_ErrorCallback(UART_HandleTypeDef *huart);//错误处理回调函数
    

    注意:

    当然,以上是基于我们使用HAL_UART_Receive_IT 函数,开启接收中断,并且初始化串口句柄的缓存相关参数。从而使用中断处理回调函数:当接收到一个字符之后,UART_Receive_IT中会把数据保存在串口句柄的成员变量pRxBuffPtr缓存中,同时RxXferCount 计数器减1。如果我们设置 RxXferSize=10,那么当接收到 10 个字符之后,RxXferCount 会由 10 减到 0(RxXferCount 初始值等于 RxXferSize),这个时候再调用接收完成回调函数 HAL_UART_RxCpltCallback 进行处理。

    如果不使用中断处理回调函数,就不需要用初始化串口句柄的中断接收缓存(没有使用HAL_UART_Receive_IT 函数,具体可翻阅源码),而是直接在要开启中断的 地方通过调用__HAL_UART_ENABLE_IT 单独开启中断即可,只不过这里还需要通过 HAL 库串口接收函数 HAL_UART_Receive 来获取接收到的字符进行相应的处理。

    3、实验二:串口中断实验

    效果:STM32串口收到什么返回什么,同时返回试验成功!

    CubeMX配置如第一章所示

    代码如下:(注意不要将串口接收中断放在while循环中)

    image-20250605155110022

    image-20250605155242354

    image-20250605155321711

    /* USER CODE BEGIN 4 */
    void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart){
    	if(huart == &huart1){
    		HAL_UART_Transmit_IT(huart, rec, sizeof(rec));
    	}
    	HAL_UART_Receive_IT(huart, rec, sizeof(rec));
    }
    
    void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart){
    	if(huart == &huart1){
    		char *str = "test successfully\r\n";
    		HAL_UART_Transmit(huart, (uint8_t *)str, strlen(str), HAL_MAX_DELAY);
    	}
    }
    
    /* USER CODE END 4 */
    

    实验效果:

    image-20250606223953073

    image-20250606223855895

    三、串口DMA模式

    1、串口DMA简介

    第二章对串口的中断模式及其数据收发进行了介绍,其可以省出不少CPU时间。

    在串口的中断模式下,发送数据寄存器每传递一字节数据给发送移位寄存器都会触发一次发送数据寄存器空中断把CPU叫回来,从内存变量中搬运下一位数据到发送数据寄存器。

    接收数据寄存器每从接收移位寄存器获得一字节接数据都会触发一次接收数据寄存器非空中断,把CPU叫回来,将数据搬运到内存变量中。

    这两个中断的处理流程HAL库已经帮我们写好。我们要做的只是调用相应的函数进行发送或者接收,以及在接收到最后一字节数据之后,在回调函数中对数据进行分析处理。但对于CPU本身来说,却是屡屡被打断,疲于在中断搬运数据与处理正常任务代码间辗转

    DMA(Direct Memory Access),即直接存储器访问,可以帮我们实现直接在寄存器与内存间搬运数据。DMA的使用非常简单,只需创建一条DMA通道告诉DMA将数据从哪里带到哪里,DMA就在合适的时机进行内存搬运,等全部搬运完成,再通过中断提醒我们。

    需要注意的是,即使是使用了DMA,其实还是有中断参与其中的。例如HAL_UART_RxCpltCallback函数还是用中断触发。当然这次就不是串口的中断了而是的传输完成中断,如下图。

    image-20250605153756384

    2、实验三:串口DMA实验

    用DMA模式完成实验二

    CubeMX配置如第一章所示,只需再配置DMA通道即可:

    image-20250605155831318

    代码:

    image-20250605162211795

    **注意:**这里需要把自动生成代码的串口初始化和DMA初始化换一下位置

    image-20250606231217155

    image-20250605162230415

    /* USER CODE BEGIN 4 */
    void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart){
    	if(huart == &huart1){
    		HAL_UART_Transmit_DMA(huart, rec, sizeof(rec));
    	}
    	HAL_UART_Receive_DMA(huart, rec, sizeof(rec));
    }
    
    void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart){
    	if(huart == &huart1){
    		char *str = "test successfully\r\n";
    		HAL_UART_Transmit(huart, (uint8_t *)str, strlen(str), HAL_MAX_DELAY);
    	}
    }
    
    /* USER CODE END 4 */
    

    实验效果:

    image-20250606231247328

    注意:

    使用HAL_UART_Receive_DMA后,会自动打开传输完成半中断和传输完成中断,如下图,大家可以翻一下源码

    image-20250608155219287

    所以打开半中断的回调函数,其也可接收处理数据:

    image-20250608155516371

    image-20250608155541532

    可以看到,先接收半中断数据,在接收全中断数据。

    四、接收不定长数据

    1、空闲中断简介

    接收不定长数据,主要靠串口空闲(Idle)中断,此中断的触发条件与接收的字节数无关,只有当RX引脚上无后续数据进入,即串口接收从忙碌转为空闲时才会触发。因此我们可以认为空闲中断发生时就是一帧数据包接收完成,在此时对数据进行分析处理即可。

    操作也非常简单,我们只需要将串口接收函数替换为HAL库为我们提供的一个扩展函数,该函数也有阻塞、中断和DMA三个版本,如下图所示:

    image-20250608164340730

    HAL_StatusTypeDef HAL_UARTEx_ReceiveToIdle_DMA(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size)
    
        /*
        1、参数huart:串口句柄的指针
        2、参数pData:指向接收缓冲区的指针
        3、参数Size:此参数并不是与普通接收函数一样填写我们想要接收到数据长度,而是要填写一次能接收的最大数据长度。一般就是填写接收数组的长度来避免接收的数据太长而导致数组越界。
        */
    

    其回调函数:

    void HAL_UARTEx_RxEventCallback(UART_HandleTypeDef *huart, uint16_t Size)
    

    与HAL_UART_RxHalfCpltCallback两者的一个重要区别是,此回调函数多了一个入参size,因为HAL_UART_RxHalfCpltCallback是接收定长数据,已知道数据长度;但HAL_UARTEx_RxEventCallback用于接收不定长数据,所以需要通过size参数来得知本次到底接收了几字节的数据。

    2、实验四:串口接收不定长数据

    效果:如实验三,发什么收什么,数据是不定长的

    image-20250608174338084

    image-20250608174357205

    image-20250608174439850

    结果:

    image-20250608174513678

    image-20250608174539557

    注意:

    除了串口空闲中断外,DMA的传输过半中断也会触发HAL_UART_RxHalfCpltCallback回调函数,我们将以上代码发送的数据长一点就会发现问题:

    image-20250608174819004

    触发了传输半中断。

    所以,我们最好关闭传输半中断:

    image-20250608175618215

    image-20250608175637296

    同时在usart.h中加入:

    image-20250608175729333

    实验效果:

    image-20250608175751346

    作者:mei_laowu

    物联沃分享整理
    物联沃-IOTWORD物联网 » 【STM32学习笔记】串口技术详解:轮询模式、中断模式、DMA模式及不定长数据接收实践

    发表回复