Embedded C has no garbage collector. Understand the stack, heap fragmentation, static allocation, and the patterns that keep firmware from mysteriously crashing at 3 AM.
Embedded C has no garbage collector. Understand the stack, heap fragmentation, static allocation, and the patterns that keep firmware from mysteriously crashing at 3 AM.
JTAG (Joint Test Action Group): 4 pins (TDI, TDO, TCK, TMS) + optional TRST. SWD (Serial Wire Debug): 2 pins (SWDIO, SWCLK) — ARM's optimized debug interface. Both allow: halt the CPU, set breakpoints, read/write memory and registers, step through code. For STM32: use ST-Link V2 (~$3). For ESP32: use JTAG with openocd -f interface/ftdi/esp32_devkitj_v1.cfg. GDB connects to OpenOCD over a local TCP socket on port 3333.
Key commands: target extended-remote :3333 — connect to OpenOCD. monitor reset halt — reset CPU, halt at reset vector. load — flash the ELF file. break main — set breakpoint at main. continue — run. step/next/stepi — single step. info registers — dump all registers. x/8xw 0x20000000 — examine 8 words of SRAM as hex. print gpio_state — print variable value. watch variable — break when variable changes.
SWO (Serial Wire Output) is a single-wire trace port that outputs ITM (Instrumentation Trace Macrocell) printf messages without blocking. Configure: enable TRC in CoreDebug, set baud in ITM_TCR, write to ITM->PORT[0].u8. OpenOCD captures SWO output. This gives you printf-style debugging with no UART connection needed and minimal performance impact. VS Code Cortex-Debug extension shows SWO output directly.
#!/bin/bash
# Debug firmware with GDB + OpenOCD
# Terminal 1: Start OpenOCD (adapt config to your hardware)
openocd -f interface/stlink.cfg -f target/stm32f1x.cfg -c "init" -c "reset halt"
# OpenOCD now listens on port 3333 (GDB) and 4444 (telnet)
# Terminal 2: Connect GDB
arm-none-eabi-gdb firmware.elf << 'EOF'
# Connect to OpenOCD
target extended-remote :3333
# Reset and halt the CPU
monitor reset halt
# Flash the firmware
load
# Set breakpoints
break main
break SysTick_Handler
break EXTI0_IRQHandler
# Run to main
continue
# When stopped at breakpoint:
# info registers — all register values
# print systick_ms — print variable
# x/16xw 0x20000000 — dump 16 words of SRAM
# watch button_pressed — break when this changes
# backtrace — call stack (if -g compiled)
# stepi — one assembly instruction
# Continue execution
continue
EOF
# Useful OpenOCD telnet commands (port 4444):
# echo 'flash list' | nc localhost 4444
# echo 'mdw 0x40020014' | nc localhost 4444 # read GPIO ODR
# echo 'mww 0x40020018 0x01' | nc localhost 4444 # write BSRR
-g3 -Og to your compiler flags. -Og is 'optimization for debugging' — it enables some optimizations while preserving variable names and line numbers. Release builds use -O2 or -O3.-g3 -Og debug flags.EXTI0_IRQHandler.info registers. Note the PC and LR values.x/8xw 0x40020010 to read GPIOA IDR directly. Does it match what you expect?Set up a hardware fault handler. When a firmware bug causes a HardFault (null pointer dereference, stack overflow, etc.), the default handler is an infinite loop — completely unhelpful. Implement a HardFault_Handler that reads the faulting address from the SCB (System Control Block), prints it via UART, and stores it in RTC memory before resetting. Research: what are CFSR, HFSR, and MMAR registers in the SCB? What information do they contain?
The foundations from today carry directly into Day 5. In the next session the focus shifts to Debugging and Testing Firmware — building directly on everything covered here.
Before moving on, verify you can answer these without looking:
Live Bootcamp
Learn this in person — 2 days, 5 cities
Thu–Fri sessions in Denver, Los Angeles, New York, Chicago, and Dallas. $1,490 per seat. June–October 2026.
Reserve Your Seat →