I'm not a professional, but I guess the only way is to receive 1 byte at a time, and copy it to a ring buffer or other buffer that can store multiple messages (or 1 if you can handle one message fast enough).

Than you have two possibilities:

  1. If it is easy to find out if the end of a message is received (for example when it ends with a certain value, or if you store the expected number of bytes so you can check against that value), than verify this in the interrupt, and set a Boolean. This Boolean can be checked in the main (non interrupt) code to process the message and clear the message. A ring buffer is ideal for this.

  2. If it is not easy to find out the end of a message, than set a Boolean that a new byte has been received, and in the main code verify if a complete message is received, if so, execute it and delete it.

Pseudo code for possibility 1:

Global

volatile uint8_t ringBuffer[MAX_BUFFFER]; 
volatile uint8_t … // Additional variables to keep track of ring buffer space
volatile bool uartMessageCompleted = false;

Initially:

HAL_UART_Receive_IT(1 byte)

Callback:

void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
    Store byte in ring buffer
    HAL_UART_Receive_IT(1 byte)

    if (isCompleteUartMessageReceived())
    {
       uartMessageCompleted = true;
    }
}

bool isCompleteUartMessageReceived
{
    return true if a complete message is received
}

In Main (or a function called from main):

void main()
{
   …
   if (uartMessageCompleted )
   {
      excecuteUartMessage(); // Implement yourself
      remove message from ring buffer
   }
   …
}
Answer from Michel Keijzers on Stack Exchange
🌐
DeepBlue
deepbluembedded.com › home › blog › stm32 uart receive unknown length (idle line detection)
STM32 UART Receive Unknown Length (IDLE Line Detection)
June 5, 2024 - The IDLE line detection feature in STM32’s UART hardware allows the microcontroller to detect a period of inactivity on the UART line. When the line remains idle for one frame duration, an IDLE flag is set in the UART status register. This flag can trigger an interrupt, allowing the software to process the received data. IDLE line detection is particularly useful for receiving unknown-length data because it provides a clear indication of when the transmission has ended.
People also ask

Does this work on all STM32 families?
HAL_UARTEx_ReceiveToIdle_IT() and HAL_UARTEx_ReceiveToIdle_DMA() are available across all STM32 families — F0, F1, F4, F7, H7, G0, G4, L4, and others — as part of the standard HAL UART extended API. If you get a compilation error, confirm the UART global interrupt is enabled in NVIC Settings (interrupt mode), or the UART RX DMA stream is added in DMA Settings (DMA mode), and that you are using HAL_UARTEx_ (capital E in Ex) not HAL_UART_.
🌐
controllerstech.com
controllerstech.com › home › stm32 tutorials › stm32 uart series › idle line reception
STM32 UART Idle Line: Receive Data via Interrupt & DMA
Can I process received data directly inside HAL_UARTEx_RxEventCallback?
Yes for short operations — setting flags, copying bytes, updating indices. Avoid long blocking operations (UART/SPI transmits, HAL_Delay, heavy computation) inside the callback because it runs in interrupt context. For anything that takes time, set a flag in the callback and do the actual processing in the while(1) main loop.
🌐
controllerstech.com
controllerstech.com › home › stm32 tutorials › stm32 uart series › idle line reception
STM32 UART Idle Line: Receive Data via Interrupt & DMA
Why do I NOT re-arm in DMA mode?
DMA mode with Circular configuration runs continuously by hardware. The DMA controller wraps to the start of the buffer automatically when it reaches the end and keeps writing without any software intervention. Re-arming would restart the DMA from the beginning, losing the current buffer position and breaking the Size cumulative tracking. Only re-arm in DMA mode if you explicitly want to restart reception from scratch (e.g. after a protocol reset).
🌐
controllerstech.com
controllerstech.com › home › stm32 tutorials › stm32 uart series › idle line reception
STM32 UART Idle Line: Receive Data via Interrupt & DMA
Top answer
1 of 4
5

I'm not a professional, but I guess the only way is to receive 1 byte at a time, and copy it to a ring buffer or other buffer that can store multiple messages (or 1 if you can handle one message fast enough).

