cancel
Showing results for 
Search instead for 
Did you mean: 

How to use the 'HAL_UART_Receive_IT' within a function?

Marco S
Associate II

Hello,

I'm working with an STM32G4 MCU and I want to listen the UART3 with interruption. I am testing with a code which changes the color of a led. The following code works fine :

//main.c
 
#include "main.h"
 
 
 
FDCAN_HandleTypeDef hfdcan3;
 
FDCAN_HandleTypeDef hfdcan4;
 
 
 
char uart3_buffer[64]={0};
 
uint8_t RxChar3;
 
 
 
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_FDCAN3_Init();
 
 MX_UART4_Init();
 
 MX_USART3_UART_Init();
 
 
 
 /* USER CODE BEGIN 2 */
 
 
 
 
 
 /* USER CODE END 2 */
 
 
 
 /* Infinite loop */
 
 /* USER CODE BEGIN WHILE */
 
 while (1)
 
 {
 
	 	/* Listen the USART3. */
 
		HAL_UART_Receive_IT(&huart3, &RxChar3, 1);
 
 
 
		/* Copy only the non null characters into the buffer. */
 
		if(RxChar3==49){ // ASCII of character 1.
 
			HAL_GPIO_WritePin(GPIOD, GPIO_PIN_1, GPIO_PIN_SET); /* Function to light on a led activating a GPIO. */
 
		}
 
		else if(RxChar3==50){ // ASCII of character 2.
 
			HAL_GPIO_WritePin(GPIOD, GPIO_PIN_2, GPIO_PIN_SET);/* Function to light on a led activating a GPIO. */
 
		}
 
		else if(RxChar3==51){ // ASCII of character 3.
 
			HAL_GPIO_WritePin(GPIOD, GPIO_PIN_1, GPIO_PIN_RESET); /* Function to power off a led deactivating a GPIO. */
 
			HAL_GPIO_WritePin(GPIOD, GPIO_PIN_2, GPIO_PIN_RESET); /* Function to power off a led deactivating a GPIO. */
 
			HAL_GPIO_WritePin(GPIOD, GPIO_PIN_4, GPIO_PIN_RESET); /* Function to power off a led deactivating a GPIO. */
 
		}
 
		else{
 
			HAL_GPIO_WritePin(GPIOD, GPIO_PIN_4, GPIO_PIN_SET); /* Function to light on a led activating a GPIO. */
 
		}
 
 
 
		/* Sends the current value of Rxchar3 variable (only for debugging). */
 
		snprintf(uart3_buffer, sizeof(uart3_buffer), "Rxchar3: %u\n", RxChar3);
 
		HAL_UART_Transmit(&huart3, (uint8_t*)uart3_buffer, strlen(uart3_buffer), 0xFFFFFFFF);
 
	 HAL_Delay(500);
 
  /* USER CODE END WHILE */
 
 
 
  /* USER CODE BEGIN 3 */
 
 }
 
 /* USER CODE END 3 */
 
}

Nevertheless, as I have more than one than one serial port configured in the MCU I want to put the 'HAL_UART_Receive_IT' function within another function which receives as input parameter the number of serial interface. I have done as follows:

main.c

//main.c
FDCAN_HandleTypeDef hfdcan3;
FDCAN_HandleTypeDef hfdcan4;
 
char uart3_buffer[64]={0};
uint8_t RxChar3;
 
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_FDCAN3_Init();
  MX_UART4_Init();
  MX_USART3_UART_Init();
 
  /* USER CODE BEGIN 2 */
 
 
  /* USER CODE END 2 */
 
  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
	  	/* Listen the USART3. */
		UART_RX_String01(3); // Listen UART3
 
		/* Copy only the non null characters into the buffer. */
		if(RxChar3==49){  // ASCII of character 1.
			HAL_GPIO_WritePin(GPIOD, GPIO_PIN_1, GPIO_PIN_SET); /* Function to light on a led activating a GPIO. */
		}
		else if(RxChar3==50){  // ASCII of character 2.
			HAL_GPIO_WritePin(GPIOD, GPIO_PIN_2, GPIO_PIN_SET);/* Function to light on a led activating a GPIO. */
		}
		else if(RxChar3==51){ //  ASCII of character 3.
			HAL_GPIO_WritePin(GPIOD, GPIO_PIN_1, GPIO_PIN_RESET); /* Function to power off a led deactivating a GPIO. */
			HAL_GPIO_WritePin(GPIOD, GPIO_PIN_2, GPIO_PIN_RESET); /* Function to power off a led deactivating a GPIO. */
			HAL_GPIO_WritePin(GPIOD, GPIO_PIN_4, GPIO_PIN_RESET); /* Function to power off a led deactivating a GPIO. */
		}
		else{
			HAL_GPIO_WritePin(GPIOD, GPIO_PIN_4, GPIO_PIN_SET); /* Function to light on a led activating a GPIO. */
		}
 
		/* Sends the current value of Rxchar3 variable (only for debugging). */
		snprintf(uart3_buffer, sizeof(uart3_buffer), "Rxchar3: %u\n", RxChar3);
		HAL_UART_Transmit(&huart3, (uint8_t*)uart3_buffer, strlen(uart3_buffer), 0xFFFFFFFF);
	  HAL_Delay(500);
    /* USER CODE END WHILE */
 
    /* USER CODE BEGIN 3 */
  }
  /* USER CODE END 3 */
}

uart_func.h

//uart_func.h
#ifndef INC_UART_FUNCTIONS_H_
#define INC_UART_FUNCTIONS_H_
 
#include "stm32g4xx_hal.h"
 
extern UART_HandleTypeDef huart3;
extern UART_HandleTypeDef huart4;
 
void UART_RX_String01(uint8_t);
 
#endif /* INC_UART_FUNCTIONS_H_ */

uart_func.c

//uart_func.c
#include "uart_func.h"
void UART_RX_String01(uint8_t port_number){
	uint8_t RxStringBuffer;
	HAL_StatusTypeDef RX_State;
 
	/* Select the UART port to be listened. */
	if (port_number==3){
		RX_State = HAL_UART_Receive_IT(&huart3, &RxStringBuffer, 1);
 
		/* Send the function's return value. */
		snprintf(uart_buffer, sizeof(uart_buffer), "Rx State %u", RX_State);
		HAL_UART_Transmit(&huart3, (uint8_t*)uart_buffer, strlen(uart_buffer), 0xFFFFFFFF);
	}
 
	if (port_number==4){
		RX_State = HAL_UART_Receive_IT(&huart4, &RxStringBuffer, 1);
 
		/* Send the function's return value. */
		snprintf(uart_buffer, sizeof(uart_buffer), "Rx State %u", RX_State);
		HAL_UART_Transmit(&huart4, (uint8_t*)uart_buffer, strlen(uart_buffer), 0xFFFFFFFF);
	}
}

The 'main.h' header file includes the 'uart_func.h' header. The problem I have with this is that in this case the MCU blocks around 20 seconds when I send a character to the serial interface. Then it continues turning. Why does this happen? Or isn't it possible to call this function out of the main.c ?

11 REPLIES 11
TDK
Guru

It looks like you're calling HAL_UART_Receive_IT repeatedly in the main loop. The idea is to call this once and handle the logic for receiving within the interrupt routine. Where is it blocking?

If you feel a post has answered your question, please click "Accept as Solution".
Marco S
Associate II

Hello TDK,

In the first code when calling 'HAL_UART_Receive_IT' repeatedly in the main loop the microcontroller seems to work well and it doesn't block. In the second code, when the 'HAL_UART_Receive_IT' is called within the function 'UART_RX_String01' (which is repeatedly called in the main loop) the microcontroller blocks during around 20 seconds in the line 9 of the file 'uart_func.c', when calling 'HAL_UART_Receive_IT'.

