You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

为何先发送数据再执行HAL_UART_Receive会出现异常?

问题

在研究UART通信时遇到异常现象,相关代码如下(初始化代码未完全展示):

uint8_t rxData[10];

while (1)
{
    HAL_UART_Receive(&huart2, rxData, 4, HAL_MAX_DELAY);
}

在HAL_UART_Receive()处设置断点观察到两种情况:

  • 先执行接收函数,再通过串口监视器发送数据(如4567),rxData数组会被正确更新。
  • 先通过串口监视器发送数据(如8901),再执行接收函数时,该函数无法读取此数据;重新发送新数据(如1234)后,rxData数组才会更新为新数据。

尝试从寄存器层面分析但未找到原因,现将HAL_UART_Receive()的源码附上,请求解释此现象的原因:

HAL_StatusTypeDef HAL_UART_Receive(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size, uint32_t Timeout)
{
  uint8_t  *pdata8bits;
  uint16_t *pdata16bits;
  uint32_t tickstart = 0U;

  /* Check that a Rx process is not already ongoing */
  if (huart->RxState == HAL_UART_STATE_READY)
  {
    if ((pData == NULL) || (Size == 0U))
    {
      return  HAL_ERROR;
    }

    huart->ErrorCode = HAL_UART_ERROR_NONE;
    huart->RxState = HAL_UART_STATE_BUSY_RX;
    huart->ReceptionType = HAL_UART_RECEPTION_STANDARD;

    /* Init tickstart for timeout management */
    tickstart = HAL_GetTick();

    huart->RxXferSize = Size;
    huart->RxXferCount = Size;

    /* In case of 9bits/No Parity transfer, pRxData needs to be handled as a uint16_t pointer */
    if ((huart->Init.WordLength == UART_WORDLENGTH_9B) && (huart->Init.Parity == UART_PARITY_NONE))
    {
      pdata8bits  = NULL;
      pdata16bits = (uint16_t *) pData;
    }
    else
    {
      pdata8bits  = pData;
      pdata16bits = NULL;
    }

    /* Check the remain data to be received */
    while (huart->RxXferCount > 0U)
    {
      if (UART_WaitOnFlagUntilTimeout(huart, UART_FLAG_RXNE, RESET, tickstart, Timeout) != HAL_OK)
      {
        huart->RxState = HAL_UART_STATE_READY;

        return HAL_TIMEOUT;
      }
      if (pdata8bits == NULL)
      {
        *pdata16bits = (uint16_t)(huart->Instance->DR & 0x01FF);
        pdata16bits++;
      }
      else
      {
        if ((huart->Init.WordLength == UART_WORDLENGTH_9B) || ((huart->Init.WordLength == UART_WORDLENGTH_8B) && (huart->Init.Parity == UART_PARITY_NONE)))
        {
          *pdata8bits = (uint8_t)(huart->Instance->DR & (uint8_t)0x00FF);
        }
        else
        {
          *pdata8bits = (uint8_t)(huart->Instance->DR & (uint8_t)0x007F);
        }
        pdata8bits++;
      }
      huart->RxXferCount--;
    }

    /* At end of Rx process, restore huart->RxState to Ready */
    huart->RxState = HAL_UART_STATE_READY;

    return HAL_OK;
  }
  else
  {
    return HAL_BUSY;
  }
}

原因分析

这个现象的核心是UART硬件接收状态与HAL_UART_Receive()轮询逻辑的不匹配,具体拆解如下:

  1. UART硬件标志特性
    UART接收完一个字节后,数据存入DR寄存器,同时置位RXNE(接收非空)标志,该标志仅在读取DR寄存器后才会清除。若DR中数据未被读取,新数据到来时会覆盖DR内容,并置位ORE(溢出错误)标志。

  2. HAL_UART_Receive()的逻辑缺陷
    函数进入接收循环前,未检查并清除调用前已存在的RXNE或ORE标志,直接进入UART_WaitOnFlagUntilTimeout等待RXNE标志变为SET状态。结合你的操作场景:

  • 若你先发送数据(如8901),程序停在断点未执行接收函数时,若UART初始化时开启了UART_IT_RXNE接收中断(代码未体现),中断服务函数会自动读取DR寄存器清除RXNE标志,导致旧数据被丢弃;
  • 若调试器在断点停留期间自动读取了UART寄存器,也会触发RXNE标志清除,旧数据丢失。
  • 当你继续执行HAL_UART_Receive()时,RXNE标志已被清除,函数会进入等待状态,直到新数据(如1234)到来,RXNE再次置位,才会完成接收。
  1. 无中断场景的特殊情况
    若未开启接收中断,先发送4字节数据后,DR寄存器仅保留最后一个字节,RXNE标志置位。此时执行HAL_UART_Receive()会读取该字节,然后等待剩余3个字节,程序会阻塞直到你发送新数据的前3个字节,最终rxData会包含旧数据的最后一个字节+新数据的前3个字节。但你描述rxData直接更新为新数据,说明大概率是中断导致旧数据被提前读取丢弃。

解决建议

  • 检查UART初始化代码,若不需要接收中断,关闭UART_IT_RXNE中断;
  • 若必须使用中断,应采用HAL_UART_Receive_IT()或HAL_UART_Receive_DMA()的中断/DMA接收方式,而非轮询方式;
  • 在调用HAL_UART_Receive()前,手动检查并清除RXNE和ORE标志:
    // 清除RXNE和ORE标志
    __HAL_UART_CLEAR_FLAG(&huart2, UART_FLAG_RXNE | UART_FLAG_ORE);
    

内容的提问来源于stack exchange,提问作者Theja

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.22 21:10:54