用 SWO + ITM 替代串口打印:Cortex-M 无阻塞调试输出与性能实测
为什么需要替代串口打印?
在嵌入式开发中,printf 重定向到 UART 是最常见的调试手段。但它存在明显痛点:
- 占用硬件资源:必须占用一个 UART 外设及对应引脚。
- 阻塞 CPU:发送一个字节需等待 TXE 标志,高波特率下仍会拖慢实时任务。
- 速率瓶颈:常用 115200bps 下,打印 100 字节需约 8.7ms,严重影响控制周期。
- 额外功耗:UART 收发器持续工作。
Cortex-M 内核自带 SWO(Serial Wire Output) 与 ITM(Instrumentation Trace Macrocell),可通过 SWD 调试接口输出调试信息,不占用任何外设、不阻塞 CPU,速率可达数 Mbps。
原理:SWO 与 ITM 如何工作
-
ITM:内核中的硬件模块,包含 32 个刺激端口(Stimulus Port),其中端口 0 用于
printf类输出。CPU 只需向ITM_STIM寄存器写入数据,硬件自动打包成 ITM 数据包。 - SWO:Cortex-M 的调试引脚(通常与 JTDO 复用),以 NRZ 或曼彻斯特编码 串行输出 ITM 数据包。
- DWT:数据观察点单元,可提供时间戳,用于测量代码执行时间。
数据流向:CPU 写 ITM_STIM → ITM 打包 → SWO 引脚 → 调试器(ST-Link/J-Link)→ IDE 控制台。
配置步骤(以 STM32CubeIDE + ST-Link 为例)
1. 硬件连接
- 确保 SWD 接口的 SWO 引脚(PA13/SWDIO、PA14/SWCLK、PB3/SWO)已连接调试器。
- 若使用 ST-Link V2,需将 SWO 引脚引出。
2. 使能 ITM 与 SWO 时钟
在 main() 初始化中调用:
// 使能 DWT 和 ITM
CoreDebug->DEMCR |= CoreDebug_DEMCR_TRCENA_Msk;
ITM->LAR = 0xC5ACCE55; // 解锁 ITM
ITM->TCR |= ITM_TCR_ITMENA_Msk; // 使能 ITM
ITM->TER |= 1UL; // 使能刺激端口 0
3. 重定向 printf 到 ITM
#include <stdio.h>
int _write(int file, char *ptr, int len) {
(void)file;
for (int i = 0; i < len; i++) {
ITM_SendChar(*ptr++);
}
return len;
}
ITM_SendChar 是 CMSIS 提供的函数,内部检查 ITM 使能状态,若未使能则直接返回,不会阻塞。
4. IDE 端配置
- STM32CubeIDE:Debug Configuration → Debugger → 勾选 Enable SWO,设置 Core Clock 为系统主频(如 72MHz)。
- Keil MDK:Debug → Settings → Trace → 勾选 Trace Enable,设置 Core Clock 与 SWO 时钟。
- IAR:Project → Options → Debugger → Setup → 勾选 SWO。
完整代码示例
#include "stm32f1xx.h"
#include <stdio.h>
// 初始化 SWO/ITM
void ITM_Init(void) {
// 使能跟踪与调试
CoreDebug->DEMCR |= CoreDebug_DEMCR_TRCENA_Msk;
// 解锁 ITM
ITM->LAR = 0xC5ACCE55;
// 使能 ITM 与端口 0
ITM->TCR = ITM_TCR_ITMENA_Msk | ITM_TCR_SYNCENA_Msk;
ITM->TER = 1UL;
// 使能 DWT 周期计数器(用于时间戳)
DWT->CTRL |= DWT_CTRL_CYCCNTENA_Msk;
DWT->CYCCNT = 0;
}
// 重定向 _write
int _write(int file, char *ptr, int len) {
(void)file;
for (int i = 0; i < len; i++) {
ITM_SendChar(*ptr++);
}
return len;
}
// 测量代码执行周期
uint32_t start, end, cycles;
int main(void) {
SystemInit();
ITM_Init();
printf("ITM ready!\n");
// 性能测试:测量一段空循环
start = DWT->CYCCNT;
for (volatile int i = 0; i < 1000; i++);
end = DWT->CYCCNT;
cycles = end - start;
printf("1000 loops: %lu cycles\n", cycles);
while (1) {
// 主循环
}
}
性能实测对比
在 STM32F103(72MHz)上,分别用 UART(115200bps)和 SWO(2MHz)打印 100 字节字符串,测量 CPU 阻塞时间:
| 方式 | 波特率/速率 | 打印 100 字节耗时 | CPU 阻塞 | |------|-------------|-------------------|----------| | UART | 115200bps | 约 8.7ms | 是 | | SWO | 2MHz | 约 0.5ms | 否(仅写寄存器) |
结论:SWO 速度提升约 17 倍,且 CPU 几乎不阻塞,适合实时性要求高的场景。
注意事项
- SWO 引脚复用:SWO 与 JTDO 共用,若使用 JTAG 需改为 SWD 模式。
- 时钟配置:IDE 中 Core Clock 必须与实际系统时钟一致,否则输出乱码。
- 调试器支持:ST-Link V2 需固件支持 SWO;J-Link 需在配置中使能 SWO。
- ITM 端口:默认使用端口 0,若需多通道可启用其他端口。
- 低功耗模式:进入 Sleep/Stop 模式后 SWO 可能停止,需唤醒后重新初始化。
- 缓冲区:ITM 硬件 FIFO 较小,高速打印大量数据时可能丢失,建议控制输出频率。
总结
SWO + ITM 是 Cortex-M 调试的利器,无需额外外设、不阻塞 CPU、速率高。通过简单的寄存器配置和 _write 重定向,即可替代传统串口打印。在实时性要求高的项目中,强烈推荐使用。