Repository navigation
Enable Serial Wire Output (SWO) for debug tracing #41
Description
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, withexterninstances linked inperipherals.cppmodules/cortex/include/cortex/peripherals/DebugSystem.hpp— DEMCR register definition withenable_tracebit (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:
- Core: Set
DEMCR.TRCENAviaDebugSystem::ExceptionMonitorControl.enable_trace - Family-specific DBGMCU:
- H7:
DBGMCU_CR.TRACECLKEN = 1,D1DBGCKEN = 1 - F4:
DBGMCU_CR.TRACE_IOEN = 1,TRACE_MODE = 0b01(async NRZ)
- H7:
- GPIO: Configure PB3 as AF0 push-pull very high speed
- TPIU:
TPIU_ACPR.SWOSCALER=(trace_ref_clock / desired_baud) - 1TPIU_SPPR.Protocol=AsyncNRZ(preferred over Manchester)
- DWT:
DWT_CTRL.CYCCNTENA = 1(cycle counter)DWT_CTRL.PCSAMPLENA = 1(periodic PC sampling)
- ITM:
ITM_TCR.ITMENA = 1,SWOENA = 1,DWTENA = 1,TSENA = 1ITM_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
Activity
emrainey commentedon Jul 27, 2026
The issue as written covers the STM32H7 family only. The STM32F4 family differs in several ways and is not yet tracked:
F4 vs H7 differences:
0xE0042004(F4) vs0x5C001004(H7)TRACE_IOEN(bit 5) +TRACE_MODE(bits 6:7) on F4, vsTRACECLKEN(bit 20) on H7TRACE_MODE=0b01selects async SWO on F4 (SWO on PB3, AF0, same pin)DBGMCUperipheral is in the STM32F405/F407 SVDs undermodules/stm32/scripts/— theCRregister hasTRACE_IOENandTRACE_MODEfieldsF4 boards in the project:
stm32_f4ve_v2andnetduinoplus2emrainey commentedon Aug 13, 2026
Implemented in PR #56 (branch
issue-41).The ST SWO/SWTF path is now programmed from the vendor
clocks():Verified on the Nucleo-H753ZI via single-session breakpoint dump at main entry: DBGMCU_CR=0x00700007, SWO_CODR=0x2b, SWO_SPPR=2, SWTF_CTRL=0x301.
Note: a J-Link reconnect clobbers DBGMCU_CR to 0x07 (unclocking the D3 debug domain), so SWO reads must be done within one session.
Deferred:
jalo: add SWO trace console viewer (issue-54)
jalo: add SWO trace console viewer (issue-54) (#57)