Than you have two possibilities:

  1. If it is easy to find out if the end of a message is received (for example when it ends with a certain value, or if you store the expected number of bytes so you can check against that value), than verify this in the interrupt, and set a Boolean. This Boolean can be checked in the main (non interrupt) code to process the message and clear the message. A ring buffer is ideal for this.

  2. If it is not easy to find out the end of a message, than set a Boolean that a new byte has been received, and in the main code verify if a complete message is received, if so, execute it and delete it.

Pseudo code for possibility 1:

Global

volatile uint8_t ringBuffer[MAX_BUFFFER]; 
volatile uint8_t … // Additional variables to keep track of ring buffer space
volatile bool uartMessageCompleted = false;

Initially:

HAL_UART_Receive_IT(1 byte)

Callback:

void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
    Store byte in ring buffer
    HAL_UART_Receive_IT(1 byte)

    if (isCompleteUartMessageReceived())
    {
       uartMessageCompleted = true;
    }
}

bool isCompleteUartMessageReceived
{
    return true if a complete message is received
}

In Main (or a function called from main):

void main()
{
   …
   if (uartMessageCompleted )
   {
      excecuteUartMessage(); // Implement yourself
      remove message from ring buffer
   }
   …
}
2 of 4
8

The best way, and the way recommended by ST in a blog post on their old forum, is to use IDLE line detection linked to the DMA controller.

