cancel
Showing results for 
Search instead for 
Did you mean: 

Is it possible to use STM32 HAL for UART full-duplex in interrupt mode?

RAltm
Senior

Hi,

I want to use the UART HAL for full-duplex communication in interrupt mode. I couldn't find any examples for that.

For the first tests, my hardware setup is as follows:

  • Nucleo-32 board connected via USB to PC, running terminal software with the COM port provided by the Nucleo-32 board
  • enabled two UARTs on the STM32 device
  • the first UART is the one connected to the on-board STlink
  • the second UART has Rx & Tx pin connected together

My application will support different modes, one of them is simple pass-through for the two UARTs (that's the reason why Rx/Tx are shorted on the 2nd UART for testing).

Now, regarding the current test firmware, as far as I understand the HAL, the IRQ handler will call the HAL_UART_RxCpltCallback() function if the required amount of bytes indicated by calling HAL_UART_Receive_IT() function has been received.

So, the main() function is as follows:

int main(void) {
  MX_USART1_UART_Init();
  MX_USART2_UART_Init();
 
  HAL_UART_Receive_IT(&huart1, &Uart1_Rx, 1);
  HAL_UART_Receive_IT(&huart2, &Uart2_Rx, 1);
  while(1) {
  };
}

Note: for readability I omitted initialization for clock, other peripherals etc, but the while(1) loop is really empty.

Uart1_Rx and Uart2_Rx are single byte variables, declared as volatile. The same exists for Tx.

In the receive complete handler, I forward the received byte to the Tx buffer of the other UART and start transmission. Also, receiption is re-enabled:

void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) {
	if(AppMode == LEGIC_DKS) {
		HAL_GPIO_TogglePin(LED_GPIO_Port, LED_Pin);
		if(huart == &huart1) {
			Uart2_Tx = Uart1_Rx;
//			while(huart1.RxState != HAL_UART_STATE_READY);
//			HAL_UART_Receive_IT(&huart1, &Uart1_Rx, 1);
			while(HAL_UART_Receive_IT(&huart1, &Uart1_Rx, 1) != HAL_OK);
 
//			while(huart2.gState != HAL_UART_STATE_READY);
//			HAL_UART_Transmit_IT(&huart2, &Uart2_Tx, 1);
			while(HAL_UART_Transmit(&huart2, &Uart2_Tx, 1, HAL_MAX_DELAY) != HAL_OK);
		}
		else if(huart == &huart2) {
			Uart1_Tx = Uart2_Rx;
//			while(huart2.RxState != HAL_UART_STATE_READY);
//			HAL_UART_Receive_IT(&huart2, &Uart2_Rx, 1);
			while(HAL_UART_Receive_IT(&huart2, &Uart2_Rx, 1) != HAL_OK);
 
//			while(huart1.gState != HAL_UART_STATE_READY);
//			HAL_UART_Transmit_IT(&huart1, &Uart1_Tx, 1);
			while(HAL_UART_Transmit(&huart1, &Uart1_Tx, 1, HAL_MAX_DELAY) != HAL_OK);
		}
	}
}

This is the current code for the callback. The functions which are commented out are different approaches to achieve the pass-through function. As you may assume, none of them is working... 😉

The current setup works like an echo function due to the Rx/Tx pins of the 2nd UART connected together. In the terminal software, typing in single characters works as expected. But transmitting "short" strings (about 20 characters) will lead to missing characters, and bigger strings (500+ characters) will cause the firmware to hang up. Investigating via debugging, it looks like it's caused by how the HAL IRQ handler works.

As long as the receive completed callback is active, it prevents the IRQ handler to update the Tx status, therefore checking the Rx/Tx status in the callback will end up in endless loop.

Note: I know that there are other ways to realize the pass-through function, e.g. DMA, or modifying the HAL source code, using LL instead of HAL, etc. However, I want to know if it's possible with unmodified HAL in interrupt mode and by using only single byte buffers.

I know that this will lead to a worst-case scenario (e.g. interrupt overhead, etc.), but I'm curious if it's possible and of course, I want to know if I'm using the HAL functions the right way.

Regards

9 REPLIES 9
KnarfB
Principal III

I would say: no. You will get sooner or later overrun errors, depending on baud rate and CPU clock. This could be checked by implementing void HAL_UART_ErrorCallback(UART_HandleTypeDef *huart). Unfortunately, the huart->ErrorCode might already be overwritten in that callback after you have called the next HAL_UART function. So, some debugging is needed to check the error condition. You have already sketched alternative approaches.

n2wx
Senior

You can use the HAL to configure the uart but for using it after it's initialized? There are too many non-interrupt-friendly HAL bits and possible races for peace of mind. The USART HAL is appropriate as a synchronous demo for didactic purposes, not much beyond that (other than initing).

As @KnarfB​ suggests be prepared to use an alternative. The good news is it's not difficult code and once you write a generalized module you'll be able to carry it around for your future projects.

S.Ma
Principal

Soon you will use a SW FIFO for RX and TX channels to deal with asynchroneous data flow.

Maybe decide that RX has ISR priority level over TX to mitigate things.

On my side I went to LL for USART even through I played the willing game of starting by HAL. It worked for SPI master (at 12 Mbps), it didn't for USART (at 115kps).

KnarfB
Principal III

Here is an example with UART@115200 Cortex-M0@48MHz:

0693W000000ThsJQAS.png

The UART 'a' is measured on the wire. The falling edge of DIO0 is triggered in the first line of USART2_IRQHandler(), the rising edge of DIO0 is triggered in the first line of HAL_UART_RxCpltCallback() which is even before the call to the next HAL_UART_Receive_IT(). No chance, you will loose char's.

But, DMA works quite nice. I have seen circular RX DMA beeing used without any callback but as a ring buffer with polling __HAL_DMA_GET_COUNTER(huart->hdmarx) in the main loop: https://github.com/yoneken/rosserial_stm32/blob/master/src/ros_lib/STM32Hardware.h

Sure, for water-tight error handling, some more efforts are required.

Mark81
Associate III

> On my side I went to LL for USART even through I played the willing game of starting by HAL. It worked for SPI master (at 12 Mbps), it didn't for USART (at 115kps).

I'm not sure to understand. Did LL work for USART too?

Is there any recommended example?

Just to be sure:

> The rising edge of DIO0 is triggered in the first line of USART2_IRQHandler(), the falling edge of DIO0 is triggered in the first line of HAL_UART_RxCpltCallback()

Did you swap rising/falling, didn't you?

I did, thanks, edited.

KnarfB

RAltm
Senior

Hello,

thank you all for your input. So it seems that the HAL is more for doing a quick evaluation instead of having a usable driver base. I've gone through the HAL UART source code, I think it could be usable if ST would make some extensions to it. Should be possible without breaking the current behaviour.

I'll give the LL approach a try - already got some pitfalls (e.g. enabling LL for a single peripheral kills the SysTick callback, regardless if there are any HAL peripherals...).

The mentioned DMA approach is also interesting, I'll take this into account.

Regards

Piranha
Chief II