Fundamentals 11 min read

How to Output Debug Logs in Embedded Systems Without a UART

This article explains several practical techniques for printing debug logs in embedded development—using SRAM buffers, SWO trace, UART with DMA, and GPIO‑bit‑banging—detailing their implementation, advantages, limitations, and code examples for reliable diagnostics on chips lacking conventional serial interfaces.

Liangxu Linux
Liangxu Linux
Liangxu Linux
How to Output Debug Logs in Embedded Systems Without a UART

Outputting debug information is essential in embedded development, especially when a chip lacks an operating system or file system, making traditional file‑based logging impossible.

1. Log to SRAM

A circular buffer is allocated in SRAM (e.g., 1 KB) and filled with log bytes. After execution, the buffer can be inspected via a debugger.

typedef struct {
    volatile u8  type;
    u8*         buffer;   /* log buffer pointer */
    volatile u32 write_idx;/* write position */
    volatile u32 read_idx; /* read position */
} log_dev;
static u8 log_buffer[LOG_MAX_LEN];

Redirect printf by implementing fputc to write characters into the buffer:

int fputc(int ch, FILE *f) {
    print_ch((u8)ch);
    return ch;
}

Define a macro for conditional logging:

#ifdef DEBUG_LOG_EN
#define DEBUG(...)  printf("usb_printer:" __VA_ARGS__)
#else
#define DEBUG(...)
#endif

When the system is idle, call output_ch() to flush the buffer to the chosen output.

2. Log via SWO (Serial Wire Output)

SWO allows trace output without occupying UART pins. Extend the log structure with a function pointer for SWO:

typedef struct {
    u8 (*init)(void *arg);
    u8 (*print)(u8 ch);
    u8 (*print_dma)(u8* buffer, u32 len);
} log_func;

typedef struct {
    volatile u8  type;
    u8*         buffer;
    volatile u32 write_idx;
    volatile u32 read_idx;
    log_func*   swo_log_func; /* SWO functions */
} log_dev;

SWO print implementation:

u8 swo_print_ch(u8 ch) {
    ITM_SendChar(ch);
    return 0;
}

During idle time, output_ch() forwards buffered characters to swo_log_func->print.

3. Log via UART with DMA

When UART is available, use DMA to transfer the log buffer to the serial port without blocking the CPU.

void uart_log_init(void *arg) {
    // GPIO and USART configuration omitted for brevity
    USART_Init(USART2, &USART_InitStructure);
    #ifdef LOG_UART_DMA_EN
    USART_DMACmd(USART2, USART_DMAReq_Tx, ENABLE);
    #endif
    USART_Cmd(USART2, ENABLE);
}

u8 uart_print_dma(u8* buffer, u32 len) {
    if (DMA1_Stream6->CR & DMA_SxCR_EN) return 1; // DMA busy
    DMA_SetCurrDataCounter(DMA1_Stream6, len);
    DMA_MemoryTargetConfig(DMA1_Stream6, (u32)buffer, DMA_Memory_0);
    DMA_Cmd(DMA1_Stream6, ENABLE);
    return 0;
}

The DMA channel (USART2 TX → DMA1 Stream6 Channel 4) moves data while the CPU can continue other tasks.

4. Log via GPIO Bit‑Banging (IO‑Simulated UART)

If no UART pins exist, a free GPIO can emulate UART timing using a timer for precise delays.

u8 simu_log_init(void *arg) {
    // Configure GPIOA Pin2 as output and TIM4 for 1 µs ticks
    baud_delay = 1000000 / (*(u32*)arg); // calculate bit delay
    return 0;
}

void simu_delay(u32 us) {
    while (us--) {
        while (TIM_GetFlagStatus(TIM4, TIM_FLAG_Update) == RESET);
        TIM_ClearFlag(TIM4, TIM_FLAG_Update);
    }
}

u8 simu_print_ch(u8 ch) {
    __asm("cpsid i"); // disable interrupts
    GPIO_ResetBits(GPIOA, GPIO_Pin_2); // start bit
    simu_delay(baud_delay);
    for (int i = 0; i < 8; ++i) {
        if (ch & 0x01) GPIO_SetBits(GPIOA, GPIO_Pin_2);
        else GPIO_ResetBits(GPIOA, GPIO_Pin_2);
        ch >>= 1;
        simu_delay(baud_delay);
    }
    GPIO_SetBits(GPIOA, GPIO_Pin_2); // stop bit
    simu_delay(baud_delay * 2);
    __asm("cpsie i"); // enable interrupts
    return 0;
}

This method requires disabling interrupts during a byte transmission to avoid corruption.

Conclusion

All presented methods are viable for embedding log output in resource‑constrained systems; the choice depends on hardware availability and timing constraints. Using DMA with UART offers minimal CPU impact, while SRAM buffering combined with SWO or bit‑banging provides alternatives when UART is inaccessible.

Reference code can be downloaded from: https://pan.baidu.com/s/1RLsrZ34MsqxXR_5BLRWRtA (password: ejgm)

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

DebuggingDMAlogUARTSWO
Liangxu Linux
Written by

Liangxu Linux

Liangxu, a self‑taught IT professional now working as a Linux development engineer at a Fortune 500 multinational, shares extensive Linux knowledge—fundamentals, applications, tools, plus Git, databases, Raspberry Pi, etc. (Reply “Linux” to receive essential resources.)

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.