为什么需要 SWO + ITM?
传统调试常用 UART 打印,但存在明显痛点:
- 占用额外引脚和串口外设,增加硬件成本。
- 打印本身耗时,尤其在中断中调用会破坏实时性。
- 高波特率下仍可能丢数据,且 CPU 需轮询或频繁进中断。
Cortex-M 内核内置 ITM(Instrumentation Trace Macrocell) 和 SWO(Single Wire Output),通过调试器(如 ST-Link、J-Link)的 SWD 接口即可输出 trace 数据。它几乎不占用 CPU 时间(写寄存器即可),且支持硬件时间戳,非常适合实时系统。
原理速览
-
ITM:内核中的一个硬件模块,提供 32 个 stimulus 端口(0~31)。向
ITM_STIM寄存器写入数据即可发送。 - SWO:物理输出引脚(通常与 JTDI 复用),以异步方式串行输出 ITM 数据。
- DWT:数据观察点与跟踪单元,可提供 CPU 周期计数器,用于时间戳。
- TPIU:跟踪端口接口单元,负责将 ITM/DWT 数据打包并通过 SWO 输出。
关键寄存器:
-
ITM_STIM0~31:写数据到对应端口。 -
ITM_TER:使能对应端口。 -
ITM_TCR:使能 ITM 和同步。 -
TPIU_ACPR:设置 SWO 波特率分频。 -
TPIU_SPPR:选择 SWO 协议(NRZ 等)。 -
DEMCR:使能跟踪。
硬件与工具准备
- 任意 Cortex-M3/M4/M7 开发板(M0/M0+ 无 ITM,不适用)。
- 调试器:ST-Link V2/V3、J-Link、CMSIS-DAP 等,需支持 SWO。
- 软件:Keil MDK、IAR 或 STM32CubeIDE,配合相应调试插件。
- 一根 SWD 排线,确保 SWO 引脚连接(通常为 4 线 SWD + SWO)。
配置步骤
1. 初始化 ITM 与 SWO
以下代码以 STM32F4(Cortex-M4)为例,直接操作寄存器。
#include "stm32f4xx.h"
#define ITM_PORT0 0
void ITM_Init(uint32_t cpu_freq, uint32_t swo_freq) {
// 使能跟踪与调试
CoreDebug->DEMCR |= CoreDebug_DEMCR_TRCENA_Msk;
// 解锁 ITM 寄存器
ITM->LAR = 0xC5ACCE55;
// 使能 ITM 和端口 0
ITM->TER = (1 << ITM_PORT0);
ITM->TCR = ITM_TCR_ITMENA_Msk | ITM_TCR_SYNCENA_Msk;
// 设置 SWO 波特率(TPIU 异步时钟分频)
// SWO_baud = cpu_freq / (ACPR + 1)
TPI->ACPR = (cpu_freq / swo_freq) - 1;
// 选择 NRZ 编码
TPI->SPPR = 0x02;
// 使能 TPIU
TPI->FFCR = 0x100; // 使能格式化
}
2. 实现 printf 重定向
通过 ITM_SendChar 发送字符,并重定向 fputc。
int fputc(int ch, FILE *f) {
if (ITM->TCR & ITM_TCR_ITMENA_Msk) {
while (ITM->PORT[ITM_PORT0].u32 == 0);
ITM->PORT[ITM_PORT0].u8 = (uint8_t)ch;
}
return ch;
}
若使用 ARM Compiler,需在工程中勾选“Use MicroLIB”或自行实现 _write。
3. 添加时间戳(可选)
使能 DWT 周期计数器,并在输出前发送时间戳。
void DWT_Init(void) {
CoreDebug->DEMCR |= CoreDebug_DEMCR_TRCENA_Msk;
DWT->CYCCNT = 0;
DWT->CTRL |= DWT_CTRL_CYCCNTENA_Msk;
}
void ITM_SendTimestamp(void) {
uint32_t ts = DWT->CYCCNT;
ITM->PORT[1].u32 = ts; // 端口 1 发送时间戳
}
在调试器中可配置解析端口 1 为时间戳。
4. 调试器端配置
- Keil:Debug -> Settings -> Trace,勾选“Enable”,设置 Core Clock 与 SWO Clock,选择 ITM 端口 0 和 1。
- STM32CubeIDE:Run -> Debug Configurations -> SWV,使能 ITM 并设置时钟。
- J-Link:使用 J-Link SWO Viewer 或 Ozone。
完整示例:带时间戳的 trace 输出
#include <stdio.h>
#include "stm32f4xx.h"
void ITM_Init(uint32_t cpu_freq, uint32_t swo_freq);
void DWT_Init(void);
int main(void) {
SystemInit();
ITM_Init(168000000, 2000000); // CPU 168MHz, SWO 2MHz
DWT_Init();
printf("System started.\n");
while (1) {
ITM_SendTimestamp();
printf("Tick: %lu\n", DWT->CYCCNT);
for (volatile int i = 0; i < 1000000; i++);
}
}
注意事项与常见问题
- 仅 Cortex-M3/M4/M7 支持 ITM,M0/M0+ 无此功能。
- SWO 引脚需在硬件上连接,且调试器支持。
- 波特率不宜过高,通常 1~4 Mbps 足够,过高易受干扰。
- 在低功耗模式下 ITM 可能停止工作,需退出低功耗后再使用。
- 多任务环境中,ITM 写入是原子的,但 printf 本身可能非线程安全,建议加锁。
- 若使用 RTOS,可在空闲任务中刷新,避免阻塞。
- 时间戳端口需在调试软件中单独配置解析。
- 确保
ITM->TCR的ITMENA置位,否则数据不会输出。
总结
SWO + ITM 是 Cortex-M 上替代串口打印的优雅方案,开销极低,不占用外设,且支持时间戳。只需简单寄存器配置和 printf 重定向,即可获得实时 trace 能力。对于追求高性能调试的嵌入式开发者,这是必备技能。