UART接收缓冲区溢出异常(HAL_UART_ERROR_ORE)问题记录


问题背景

开发环境

  • 硬件:GD32F407VET6开发版
  • IDE:STM32CubeMX + Clion + ARM GNU
  • 烧录:OpenOCD

实验目标

使用Cube配置串口USART1及其中断,引脚复用PA9/PA10,通过使能接收中断 Receive_IT,实现接收7字节时,在中断回调中处理该7个字节数据(简单回传,即echo),并再次开启接收中断,从而实现不断接收7字节、回传7字节的功能。

遇到问题

发送字节数为7时,能够正常echo;但是不为7时,本来期望是单次发送字节数:

  • 大于7字节时,应该也能每次触发中断,只是多余的字节数被丢弃了而已
  • 少于7字节时(例如5),则通过再次发送也能触发中断

但实验过程中发现这两种情况下,会发送echo了一两次之后,后面无论发送多少数据都无法再echo的情况

情景再现

Cube配置

image-20241112212649840

image-20241112212742297

image-20241112212813159

image-20241112212840656

image-20241112212935188

image-20241112212959621

image-20241112213059038

image-20241112213254243

导入Clion

编译配置-交叉编译

image-20241112213601046

Cmake配置

image-20241112213656961

OpenOCD烧录配置

image-20241112213740944

daplink.cfg

# choose st-link/j-link/dap-link etc.
adapter driver cmsis-dap
transport select swd

# 0x10000 = 64K Flash Size
# set FLASH_SIZE 0x20000

# 512KB Flash
set FLASH_SIZE 0x80000

source [find target/stm32f4x.cfg]

# download speed = 10MHz
# adapter speed 10000

代码流程

  • 上电后启用接收中断
  • 在接收完毕回调中echo,并再次启用接收中断

main.c

/* USER CODE BEGIN 0 */
uint8_t rxbuf[7] = {0};
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) {
    HAL_UART_Transmit(&huart1, rxbuf, 7, 100);
    HAL_UART_Receive_IT(&huart1, rxbuf, 7);
}
/* USER CODE END 0 */

