Skip to content

Enable Serial Wire Output (SWO) for debug tracing #41

Description

@emrainey

Summary

SWO (Serial Wire Output) provides a low-cost, single-pin trace channel for debug output via the ITM/TPIU. The hardware infrastructure exists in the codebase but is incomplete — the core trace peripherals are defined and the cortex::swo::emit() API works, but no initialization code connects them to the physical pin.

This issue covers enabling SWO on both the STM32H7 and STM32F4 families used in this project.

Current State

The following already exists (family-independent):

  • modules/cortex/include/cortex/swo.hpp — Public API (cortex::swo::emit() for ports 0–1)
  • modules/cortex/source/swo.cpp — Implementation (blocks on FIFO ready, writes ITM stimulus port)
  • modules/cortex/include/cortex/peripherals/InstructionTraceMacrocell.hpp — Full register maps for ITM, DWT, and TPIU, with extern instances linked in peripherals.cpp
  • modules/cortex/include/cortex/peripherals/DebugSystem.hpp — DEMCR register definition with enable_trace bit (TRCENA)

What's Missing

1. Family-specific DBGMCU register headers

No STM32 DBGMCU register header exists for either family. The key registers and fields differ:

STM32H7 (DBGMCU_CR at 0x5C001004):

Bit Field Purpose
20 TRACECLKEN Enable trace port clock
21 D1DBGCKEN Enable D1 debug clock (trace runs in D1 domain)
28 TRGOEN External trigger output (optional)

Currently reads 0x00000007 — only debug-sleep/stop/standby bits are set.

STM32F4 (DBGMCU_CR at 0xE0042004):

Bit Field Purpose
5 TRACE_IOEN Enable trace port I/Os
6:7 TRACE_MODE 0b00=async, 0b01=async SWO/NRZ, 0b10=async SWO/Manchester, 0b11=parallel
20 TRACECLKEN Enable trace clock (some F4 variants)

Both are defined in their respective SVD files under modules/stm32/scripts/.

2. SWO pin configuration

The SWO pin is the same on both families:

  • Pin: PB3, Alternate Function AF0 (TRACESWO)
  • Mode: Alternate Function
  • Output Type: Push-Pull
  • Speed: Very High
  • Pull: None

On the Nucleo-H753ZI, PB3 is also used as SPI1_SCL on the Arduino header (solder-bridge SB63/SB64 isolates it). On some STM32F4 packages PB3 may also serve as JTDO — board-specific handling may be needed.

3. System-level trace initialization sequence

A new cortex::initialize::trace() function that performs:

  1. Core: Set DEMCR.TRCENA via DebugSystem::ExceptionMonitorControl.enable_trace
  2. Family-specific DBGMCU:
    • H7: DBGMCU_CR.TRACECLKEN = 1, D1DBGCKEN = 1
    • F4: DBGMCU_CR.TRACE_IOEN = 1, TRACE_MODE = 0b01 (async NRZ)
  3. GPIO: Configure PB3 as AF0 push-pull very high speed
  4. TPIU:
    • TPIU_ACPR.SWOSCALER = (trace_ref_clock / desired_baud) - 1
    • TPIU_SPPR.Protocol = AsyncNRZ (preferred over Manchester)
  5. DWT:
    • DWT_CTRL.CYCCNTENA = 1 (cycle counter)
    • DWT_CTRL.PCSAMPLENA = 1 (periodic PC sampling)
  6. ITM:
    • ITM_TCR.ITMENA = 1, SWOENA = 1, DWTENA = 1, TSENA = 1
    • ITM_TER = enable stimulus ports 0 and 1
    • Unlock via ITM_LAR = 0xC5ACCE55

4. Clock frequency plumbing

The TPIU SWO baud prescaler formula is the same for both:

SWOSCALER = (trace_ref_clock / desired_baud) - 1
Family Trace ref clock Typical 2 MHz SWO prescaler
H7 rcc_hclk6 200 MHz 99
F4 HCLK 168 MHz 83

The clock tree must be initialized before trace init, since the prescaler depends on the measured frequency.

Measured Live Register Values (from J-Link on H753)

Register Value Key bits
DBGMCU_CR (H7) 0x00000007 TRACECLKEN=0, TRGOEN=0
DBGMCU_CR (F4) TBD TBD
ITM_TCR 0x00000001 ITMENA=1, SWOENA=0, DWTENA=0, TSENA=0
ITM_TER 0x00000003 ports 0,1 enabled — but no output path
TPIU_ACPR 0x00000000 prescaler=0 — not configured
TPIU_SPPR 0x00000000 protocol=Manchester — fallback
DEMCR 0x01000000 TRCENA=1 — core-level trace enabled
DWT_CTRL 0x400003FF CYCCNTENA=1, cycle counter running
DWT_CYCCNT 15742475 free-running

The ITM and DWT are partially initialized (likely by debugger or reset defaults), but the system-level path to the pin is gated off.

Acceptance Criteria

  • Common cortex::initialize::trace() function handles both H7 and F4
  • Family-specific DBGMCU register headers under modules/stm32/include/stm32/
  • SWO stimulus port 0 emits characters visible via J-Link SWO viewer on both platforms
  • SWO baud rate is configurable (default 2 MHz)
  • DWT cycle counter and PC sampling packets appear on SWO
  • Works after cortex::system::main() in normal startup
  • No interference with existing PB3 functionality when SWO is not enabled

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions