Fading Coder

One Final Commit for the Last Sprint

Home > Tech > Content

Receiving Framed Data on STM32 Using the USART Idle Line Interrupt (No DMA)

Tech 1

This guide shows how to receive variable-length serial frames on STM32 microcontrollers using the USART idle line interrupt, without DMA. The pattern is simple: collect bytes via RXNE in the ISR and treat an IDLE event as the frame delimiter.

Background: UART in brief

  • UART sends data asynchronously as frames: start bit, data bits, optional parity, and stop bit(s).
  • Both ends must agree on baud rate, data bits, parity, and stop bits.
  • An idle line occurs when the RX line stays high for at least one frame duration after the last received byte.

Why the idle line interrupt

  • IDLE triggers when the receiver detects a frame-time gap on the RX line after at least one byte has been received since the last IDLE clear.
  • It is ideal to delimit packets of unknown length without scanning for timeouts in software.
  • After clearing the IDLE condition, the next receptino sequence can generate IDLE again.

Minimal setup (HAL)

Assume the USART/LPUART is already initialized by CubeMX or equivalent. Enable RXNE and IDLE interrupts and configure the NVIC.

#include "stm32xxxx_hal.h"
#include <string.h>

extern UART_HandleTypeDef hlpuart1;  // Created by CubeMX or user code

static uint8_t  s_rx_buf[512];
static volatile uint16_t s_rx_len = 0;

static void on_uart_frame(const uint8_t* data, uint16_t len);

static void USART_EnableIdleLine(UART_HandleTypeDef* huart)
{
    // Enable data-ready and idle interrupts
    __HAL_UART_ENABLE_IT(huart, UART_IT_RXNE);
    __HAL_UART_ENABLE_IT(huart, UART_IT_IDLE);

    // Clear any pending idle flag before starting
    __HAL_UART_CLEAR_IDLEFLAG(huart);
}

static void USART_ConfigNVIC(void)
{
    HAL_NVIC_SetPriority(LPUART1_IRQn, 5, 0);
    HAL_NVIC_EnableIRQ(LPUART1_IRQn);
}

void App_SerialInit(void)
{
    // hlpuart1 is already initialized with baud/parity/word length/stop bits
    USART_EnableIdleLine(&hlpuart1);
    USART_ConfigNVIC();
}

Notes:

  • If your HAL/MCU family differentiates DR vs RDR, use the register available on your part. The RDR read clears RXNE on STM32 series with the USART ISR/RDR interface.

Interrupt handler

Read as many bytes as available on RXNE. When IDLE fires, clear the flag and treat the data collected so far as one frame.

void LPUART1_IRQHandler(void)
{
    UART_HandleTypeDef* huart = &hlpuart1;

    // RXNE: byte ready
    if (__HAL_UART_GET_FLAG(huart, UART_FLAG_RXNE))
    {
        uint8_t byte = (uint8_t)(huart->Instance->RDR & 0xFFu);
        if (s_rx_len < sizeof(s_rx_buf))
        {
            s_rx_buf[s_rx_len++] = byte;
        }
        else
        {
            // Overflow: drop or handle error
            (void)byte; // prevent unused warning
        }
    }

    // IDLE: gap detected, frame ended
    if (__HAL_UART_GET_FLAG(huart, UART_FLAG_IDLE))
    {
        __HAL_UART_CLEAR_IDLEFLAG(huart);  // Clears by the proper HAL sequence

        if (s_rx_len > 0)
        {
            on_uart_frame(s_rx_buf, s_rx_len);
            s_rx_len = 0;  // ready for next frame
        }
    }
}

Optional guards:

  • If you want to avoid growing latency in the ISR, copy the pointer/length to a queue or set a flag and process the frame in the main loop/RTOS task.

Frame callback

Only dispatch for plausible frames and keep ISR work minimal.

static void on_uart_frame(const uint8_t* data, uint16_t len)
{
    // Example: only process when length looks valid
    if (len > 5)
    {
        // Application-specific parsing; keep it fast or offload
        parse_debug_frame(data, len);
    }
}

Example parser: HEAD(1B) + CMD(1B) + LEN(2B) + DATA + CS(1B)

The example below validates a simple framed portocol:

  • HEAD is a fixed byte (e.g., 0xA5)
  • LEN is big-endian payload length
  • CS is a one-byte sum over all previous bytes (modulo 256)
#include <stdbool.h>

#define FRAME_HEAD 0xA5
#define RX_COPY_MAX 512

static volatile bool     g_frame_ready = false;
static uint8_t           g_frame_buf[RX_COPY_MAX];
static volatile uint16_t g_frame_len = 0;

static uint8_t checksum_sum8(const uint8_t* buf, uint16_t len)
{
    uint32_t s = 0;
    for (uint16_t i = 0; i < len; ++i) s += buf[i];
    return (uint8_t)(s & 0xFF);
}

bool parse_debug_frame(const uint8_t* buf, uint16_t len)
{
    // Minimum: HEAD + CMD + LEN(2) + CS
    if (len < 1 + 1 + 2 + 1)
        return false;

    if (buf[0] != FRAME_HEAD)
        return false;

    uint8_t cmd = buf[1];
    uint16_t payload_len = ((uint16_t)buf[2] << 8) | buf[3];

    // Expected total length
    uint16_t expected = 1 + 1 + 2 + payload_len + 1;
    if (len != expected)
        return false;

    uint8_t cs_calc = checksum_sum8(buf, len - 1);
    uint8_t cs_recv = buf[len - 1];
    if (cs_calc != cs_recv)
        return false;

    // At this point the frame is valid; store/copy for later processing
    if (len <= RX_COPY_MAX)
    {
        memcpy(g_frame_buf, buf, len);
        g_frame_len   = len;
        g_frame_ready = true;
    }

    // Use cmd and data (buf + 4, length payload_len) as needed
    (void)cmd;

    return true;
}

Alternative: scatter-gather buffering

If you expect large bursts or want to avoid copying in the ISR, collect bytes into a ring buffer on RXNE and, on IDLE, record the current write index as a snapshot for processing outside the ISR. This reduces latency and jitter when frames are frequent or large.

Tags: STM32

Related Articles

Understanding Strong and Weak References in Java

Strong References Strong reference are the most prevalent type of object referencing in Java. When an object has a strong reference pointing to it, the garbage collector will not reclaim its memory. F...

Comprehensive Guide to SSTI Explained with Payload Bypass Techniques

Introduction Server-Side Template Injection (SSTI) is a vulnerability in web applications where user input is improper handled within the template engine and executed on the server. This exploit can r...

Implement Image Upload Functionality for Django Integrated TinyMCE Editor

Django’s Admin panel is highly user-friendly, and pairing it with TinyMCE, an effective rich text editor, simplifies content management significantly. Combining the two is particular useful for bloggi...

Leave a Comment

Anonymous

◎Feel free to join the discussion and share your thoughts.