I would recommend using an ISR to handle each character that is received by the UART, and maintaining a state machine indicating the current reception status (e.g. waiting for SOF, reading data, waiting for EOF). This has the advantage that it will naturally re-synchronise with the data stream if it is ever interrupted, which is hard to do if you are DMAing fixed sized blobs of data and hoping they are aligned.

I'd also maintain a circular buffer of received messages, which the ISR is writing into. I'd then have my main "thread" loop reading messages out of this queue, processing them, and sending responses. This means that if a bunch of messages come in really quickly, and the processing can't keep up, you won't lose any.

Some commented (untested) code that hopefully shows the idea:

#define DATA_SIZE    4u   // How many bytes in each message
#define NUM_MESSAGES 32u  // How many messages can be in the queue

enum state_e {
    STATE_WAIT_FOR_SOF,
    STATE_DATA,
    STATE_WAIT_FOR_EOF
};

struct message {
    uint8_t data[DATA_SIZE];
};

// A circular buffer of messages
// Volatile since the ISR modifies them. (Probably not necessary, but harmless)
static volatile struct message msgs[NUM_MESSAGES];

// The index for the next entry to be written
// Volatile since the ISR can modify it
static volatile uint8_t head_ix = 0u;

// The index for the next entry to read
static uint8_t tail_ix = 0u;

// Start off waiting for SOF
static enum state_e state = STATE_WAIT_FOR_SOF;

// An index into the current message (being written)'s data[] array
static uint8_t data_ix;

static void process_msg(struct message *msg)
{
    // Process msg and send response
}

int main(void)
{
    // Any other initialisation happens...
  
    while (1)
    {
        // Any messages in the queue? Queue is empty if head_ix == tail_ix.
        if (tail_ix != head_ix)
        {
            // Consume a message
            process_message(&msgs[tail_ix]);
            tail_ix = (tail_ix + 1u) % NUM_MESSAGES;
        }
    }
}

// A ficticious interrupt handler that deals with a single character.
// Your real implementation will have to deal with reading the character
// from the UART, clearing the interrupt, etc, then call something like this.
void handle_rx_interrupt(uint8_t datum)
{
    switch (state)
    {
    case STATE_WAIT_FOR_SOF:
        if (datum == SOF)
        {
            // Got SOF, move to data state
            state = STATE_DATA;

            // Waiting for the first byte of data
            data_ix = 0u;
        }
        break;

    case STATE_DATA:
        // Store this datum in the message
        msgs[head_ix].data[data_ix] = datum;
        ++data_ix;

        // If we have a full message, move to waiting for EOF state
        if (data_ix == DATA_SIZE)
        {
            state = STATE_WAIT_FOR_EOF;
        }
        break;

    case STATE_WAIT_FOR_EOF:
        if (datum == EOF)
        {
            // See if we have space in the queue for this message
            uint8_t new_head = (head_ix + 1u) % NUM_MESSAGES;

            if (new_head != tail_ix)
            {
                // Yes, add this message
                head_ix = new_head;
            }
            else
            {
                // No space for this message. It will just be overwritten
                // in place by the next message
            }
        }

        // No matter what happened, we're now waiting for SOF
        state = STATE_WAIT_FOR_SOF;
        break;
    }
}

Note that in reality there can only ever be NUM_MESSAGES - 1 entries in the queue, due to how the full/empty condition works. The alternative (to reclaim the "wasted" entry) involves things like disabling interrupts in certain places etc, and adds more work for little gain.

Answer from pmacfarlane on Stack Overflow
Top answer
1 of 2
3

I would recommend using an ISR to handle each character that is received by the UART, and maintaining a state machine indicating the current reception status (e.g. waiting for SOF, reading data, waiting for EOF). This has the advantage that it will naturally re-synchronise with the data stream if it is ever interrupted, which is hard to do if you are DMAing fixed sized blobs of data and hoping they are aligned.

I'd also maintain a circular buffer of received messages, which the ISR is writing into. I'd then have my main "thread" loop reading messages out of this queue, processing them, and sending responses. This means that if a bunch of messages come in really quickly, and the processing can't keep up, you won't lose any.

Some commented (untested) code that hopefully shows the idea:

#define DATA_SIZE    4u   // How many bytes in each message
#define NUM_MESSAGES 32u  // How many messages can be in the queue

