Skip to main content
Explorer II
January 2, 2023
Question

STM32 HAL Uart receive interrupt stops receiving at random times

  • January 2, 2023
  • 7 replies
  • 16523 views

I am using an F303RE and UART communications for a project.

I'm using all 5 uarts, all with the HAL-library interrupt based transmit and receive.

The main communication protocol is MAVLINK, with one of the UARTs being SBUS.

My issue:

After a random amount of time, a UART channel stops receiving messages.

It is also not always the same channel to stop, and it might be multiple channels to do so (not at the same time).

Note:

I'm dealing with this issue for 3 weeks now, without any progress.

I know the 'correct' way would be to write a new HAL, but I don't have resources or knowledge to do so at the moment.

I am using HAL_UART_RxCpltCallback, this way:

(There's HAL_UART_Receive_IT at the end of each reading function)

(I know the code isn't pretty, a switch-case would be a better fit for readability)

void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
	USART_TypeDef *uart = huart->Instance;
	if(uart == USART1)
	{
		readUartOne();
	}
	else if(uart == USART2) // from PC
	{
		// do some stuff (not too much, though)
		HAL_UART_Receive_IT(&huart2, &inBuff2[0], 1);
	}
	else if(uart == USART3) // from SBUS
	{
		readSbusPacket();
	}
	else if(uart == UART4)
	{
		readUartFourPacket();
	}
	else if(uart == UART5)
	{
		readNVPacket();
	}
}

I've tried to catch an interrupt override using some variables - I added an if(uart != huart->Instance) to see if the interrupt is being overridden.

But couldn't find any, so I assume it's not the case.

Please let me know if you have any ideas or tips on how to handle this.

    This topic has been closed for replies.

    7 replies

    Super User
    January 2, 2023

    Watch for overrun condition. It can block reception.

    DBERG.1Author
    Explorer II
    January 2, 2023

    Thanks.

    How do I check that?

    Super User
    January 2, 2023

    0693W00000Y7E13QAF.png 

    JW

    Visitor II
    January 2, 2023

    Overrun detection reports interrupt latency failure. Suggest to use LL low layer for uart communication to reduce WCET.

    Visitor II
    February 19, 2024

    Hey S.Ma, a year after the creation of this post I found myself in a simular situation and I found your response interesting but since i'm still getting used to the terminology can you maybe explain in more words what you mean with "to use LL low layer for uart communication to reduce WCET" ? Thank you in advance

     

     
    Graduate II
    February 19, 2024

    https://en.wikipedia.org/wiki/Worst-case_execution_time

    With the UART's it's usually better to service very quickly, most STM32 only have a single byte buffer for each direction, and the interrupt isn't going to be re-entrant (won't pre-empt, and all the UART1 interrupts come through a single vector).

    Do your work quickly and leave. Buffer data. Process when the execution time is not critical, it's easy to get distracted decoding and processing the stream in the interrupt.

    The interrupt / callback are done in interrupt context.

    Most STM32 interrupt for every single byte, regardless of what you do with HAL_UART_ReceiveIT()

    Observe and clear pending error status (ie Noise, Overrun, Underrun, Parity, Framing, etc) when you dispatch new data to a buffer, and when handling the interrupt(s)

    Graduate II
    January 2, 2023

    @DBERG.1​ The problem is not overrun but how you're using HAL_UART_Receive_IT incorrectly

    .

    For UART2, I see that you don't even look at the HAL return status. Once HAL_UART_Receive_IT does not return HAL_OK, then that UART port will no longer receive an interrupt. HAL returns HAL_OK or HAL_BUSY where the latter is the most common when UART stops receiving data. That UART port is now dead in the water. There is HAL_ERROR but that is a very rare case.

    I assume in the other reading functions you're calling HAL_UART_Receive_IT but also not looking at HAL return status?

    What you need to do is set an error flag so you can then later in a polling routine, try to enable interrupts again for that UART port when HAL is not busy.

    Here is some code snippet so you can see how the error flag comes into play to make sure receive interrupts always gets enabled.

    /*
     * PollingRoutine.c
     *
     * Created on: Jan 2, 2023
     * Author: karl
     */
     
    #include "main.h"
    #include "PollingRoutine.h"
    #include "stdbool.h"
     
     
    extern UART_HandleTypeDef huart4;
    extern UART_HandleTypeDef huart5;
    extern UART_HandleTypeDef huart1;
    extern UART_HandleTypeDef huart2;
    extern UART_HandleTypeDef huart3;
     
    uint8_t inBuff1[1] = {0};
    uint8_t inBuff2[1] = {0};
    uint8_t inBuff3[1] = {0};
    uint8_t inBuff4[1] = {0};
    uint8_t inBuff5[1] = {0};
     
    bool dataRdy1 = false;
    bool dataRdy2 = false;
    bool dataRdy3 = false;
    bool dataRdy4 = false;
    bool dataRdy5 = false;
     
    bool rxIntErrFlag1 = false;
    bool rxIntErrFlag2 = false;
    bool rxIntErrFlag3 = false;
    bool rxIntErrFlag4 = false;
    bool rxIntErrFlag5 = false;
     
     
    void PollingInit(void)
    {
    	// enable receive interrupts for each port
    	rxIntErrFlag1 = UART_EnableRxInterrupt(&huart1, inBuff1, 1);
    	rxIntErrFlag2 = UART_EnableRxInterrupt(&huart2, inBuff2, 2);
    	rxIntErrFlag3 = UART_EnableRxInterrupt(&huart3, inBuff3, 3);
    	rxIntErrFlag4 = UART_EnableRxInterrupt(&huart4, inBuff4, 4);
    	rxIntErrFlag5 = UART_EnableRxInterrupt(&huart5, inBuff5, 5);
    }
     
    void PollingRoutine(void)
    {
     
    	// Do other tasks here
    	// I2C tasks, SPI tasks, CAN bus, etc
     
     
    	// parse each UART port data
    	readUartOne();
    	readUartTwo();
    	readSbusPacket();
    	readUartFourPacket();
    	readNVPacket();
     
    	// check error flags for each UART port
    	checkRxIntErrFlag(1);
    	checkRxIntErrFlag(2);
    	checkRxIntErrFlag(3);
    	checkRxIntErrFlag(4);
    	checkRxIntErrFlag(5);
    }
     
    /*
     * You have to consider if you can you process the data before the next receive interrupt occurs and overwrites you current data value.
     *
     *
     */
    void readUartOne(void) {
    	if (dataRdy1) {
    		dataRdy1 = false;
     
    		// parse inBuff1
    	}
    }
     
    void readUartTwo(void) {
    	if (dataRdy2) {
    		dataRdy2 = false;
     
    		// parse inBuff2
    	}
    }
     
    void readSbusPacket(void) {
    	if (dataRdy3) {
    		dataRdy3 = false;
     
    		// parse inBuff3
    	}
    }
     
    void readUartFourPacket(void) {
    	if (dataRdy4) {
    		dataRdy4 = false;
     
    		// parse inBuff4
    	}
    }
     
    void readNVPacket(void) {
    	if (dataRdy5) {
    		dataRdy5 = false;
     
    		// parse inBuff5
    	}
    }
     
     
     
    /*
     * Depending on baud rate, the packet size and how often data packets are being received, a ring buffer should be implemented.
     * For now, we're setting a ready flag to process data in polling routine.
     *
     */
    void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) {
    	USART_TypeDef *uart = huart->Instance;
    	if (uart == USART1) {
    		dataRdy1 = 1;
    		rxIntErrFlag1 = UART_EnableRxInterrupt(&huart1, inBuff1, 1);
    	}
    	else if (uart == USART2) // from PC
    	{
    		dataRdy2 = 1;
    		rxIntErrFlag2 = UART_EnableRxInterrupt(&huart2, inBuff2, 1);
    	}
    	else if (uart == USART3) // from SBUS
    	{
    		dataRdy3 = 1;
    		rxIntErrFlag3 = UART_EnableRxInterrupt(&huart3, inBuff3, 1);
    	}
    	else if (uart == UART4)
    	{
    		dataRdy4 = 1;
    		rxIntErrFlag4 = UART_EnableRxInterrupt(&huart4, inBuff4, 1);
    	}
    	else if (uart == UART5)
    	{
    		dataRdy5 = 1;
    		rxIntErrFlag5 = UART_EnableRxInterrupt(&huart5, inBuff5, 1);
    	}
    }
     
    /*
     * This function is the most crucial part of enabling the UART receive interrupt.
     * If return status is HAL_BUSY, you need to set an error flag
     * 	 so you can try to enable it again later when it's not busy.
     *
     */
    int UART_EnableRxInterrupt(UART_HandleTypeDef *huart, uint8_t *pData, uint8_t Size)
    {
    	if(HAL_UART_Receive_IT(huart, pData, Size) != HAL_OK)
    	{
    		return 1;
    	}
     
    	return 0;
    }
     
    /*
     * If error flag, try to enable UART Receive again.
     */
    void checkRxIntErrFlag(uint8_t uartPort) {
    	switch (uartPort) {
    	case 1:
    		if (rxIntErrFlag1) {
    			rxIntErrFlag1 = UART_EnableRxInterrupt(&huart1, inBuff1, 1);
    		}
    		break;
    	case 2:
    		if (rxIntErrFlag2) {
    			rxIntErrFlag2 = UART_EnableRxInterrupt(&huart2, inBuff2, 1);
    		}
    		break;
    	case 3:
    		if (rxIntErrFlag3) {
    			rxIntErrFlag3 = UART_EnableRxInterrupt(&huart3, inBuff3, 1);
    		}
    		break;
    	case 4:
    		if (rxIntErrFlag4) {
    			rxIntErrFlag4 = UART_EnableRxInterrupt(&huart4, inBuff4, 1);
    		}
    		break;
    	case 5:
    		if (rxIntErrFlag5) {
    			rxIntErrFlag5 = UART_EnableRxInterrupt(&huart5, inBuff5, 1);
    		}
    		break;
    	default:
    		break;
    	}
    }
     
     

    I assume you used CubeMX or CubeIDE to generate code? if so, then in main.c you'll see how i'm calling PollingInit() and PollingRoutine()

    int main(void)
    {
     /* USER CODE BEGIN 1 */
     
     /* 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_UART4_Init();
     MX_UART5_Init();
     MX_USART1_UART_Init();
     MX_USART2_UART_Init();
     MX_USART3_UART_Init();
     /* USER CODE BEGIN 2 */
     
     /* USER CODE END 2 */
     
     /* Infinite loop */
     /* USER CODE BEGIN WHILE */
     PollingInit();
     while (1)
     {
    	 PollingRoutine();
     /* USER CODE END WHILE */
     
     /* USER CODE BEGIN 3 */
     }
     /* USER CODE END 3 */
    }

    DBERG.1Author
    Explorer II
    January 2, 2023

    Thanks!

    > I assume in the other reading functions you're calling HAL_UART_Receive_IT but also not looking at HAL return status?

    Yep.

    I discussed it with a fellow engineer and we said exactly what you were saying (almost to the word!).

    I haven't look in your code snippets, on my next work day I'll read it to depth and implement what's needed.

    Again, thanks.

    Graduate
    January 2, 2023

    Does your protocol allow you to read multiple bytes at once?

    => If so consider HAL_UART_Receive_IT( ... , x > 1) so you reduce the overhead.

    Consider using DMA, then the load on the processor will be much smaller.

    => For receive you can use circular DMA (ring buffer approach), for send this is not necessary not. In this case consider the main loop for processing what comes out of the buffers.

    Super User
    January 2, 2023

    Besides of reducing overhead as @Johi​ mentioned, HAL_UART_Receive_IT(..., ..., 1) should be avoided. It disables the RX interrupt after receiving the requested number of bytes (1). This opens a time window until the interrupt is enabled again, and RX event can be lost or overrun can occur.

    HAL_UART_Receive_IT( ... , x > 1) is only slightly better. If a received byte is lost because of line noise or framing errors, the requested number of bytes may be not received in expected time. It is unlikely but things happen.

    I don't say to avoid HAL totally, but for UART you'll want to do so.

    DBERG.1Author
    Explorer II
    January 3, 2023

    @S.Ma​ @Pavel A.​ 

    Understood. Anyways using HAL_UART_Receive_IT( ... , x > 1) wouldn't work for me since the protocol allows for different packet lengths.

    Would you say DMA should be an OK solution, or should I go LL?

    Consider that I'm with limited resources as far as development time, and have no experience with LL at the moment.

    Graduate II
    January 3, 2023

    If you're going to use DMA with variable data length, you can use HAL_UARTEx_ReceiveToIdle_DMA. For the data length juse use a number greater than what you're expecting.

    When packets of data stop coming in, it'll interrupt. I've checked on a oscilloscope and after about 4 bits of none activity it'll interrupt. Thats for baud rate of 115200. I haven't checked but it is probably 4 bits for idle for all other baud rates as well.

    Then use HAL_UARTEx_RxEventCallback instead of HAL_UART_RxCpltCallback.

    I'm starting to move my code over to use HAL_UARTEx_RxEventCallback instead of HAL_UART_Receive_IT because now i don't have to look for a delimiter to know it's the end of a string. And works just as good with binary data packets as well.

    Visitor II
    January 3, 2023

    I concur. Assuming all application usart protocol to use known packet lenght before reception is already strinking out the universal console application. As previously said, LL for uart is preffered.