STM32 UART Part 4: Receiving Unknown Length of Data

In this part of our UART guide series, we will explore how to receive data of unknown length using interrupts triggered on each incoming character.
This approach allows the STM32 to handle variable-length messages efficiently, reacting in real time without blocking the CPU.

In this guide, we shall cover the following:

  • Introduction.
  • Firmware Setup.
  • Results.

1. Introduction:

Up to this point, we have explored UART transmission and reception using polling, interrupts, and DMA, each suited for specific scenarios. However, many real-world applications introduce a new challenge: the incoming data does not always have a fixed or known length. For example, commands from a PC terminal, GPS NMEA sentences, or sensor messages may vary in length and arrive asynchronously. In such cases, simply waiting for a predefined number of bytes is not practical.

This is where single-character interrupt reception becomes a powerful tool. Instead of waiting for an entire buffer to be filled, the UART peripheral can be configured to generate an interrupt every time a new character (byte) arrives. The CPU then quickly retrieves this character inside the interrupt service routine (ISR) and stores it in a buffer for further processing. This allows the system to build a message byte by byte, handling variable-length data streams naturally.

From a system design perspective, this approach provides several advantages:

  • Responsiveness – The CPU reacts immediately to each new character, ensuring that data is not lost, even at higher baud rates.
  • Flexibility – Since the length of incoming data is not fixed, the application can define its own rules for detecting the end of a message. Common methods include looking for a special termination character (e.g., newline \n), monitoring for a timeout between bytes, or using higher-level protocols.
  • Efficiency – While this method still requires frequent interrupts (one per byte), it prevents the CPU from blocking in polling loops and allows it to perform other tasks when no data is arriving.

It is important to note the trade-offs as well. Handling every single character in an interrupt can introduce overhead, especially at very high baud rates, since the CPU must service many interrupts per second. This makes buffer management and ISR efficiency critical to avoid data loss. For most medium-speed UART applications, however, this method provides an excellent balance between reliability and flexibility.

In summary, single-character interrupt reception is one of the most practical solutions for working with UART when the message size cannot be determined in advance. It gives developers full control over how to interpret the incoming stream, while still maintaining non-blocking, event-driven operation that fits well into multitasking or real-time systems.

2. Firmware Development:

We shall continue from this guide here.

Open main.c. In user code begin PV, declare the following:

#define BufferSize 100

uint8_t uart_rx_data[BufferSize]={0};

This is the buffer to hold the received data, the size is determined by BufferSize, in this guide, 100 bytes is enough.

Next, declare the following variable:

uint8_t idx=0;

This variable will track the number of received data and store it in the correct place in the buffer.

Finally, declare the following variable:

uint8_t TempData=0;

This will hold the incoming data from the UART. Then we shall move it to the buffer.

Next, in user code begin 2 in main function, start the UART reception in interrupt mode as follows:

 HAL_UART_Receive_IT(&huart2, &TempData, 1);

This with first character is received, an interrupt callback will be called:

void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
	if(huart->Instance==USART2)
	{

		uart_rx_data[idx]=TempData;
		if(++idx>=BufferSize){idx=0;}
		HAL_UART_Receive_IT(huart, &TempData, 1);
	}

}

Function:

void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
  • This is a HAL-provided weak callback function.
  • It is automatically called by the HAL library whenever a UART reception (in interrupt mode) is completed.
  • You override it in your code to define what should happen when a byte is received.

Inside the function

if(huart->Instance == USART2)
  • Ensures this code only runs for USART2.
  • Useful because the same callback is shared by all UART peripherals (USART1, USART2, etc.).

uart_rx_data[idx] = TempData;
  • Stores the newly received byte (TempData) into the receive buffer (uart_rx_data) at position idx.
  • This is building your ring buffer byte by byte.

if (++idx >= BufferSize) { idx = 0; }
  • Increments the buffer index (idx).
  • If the index reaches the buffer’s maximum size, it wraps around to 0.
  • This creates the circular behavior of the ring buffer → new data overwrites from the beginning when the buffer is full.

HAL_UART_Receive_IT(huart, &TempData, 1);
  • Re-arms the UART reception interrupt for the next byte.
  • Without this line, you would only capture one character and stop.
  • This makes reception continuous, one byte at a time.

Summary

This callback:

  1. Runs when a byte is received by USART2.
  2. Stores the byte into your buffer at the current index.
  3. Advances the index (with wrap-around).
  4. Restarts UART reception in interrupt mode for the next byte.

In effect, it creates a continuous stream receiver, feeding data into a circular buffer, which the rest of your program can later read and process.

That all for the firmware. Save the project and start a debugging session as follows:

3. Results:

As you can see, the buffer is being filled despite we are transmitting variable length of data.

Next, we shall use more advanced techniques o handle this using DMA.

Stay tuned.

Happy coding 😉

Add Comment

Your email address will not be published. Required fields are marked *