串口接收函数(HAL_UART_Receive)长度不匹配时的现象

在使用 STM32F407 的 HAL 库进行串口开发时,HAL_UART_Receive 是常用的阻塞接收函数。很多开发者会认为它就是一个简单的“接收指定数量字节”的函数,但在实际应用中,当上位机发送的数据量不等于我们期望接收的数据量时,程序会表现出一些值得注意的行为。本文通过一个简单的测试,带大家深入理解 HAL_UART_Receive 的内部逻辑及处理边界情况的方式。

一、函数基础回顾

测试基于 STM32F407 的 UART1,配置为 115200-8-N-1,使用 STM32CubeMX生成的HAL 库。

HAL_UART_Receive阻塞式串口接收函数,快速过一下 HAL_UART_Receive函数 的核心源码:

HAL_StatusTypeDef HAL_UART_Receive(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size, uint32_t Timeout)
{
  // ... 初始化代码省略 ...
  
  /* 核心循环:等待接收 Size 个数据 */
  while (huart->RxXferCount > 0U)
  {
    // 等待 RXNE 标志(接收数据寄存器非空),超时则退出
    if (UART_WaitOnFlagUntilTimeout(huart, UART_FLAG_RXNE, RESET, tickstart, Timeout) != HAL_OK)
    {
      huart->RxState = HAL_UART_STATE_READY;
      return HAL_TIMEOUT; // 超时返回
    }
    
    // 读取数据到缓冲区(处理 9位/8位 情况)
    if (pdata8bits == NULL) { /* 9位数据处理 */ }
    else                      { /* 8位数据处理,存入 pData */ }
    
    huart->RxXferCount--; // 每收到一个字节,计数减 1
  }
  
  huart->RxState = HAL_UART_STATE_READY;
  return HAL_OK; // 成功接收到 Size 个字节
}

关键逻辑提炼

  1. 函数通过 while 循环尝试接收 恰好 Size 个字节
  2. 每收到一个字节,存入 pData 缓冲区,并将剩余计数 RxXferCount 减 1;
  3. 若在超时时间内收到 Size 个字节 → 返回 HAL_OK
  4. 若等待超时(未收满 Size 个) → 返回 HAL_TIMEOUT

二、测试场景与代码

设定:期望接收长度 nUartRxSize = 5,超时时间 nTimeout 为固定值(非无限等待)。测试三种场景:

  1. 上位机发送 3 个字节(小于设定长度 5)
  2. 上位机不发送任何数据
  3. 上位机发送 7 个字节(大于设定长度 5)

测试核心代码:

// 定义变量
uint8_t rxUartBuff[50] = {0};  // 接收缓冲区,固定长度5
uint16_t nUartRxSize = 5;      // 设定接收长度:5字节
uint16_t nTimeout = 500;      // 设定超时时间:500ms
uint16_t nUartRxFrame = 0;    // 记录接收到的帧数
uint8_t nFor = 0;
HAL_StatusTypeDef uart_rx_stat;

// 阻塞接收:期望收5个字节
uart_rx_stat = HAL_UART_Receive(&huart1, rxUartBuff, nUartRxSize , nTimeout );

// 打印整个接收缓冲区的所有数据(不管函数返回值)
printf("nRx1=%03d:",nUartRxFrame);
for(nFor=0;nFor<nUartRxSize ;nFor++)
{
    printf("%d=%02x,",nFor,rxUartBuff[nFor]);
}
printf("\r\n");

// 只有返回HAL_OK才会执行的逻辑
if(uart_rx_stat == HAL_OK)
{
    printf("nRx2=%03d:",++nUartRxFrame);
    // 业务处理代码(实测:数据不匹配时,这里永远不会执行)
}

三、实验结果与现象分析

设定 nUartRxSize = 5,即期望接收 5 个字节。下面是三种典型情况的测试日志。