Guenael Cadier
ST Employee

Hello @Community member​ 

HAL_UART_Receive_IT() API is used for initiating a reception : when function exits (with HAL_OK return code is call is successfull) then reception is active and any further incoming byte will be copied in reception buffer. So, in first implementation, immediately after calling HAL_UART_Receive_IT(), you have to wait for the character to be received (until that, value of RxChar3 is not updated).

I guess your while(1) loop is performing HAL_GPIO_WritePin actions endlessly using the stored RxChar3 value, but this will change only when a char is received.

Moreover, when a reception is started with HAL_UART_Receive_IT() API, and till it is completed (1 byte received in your case), further calls to HAL_UART_Receive_IT() API will be ending with HAL_BUSY return code (nothing done, as reception is already ongoing).

To know when a reception in IT mode could be considered as completed, you should wait for HAL_UART_RxCpltCallback() callback execution.

A weak (empty body) definition of this callback exists in HAL code, you could define your own callback in your code.The callback has a parameter which is the UART handle, so you could adapt treatment to huart3 or huart4, when called.

Note : In your 2nd implementation, received char will be stored in RxStringBuffer, which is a UART_RX_String01() local variable, so not visible outside the function. your main loop is still testing RxChar3 value, so will not reflect received char value anyway.

Here is one proposal for avoiding endless calls to HAL_UART_Receive_IT, if my understanding of what you want to do is right (not a final implementation, not tested nor compiled, just ideas for understanding the API calls then callbacks execution scheme). Goal is to use a global variable indicating if reception is completed or not :

main.c

+
 
/**
  * @brief  Rx Transfer completed callback.
  * @param  huart UART handle.
  * @retval None
  */
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
  /* Inidicate reception is completed */
  if (huart == &huart3)
  {
     usart3_reception_ongoing = 0;
     ...    
  }
}

uart_func.c