enum state_e {
    STATE_WAIT_FOR_SOF,
    STATE_DATA,
    STATE_WAIT_FOR_EOF
};

struct message {
    uint8_t data[DATA_SIZE];
};

// A circular buffer of messages
// Volatile since the ISR modifies them. (Probably not necessary, but harmless)
static volatile struct message msgs[NUM_MESSAGES];

// The index for the next entry to be written
// Volatile since the ISR can modify it
static volatile uint8_t head_ix = 0u;

// The index for the next entry to read
static uint8_t tail_ix = 0u;

// Start off waiting for SOF
static enum state_e state = STATE_WAIT_FOR_SOF;

// An index into the current message (being written)'s data[] array
static uint8_t data_ix;

static void process_msg(struct message *msg)
{
    // Process msg and send response
}

int main(void)
{
    // Any other initialisation happens...
  
    while (1)
    {
        // Any messages in the queue? Queue is empty if head_ix == tail_ix.
        if (tail_ix != head_ix)
        {
            // Consume a message
            process_message(&msgs[tail_ix]);
            tail_ix = (tail_ix + 1u) % NUM_MESSAGES;
        }
    }
}

// A ficticious interrupt handler that deals with a single character.
// Your real implementation will have to deal with reading the character
// from the UART, clearing the interrupt, etc, then call something like this.
void handle_rx_interrupt(uint8_t datum)
{
    switch (state)
    {
    case STATE_WAIT_FOR_SOF:
        if (datum == SOF)
        {
            // Got SOF, move to data state
            state = STATE_DATA;

            // Waiting for the first byte of data
            data_ix = 0u;
        }
        break;

    case STATE_DATA:
        // Store this datum in the message
        msgs[head_ix].data[data_ix] = datum;
        ++data_ix;

        // If we have a full message, move to waiting for EOF state
        if (data_ix == DATA_SIZE)
        {
            state = STATE_WAIT_FOR_EOF;
        }
        break;

    case STATE_WAIT_FOR_EOF:
        if (datum == EOF)
        {
            // See if we have space in the queue for this message
            uint8_t new_head = (head_ix + 1u) % NUM_MESSAGES;

            if (new_head != tail_ix)
            {
                // Yes, add this message
                head_ix = new_head;
            }
            else
            {
                // No space for this message. It will just be overwritten
                // in place by the next message
            }
        }

        // No matter what happened, we're now waiting for SOF
        state = STATE_WAIT_FOR_SOF;
        break;
    }
}

Note that in reality there can only ever be NUM_MESSAGES - 1 entries in the queue, due to how the full/empty condition works. The alternative (to reclaim the "wasted" entry) involves things like disabling interrupts in certain places etc, and adds more work for little gain.

2 of 2
1

You can create a basic buffer handling data structure: a pointer to a buffer (or buffer array) and an index.

typedef struct{
  int8_t* RxBuffer;
  volatile uint8_t RxFirstEmptyIndex;
  uint8_t RxBufferMaxLength;
}UART_RxBufferHandle;

Allocate some memory for RxBuffer (malloc or anything else), put max length into the appropriate variable.

Upon data received interrupt, you read UART data register into RxBuffer[RxFirstEmptyIndex], then increment RxFirstEmptyIndex by 1. If it reaches MaxLength (end of buffer), you need to handle it (otherwise USART peripheral will have overrun error and will refuse to work until you resolve it - read incoming data and clear error flag). After receiving N characters you can handle the input (for example, after receiving 6 characters - when RxFirstEmptyIndex becomes 6), and reset RxFirstEmptyIndex back to 0; Volatile because its value is changes in an interrupt.

You can create a global variable from the struct to make it visible for the interrupt handler. Or find some other solution for it.

You can utilize more or less similar mechanisms for other protocols and communications by adding/changing variables in such a struct. But buffer handling usually happens similar to this. Obviously, if you feel like 256 characters is too little, you're welcome to change index counter and length to uint16_t or uint32_t.

🌐
STMicroelectronics Community
community.st.com › t5 › stm32-mcus-products › how-best-to-buffer-receive-this-uart-data › td-p › 59268
How Best To Buffer/Receive This UART Data? - STMicroelectronics Community
November 20, 2023 - ... What does this mean? DMA in cyclic mode allows to receive continuously to a ring buffer. Use a buffer of reasonable size, parse the data in background. STM32L010F4 has only two UARTs, one of them is LPUART with baudrate only up to 9600.
Discussions