A simple configuration would set the DMA to detect the Maximum possible UART message length you expect to handle, which would trigger the UART Rx Complete Callback. In addition you would enable the UART IDLE interrupt, and when that interrupt is triggered, you would force the same transfer complete callback (this is achieved on some STM32's by disabling the associated DMA Stream) but this time checking the DMA's NDTR (Number of Data Register) to read the received number of bytes in the UART Rx Complete Callback.

/**
 * \brief       Global interrupt handler for USART2
 */
void USART2_IRQHandler(void) {
    /* Check for IDLE flag */
    if (USART2->SR & USART_FLAG_IDLE) {         /* We want IDLE flag only */
        /* This part is important */
        /* Clear IDLE flag by reading status register first */
        /* And follow by reading data register */
        volatile uint32_t tmp;                  /* Must be volatile to prevent optimizations */
        tmp = USART2->SR;                       /* Read status register */
        tmp = USART2->DR;                       /* Read data register */
        (void)tmp;                              /* Prevent compiler warnings */
        DMA1_Stream5->CR &= ~DMA_SxCR_EN;       /* Disabling DMA will force transfer complete interrupt if enabled */
    }
}

This blog post has a more detailed example and explanation.

The post by ST detailing the implementation seems to have been lost during their migration but try this link and click on the attachment to see a code example.

We can use very useful feature in UART peripheral, called IDLE line detection. Idle line is detected on RX line when there is no received byte for more than 1 byte time length. So, if we receive 10 bytes one after another (no delay), IDLE line is detected after 11th bytes should be received but it is not.

We are able to force DMA to call transfer complete interrupt when we disable DMA stream by hand, thus disabling enable bit in stream control register. In this case DMA will make an interrupt if they are enabled and we can read number of bytes we need to still receive by reading NDTR register in DMA stream. From here, we can calculate how many elements we already received.

🌐
STMicroelectronics Community
community.st.com › t5 › stm32-mcus-products › stm32h7-uart-dma-receive-unknown-length-of-data › td-p › 86869
Solved: STM32H7 UART DMA Receive unknown length of data. - STMicroelectronics Community
July 21, 2022 - Solved: I use STM32H753 and USART1 with DMA to receive data of unknown length (Min: 8 bytes Max: 512 bytes) from PC. Data will be sent in burst after
🌐
GitHub
github.com › cuongdv1 › STM32_USART_DMA_RX
GitHub - cuongdv1/STM32_USART_DMA_RX: Examples for efficient use of DMA for UART receive on STM32 microcontrollers when receive length is unknown
If even this is not available, ... examples below. IDLE line detection (or Receiver Timeout) can trigger USART interrupt when receive line is steady without any communication for at least 1 character length....
Starred by 7 users
Forked by 6 users
Languages   C 96.8% | Assembly 1.7% | C++ 1.5% | C 96.8% | Assembly 1.7% | C++ 1.5%
Top answer
1 of 1
3

Well,the best way is two buffers and FreeRTOS. The problem is that you may not receive one by one, but can get number of bytes at one time. So you need two buffers: "receive buffer" and "data buffer". Also you may need some kind of "clear buffer" to empty your others buffers. That is faster then empty buffer in cycle.

So.Let's see. I write example for STM32F303. First of all create a task for FreeRTOS in CubeMX, let it be consoleTaskHandle. Then add or edit in stm32f3xx_it.c:

/* Private includes -----------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include "cmsis_os.h"
/* USER CODE END Includes */

//........................
//........................

/* USER CODE BEGIN EV */
extern osThreadId consoleTaskHandle;
/* USER CODE END EV */

//........................
//........................

/**
* @brief This function handles USART2 global interrupt
*/
void USART2_IRQHandler(void) {
    /* USER CODE BEGIN USART2_IRQn 0 */
    BaseType_t xHigherPriorityTaskWoken;
    xHigherPriorityTaskWoken = pdFALSE;

    vTaskNotifyGiveFromISR(consoleTaskHandle, &xHigherPriorityTaskWoken);

    portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
    /* USER CODE END USART2_IRQn 0 */
    HAL_UART_IRQHandler(&huart2);
    /* USER CODE BEGIN USART2_IRQn 1 */

    /* USER CODE END USART2_IRQn 1 */
}

That means, that every time IT executes we will send notification to FreeRTOS task.

Now, let's write task to work with our recieved data. So main.c:

/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include <string.h>
#include <stdio.h>
/* USER CODE END Includes */

//........................
//........................

/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */
/* USART */
#define USART_CONSOLE_BUFFER_SIZE ((uint8_t)64)
/* USER CODE END PD */

//........................
//........................

/* USER CODE BEGIN PV */
/* USART */
static const char emptyBuffer[USART_CONSOLE_BUFFER_SIZE] = {0x00};
char consoleRxBuffer[USART_CONSOLE_BUFFER_SIZE] = {0x00};
char consoleTxBuffer[USART_CONSOLE_BUFFER_SIZE] = {0x00};
/* USER CODE END PV */

//........................
//........................

/* USER CODE BEGIN Header_StartConsoleTask */
/**
* @brief Function implementing the consoleTask thread.
* @param argument: Not used
* @retval None
*/
/* USER CODE END Header_StartConsoleTask */
void StartConsoleTask(void const * argument) {
    /* USER CODE BEGIN StartConsoleTask */
    uint32_t ulNotifiedValue     = 0;
    static uint8_t rxBufferLen   = 0; /*!< Current RX buffer length */ 
    static uint8_t txBufferLen   = 0; /*!< Current TX buffer length */
    static uint8_t lastBufferLen = 0; /*!< Last RX buffer length */


    static bool gotCommand = false;  /*!< Recieved command flag */

    char command[USART_CONSOLE_BUFFER_SIZE] = {0x00}; /*!< Command buffer */

    if (HAL_UART_Receive_IT(&huart2, (uint8_t *)consoleRxBuffer, USART_CONSOLE_BUFFER_SIZE) != HAL_OK) {
        // Uart error handle
    }

    for (;;) {
        ulNotifiedValue = ulTaskNotifyTake(pdFALSE, (TickType_t) ULONG_MAX);

        // Got notification from IT
        if (ulNotifiedValue > 0) {
            // Let's check the RX buffer first
            rxBufferLen = strlen(consoleRxBuffer);

            if (rxBufferLen > 0) {
                // Let's send data back to console
                // or we gonna print data blindly
                if (rxBufferLen > lastBufferLen) {
                    /*
                        If the last symbol is '\r', then:
                            - Stop RX
                            - Send '\n' next
                            - Copy RX buffer to command buffer
                            - Empty RX buffer and start IT again
                            - Set 'GotCommand flag'
                        ELSE
                            - Simply send data back to cosole
                     */

                    // Count amount of data to send back
                    txBufferLen = rxBufferLen - lastBufferLen;

                    // Copy data from RX buffer to TX buffer
                    strncpy(consoleTxBuffer, &consoleRxBuffer[lastBufferLen], txBufferLen);
                    consoleTxBuffer[txBufferLen] = '\0';

                    if (consoleRxBuffer[rxBufferLen - 1] == '\r') {
                        gotCommand = true;

                        HAL_UART_AbortReceive_IT(&huart2);

                        consoleTxBuffer[txBufferLen] = '\n';
                        consoleTxBuffer[txBufferLen + 1] = '\0';

                        /* Copy command from RX buffer to command buffer */
                        // Let's trim spaces from the beginning
                        uint8_t commandStartPos = 0;

                        for (int i = 0; i < USART_CONSOLE_BUFFER_SIZE; ++i) {
                            if (consoleRxBuffer[0] == ' ') {
                                commandStartPos++;
                            } else {
                                break;
                            }
                        }

                        strncpy(command, &consoleRxBuffer[commandStartPos], strlen(consoleRxBuffer) - commandStartPos + 1);

                        /*
                            Empty RX buffer
                            I clean it by copying empty buffer to RX. That increases memory 
                            consumption, but works faster. You can do it in cycle.
                         */
                        strncpy(consoleRxBuffer, emptyBuffer, USART_CONSOLE_BUFFER_SIZE);

                        lastBufferLen = 0;

                        // Start the UART IT again
                        if (HAL_UART_Receive_IT(&huart2, (uint8_t *)consoleRxBuffer, USART_CONSOLE_BUFFER_SIZE) != HAL_OK) {
                            // Uart error handle
                        }

                    } else {
                        lastBufferLen = rxBufferLen;
                    }

                    // Send data back to console through UART DMA (you must turn it on in settings)
                    if (HAL_UART_Transmit_DMA(&huart2, (uint8_t *)consoleTxBuffer, strlen(consoleTxBuffer)) != HAL_OK) {
                        // Uart error handle
                    }

                    while (__HAL_UART_GET_FLAG(&huart2, UART_FLAG_TC) == RESET) {
                        vTaskDelay(1);
                    }
                }

                /* If we got command in last condition */
                if (gotCommand == true) {
                    gotCommand = false; // Reset flag

                    // Do some work with recieved command/data
                }
            }
        }
    }
}
🌐
EmbeddedExpertIO
blog.embeddedexpert.io
STM32 UART Part 4: Receiving Unknown Length of Data – EmbeddedExpertIO
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.
Find elsewhere
🌐
ControllersTech®
controllerstech.com › home › stm32 tutorials › stm32 uart series › idle line reception
STM32 UART Idle Line: Receive Data via Interrupt & DMA
When receiving UART data of unknown length, the standard HAL_UART_Receive_IT() approach falls short — it requires you to specify an exact byte count up front, and stalls if the sender transmits fewer bytes than expected.
Published   April 30, 2026
🌐
GitHub
gist.github.com › dzanis › 26ce4581368c24680c6d1ee5acdb3772
stm32 read unknown data length from UART · GitHub
stm32 read unknown data length from UART · Raw · stm32_uart_rx_read.c · This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Top answer
1 of 8
1
To get a better understanding of what is going on... · If the packet has no protocol or some kind of identifier, how do you know which packet is what? · What are the purpose of these variable length packets after you calculate the CRC and send it out over UART? · Is it possible that the sending device may pause in the middle of sending the large packet? · How often are the packets being sent? · What are some examples size of these large packets? ·   · My questions: · How can I reliably receive an undefined-length UART stream without framing or start/stop bytes? Use DMA · What techniques can I use to detect the end of transmission if there is no protocol? Use DMA with idle. · Are there best practices for handling UART reception and CRC calculation in an interrupt-driven system at very high baud rates? Don't calculate in the interrupt/callback · How can I avoid losing bytes or corruption when the incoming data size is large? Use DMA · Any example code snippets or references for a similar use case would be great!  · I try to use dma (ping pong buffer and update crc but it dosent work because i cant find the best time and place to calculate crc...) You need to save the DMA data to a queue buffer as the TC and HT occur. When you get an idle interrupt, you can increment the queue pointer. So as you're calculating the CRC on the 1st queue in the main while loop, any new DMA data will be saved to the next queue. That way you don't miss any incoming data as you're incrementing through the current queue and calculating the CRC · If a reply has proven helpful, click on Accept as Solution so that it'll show at top of the post.CAN Jammer an open source CAN bus hacking toolCANableV3 Open Source
2 of 8
0
> After receiving all data, I must output the CRC via UART. · How do you decide that all data is received?   · Do you need to calculate the CRC over *all* data or over each "stream" or how you define the "units" of the data? · STM32H7 can handle continuous UART RX interrupts at ~ 100 per ms, with properly written code (not the "HAL" library).  Enabling the UART FIFO can help as well. Using the CRC accelerator of STM32 can help, if the CRC algorithm is suitable for you.  DMA [with timeout/idle detection] seems more attractive, but more complicated. ·   · > How can I avoid losing bytes or corruption when the incoming data size is large? · An excellent question. The "streams" should not be too long, exactly with purpose of lessening chance of loss or corruption within single "stream" and minimize retransmissions.  This cannot be avoided and should be handled properly.
🌐
EmbeddedExpertIO
blog.embeddedexpert.io
STM32 UART Part 5: Receive Unknown Length Using DMA – EmbeddedExpertIO
Instead of blindly filling buffers, the DMA captures bursts of unknown-length data in one shot, and the IDLE flag elegantly signals the firmware to process the frame and re-arm DMA for the next transfer. ... Interrupt vs DMA-IDLE line approach. STM32CubeMX configuration.
🌐
YouTube
youtube.com › watch
STM32 UART DMA and IDLE LINE || Receive unknown length DATA - YouTube
Purchase the Products shown in this video from :: https://controllerstech.store______________________________________________________________________________...
Published   June 19, 2021
🌐
STMicroelectronics Community
community.st.com › t5 › stm32-mcus-products › stm32-uart-rx-with-unknown-length › td-p › 182830
STM32 UART Rx with unknown length - STMicroelectronics Community
September 22, 2022 - What I tend to do is arrange a DMA-driven circular receive-buffer, and then poll that. ... Have a buffer with size bigger than your incoming data may be. After init, enable UART RX Timeout by:
Top answer
1 of 4
1
Hi @Inigo​  · First, I think H7 is handling classic DMA and Basic DMA (BDMA). All are managed within same HAL APIs, that's why on H7 Instance field of DMA_HandleTypeDef structure is a (void*) element, that will be later casted on either (DMA_Stream_TypeDef *) type or on (BDMA_Channel_TypeDef *) type, depending on the used channel. · In order to retrieve the number of remaining data units in the current DMA Stream transfer, I think you should be able to use __HAL_DMA_GET_COUNTER() macro. · This should work on both platforms. · Secondly, if you want to use HAL API for unknown lengths reception on UART, another option for using IDLE event for data reception would be to implement something similar to what is shown in UART_ReceptionToIdle_CircularDMA example provided in STM32 Firmware packages. · These new APIs (ReceptionToIdle) have been added recently. Advantage is that you will not have to deal with such DMA structure changes between F4 and H7. · A dedicated example for STM32F4 (but easily portable on other STM32) could be found here. · This example shows use of HAL_UARTEx_ReceiveToIdle_DMA() API. · Using a circular DMA, this API allows user to get notified thanks to a HAL_UARTEx_RxEventCallback callback on 3 types of events : ·   - HT (Half Transfer) : Half of Rx buffer is filled ·   - TC (Transfer Complete) : Rx buffer is full. ·     (In case of Circular DMA, reception could go on, and next reception data will be stored ·     in index 0 of reception buffer by DMA). ·   - Idle Event on Rx line : Triggered when RX line has been in idle state (normally high state) ·     for 1 frame time, after last received byte. · Hope this helps. · Regards
2 of 4
0
I was trying to do the same (transfer from Windows PC) to the H7 CPU, however the general problem with receive to idle seems to be, that the idle is triggered too fast, resulting in reception of 1 or just several bytes. For the embedded senders the idle works because you can deterministically control the byte spacing, so that the idle time is not exceeded, however this is not the case with Windows. I am sending a serial message from Qt application with serialPort->write(msg) which sends the message as a block, but what Winz are doing cannot be controlled. · After long struggle, I came around this problem by using HAL_UART_Receive_IT() with full buffer length. I am checking huart.RxXferCount within periodic timer interrupt, which is used anyway for other tasks. · If huart.RxXferCount does not change for a while (e.g. 100ms) and it has different value than full buffer length, I am assuming reception / sending from PC is finished. So far this is working much more reliable than idle detection. · The code looks something like this: · volatile uint8_t msgReceived = 0; · main() { · // Start reception · HAL_UART_Receive_IT(&huart1, serRxBuffer, SER_BUF_LEN); · ... · while() { · if (msgReceived) { · msgReceived = 0; · HAL_UART_AbortReceive(&huart1); // --> Abort current reception · process_rx_message(); · HAL_UART_Receive_IT(&huart1, serRxBuffer, SER_BUF_LEN); // --> restart for the next msg · } · } · } · void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) · { · static uint16_t serRxBytes = SER_BUF_LEN; · · if (huart1.RxXferCount != serRxBytes) { · serRxBytes = huart1.RxXferCount; · } · else { · if ((huart1.RxXferCount == serRxBytes) && (serRxBytes != SER_BUF_LEN)) { · msgReceived = 1; · } · } · }
🌐
STMicroelectronics Community
community.st.com › stmicroelectronics community › discussions › product forums › stm32 mcus › stm32 mcus products › receive unknown length frames with uart
receive unknown length frames with UART | Community
June 24, 2025 - Use HAL_UARTEx_ReceiveToIdle_DMA and use the IDLE event to know when a frame stops. STM32CubeF4/Projects/STM32446E-Nucleo/Examples/UART/UART_ReceptionToIdle_CircularDMA/Src/main.c at 0c04cc8f2360e54ced149afb43195a03f529a1c5 ·
🌐
STMicroelectronics Community
community.st.com › t5 › stm32-mcus-products › dma-to-receive-unknown-length-frames-with-uart › td-p › 251852
DMA to receive unknown length frames with UART - STMicroelectronics Community
May 12, 2020 - I need to listen to Rx UART to receive unknown length frames (I will search for a frame start byte, then read the frame size and then test a CRC for frame validating) I would like to use DMA for performance but I only see half or full reception interrupt of a fixed number of bytes. Is it possible to do that with DMA or must I using simple interrupts, with 1interrupt for each byte received ? thanks in advance · PS : I'm using STM32L431 with STM32cubeIDE ·
🌐
STMicroelectronics Community
community.st.com › t5 › stm32-mcus-products › how-to-handle-the-usart-receive-with-unknow-data-length › td-p › 253326
How to handle the Usart receive with unknow Data length.
July 30, 2020 - With usr_uart_SerialAvailable you check is there is incoming data based in a timeout value USR_UART_BUFFER_TIMEOUT, if there is incoming data it keep reading it until the buffer is full or the timeout is reached, the buffer size is USR_UART_BUFFER_SIZE, after that you call usr_uart_ReadString for copy the incoming data. Please check it ... how to let SPI_fullDuplex_DMA_Slave runs continously using the HAL example in STM32 MCUs Embedded software 2026-01-14
🌐
DeepBlue
deepbluembedded.com › home › blog › stm32 uart interrupt, dma, polling | uart receive examples
STM32 UART Interrupt, DMA, Polling | UART Receive Examples
July 26, 2025 - Thanks a lot for this article. I usually use uart with interrupt on each byte, as the length of the data is unknown. Sometimes it is only 1 byte, and other times it can be 50 bytes. I know the end of the transmission ends with \n, as l usually design both sides..
🌐
GitHub
github.com › bluehash › STM32_USART_DMA_RX
GitHub - bluehash/STM32_USART_DMA_RX: Examples for efficient use of DMA for UART receive on STM32 microcontrollers when receive length is unknown · GitHub
IDLE line detection (or Receiver Timeout) can trigger USART interrupt when receive line is steady without any communication for at least 1 character for reception. Practicle example: Imagine we received 10 bytes at 115200 bauds.
Author   bluehash