cancel
Showing results for 
Search instead for 
Did you mean: 

NVIC and/or USART appears to hold pending requests stuck (Cortex-M0 in STM32L071)

AGrigoriev
Associate II

With STM32L071, I've been fighting a problem of losing UART RX characters. The RX path uses HAL_UART_ReceiveIT to queue a single character buffer, and then re-queues it from HAL_UART_RxCpltCallback. The received character is then stored in a software FIFO. The main loop reads from the software FIFO and echoes it back. The TX path uses DMA (TX is used for high volume debug output as well). Simple action of pasting a line to the terminal on UART5 (with echoing back) could cause it to drop a few characters. But one thing helped. After initiating the next RX operation (for a single byte buffer), check RXNE and set the interrupt pending:

 

 

 

// If RXNE is already set, request an immediate interrupt,
  // otherwise it may be lost and cause RX overrun
  if (READ_REG(huart->Instance->ISR) & USART_ISR_RXNE)
  {
    if (huart->Instance == USART4
      || huart->Instance == USART5)
    {
      NVIC_SetPendingIRQ(USART4_5_IRQn);
    }
    else if (huart->Instance == USART1)
    {
      NVIC_SetPendingIRQ(USART1_IRQn);
    }
    else if (huart->Instance == USART2)
    {
      NVIC_SetPendingIRQ(USART2_IRQn);
    }
  }

 

 

 

I then wrote additional test code to echo the data to another UART, get the TX looped back to its RX, and echoing back. Same as in previous example, The RX callback would queue the data, and then the main loop would read the data and transmit it. This was based on a different codebase, and TX DMA was NOT used. The main loop was checking if NVIC interrupt was pending, and/or RXNE bit was set in the UARTs. Surprisingly, once in a few tries, the test code managed to catch either RXNE or an interrupt pending bit set in NVIC. Some of those were caught for UART URQ, but also for SysTick IRQ and for RTC wakeup event IRQ. And it's not like the test loop was tight, it was doing some other stuff and turned about 1000 times a second. And by sampling NVIC pending register just 1000 times a second, it read interrupt pending.

WHAT'S HAPPENING?

One would have thought that code running with interrupts enabled would never see a pending interrupt or RXNE bit set (with RXNEIE also set), because these are supposed to cause an immediate interrupt to clear them. Yet here we are, catching them by sampling just 1000 times per second.

Yes, I've read the NVIC description. And honestly, when I encountered the phrase "Cortex-M0+ interrupts are both level-sensitive and pulse-sensitive. Pulse interrupts are also described as edge-triggered interrupts.", my first thought was "Uh oh, they probably screwed it up". And apparently they did.

12 REPLIES 12
Pavel A.
Evangelist III

UARTs of low-end STM32s don't have FIFO and so are more vulnerable to receiver overrun.

 After initiating the next RX operation (for a single byte buffer)

Interrupt-driven RX of size 1 using the ST "HAL" library is definitely NOT the way to go. Read the code and understand that the RX routine enables the interrupt before, and disables it after receive (of one byte). This has huge overhead and creates time window of disabled interrupt, when the RX overflow can occur. 

Bottom line: for good result, use cyclic DMA or write solid, fast, robust interrupt handlers. Do code reviews.  Be happy.

 

KnarfB
Principal III

As @Pavel A. said, keeping up with single char RX interrupts at higher baud rates may not always work. In the early days (16550A UART), loops were used in the IRQ handler to catch all bytes/events once the handler was running.

Consider using HAL_UARTEx_ReceiveToIdle_DMA or the cyclic DMA routines by Tilen Majerle https://github.com/MaJerle/stm32-usart-uart-dma-rx-tx/tree/main

hth

KnarfB

Karl Yamashita
Lead III

Show your code so we can see how you're exactly saving the data and transmitting it.

Tips and Tricks with TimerCallback https://www.youtube.com/@eebykarl
If you find my solution useful, please click the Accept as Solution so others see the solution.

> As @Pavel A. said, keeping up with single char RX interrupts at higher baud rates may not always work.

That's not what he said. Rather, he said, it's a bad idea to use Cube/HAL functions to do this.

Granted, interrupt-based UART Rx has its limitations; however, under usual circumstances, it's okay. Just write your ISRs yourself, and in reasonable way.

JW

I'm afraid these replies are missing the point.

The character loss would not happen if NVIC+USART would function as you'd expect: invoke the interrupt as soon as RXNE+RXNEIE are set and the MCU is running in interrupt-able state. In this case, the main loop would never be picking RXNE set and/or a request being pending in NVIC, because these conditions would immediately cause an interrupt handler being invoked and clearing them. But the main loop sees these conditions (while sampling them less than 1000 times a second), which means the interrupts are not happening in timely manner, but are being held pending for some reason.

One option is that me (along with writers of CubeMX STM HAL code) don't understand some aspect of USART and NVIC design; another option is that NVIC/USART is broken. I'm trying to find which is that.

I'll make a minimal version of my code and post it.

 

Pavel A.
Evangelist III

Yes this makes sense. Do you have a well known STM32L071 board to repro? Or maybe a C0?

I'm using one of my company's boards.

Did you use to frequent OSR forums? Those were the times. I'm not doing Windows kernel these days.

Me same. this is a small world.