c - HAL STM32 Uart Receive Interrupt: Reset Receive Buffer Problem - Stack Overflow
I work with mcu STM32F407VGT6 ,ide STM32CubeIDE. Right now I'm examining some basic operation and I'm little confused about how works uart in interrupt mode when receive data. I will explain what I... More on stackoverflow.com
🌐 stackoverflow.com
c - How receive data with HAL_UART? - Stack Overflow
I'm learning about the STM32. I'm want receive data by UART byte-to-byte with interruption. HAL_UART_Receive_IT(&huart1, buffer, length) Where &huart1 is my uart gate, buffer is the input More on stackoverflow.com
🌐 stackoverflow.com
'Best' way to load UART data to ring buffer with STM32/HAL - Page 1
'Best' way to load UART data to ring buffer with STM32/HAL - Page 1 More on eevblog.com
🌐 eevblog.com
November 15, 2016
embedded - STM32 - HAL_UART_Receive first byte is always zero - Electrical Engineering Stack Exchange
I am implementing communication between Nucleo-F072RB board and an evaluation board with TI BQ75614 BMS IC. I have succeeded to communicate with the IC using 1 Mbps UART interface, successfully sen... More on electronics.stackexchange.com
🌐 electronics.stackexchange.com
🌐
DeepBlue
deepbluembedded.com › home › blog › stm32 uart interrupt, dma, polling | uart receive examples
STM32 UART Interrupt, DMA, Polling | UART Receive Examples
July 26, 2025 - The DMA will notify back the CPU upon reception completion and the received data buffer will be in the pre-programmed location. The DMA can prioritize the channels, decide on the data width, and also the amount of data to be transferred up to ...
🌐
Stack Overflow
stackoverflow.com › questions › 59861517 › hal-stm32-uart-receive-interrupt-reset-receive-buffer-problem
c - HAL STM32 Uart Receive Interrupt: Reset Receive Buffer Problem - Stack Overflow
So imagine buffer is filled less then 1/5. ... while(1){ if(HAL_UART_Receive_IT(&huart3, rxBuff, strlen(rxBuff)) != HAL_OK) { Error_Handler(); } HAL_Delay(3000); temp = rxBuff[0]; if(HAL_UART_Receive_IT(&huart3, rxBuff, strlen(rxBuff)) != HAL_OK) { Error_Handler(); } }
🌐
STMicroelectronics Community
community.st.com › t5 › stm32-mcus-products › how-to-clear-the-receive-buffer-of-hal-uart-receive-it › td-p › 104749
how to clear the receive buffer of HAL_UART_Receiv... - STMicroelectronics Community
September 2, 2022 - If you wish to stop this before you've received all the bytes you've specified, you are supposed to call HAL_UART_AbortReceive_IT() and then wait until the respective callback confirming that the abort is complete, is called.
🌐
STMicroelectronics Community
community.st.com › t5 › stm32-mcus-products › efficient-way-to-process-usart-received-data-and-flush-rx-buffer › td-p › 151379
Efficient way to process USART-received data and flush RX buffer ?
May 28, 2021 - So far, firing HAL_UART_RxCpltCallback() at every single byte received is the most secure way I've found. ... @marcosartore​ perhaps you should receive any character from outside device and then do parsing in main loop? With ring-buffer? Or by using DMA in circular mode and then process any received byte: https://github.com/MaJerle/stm32-usart-uart-dma-rx-tx
🌐
STMicroelectronics Community
community.st.com › stmicroelectronics community › resources › knowledge base › stm32 mcus › implementing uart receive and transmit functions on an stm32
Implementing UART receive and transmit functions on an STM32 | Community
October 21, 2024 - If you initialized your STM32CubeIDE project with the default settings, it will be 115200. Here you can enter a character from your keyboard to interface with UART. As you can see through this example, using a DMA channel functions similarly to using an interrupt. The LED will toggle the entire time and isn’t blocked by the UART receive being incomplete. Similar to the above, we can change the size to 5 to see how the behavior changes. 1. Change the UART buffer initialization to size 5 under /* USER CODE BEGIN PV */
Find elsewhere
Top answer
1 of 1
4

