cancel
Showing results for 
Search instead for 
Did you mean: 

STM32 HAL Uart receive interrupt stops receiving at random times

DBERG.1
Associate II

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.

17 REPLIES 17
Pavel A.
Evangelist III

Watch for overrun condition. It can block reception.

Thanks.

How do I check that?

0693W00000Y7E13QAF.png 

JW

S.Ma
Principal

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

Karl Yamashita
Lead II

@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 */
}

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.

Johi
Senior III

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.

Pavel A.
Evangelist III

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.

S.Ma
Principal

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.