目录

1.  简介

2.  物理层

3.  协议层

4.  工程配置

5.  printf() 重定向

6.  阻塞式接收定长数据

7.  中断式接收定长数据

9.  中断式接收不定长数据


1.  简介

        串口通讯 (Serial Communication) 是一种设备间非常常用的串行通讯方式,因为它简单便捷,因此大部分电子设备都支持该通讯方式,电子工程师在调试设备时也经常使用该通讯方式输出调试信息。

        在计算机科学里,大部分复杂的问题都可以通过分层来简化。如芯片被分为内核层和片上外设;STM32 标准库则是在寄存器与用户代码之间的软件层。对于通讯协议,我们也以分层的方式来理解,最基本的是把它分为物理层和协议层。物理层规定通讯系统中具有机械、电子功能部分的特性,确保原始数据在物理媒体的传输。协议层主要规定通讯逻辑,统一收发双方的数据打包、解包标准。简单来说物理层规定我们用嘴巴还是用肢体来交流,协议层则规定我们用中文还是英文来交流。

2.  物理层

        根据通讯使用的电平标准不同,串口通讯可分为 TTL 标准及 RS-232 标准,见表 TTL 电平标准与 RS-232 电平标准。

标准 逻辑 1(高电平) 逻辑 0(低电平) 典型应用场景 传输距离
TTL +3.3V ~ +5V 0V 单片机内部、板级通信 短(厘米级)
RS-232 -3V ~ -15V +3V ~ +15V 工业控制、老式外设(如调制解调器) 较长(可达 15 米)

        因为控制器一般采用 TTL 电平标准,因此需借助MAX3232 芯片实现TTL 电平与 RS-232 电平的双向转换,这是串口通讯中实现控制器与 RS-232 设备互联的关键环节。

3.  协议层

        串口通讯的数据包由发送设备通过自身的TXD 接口传输到接收设备的RXD 接口。在协议层,数据包由起始位、主体数据、校验位和停止位组成,通讯双方必须约定一致的数据包格式,才能正常收发数据。

组成部分 作用 典型配置
起始位 标志一个数据包的开始,使接收方同步接收数据 固定为 1 位,逻辑电平为低电平
主体数据 实际传输的有效信息,长度可变 常见为 8 位(1 字节)
校验位 用于检测传输过程中是否发生错误,可选奇校验、偶校验或无校验 1 位或无
停止位 标志一个数据包的结束,提供接收方的缓冲时间 常见为 1 位或 2 位,逻辑电平为高电平

4.  工程配置

        还是一样,重复的配置,这里不再一步一步演示步骤,详细可以参考:

STM32学习·HAL库速通篇(三)·如何在STM32CubeMX新建工程_stm32cubemx 配置cmsis库-CSDN博客

        只进行一些上面没有的配置,现在我们想要配置串口1,找到图示位置进行配置:

  • Disable:禁用
  • Asynchronous:异步模式
  • Synchronous:同步模式
  • Single Wire (Half-Duplex):单线(半双工)模式
  • Multiprocessor Communication:多处理器通讯模式
  • IrDA:红外数据协会(红外通讯)模式
  • LIN:本地互连网络(LIN 总线)模式
  • SmartCard:智能卡模式

        然后配置数据通讯协议相关参数,这里直接使用默认参数:

        波特率指数据信号对载波的调制速率,它用单位时间内载波调制状态改变次数来表示,单位为波特。比特率指单位时间内传输的比特数,单位 bit/s (bps)。对于 USART 波特率与比特率相等,以后不区分这两个概念。波特率越大,传输速率越快。

USART 的发送器和接收器使用相同的波特率。计算公式如下:

        其中,fPLCK​ 为 USART 时钟,USARTDIV 是一个存放在波特率寄存器 (USART_BRR) 的一个无符号定点数。其中 DIV_Mantissa [11:0] 位定义 USARTDIV 的整数部分,DIV_Fraction [3:0] 位定义 USARTDIV 的小数部分。

         然后就是对项目名称等的设置(参考上方链接),生成完可以看到,串口初始化成功:

/* USER CODE BEGIN Header */
/**
  ******************************************************************************
  * @file    usart.c
  * @brief   This file provides code for the configuration
  *          of the USART instances.
  ******************************************************************************
  * @attention
  *
  * Copyright (c) 2026 STMicroelectronics.
  * All rights reserved.
  *
  * This software is licensed under terms that can be found in the LICENSE file
  * in the root directory of this software component.
  * If no LICENSE file comes with this software, it is provided AS-IS.
  *
  ******************************************************************************
  */
/* USER CODE END Header */
/* Includes ------------------------------------------------------------------*/
#include "usart.h"

/* USER CODE BEGIN 0 */

/* USER CODE END 0 */

UART_HandleTypeDef huart1;

/* USART1 init function */

void MX_USART1_UART_Init(void)
{

  /* USER CODE BEGIN USART1_Init 0 */

  /* USER CODE END USART1_Init 0 */

  /* USER CODE BEGIN USART1_Init 1 */

  /* USER CODE END USART1_Init 1 */
  huart1.Instance = USART1;
  huart1.Init.BaudRate = 115200;
  huart1.Init.WordLength = UART_WORDLENGTH_8B;
  huart1.Init.StopBits = UART_STOPBITS_1;
  huart1.Init.Parity = UART_PARITY_NONE;
  huart1.Init.Mode = UART_MODE_TX_RX;
  huart1.Init.HwFlowCtl = UART_HWCONTROL_NONE;
  huart1.Init.OverSampling = UART_OVERSAMPLING_16;
  if (HAL_UART_Init(&huart1) != HAL_OK)
  {
    Error_Handler();
  }
  /* USER CODE BEGIN USART1_Init 2 */

  /* USER CODE END USART1_Init 2 */

}

void HAL_UART_MspInit(UART_HandleTypeDef* uartHandle)
{

  GPIO_InitTypeDef GPIO_InitStruct = {0};
  if(uartHandle->Instance==USART1)
  {
  /* USER CODE BEGIN USART1_MspInit 0 */

  /* USER CODE END USART1_MspInit 0 */
    /* USART1 clock enable */
    __HAL_RCC_USART1_CLK_ENABLE();

    __HAL_RCC_GPIOA_CLK_ENABLE();
    /**USART1 GPIO Configuration
    PA9     ------> USART1_TX
    PA10     ------> USART1_RX
    */
    GPIO_InitStruct.Pin = GPIO_PIN_9;
    GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
    HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);

    GPIO_InitStruct.Pin = GPIO_PIN_10;
    GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
    GPIO_InitStruct.Pull = GPIO_NOPULL;
    HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);

  /* USER CODE BEGIN USART1_MspInit 1 */

  /* USER CODE END USART1_MspInit 1 */
  }
}

void HAL_UART_MspDeInit(UART_HandleTypeDef* uartHandle)
{

  if(uartHandle->Instance==USART1)
  {
  /* USER CODE BEGIN USART1_MspDeInit 0 */

  /* USER CODE END USART1_MspDeInit 0 */
    /* Peripheral clock disable */
    __HAL_RCC_USART1_CLK_DISABLE();

    /**USART1 GPIO Configuration
    PA9     ------> USART1_TX
    PA10     ------> USART1_RX
    */
    HAL_GPIO_DeInit(GPIOA, GPIO_PIN_9|GPIO_PIN_10);

  /* USER CODE BEGIN USART1_MspDeInit 1 */

  /* USER CODE END USART1_MspDeInit 1 */
  }
}

/* USER CODE BEGIN 1 */

/* USER CODE END 1 */

5.  printf() 重定向

        C 语言中的 printf () 默认输出到显示器,在嵌入式中没有显示器,需要重定向到串口,通过重写 fputc () 函数,把 printf () 输出的字符通过串口发出去:

#include <stdio.h>
#include <string.h>

int fputc(int ch, FILE *f)
{
    HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1, HAL_MAX_DELAY);
    return ch;
}

        直接将代码复制进去即可:

        然后找到图示位置勾选上,否则 printf () 可能无法正常使用:

        随便在主函数打一句话:

6.  阻塞式接收定长数据

        什么是阻塞接收?调用接收函数后,程序会一直等待数据到来,一旦接收到指定数量的数据或超时,函数才会返回。

特点:简单直接、但会 “卡住程序”,不适合实时性强的场景。

        这里主要用到 HAL_UART_Receive() 函数:

        简单翻译一下:

/**
  * @brief  以阻塞模式接收指定长度的数据。
  * @note   当UART校验位未使能(PCE = 0)且字长配置为9位(M1-M0 = 01)时,
  *         接收到的数据会被当作一组u16类型数据处理。这种情况下,Size参数必须指明
  *         pData缓冲区中可存放的u16数据个数。
  * @param  huart 指向UART_HandleTypeDef结构体的指针,该结构体包含指定UART模块的
  *               配置信息。
  * @param  pData 指向数据缓冲区的指针(数据元素类型为u8或u16),用于存储接收到的数据。
  * @param  Size  要接收的数据元素个数(u8或u16类型)。
  * @param  Timeout 超时持续时间(单位:ms)。
  * @retval HAL状态码(HAL_OK/HAL_ERROR/HAL_TIMEOUT/HAL_BUSY)。
  */
HAL_StatusTypeDef HAL_UART_Receive(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size, uint32_t Timeout)
{
  uint8_t  *pdata8bits;   // 指向8位数据缓冲区的指针(常规场景使用)
  uint16_t *pdata16bits;  // 指向16位数据缓冲区的指针(9位无校验场景使用)
  uint32_t tickstart = 0U;      // 记录函数开始执行的系统时间,用于超时判断

  /* 检查当前是否有其他Rx(接收)过程正在进行 */
  if (huart->RxState == HAL_UART_STATE_READY)
  {
    /* 校验入参合法性:数据缓冲区指针为空 或 接收长度为0,直接返回错误 */
    if ((pData == NULL) || (Size == 0U))
    {
      return  HAL_ERROR;
    }

    // 初始化UART错误码为“无错误”
    huart->ErrorCode = HAL_UART_ERROR_NONE;
    // 将UART接收状态标记为“忙(接收中)”,防止多线程重复调用
    huart->RxState = HAL_UART_STATE_BUSY_RX;
    // 标记接收类型为“标准接收模式”(区别于IDLE中断等特殊模式)
    huart->ReceptionType = HAL_UART_RECEPTION_STANDARD;

    /* 记录当前系统滴答定时器值,作为超时判断的起始时间 */
    tickstart = HAL_GetTick();

    // 保存要接收的总数据长度
    huart->RxXferSize = Size;
    // 初始化剩余接收数据计数(初始值等于总长度)
    huart->RxXferCount = Size;

    /* 特殊场景处理:9位字长 + 无校验位时,pData需当作uint16_t指针处理
       (因为9位数据无法用8位存储,需占用16位变量的低9位) */
    if ((huart->Init.WordLength == UART_WORDLENGTH_9B) && (huart->Init.Parity == UART_PARITY_NONE))
    {
      pdata8bits  = NULL;                  // 8位指针置空
      pdata16bits = (uint16_t *) pData;    // 转换为16位数据缓冲区指针
    }
    else
    {
      // 常规场景(8位/带校验的9位):使用8位数据缓冲区指针
      pdata8bits  = pData;
      pdata16bits = NULL;                  // 16位指针置空
    }

    /* 循环接收数据,直到剩余接收计数为0 */
    while (huart->RxXferCount > 0U)
    {
      /* 等待RXNE标志(接收数据寄存器非空)置位,直到超时
         RXNE=1表示DR寄存器已接收到数据,可以读取 */
      if (UART_WaitOnFlagUntilTimeout(huart, UART_FLAG_RXNE, RESET, tickstart, Timeout) != HAL_OK)
      {
        huart->RxState = HAL_UART_STATE_READY; // 恢复UART为就绪状态
        return HAL_TIMEOUT;                   // 返回超时错误
      }

      // 根据数据类型,从UART数据寄存器(DR)读取数据到缓冲区
      if (pdata8bits == NULL)
      {
        // 9位无校验场景:读取DR寄存器低9位数据(0x01FF是9位掩码)
        *pdata16bits = (uint16_t)(huart->Instance->DR & 0x01FF);
        pdata16bits++; // 指针后移,指向下一个16位缓冲区位置
      }
      else
      {
        // 8位场景:区分“带校验”和“无校验”,读取对应有效位
        if ((huart->Init.WordLength == UART_WORDLENGTH_9B) || 
            ((huart->Init.WordLength == UART_WORDLENGTH_8B) && (huart->Init.Parity == UART_PARITY_NONE)))
        {
          // 8位无校验 或 9位模式(校验位占用第9位):读取低8位有效数据(0x00FF掩码)
          *pdata8bits = (uint8_t)(huart->Instance->DR & (uint8_t)0x00FF);
        }
        else
        {
          // 8位带校验(奇/偶校验):仅读取低7位有效数据(0x007F掩码,第8位是校验位)
          *pdata8bits = (uint8_t)(huart->Instance->DR & (uint8_t)0x007F);
        }
        pdata8bits++; // 指针后移,指向下一个8位缓冲区位置
      }
      huart->RxXferCount--; // 剩余接收计数减1
    }

    /* 接收过程结束,恢复UART接收状态为就绪 */
    huart->RxState = HAL_UART_STATE_READY;

    // 返回接收成功
    return HAL_OK;
  }
  else
  {
    // UART正忙(已有其他接收/发送过程),返回忙状态
    return HAL_BUSY;
  }
}

        我们来调用一下看看,来到主函数,定义一个数组,用来存放接收数据:

uint8_t ReBuf[2];

        然后通过调用 HAL_UART_Receive() 函数长期阻塞等待数据发送过来:

HAL_UART_Receive(&huart1, ReBuf, 2, HAL_MAX_DELAY);
  • &huart1:串口句柄指针,指定使用USART1进行数据接收;
  • ReBuf:存放接收的数据缓冲区指针;
  • 2:接收的数据长度;
  • HAL_MAX_DELAY:超时时间,宏定义值为0xFFFFFFFFU(无符号 32 位最大值),表示无限等待,直到数据发送完成(不会触发超时错误)。
/**
  * @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 */

	uint8_t ReBuf[2];

  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
		printf("\r\n当前处于阻塞接收模式,等待数据发送!!!\r\n");

		HAL_UART_Receive(&huart1, ReBuf, 2, HAL_MAX_DELAY);

		printf("当前接收的数据:");

		HAL_UART_Transmit(&huart1, ReBuf, 2, HAL_MAX_DELAY);
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
  }
  /* USER CODE END 3 */
}

        然后下载程序,打开串口调试助手,我们可以看到,一直处于等待接收状态,等待数据传过来:

        我们点个AA,发送一下,可以发现数据被正常接收:

完整工程:

基于STM32实现串口(USART)阻塞式接收数据(HAL库版本)资源-CSDN下载

7.  中断式接收定长数据

        什么是中断接收?当串口接收到预定数量的数据后(例如 1 个或 4 个字节),硬件会自动触发一个中断信号。

非阻塞:不会卡住主程序

自动响应:数据一到,立即中断处理

适合定长数据包:可以设置接收固定字节数

回调函数处理:数据到达后,自动调用回调函数处理数据

功能 HAL_API 说明
启动中断接收 HAL_UART_Receive_IT() 启动中断方式的接收,指定接收缓冲区和长度,数据接收完成后触发中断
发送数据(中断方式) HAL_UART_Transmit_IT() 使用中断方式发送数据,发送过程不阻塞主程序
接收完成回调函数 HAL_UART_RxCpltCallback() 接收完成后自动被 HAL 库调用,用户可在此函数中编写数据处理逻辑

        我们找到工程的.ioc文件,回到CubeMX当中:

        对一些数据进行修改,首先来到图示位置,将 USART1 的中断打开:

        当然也可以来到 NVIC 进行勾选上,中断等级这里默认:

        然后重新生成代码,可以看到中断部分已经使能完成:

        首先在主函数启动中断接收:

/**
  * @brief  以非阻塞模式(中断方式)接收指定长度的数据。
  * @note   当UART校验位未使能(PCE = 0)且字长配置为9位(M1-M0 = 01)时,
  *         接收到的数据会被当作一组u16类型数据处理。这种情况下,Size参数必须指明
  *         pData缓冲区中可存放的u16数据个数。
  * @param  huart 指向UART_HandleTypeDef结构体的指针,该结构体包含指定UART模块的
  *               配置信息。
  * @param  pData 指向数据缓冲区的指针(数据元素类型为u8或u16),用于存储接收到的数据。
  * @param  Size  要接收的数据元素个数(u8或u16类型)。
  * @retval HAL状态码(HAL_OK/HAL_ERROR/HAL_BUSY)。
  */
HAL_StatusTypeDef HAL_UART_Receive_IT(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size)
{
  /* 检查当前是否有其他Rx(接收)过程正在进行
     只有UART处于READY状态时,才能启动新的中断接收 */
  if (huart->RxState == HAL_UART_STATE_READY)
  {
    /* 校验入参合法性:数据缓冲区指针为空 或 接收长度为0,直接返回错误 */
    if ((pData == NULL) || (Size == 0U))
    {
      return HAL_ERROR;
    }

    /* 将接收类型设置为“标准接收模式”
       区别于IDLE中断、奇偶校验错误中断等特殊接收模式 */
    huart->ReceptionType = HAL_UART_RECEPTION_STANDARD;

    /* 调用底层函数UART_Start_Receive_IT,真正完成中断接收的初始化:
       1. 配置接收缓冲区和长度
       2. 使能UART接收中断(RXNEIE)
       3. 更新UART状态为“忙(中断接收中)”
       返回该函数的执行结果(HAL_OK/HAL_ERROR) */
    return (UART_Start_Receive_IT(huart, pData, Size));
  }
  else
  {
    // UART正忙(已有接收/发送过程),无法启动新的中断接收,返回忙状态
    return HAL_BUSY;
  }
}

        使用一下,首先声明一个接收数据缓冲区:

#define LENGTH   5//接收缓冲区大小

uint8_t RxBuff[LENGTH];//接收缓冲区

        然后调用 HAL_UART_Receive_IT() 函数使能接收事件:

HAL_UART_Receive_IT(&huart1, RxBuff, LENGTH);//使能接收事件

printf("当前已开启接收中断,等待数据发送!!!\r\n");

        接收完成回调函数,如何被使用的呢?首先,我们找到 stm32f1xx_it.c 下滑到最下面看到:

        跳转一下,来到下图位置,很长的一段代码,这里我们关注一下圈住的部分:

        当我们正常使能和接收到数据后,会调用 UART_Receive_IT() 函数:

/* If no error occurs */
//提取中断状态中的错误标志位,并判断是否有错误
errorflags = (isrflags & (uint32_t)(USART_SR_PE | USART_SR_FE | USART_SR_ORE | USART_SR_NE));
if (errorflags == RESET)
{
  //确认是“接收数据寄存器非空”中断,且该中断已使能
  if (((isrflags & USART_SR_RXNE) != RESET) && ((cr1its & USART_CR1_RXNEIE) != RESET))
  {
    //调用中断接收处理函数,处理接收到的数据
    UART_Receive_IT(huart);
    return;
  }
}

        跳转看一下,也是一段很长的代码,来到:

        最终经过一系列的运算,会调用 HAL_UART_RxCpltCallback() 函数,并且我们可以看到,库函数里又一个若定义:

拓展:
__weak:弱定义修饰符

如果用户在自己的代码中定义了同名、同参数的HAL_GPIO_EXTI_Callback函数,编译器会优先使用用户写的版本;如果用户没定义,就使用这个空的默认版本。

UNUSED(GPIO_Pin):