Well, there are a few issues here.

Arrays and their contents

data[i++] = receive;

stores the address of the receive buffer, a memory pointer value, into the data array. That's certainly not what you want. As this is a very basic C programming paradigm, I'd recommend reviewing the chapter on arrays and pointers in a good C textbook.

What you send and what you expect

while (data != '\r')

Even if you'd get the array address and its value right (see above), you are sending a string terminated with '\n', and check for a '\r' character, so change one or the other to get a match.

Missing volatile

uint8_t receive[10];

The receive buffer should be declared volatile, as it would be accessed by an interrupt handler. Otherwise the main program would miss writes to the buffer even if it had checked whether the receiving is complete (see below).

Working with hardware in realtime

while (HAL_UART_Receive_IT(&huart1, buffer, length) != HAL_OK) osDelay(1);

This would enable the UART receive (and error handling) interrupt to receive one byte. That's fine so far, but the function returns before receiving the byte, and as it's called again immediately, it would return HAL_BUSY the second time, and wait a millisecond before attempting it again. In that millisecond, it would miss most of the rest of the transmission, as bytes are arriving faster than that, and your program does nothing about it.

Moreover, the main program does not check when the receive is complete, possibly accessing the buffer before the interrupt handler places a value in it.

If you receive data using interrupts, you'd have to do something about that data in the interrupt handler. (If you don't use interrupts, but polling for data, then be sure that you'd meet the deadline imposed by the speed of the interface).

HAL is not suited for this kind of tasks

HAL has no interface for receiving an unknown length of data terminated by a specific value. Of course the main program can poll the receiver one byte at a time, but then it must ensure that the polling occurs faster than the data comes in. In other words, the program can do very little else while expecting a transmission.

There are some workarounds, but I won't even hint at them now, because they would lead to deadlocks in an RTOS environment which tend to occur at the most inconvenient times, and are quite hard to investigate and to properly avoid.

Write your interrupt handler instead

(all of this is detailed in the Reference Manual for your controller)

  • set the UART interrupt priority NVIC_SetPriority()
  • enable the interrupt with NVIC_EnableIRQ()
  • set the USART_CR1_RXNEIE bit to 1

in the interrupt handler,

  • read the SR register
  • if the RXNE bit is set,
    • read the data from the data register
    • store it in the buffer
    • set a global flag if it matches the terminating character
    • switch to another buffer if more data is expected while the program processes the first line.

Don't forget declaring declaring all variables touched by the interrupt handler as volatile.