void UART_RX_String01(uint8_t port_number)
{
  HAL_StatusTypeDef RX_State;
 
  /* Select the UART port to be listened. */
  if (port_number==3)
  {
    if ( usart3_reception_ongoing == 0)
    {
      usart3_reception_ongoing = 1;
      RX_State = HAL_UART_Receive_IT(&huart3, &RxStringBuffer, 1);
      ...
     }

with following decalarations of global variables :

volatile uint8_t usart3_reception_ongoing = 0;

Volatile attribute is useful to remain independent on your compiler, and on your code optimization options, regarding proper access to variables that are accessed either in interrupt routines (and callbacks), and in other code parts;

Hope this helps.

Regards

Guenael

if ( usart3_reception_ongoing == 0)
    {
      usart3_reception_ongoing = 1;
      ...
     }

This is flawed because it's not interrupt-safe.

And how about ST making an actually useful high-level drivers, not this "call receive for a single character" idiot made crap HAL is providing now? Is a multi-billion company able of making usable USART driver in 12+ years, which I (one person) was able to make in a few days?

As stated in my comment to initial post, my purpose was just for "understanding the API calls then callbacks execution scheme".

Regarding HAL UART API, we will extend it with enhanced services, that will allow to address specific use cases as reception of unknown length.

Guenael

Giving an example in one aspect with a broken code otherwise is a bad example - just as in most ST's examples. As a quick placeholder you could put, for example, __disable_irq()/__enable_irq() or at least relevant comments in a critical section enter/exit places.

Enhanced services for a specific use cases... USART is a stream based protocol. It's the only use case for it! It doesn't have a concept of known length messages at all. Such a concept exists in HAL only because of designers' incompetence. In case of USART, messages and other parsing, if such are needed, are tasks for a higher layer protocols. And blocking and timeouts also don't belong in a driver layer. But, what really is necessary in a driver layer, is a FIFO buffer. Look at how reception in Ethernet and lwIP is organized:

  1. Hardware and/or driver code puts data in RAM buffers.
  2. Driver calls a callback from interrupt to notify that a new data is available.
  3. Callback activates data processing task.
  4. Data processing task reads and processes all data available.

Does HAL USART support that? No, because it's designers are unable of understanding such a concepts as stream and asynchronous processing. And talking about callbacks...

void HAL_USART_TxHalfCpltCallback(USART_HandleTypeDef *husart);
void HAL_USART_TxCpltCallback(USART_HandleTypeDef *husart);
void HAL_USART_RxCpltCallback(USART_HandleTypeDef *husart);
void HAL_USART_RxHalfCpltCallback(USART_HandleTypeDef *husart);
void HAL_USART_TxRxCpltCallback(USART_HandleTypeDef *husart);
void HAL_USART_ErrorCallback(USART_HandleTypeDef *husart);
void HAL_USART_AbortCpltCallback(USART_HandleTypeDef *husart);

Splitting interrupt event types to separate callbacks is again a stupid design leading to code bloat. Try making a real software (not a kindergarten level crap like in ST's examples) using all these callbacks for multiple peripheral instances! There must be one callback per peripheral instance and events should be passed as flags in a parameter. And, if it's a high-level driver, then it must expose driver level (higher abstraction) events through it's API, not just pass over hardware interrupt flags.

Dr X
Associate II

Hello,

I've got exactly same problem, when I declare

static uint8_t RxStringBuffer;

locally or as @Guenael Cadier​  mentioned globally it works fine but with similar approach to uart_func.c which declares this variable locally (with or without volatile specifier) it does not receive data correctly.

Data will be received correctly with both definition for HAL_UART_Receive() though.

Hi @Dr X​ 

When using initial split of code between main.c/uart_func.c approach, with UART_RX_String01() function declaring RxStringBuffer variable as local, here it what is happening :

  • RxStringBuffer variable is declared locally, i.e. its scope is restricted to function UART_RX_String01(). Adress of this variable will be in stack.
  • inside this function, calling
RX_State = HAL_UART_Receive_IT(&huart3, &RxStringBuffer, 1);
  • indicates that received data should be copied at this address (&RxStringBuffer)
  • So when function exits, your received data could be considered as "lost" (stored in an area that is no more referenced).
  • Tests executed in main.c have no link with received value :
if(RxChar3==49){ ...
  • It is not possible that RxChar3 could contain any of the received data.

If you want to be able to use the value of your received data in the main.c scope, the variable containing it, should be "visible" in the main.c. A variable declared locally in a function is no more available when function exits.

I hope I understood your question well ...

Regards

Hi @Guenael Cadier​ 

Thanks.

I understand that HAL_UART_Transmit/ Receive_IT function are meant to be non-blocking and by little bit digging I found out that in fact as you mention, these two functions delegate the execution to other function that may execute after exiting the uart_func.c (therefore RxStringBuffer

is not in the scope anymore ). to prove it I did the following experiment with HAL_UART_Transmit_IT();

void  NonCriticalTask(UART_HandleTypeDef *usrt)
{
	char msg[30];
	
	sprintf(msg, "\r\n this is just a test \r\n");
	HAL_UART_Transmit_IT(usrt, (uint8_t *)msg, strlen(msg));
 
       /* delay to keep msg in the scope when micro spits our the data */
      /* HAL_UART_TxCpltCallback can be used but a delay is enought to prove the point */
       HAL_Delay(10000);
 
	
}

And it works fine with the delay but spits out gibberish without delay.

From strict C language point of view NonCriticalTask() function should work fine with or without delay since HAL_UART_Transmit_IT

is called when msg is still in the scope, but from HAL point of view because of the delegation that I mentioned before, it doesn’t work. To me it is counter intuitive though, and I failed to find anything related to this consideration in HAL documentation. However your answer was clear enough for me and I understand it now :smiling_face_with_smiling_eyes: .