Bare Metal STM32 · Part 6 of 7
Debugging with SWO Trace & ITM Printf
18:30
Part 6 / 7
⚙️ Bare Metal STM32 Series · 7 Parts
Part 6 of 7
Part 01
HAL vs Bare Metal: Which Should You Use?
38:22
Part 02
Clock Configuration with RCC: From HSI to PLL
22:10
Part 03
Interrupts & NVIC: Complete Configuration Guide
47:22
Part 04
SPI DMA Transfers: No CPU Overhead
33:05
Part 05
ADC DMA Circular Mode: Continuous Sampling
29:45
Part 06
Debugging with SWO Trace & ITM Printf
18:30
Part 07
Low-Power Stop Mode with Wake-Up on EXTI
34:12
01
Overview
Adding a UART just for debug output wastes a peripheral and burns CPU cycles waiting for the TX FIFO. The SWO (Single Wire Output) pin on Cortex-M is already wired to your ST-Link — all you need is 10 lines of code to redirect printf() through the ITM stimulus port and read it live in CubeIDE's SWV Console. Zero extra hardware, zero CPU blocking, zero peripheral cost.
- Configure the ITM and TPIU to output on SWO at the correct trace clock frequency
- Override
__io_putchar()to redirectprintf()through ITM stimulus port 0 - Set up SWV in STM32CubeIDE to capture and display the trace output
- Use multiple ITM ports to separate log categories without extra parsing
02
Core Concepts
SWO Pin
Single Wire Output — a dedicated pin on the SWD connector (pin 6 on the standard 10-pin ARM header). Already connected on Nucleo and Discovery boards via ST-Link. No extra wiring needed.
PB3 = TRACESWO on STM32F4
ITM Stimulus Ports
32 independent FIFO ports. Port 0 is the standard printf channel. Use ports 1–31 for structured data (ADC values, task names, event IDs) without mixing with console text.
ITM->PORT[n].u8 = byte
Non-Blocking Writes
The ITM FIFO can fill up if the debugger can't drain it fast enough. Always check FIFOREADY before writing, or use the CMSIS ITM_SendChar() helper which does this automatically.
ITM->PORT[0].u32 != 0
03
Code
01
ITM init and printf redirect — drop this in main.citm_trace.c
C
/* ── 1. Enable ITM trace ────────────────────────────────── */ void ITM_Trace_Init(void) { /* Enable TPIU: set SWO baud = CoreClock / (prescaler+1) With CoreClock=168MHz and prescaler=0 → 168 MHz SWO clock Match this value exactly in CubeIDE → Run → Debug Configurations → Debugger tab → Serial Wire Viewer → Core Clock field */ CoreDebug->DEMCR |= CoreDebug_DEMCR_TRCENA_Msk; TPI->ACPR = 0; // SWO prescaler (0 = divide by 1) TPI->SPPR = 2; // 2 = NRZ (UART) encoding TPI->FFCR = 0x100; // Continuous formatter ITM->LAR = 0xC5ACCE55; // Unlock ITM registers ITM->TCR = ITM_TCR_TraceBusID_Msk | ITM_TCR_SWOENA_Msk | ITM_TCR_ITMENA_Msk; // Enable ITM ITM->TER = 0xFFFFFFFF; // Enable all 32 stimulus ports } /* ── 2. Redirect printf → ITM port 0 ───────────────────── */ int __io_putchar(int ch) { // Wait for FIFO ready (bit 0 of TER/PORT register) if (ITM->PORT[0].u32) { ITM->PORT[0].u8 = (uint8_t)ch; } return ch; } /* ── 3. Usage ───────────────────────────────────────────── */ void main_loop(void) { ITM_Trace_Init(); uint32_t count = 0; while(1) { printf("count=%lu adc=%u\r\n", count++, adc_buf[0]); // Structured data on port 1 — no parsing in viewer ITM->PORT[1].u16 = adc_buf[0]; HAL_Delay(100); } }
Set the SWO clock in CubeIDE to match your CoreClock
In CubeIDE: Run → Debug Configurations → Debugger tab → Enable Serial Wire Viewer → set Core Clock to 168000000. A mismatch produces garbled output. The SWO baud is CoreClock ÷ (ACPR+1), so ACPR=0 means SWO runs at full core speed.
Continue the Series
Work through all 7 parts of Bare Metal STM32 to master low-level embedded development from the ground up.