🌐
ControllersTech®
controllerstech.com › home › stm32 tutorials › stm32 uart series › receive data using dma
STM32 UART DMA Receive: Normal & Circular Mode (HAL)
We can use the DMA in NORMAL mode to receive this data over the UART and then store the data into the buffer. A situation like this can work for few kilobytes of data as most of the STM32 MCUs has RAM in few kilobytes.
Published   April 30, 2026
🌐
STMicroelectronics Community
community.st.com › t5 › stm32-mcus-products › how-to-clear-uart-s-rx-buffer-in-stm32f030cct6 › td-p › 213196
How to clear UART's RX_buffer in stm32f030cct6 - STMicroelectronics Community
October 28, 2021 - void HAL_UARTEx_RxEventCallback(UART_HandleTypeDef *huart, uint16_t Size) { // process message of size Size in buffer HAL_UARTEx_ReceiveToIdle_IT(&huart2, &buffer, sizeof(buffer) ); }
🌐
ControllersTech®
controllerstech.com › home › stm32 tutorials › stm32 uart series › idle line reception
STM32 UART Idle Line: Receive Data via Interrupt & DMA
In this tutorial, you learned three ways to receive variable-length UART data on STM32 using IDLE line detection — interrupt mode for small frames, single DMA call for kilobyte-range data, and circular DMA with chunked memcpy for large continuous streams. The key things to carry forward: always re-arm in interrupt mode, never re-arm in DMA Circular mode, understand that Size in DMA mode is a cumulative buffer position not a per-callback byte count, and keep callback processing short to avoid blocking in interrupt context.
Published   April 30, 2026
Top answer
1 of 2
4

AFAIK there is no similar function in HAL. The common way to do it is:

// index of first unread byte in the RX buffer
static uint32_t rxpos = 0;
// find end of data position in the RX buffer
uint32_t pos = rxsize - puart->hdmarx->Instance->CNDTR;
// pass accumulated bytes to listener
if (pos != rxpos) {
    if (pos > rxpos) {
        // Process data directly by subtracting indexes
            processData(rxbuffer + rxpos, pos - rxpos);
        } else {
        // First process data to the end of the buffer
            processData(rxbuffer + rxpos, rxsize - rxpos);
        // Continue with beginning of the buffer
            processData(rxbuffer, pos);
        }
}
// update starting index
rxpos = (pos == rxsize)? 0 : pos;

You can call the above code from HAL_UART_RxHalfCpltCallback, HAL_UART_RxCpltCallback and, if you have code for processing either IDLE or RTO events, from corresponding handlers as well.

Note that current HAL implementation treats RTO as an error, eliminating the possibility to handle it properly. You'd have to bypass that if you want to get circular DMA transfers handled without added latency.

UPDATE

To answer your question in comments, please note that checking for available bytes in a buffer and using blocking HAL_UART_Receive call are two different approaches. You did not specify baud rate or whether or not you have variable message length. These two really define correct method of doing things in STM32.

But in general, you either need low latency communication, or you don't care how long it takes and your code has nothing to do meanwhile.

In the former case you have to use either interrupt or circular DMA versions of Receive function. With DMA you can check for available data length as described above.

In the latter you can do a loop calling blocking Receive which will either give you expected data or will time out. At low speeds you may even be better handling incoming data byte by byte in UART interrupt handler.

The methods available to you are quite different from Arduino approach. I would suggest to follow the examples available for HAL instead of trying to fit HAL functions into Arduino perspective.

2 of 2
3

Arduino environment does a lot of stuff behind the scenes to implement it. It sets up data reception to happen with interrupts that stores the received data into a buffer, which keeps state how many bytes there is in the buffer.

Obviously, no MCU library will not provide you with the exact same interface, for multiple reasons. One of the reasons is that you can just as easily implement the same concept yourself on any MCU, with or without any HAL. If you don't need a buffer or interrupts, you can simply use the HAL to see if has received a byte or not, and if it has then read the data. Or if you want similar what Arduino does, the concept is exactly the same - set up receive interrupts, store data into buffer, keep count how many bytes there are in the buffer.

🌐
ControllersTech®
controllerstech.com › home › stm32 tutorials › stm32 low level › stm32 uart using ll drivers (part 4): receive data in interrupt mode
STM32 UART using LL Drivers (Part 4): Receive Data in Interrupt Mode
March 25, 2026 - In this tutorial, we learned how to receive UART data on an STM32 using interrupt mode with LL drivers. We started by understanding the RXNE flag and how the NVIC allows the CPU to respond to UART interrupts. Then, we configured the UART and NVIC, created a receive buffer, and wrote a USART interrupt handler to store incoming data in a circular buffer.
🌐
EmbeddedExpertIO
blog.embeddedexpert.io
STM32 UART Part 4: Receiving Unknown Length of Data – EmbeddedExpertIO
Useful because the same callback is shared by all UART peripherals (USART1, USART2, etc.). ... Stores the newly received byte (TempData) into the receive buffer (uart_rx_data) at position idx.
🌐
DeepBlue
deepbluembedded.com › home › blog › stm32 uart (usart) tutorial + examples (dma, interrupt)
STM32 UART (USART) Tutorial + Examples (DMA, Interrupt)
June 12, 2024 - Transfer detection flags: (Receive buffer full – Transmit buffer empty – End of Transmission flags) 4 error detection flags: (Overrun error – Noise error – Frame error – Parity error) ... In this section, we’ll get a deep insight into the STM32 USART module hardware, its block diagram, functionalities, BRG, modes of operations, and data reception/transmission.
🌐
STM32F4 Discovery
stm32f4-discovery.net › home › stm32 tutorial: efficiently receive uart data using dma
STM32 tutorial: Efficiently receive UART data using DMA - STM32F4 Discovery
November 30, 2019 - It is good to set receive buffer to at least 100 bytes unless you can make sure your processing approach is faster than burst of data ... This section describes possible 4 possible cases and one additional which explains why HT/TC events are necessary by application ...