本质是一个宏(通常定义为#define UNUSED(x) (void)x),作用是显式告诉编译器 “该参数虽未使用,但并非疏忽”,避免编译时抛出 “未使用参数” 的警告。
因为默认版本是空实现,GPIO_Pin参数没被用到,必须加这个宏抑制警告。

        我们将这一段复制下来,来到主函数,将一些多余部分删掉:

        首先我们根据声明判断是否是 USART1 发生了中断:

/**
  * @brief  Rx Transfer completed callbacks.
  * @param  huart  Pointer to a UART_HandleTypeDef structure that contains
  *                the configuration information for the specified UART module.
  * @retval None
  */
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
	if(huart -> Instance == USART1)
	{

	}
}

        如果是则回显接收到的数据:

/**
  * @brief  Rx Transfer completed callbacks.
  * @param  huart  Pointer to a UART_HandleTypeDef structure that contains
  *                the configuration information for the specified UART module.
  * @retval None
  */
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
	if(huart -> Instance == USART1)
	{
		HAL_UART_Transmit_IT(&huart1, RxBuff, LENGTH);//回显发送数据
	}
}

        这里需要注意,HAL 库的中断接收是 “单次触发” 设计,我们在初始化阶段,使用HAL_UART_Receive_IT(&huart1, RxBuff, LENGTH) 并不是 “永久开启接收中断”,而是为 “一次指定长度的接收” 配置中断,当调用该函数时,HAL 库会做 3 件事:

  • 绑定接收缓冲区RxBuff和接收长度LENGTH;
  • 重置接收计数(RxXferCount = LENGTH);
  • 使能串口的RXNEIE(接收数据寄存器非空中断使能位)。

        当接收到LENGTH个字节后,HAL 库会自动关闭RXNEIE中断使能位,并将串口状态置为HAL_UART_STATE_READY;此时硬件层面的接收中断已被关闭,若不重新调用HAL_UART_Receive_IT(),后续接收到数据也不会触发中断。

        所以每次接收完后,我们想要继续接收下次的数据,就需要重新使能接收事件:

/**
  * @brief  Rx Transfer completed callbacks.
  * @param  huart  Pointer to a UART_HandleTypeDef structure that contains
  *                the configuration information for the specified UART module.
  * @retval None
  */
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
	if(huart -> Instance == USART1)
	{
		printf("已经接收到数据数据如下:");

		HAL_UART_Transmit_IT(&huart1, RxBuff, LENGTH);//回显发送数据

		HAL_UART_Receive_IT(&huart1, RxBuff, LENGTH);//使能接收事件
	}
}

        下载看一下,能够正常接收数据并回显:

完整工程:

基于STM32实现串口(USART)中断接收数据(HAL库版本)资源-CSDN下载

9.  中断式接收不定长数据

        使用 HAL 库提供的 HAL_UARTEx_ReceiveToIdle_IT() 实现接收任意长度数据自动判断接收完成(通过 UART 空闲中断)。

避免循环接收单字节 + 自己判断结束,代码更简洁;

接收到数据后,在回调函数 HAL_UARTEx_RxEventCallback () 中处理;

通常配合环形缓冲区、消息帧处理使用。

        其和定长数据的接收,主要区别就是调用API的不同,首先我们来看一下:

        简单翻译一下:

/**
  * @brief  在中断模式下接收一定量的数据,直到接收到预期数量的数据,或触发 IDLE(空闲)事件为止。
  * @note   接收过程由本函数调用启动,后续的接收进度依赖于
  *         RXNE(接收数据寄存器非空)和 IDLE(空闲)事件触发的 UART 中断来推进。
  *         接收完成时会调用回调函数,并告知接收到的数据元素数量。
  * @note   当 UART 校验位未使能(PCE = 0)且字长配置为 9 位(M = 01)时,
  *         接收到的数据会被当作一组 uint16_t 类型数据处理。此时,Size 参数需指定
  *         pData 缓冲区中可用的 uint16_t 数据个数。
  * @param  huart  UART 句柄(Handle)。
  * @param  pData  指向数据缓冲区的指针(数据元素类型为 uint8_t 或 uint16_t)。
  * @param  Size   待接收的数据元素个数(单位:uint8_t 或 uint16_t)。
  * @retval HAL 状态码(HAL_StatusTypeDef 枚举类型)
  */
