STM32学习·HAL库速通篇(九)·串口(USART)接收数据运用
本文详细介绍了STM32串口通信的实现方法,包括物理层和协议层的概念解析。物理层区分了TTL和RS-232电平标准,协议层说明了数据包格式。文章重点讲解了三种接收方式:阻塞式接收定长数据,通过HAL_UART_Receive()实现;中断式接收定长数据,使用HAL_UART_Receive_IT()和回调函数处理;中断式接收不定长数据,利用HAL_UARTEx_ReceiveToIdle_IT()

目录
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,发送一下,可以发现数据被正常接收:
完整工程:
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);//使能接收事件
}
}
下载看一下,能够正常接收数据并回显:

完整工程:
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个字节数据,超出将不接收:

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

完整工程:


更多推荐
所有评论(0)