为何先发送数据再执行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()轮询逻辑的不匹配,具体拆解如下:
UART硬件标志特性
UART接收完一个字节后,数据存入DR寄存器,同时置位RXNE(接收非空)标志,该标志仅在读取DR寄存器后才会清除。若DR中数据未被读取,新数据到来时会覆盖DR内容,并置位ORE(溢出错误)标志。HAL_UART_Receive()的逻辑缺陷
函数进入接收循环前,未检查并清除调用前已存在的RXNE或ORE标志,直接进入UART_WaitOnFlagUntilTimeout等待RXNE标志变为SET状态。结合你的操作场景:
- 若你先发送数据(如8901),程序停在断点未执行接收函数时,若UART初始化时开启了
UART_IT_RXNE接收中断(代码未体现),中断服务函数会自动读取DR寄存器清除RXNE标志,导致旧数据被丢弃; - 若调试器在断点停留期间自动读取了UART寄存器,也会触发RXNE标志清除,旧数据丢失。
- 当你继续执行
HAL_UART_Receive()时,RXNE标志已被清除,函数会进入等待状态,直到新数据(如1234)到来,RXNE再次置位,才会完成接收。
- 无中断场景的特殊情况
若未开启接收中断,先发送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
相关产品推荐
相关产品推荐