HAL_StatusTypeDef HAL_UARTEx_ReceiveToIdle_IT(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size)
{
  HAL_StatusTypeDef status;

  /* 检查接收进程是否未处于运行状态 */
  if (huart->RxState == HAL_UART_STATE_READY)
  {
    if ((pData == NULL) || (Size == 0U))
    {
      return HAL_ERROR;
    }

    /* 将接收类型设置为“直到 IDLE 事件触发为止的接收模式” */
    huart->ReceptionType = HAL_UART_RECEPTION_TOIDLE;
    huart->RxEventType = HAL_UART_RXEVENT_TC;

    status =  UART_Start_Receive_IT(huart, pData, Size);

    /* 检查接收进程是否已成功启动 */
    if (status == HAL_OK)
    {
      if (huart->ReceptionType == HAL_UART_RECEPTION_TOIDLE)
      {
        __HAL_UART_CLEAR_IDLEFLAG(huart); // 清除 UART 空闲标志位
        ATOMIC_SET_BIT(huart->Instance->CR1, USART_CR1_IDLEIE); // 原子操作置位 IDLE 中断使能位
      }
      else
      {
        /* 当接收启动时若已有错误挂起(例如溢出错误),
           中断可能已被触发并导致接收终止,
           此时接收类型会被重置为标准接收模式(HAL_UART_RECEPTION_STANDARD)。 */
        status = HAL_ERROR;
      }
    }

    return status;
  }
  else
  {
    return HAL_BUSY; // 接收进程已在运行,返回“忙”状态
  }
}

        我们来到主函数,将使能中断更改为空闲中断使能:

	HAL_UARTEx_ReceiveToIdle_IT(&huart1, RxBuff, LENGTH);//使能接收事件
	printf("当前已开启接收中断,等待数据发送!!!\r\n");

        然后来到回调函数,找到:

/**
  * @brief  接收事件回调函数(使用高级接收服务后,会调用此函数通知接收事件)。
  * @param  huart  UART 句柄
  * @param  Size   应用层接收缓冲区中有效数据的字节数(该值指示接收缓冲区中
  *                数据有效范围的结束位置,即从缓冲区起始到该位置的数据均为有效)
  * @retval 无返回值
  */
__weak void HAL_UARTEx_RxEventCallback(UART_HandleTypeDef *huart, uint16_t Size)
{
  /* 防止未使用参数导致的编译警告 */
  UNUSED(huart);
  UNUSED(Size);

  /* 注意:此函数不应被修改。当需要使用回调功能时,
            应在用户文件中重新实现 HAL_UARTEx_RxEventCallback 函数。
   */
}

        然后来到主函数,对Callback函数进行修改:

/**
  * @brief  Rx Transfer completed callbacks.
  * @param  huart  Pointer to a UART_HandleTypeDef structure that contains
  *                the configuration information for the specified UART module.
  * @retval None
  */
void HAL_UARTEx_RxEventCallback(UART_HandleTypeDef *huart, uint16_t Size)
{
	if(huart -> Instance == USART1)
	{
		printf("已经接收到数据数据如下:");

		HAL_UART_Transmit_IT(&huart1, RxBuff, Size);//回显发送数据

		HAL_UARTEx_ReceiveToIdle_IT(&huart1, RxBuff, LENGTH);//使能接收事件
	}
}

        下载看一下,可以发现可以正常接收不定长数据:

        不过需要注意,由于我们接收缓冲区宏定义为5,因此最大只能接受5个字节数据,超出将不接收:

        如果想要接收更多字节数据,只需要将宏定义增大即可:

完整工程:

基于STM32实现串口(USART)中断接收不定长数据(HAL库版本)资源-CSDN下载

HAL库学习笔记_时光の尘的博客-CSDN博客

Logo

腾讯云面向开发者汇聚海量精品云计算使用和开发经验,营造开放的云计算技术生态圈。

更多推荐