STM32 UART Part 3: Receiving Data using Polling and Interrupt

In this part, we focus on receiving data using both polling and interrupt-driven methods. Polling continuously checks the UART status flags for incoming data, while interrupt mode allows the CPU to handle other tasks and only responds when data actually arrives.

In this guide, we shall cover the following:

  • Polling vs interrupt.
  • Polling mode firmware development.
  • Interrupt mode firmware development.

1. Polling Vs Interrupt:

Polling in STM32 UART Reception

  • How it works:
    In polling mode, the CPU actively waits and continuously checks (or “polls”) the UART status flags (like RXNE – Receive Data Register Not Empty). The CPU keeps asking: “Has a new byte arrived?” until the flag is set.
  • CPU involvement:
    The CPU is fully occupied during this waiting, wasting cycles if no data is arriving.
  • Data handling:
    Once the flag indicates data is available, the CPU immediately reads the byte from the UART data register (USARTx->RDR). If the CPU is late, there’s a risk of missing data if the buffer overflows.
  • Performance:
    This method is simple but inefficient, especially if data arrives infrequently or irregularly. The CPU is locked in checking instead of doing useful work.
  • Use cases:
    Useful for very simple or low-speed communication, testing, or when data reception is rare and predictable.

Interrupt-based Reception in STM32

  • How it works:
    In interrupt mode, the CPU is free to execute other tasks until the UART peripheral signals that a new byte has arrived. When the RXNE flag is set, the UART triggers an interrupt request (IRQ) to the NVIC (Nested Vectored Interrupt Controller).
  • CPU involvement:
    The CPU only reacts when needed. It executes the corresponding ISR (Interrupt Service Routine), where the data is read from the UART data register and processed or stored in a buffer.
  • Efficiency:
    Since the CPU is not constantly polling, interrupt mode is far more efficient. It allows the MCU to handle multiple peripherals or tasks concurrently.
  • Reliability:
    Interrupts reduce the chance of missing data, provided the ISR is short and fast. However, if interrupts are delayed (due to higher-priority interrupts or disabled IRQs), data loss is still possible at very high baud rates.
  • Use cases:
    Best for moderate to high-speed communication, multitasking systems, and FreeRTOS-based applications where CPU time must be shared efficiently.

Polling vs Interrupt (STM32 perspective)

  • Polling = CPU babysits UART → Simple but wasteful.
  • Interrupts = UART babysits CPU → Efficient, event-driven, and scalable.

In STM32 projects, polling is often only used for quick tests or debugging, while interrupt-driven reception is the practical choice for real-time embedded systems.

Polling vs Interrupt in STM32 UART Reception

AspectPollingInterrupt
How it worksCPU keeps checking UART status flag (RXNE) in a loop.UART peripheral triggers an interrupt when data arrives.
CPU usageHigh (CPU is busy-waiting, wasting cycles).Low (CPU free until interrupt occurs).
EfficiencyInefficient, especially at low data rates.Efficient, scales well with varying data rates.
ReliabilityRisk of missing data if CPU is late in checking.More reliable; ISR reacts immediately to incoming data.
Response timeDetermined by how fast the loop checks the flag.Determined by NVIC interrupt latency (very fast).
ComplexityVery simple to implement.Requires interrupt configuration & ISR management.
Best forDebugging, very low-speed or simple cases.Real applications, multitasking systems, moderate/high-speed comms.
Example STM32 flagCPU polls USARTx->ISR & RXNE.RXNE interrupt triggers ISR via NVIC.

In short:

  • Polling = wasteful babysitter (CPU always watching).
  • Interrupt = efficient notifier (UART tells CPU only when needed).

2. Polling Mode Firmware Development:

We shall continue from the previous guide here.

There won’t be any modification since the interrupt is already enabled from the previous guide.

In main.c fine, in user begin PV (Private variable), declare the following array:

uint8_t uart_rx_data[20]={0};

This array shall hold the received data from the UART in both, interrupt and polling modes.

Next, in while loop of the main function, start the uart to receive in polling mode as follows:

 HAL_UART_Receive(&huart2, uart_rx_data, 5, HAL_MAX_DELAY);

Explanation of Parameters

  1. &huart2
    • Handle of the UART peripheral you’re using.
    • huart2 refers to USART2 instance configured earlier in CubeMX or manually.
    • Contains all settings (baudrate, word length, parity, etc.) and hardware registers mapping.
  2. uart_rx_data
    • Pointer to the buffer where received data will be stored.
    • In this case, the received bytes will be written into uart_rx_data[].
  3. 5
    • Number of bytes you want to receive.
    • The function will keep receiving until exactly 5 bytes are received.
  4. HAL_MAX_DELAY
    • Timeout duration (in milliseconds).
    • HAL_MAX_DELAY means it will wait forever (blocking) until all 5 bytes are received.

What Happens Internally

  • The function blocks the CPU until either:
    1. All 5 bytes are received into uart_rx_data, OR
    2. Timeout expires (but here it never expires since you used HAL_MAX_DELAY).
  • It works in polling mode:
    • The HAL driver keeps checking the RXNE flag (Receive Not Empty) inside USART registers.
    • Once each byte arrives, it is copied into your buffer.
    • After 5 bytes, the function returns HAL_OK.

Once all the 5 bytes are received, we shall echo back the received characters are follows:

HAL_UART_Transmit(&huart2, uart_rx_data, 5, 20);

Thats all for the polling mode. Save the project and run it on your MCU as follows:

Using your favourite uart terminal and set the baud rate to 115200, send any 5 characters and you should get the transmitted characters as shown below:

Notice that I am sending the data each 1 second and I am getting the characters I send back without any issue.

Notice that the CPU will be spending most of its time waiting for the incoming character and store it in the buffer. This is inefficient method to receive data.

3. Interrupt Mode Firmware Development:

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

HAL_UART_Receive_IT(&huart2, uart_rx_data, 5);

Once all 5 characters have been received, HAL_UART_RxCpltCallback function shall be called.

In user code begin 0:

void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
	if(huart->Instance==USART2)
	{
		HAL_UART_Transmit(&huart2, uart_rx_data, 5, 20);

		HAL_UART_Receive_IT(&huart2, uart_rx_data, 5);

	}

}

First check which UART generate the interrupt (useful when multiple uart is involved) which is in this case USART2, then echo the received data using polling mode and start the reception again in the interrupt mode.

Note: Make sure the while loop is empty.

Thats all for the interrupt mode. Save the project and run it on your MCU as follows:

Using your favourite uart terminal and set the baud rate to 115200, send any 5 characters and you should get the transmitted characters as shown below:

Note that the loop shall send data each 10ms and we are receiving each character without any issue.

In part 4, we shall start using the interrupt to receive unknown length of data using both interrupt and DMA.

Stay tuned.

Happy coding 😉

Add Comment

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