int main(void) {

    /* USER CODE BEGIN 1 */
    __HAL_RCC_HSI_ENABLE();
    __HAL_RCC_SYSCLK_CONFIG(RCC_SYSCLKSOURCE_HSI);
    /* 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 */
    HAL_UART_Receive_IT(&huart1, rxbuf, 7);
    /* USER CODE END 2 */

    /* Infinite loop */
    /* USER CODE BEGIN WHILE */
    while (1) {
        //        HAL_UART_Transmit_IT(&huart1, (uint8_t *)"Hello, World!\r\n", 14);
        //        HAL_Delay(1000);
        /* USER CODE END WHILE */

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

源码分析

启用接收中断HAL_UART_Receive_IT

uint8_t rxbuf[7] = {0};

HAL_UART_Receive_IT(&huart1, rxbuf, 7);

HAL_UART_Receive_IT -> UART_Start_Receive_IT

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

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

...
    
/* Enable the UART Error Interrupt: (Frame error, noise error, overrun error) */
__HAL_UART_ENABLE_IT(huart, UART_IT_ERR);

/* Enable the UART Data Register not empty Interrupt */
__HAL_UART_ENABLE_IT(huart, UART_IT_RXNE);
  • pRxBuffPtr保存我们传入的buf指针
  • RxXferSize保存我们传入的buf大小
  • RxXferCount初始化待接收数据数量
  • ErrorCode:初始化错误码为 no error
  • RxState标识UART处于接收状态(Data Reception process is ongoing)
  • 开启UART异常中断
    • 帧异常(Frame error)
    • 噪声异常(noise error)
    • 接收缓冲区溢出异常(overrun error)
  • 开启接收寄存器非空中断(Enable the UART Data Register not empty Interrupt)

UART中断向量表

startup_stm32f407vetx.s

.word     USART1_IRQHandler                 /* USART1                       */       

stm32f4xx_it.c

void USART1_IRQHandler(void)
{
  /* USER CODE BEGIN USART1_IRQn 0 */

  /* USER CODE END USART1_IRQn 0 */
  HAL_UART_IRQHandler(&huart1);
  /* USER CODE BEGIN USART1_IRQn 1 */

  /* USER CODE END USART1_IRQn 1 */
}

数据接收处理

stm32f4xx_it.c

void HAL_UART_IRQHandler(UART_HandleTypeDef *huart) {
    /* If no error occurs */
    errorflags = (isrflags & (uint32_t) (USART_SR_PE | USART_SR_FE | USART_SR_ORE | USART_SR_NE));
    if (errorflags == RESET) {
        /* UART in mode Receiver -------------------------------------------------*/
        if (((isrflags & USART_SR_RXNE) != RESET) && ((cr1its & USART_CR1_RXNEIE) != RESET)) {
            UART_Receive_IT(huart);
            return;
        }
    }
...

执行接收数据逻辑(UART_Receive_IT)的前置条件:

  • 没有UART相关的异常(errorflags)
  • 接收寄存器非空(USART_SR_RXNE)
  • 接收寄存器非空中断使能是开启的(USART_CR1_RXNEIE)

逐字接收数据(Data Register)

stm32f4xx_hal_uart.c/UART_Receive_IT

/* Check that a Rx process is ongoing */
if (huart->RxState == HAL_UART_STATE_BUSY_RX) {
    ...
    *pdata8bits = (uint8_t) (huart->Instance->DR & (uint8_t) 0x00FF);
	huart->pRxBuffPtr += 1U;
    ...
    if (--huart->RxXferCount == 0U) {
        /*Call legacy weak Rx complete callback*/
		HAL_UART_RxCpltCallback(huart);
  • 判断UART是不是处于接收进程中
    • 我们之前调用过 HAL_UART_Receive_IT,里面会置位这个状态 huart->RxState = HAL_UART_STATE_BUSY_RX
  • 从DR(UART数据寄存器)中读取一个字节到用户自定义缓冲区中(我们调用 HAL_UART_Receive_IT 时传入过一个7字节的buf
  • 递减剩余待接收数据数量 huart->RxXferCount(之前被初始化为7)
    • 如果递减为0,则说明接收的字节数填满了用户指定缓冲区大小
    • 然后调用 HAL_UART_RxCpltCallback,这是一个 weak函数(默认是一个空实现),用户可以声明一个对应的非 weak版以实现回调处理。这个理念是经典的hook钩子函数

重写缓冲区接收完毕回调

原型:

stm32f4xx_hal_uart.c

__weak void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) {
    /* Prevent unused argument(s) compilation warning */
    UNUSED(huart);
    /* NOTE: This function should not be modified, when the callback is needed,
             the HAL_UART_RxCpltCallback could be implemented in the user file
     */
}

重写:

main.c

/* USER CODE BEGIN 0 */
uint8_t rxbuf[7] = {0};
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) {
    HAL_UART_Transmit(&huart1, rxbuf, 7, 100);
    HAL_UART_Receive_IT(&huart1, rxbuf, 7);
}
/* USER CODE END 0 */
  • 将缓冲区7字节数据通过UART TX发送,采用轮询方式(每发一个字节轮询发送状态),超时时间100ms
  • 重新开启接收中断,以实现下一次的echo

定长7字节echo测试

至此,定长7字节数据echo功能就实现了

image-20241113084959956

超7字节echo测试

image-20241113085040298

为什么超过我们指定的缓冲区大小(HAL_UART_Transmit的入参 Size)后,功能就不整行了呢?

UART中断ISR-异常流程

void HAL_UART_IRQHandler(UART_HandleTypeDef *huart) {
    /* If no error occurs */
    errorflags = (isrflags & (uint32_t) (USART_SR_PE | USART_SR_FE | USART_SR_ORE | USART_SR_NE));
    if (errorflags == RESET) {
        UART_Receive_IT(huart);
     
     /* If some errors occur */
    if ((errorflags != RESET) && (((cr3its & USART_CR3_EIE) != RESET)
                                  || ((cr1its & (USART_CR1_RXNEIE | USART_CR1_PEIE)) != RESET))) {
        /* UART Over-Run interrupt occurred
         huart->ErrorCode |= HAL_UART_ERROR_ORE;
  • 没有异常时,会执行 UART_Receive_IT逐字接收数据并在填满缓冲区后触发接收完毕回调 HAL_UART_RxCpltCallback
  • 但是当我们发送8字节时,会触发UART的接收溢出错误,继而转向异常处理流程
 /* Call UART Error Call back function if need be --------------------------*/
if (huart->ErrorCode != HAL_UART_ERROR_NONE) {
     /*Call legacy weak error callback*/
    HAL_UART_ErrorCallback(huart);

因为异常处理流程不会执行我们自定义的 HAL_UART_RxCpltCallback,也就没有重新开启接收中断 HAL_UART_RxCpltCallback,所以就出现了发送8字节时,后续没有回传的现象:

image-20241113085040298

思考/遗留问题

USART_SR_ORE是如何被设置的

为什么发送8字节时会触发USART_SR_ORE错误?


文章作者: 安文
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 安文 !
  目录