场景 1:上位机发送 3 个字节(31 32 33

[2026-04-15 11:00:29.660]# SEND HEX/3 >>>
31 32 33 
[2026-04-15 11:00:29.966]# RECV ASCII/36 <<<
nRx1=000:0=31,1=32,2=33,3=00,4=00,

现象分析

  1. 函数返回值:不是 HAL_OK,而是 HAL_TIMEOUT
  2. 接收缓冲区:前 3 个字节是正确的接收数据,后 2 个字节保持默认 0
  3. if(uart_rx_stat==HAL_OK) 内的代码不会执行

场景 2:上位机不发送任何数据

[2026-04-15 11:00:30.470]# RECV ASCII/36 <<<
nRx1=000:0=00,1=00,2=00,3=00,4=00,

现象分析

  1. 函数直接超时,返回 HAL_TIMEOUT
  2. 接收缓冲区全部为默认值 0
  3. 业务处理代码依旧不执行

场景 3:上位机发送 7 个字节(31 32 33 34 35 36 37

[2026-04-15 11:00:31.020]# SEND HEX/7 >>>
31 32 33 34 35 36 37 
[2026-04-15 11:00:31.053]# RECV ASCII/82 <<<
nRx1=000:0=31,1=32,2=33,3=34,4=35,
nRx2=001:0=31,1=32,2=33,3=34,4=35,

[2026-04-15 11:00:31.561]# RECV ASCII/36 <<<
nRx1=001:0=36,1=00,2=00,3=00,4=00,

现象分析

  1. 第一次调用 HAL_UART_Receive(期望 5 字节)成功接收了 5 个字节(31~35),因此返回 HAL_OK,并执行了 if 分支内的打印(nRx2)。

  2. 上位机剩余的 2 个字节(36、37)留在了串口接收缓冲区(硬件 FIFO 或移位寄存器)中。

  3. 紧接着的第二次调用(31.561)试图再次接收 5 个字节,此时从硬件中取出了仅有的 2 个字节(36、37),随后超时返回,缓冲区显示 0=36,1=00,2=00,3=00,4=00实际 37 并未出现在缓冲区中,为何?


四、核心原理:为什么会出现这种现象?

结合 HAL 库源码,可以总结出关键执行流程:

1、检查 RxState 是否就绪,若忙则返回 HAL_BUSY

2、设置接收状态为 HAL_UART_STATE_BUSY_RX,初始化计数变量。

3、循环接收,直至 RxXferCount == 0

  • 调用 UART_WaitOnFlagUntilTimeout 等待 RXNE 标志(接收数据寄存器非空),若超时则提前退出并返回 HAL_TIMEOUT
  • 读取数据寄存器 DR,根据数据位宽和校验设置存入 pData 缓冲区,指针递增。
  • RxXferCount 递减。

4、若循环正常结束(即收够了 Size 个字节),状态恢复为 READY,返回 HAL_OK

因此:

  • 数据量不足:函数会在超时后返回 HAL_TIMEOUT,此时已接收的数据已经存入缓冲区,未接收部分不会被主动清零,但后续再次调用会覆盖它们。

  • 数据量超额:函数只取前 Size 个字节,返回 HAL_OK,多余的字节留在硬件中,可能在下一次接收时被读到(若无溢出)。


五、关键结论(开发必看)

  1. 不要仅依赖返回值判断接收是否完整
    若通信协议帧长不固定,应结合超时机制(如空闲中断、DMA 半满中断)或使用 HAL_UART_Receive_IT(中断模式)来实现不定长接收。

  2. 注意缓冲区的残留数据
    当 HAL_UART_Receive 因超时返回时,未填满的区域可能包含上一次接收的旧数据。在解析前,建议用接收计数变量(如实际接收长度)来标识有效数据,而非假定缓冲区全为有效。

  3. 避免数据溢出
    如果上位机连续发送大量数据,而 MCU 调用 HAL_UART_Receive 的间隔过长,硬件 FIFO 可能溢出导致丢包。适当提高主循环频率、使用 DMA 接收或合理配置硬件 FIFO 可缓解此问题。

  4. 阻塞接收的适用场景
    HAL_UART_Receive 适合接收固定长度、对实时性要求不高的命令帧(如每次固定 8 字节的查询指令)。对于交互式或变长协议,更推荐中断或 DMA 方式。


六、总结

通过本次实验,验证了 HAL_UART_Receive 在面临数据量不匹配时的具体行为:

  • 接收数据不足:超时返回,已收数据存入缓冲区,后续调用会覆盖未收满部分。

  • 接收数据超额:返回成功,但仅读取指定数量,剩余数据留待下次处理(需注意溢出风险)。

理解这些细节有助于我们编写更健壮的 STM32 串口通信代码,避免因对 HAL 库行为的误解而产生奇怪的 bug。希望这篇心得对正在使用 STM32 HAL 库的朋友有所帮助!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值