cancel
Showing results for 
Search instead for 
Did you mean: 

Best way to implement UART polling packet receiver.

federico.massimi
Associate III

Hi, I'm doing a firmware for a micro STM32F4.

The micro is always in an infinite loop in the main(), at the end of each cycle it should check if there are any data on UART buffer, in case there are read all data and continue with the normal infinite loop.

Something like that:

while (1)  {
          do things;
          do things;
           .....
 
           if (is there data on the UART buffer?)
                  read data; 
            }
}

I have read on forum and docuimentation that there are many ways to do it (USART_GetFlagStatus, USART_FLAG_RXNE, HAL_UART_Receive) but since it is the first time that I do such a thing, can someone give me some example code or some indication?

Thanks in advance

PS: the package to receive is of variable length, but I can know when it is finished analyzing the received data.

9 REPLIES 9
Ozone
Lead II

I used to do it this way:

A UART receive interrupt does minimal syntax checks (packet start/end character, buffer overflow), and stores packets.

When a packet end character is detected (in the receive interrupt handler) , the packet is copied, and a flag is set.

The application main loop checks for the flag periodically, processes the packet, and resets the flag.

If the interrupt handler finds the flag set, a packet overflow occured. Proceed at your discretion - I would drop the packet.

Reception in interrupt and processing in main loop work on different buffers.

The interrupt handler doesn't care about packet content, only works on start/end. And needs to handle receive errors or corrupted packets (buffer overflow).

Bob S
Principal

Or use DMA receive in circular mode on the UART and do all that processing in the main code loop. No interrupt code needed. There have been posts elsewhere on this forum that show (or at least hint) at how to handle extracting bytes from the receive buffer. Probably search for "uart" "dma" and "circular" would be my guess.

The F4 USART has no buffer depth or FIFO, this is going to fail.

What you need to do is use interrupts to manage FIFO/Ring Buffers in the background to service the critical time expectations, you can then more leisurely consume buffer content in your primary loop. Set a buffer depth which matches the in/out going data rates and your speed of processing/production.

Tips, Buy me a coffee, or three.. PayPal Venmo
Up vote any posts that you find helpful, it shows what's working..
federico.massimi
Associate III

@Ozone​ 

That's exactly what I tried to do but I have problems:

this is my code:

In the stm32f4xx_it.c i change function void USART1_IRQHandler(void) in this way to handle IDLE line detection interrupt

void USART1_IRQHandler(void)
{
  /* USER CODE BEGIN USART1_IRQn 0 */
	HAL_IWDG_Refresh(&hiwdg); // reset watchdog
 
	if (huart1.Instance->SR & UART_FLAG_IDLE) { 		/* if Idle-line flag is set */
		hdma_usart1_rx.Instance->CR &= ~DMA_SxCR_EN;    /* Disabling DMA -> force transfer complete interrupt if enabled */
	}
  /* USER CODE END USART1_IRQn 0 */
  HAL_UART_IRQHandler(&huart1);
  /* USER CODE BEGIN USART1_IRQn 1 */
  HAL_IWDG_Refresh(&hiwdg); // reset watchdog
  /* USER CODE END USART1_IRQn 1 */
}

Then my callback is:

void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) {
        uint16_t data_size = RX_BUFFER_SIZE - __HAL_DMA_GET_COUNTER(huart1.hdmarx);
	process_buffer( usartRxBuffer, data_size ); // this function set the flag checked in the mainloop
 
	memset(usartRxBuffer, 0x00, RX_BUFFER_SIZE); // clear buffer
 
	HAL_UART_Receive_DMA(&huart1, usartRxBuffer, RX_BUFFER_SIZE); // restart receiver for next packet
}

Since I also send data with the same UART in the mainloop, when I restart with the function ... in the callback, UART often gets stuck and I have to reset the micro.

I found these discussions about it, it seems a known problem:

https://community.st.com/s/question/0D50X00009XkgrbSAB/best-way-to-use-hal-uart-receiver-it-function

https://community.st.com/s/question/0D50X00009XkgrbSAB/best-way-to-use-hal-uart-receiver-it-function

but I failed to apply the proposed solutions in my specific case, so I wanted to switch to polling to avoid this situation.

Do you know how to solve for the interrupt in my specific case?

@Community member​ 

Are you referring to the solution proposed by @Ozone​  or @Bob S​  ?

> Since I also send data with the same UART in the mainloop, when I restart with the function ... in the callback, UART often gets stuck and I have to reset the micro.

Sorry, but I have no experience with Cube code.

I rejected the Cube/HAL stuff from the beginning, it tends to produce more problems then it solves - at least for non-trivial projects.

Callbacks are a dangerous thing, especially for beginners not realizing the implications of interrupt context.

Practically, full-duplex and independant UART receive and transmit is possible.

Transmission would be based on the TxE interrupt, or the TC interrupt if using DMA.

My projects are based on the SPL, or CMSIS-header based "register hacking".

federico.massimi
Associate III

@Ozone​  How can I manage UART, Interrupt without using the HAL_xxxx functions?

You know where I can find code examples or docuemntation to start, everything I found uses the HAL_*** functions.

Take a look at link below with examples. You can run them in CubeIDE directly, no HAL in this case.

https://github.com/MaJerle/STM32_USART_DMA_RX

Cheers, Tilen

from what I see in the examples you sent me, in my case it would be enough to delete the HAL_UART_RxCpltCallback and write the USART1_IRQHandler like:

void
USART1_IRQHandler(void) {
      /* Check for IDLE line interrupt */
      if (LL_USART_IsEnabledIT_IDLE(USART3) && LL_USART_IsActiveFlag_IDLE(USART3)) {
              LL_USART_ClearFlag_IDLE(USART3);        /* Clear IDLE line flag */
              
              uint16_t data_size = RX_BUFFER_SIZE - __HAL_DMA_GET_COUNTER(huart1.hdmarx);
	      process_buffer( usartRxBuffer, data_size ); // this function set the flag checked in the mainloop
 
	      memset(usartRxBuffer, 0x00, RX_BUFFER_SIZE); // clear buffer
       }
    
}

in practice LL_USART_ClearFlag_IDLE (USART1); prepare the UART for the next package, did I get it?

Yes, you might try the LL approach, a "low-layer" subset of Cube/Hal, with sometimes incomplete support for certain MCU types.

Last time I checked, SPL code was still available for download from ST, at least for the F407 (F4 discovery).

Either the Discovery firmware package, or the more generic CMSIS DSP package.