3. JTAG/SWD 调试接口:用 SWO 引脚撬开 CPU 的嘴

烧录失败后,芯片往往还活着,只是不按你的意愿跑。这时候最怕什么?怕它「沉默」。JTAG/SWD 接口就是撬开它嘴的那把螺丝刀,而 SWO 引脚,是它愿意开口说话的那条缝。

我早年做一款电机驱动板,烧录到一半掉电,重新上电后芯片死活连不上调试器。当时急得满头汗,后来发现其实是 SWD 的 SWCLK 引脚被一个 10k 下拉电阻拉死了。嗯,从那以后,我每次画板子都会专门检查调试接口的上下拉配置。

3.1 JTAG 与 SWD:选哪个?

先搞清楚这两个接口的区别。JTAG 是大哥,5 根线,功能全;SWD 是小弟,2 根线,够用还省引脚。现在绝大多数 Cortex-M 内核的芯片都支持 SWD,我个人习惯——只要不是必须用 JTAG 做边界扫描,一律用 SWD。

特性 JTAG SWD
引脚数 5(TCK, TMS, TDI, TDO, nTRST) 2(SWCLK, SWDIO)
调试功能 完整(含边界扫描) 完整(不含边界扫描)
烧录速度 最高 50 MHz 最高 50 MHz(实际受线长影响)
适用场景 FPGA、复杂 SoC、多核调试 绝大多数 Cortex-M 单片机
SWO 支持 需额外引脚 原生支持(SWO 独立引脚)
我的小习惯: 画原理图时,SWD 接口一定要留出 SWO 引脚。哪怕你暂时不用,也焊个排针。等烧录失败需要抓 CPU 状态时,你会感谢这个决定。

3.2 SWO 是什么?为什么它这么重要?

SWO(Serial Wire Output)是 SWD 协议里的一条单向输出线。它不参与烧录,也不参与调试握手,它的唯一任务——把 CPU 内部的状态信息「吐」出来。

说白了,它就是 CPU 的「黑匣子数据链」。烧录失败后,芯片可能跑飞、死循环、或者卡在某个异常向量里。你没法打断它,也没法单步调试。但 SWO 可以让你在不打断 CPU 的情况下,实时看到 PC 指针、异常号、中断嵌套层级、甚至 printf 输出。

为什么会这样?因为 SWO 走的是 ITM(Instrumentation Trace Macrocell)模块,它直接挂在 CPU 内核总线上,不占用用户代码的执行时间。你想想看,这相当于给 CPU 装了一个独立的「日志记录仪」。

3.3 硬件连接:别让一根线毁了所有

连接其实很简单。以 ST-Link/V2 为例:

  • SWCLK → 芯片 SWCLK 引脚
  • SWDIO → 芯片 SWDIO 引脚
  • SWO → 芯片 SWO 引脚(也叫 TDO 或 TRACESWO)
  • GND → 共地
  • 3.3V → 目标板供电(可选,建议独立供电)
我曾经踩过的坑: SWO 线不能太长,超过 10cm 就容易丢数据。有一次我用了 20cm 的杜邦线,结果 SWO 输出的数据全是乱码。后来换成 5cm 的排线,问题立刻解决。另外,SWO 引脚不要接上拉或下拉电阻,否则信号幅度会被拉偏。

3.4 软件配置:让 SWO 真正工作起来

硬件接好了,软件不配等于白搭。以 STM32 为例,你需要做两件事:

  1. 使能 ITM 和 SWO 时钟——在 SystemInit() 或你的初始化代码里配置。
  2. 设置 SWO 波特率——通常与调试器协商,但也可以手动指定。

下面是我常用的初始化代码片段:

/* 使能 ITM 和 SWO */
void SWO_Init(uint32_t portBits, uint32_t cpuFreqMHz, uint32_t swoFreqMHz)
{
    uint32_t demcr = *(volatile uint32_t *)0xE000EDFC;
    demcr |= (1 << 24);  // 使能 ITM 全局
    *(volatile uint32_t *)0xE000EDFC = demcr;

    /* 设置 SWO 波特率:SWO 频率 = CPU 频率 / (SWO 分频 + 1) */
    uint32_t prescaler = (cpuFreqMHz * 1000000) / (swoFreqMHz * 1000000) - 1;
    *(volatile uint32_t *)0xE0040004 = prescaler;

    /* 使能 ITM 端口 0 */
    *(volatile uint32_t *)0xE0000000 = 0xC5ACCE55;  // 解锁 ITM
    *(volatile uint32_t *)0xE0000000 = portBits;    // 使能对应端口
}
关键点: SWO 波特率必须与调试器侧匹配。J-Link 默认用 2 MHz,ST-Link 默认用 4 MHz。如果你改了芯片侧的 SWO 分频,调试器那边也要同步改,否则收到的全是乱码。

3.5 数据采集:到底能抓到什么?

SWO 输出的数据分几种类型,我列个表方便你对照:

ITM 端口 数据类型 典型用途
端口 0 软件事件(printf 输出) 调试日志、变量值打印
端口 1-31 用户自定义事件 状态机跳转、中断触发标记
硬件事件 异常、中断、PC 采样 分析死机位置、中断延迟
时间戳 全局时间计数 测量代码执行时间

烧录失败后,我最常用的是 硬件事件 和 PC 采样。举个例子:芯片上电后直接跑飞,你连调试器都连不上。这时候可以先用 SWO 抓 PC 采样,看看 CPU 到底在哪个地址转悠。如果 PC 停在 0xFFFFFFF0 附近,那多半是中断向量表没配好;如果 PC 在某个外设地址区间反复跳,那就是外设时钟没开。

3.6 实战流程:烧录失败后的 SWO 三板斧

我总结了一套流程,每次烧录失败后按这个顺序来,基本能定位 80% 的问题:

  1. 第一步:检查调试器连接——用 J-Link Commander 或 ST-Link Utility 读芯片 ID。读不到?查硬件连接和供电。
  2. 第二步:强制复位并抓 SWO 数据——按住复位键,在调试器侧启动 SWO 采集,然后松手。你会看到 CPU 从复位向量开始执行的全过程。
  3. 第三步:分析异常事件——如果 SWO 输出里出现 HardFault、MemManage、BusFault 等异常号,直接定位到异常向量表,看是哪个外设或指令触发的。
一个实用技巧: 在代码里加一个死循环前的 SWO 输出。比如在 HardFault_Handler 里加一句 ITM_SendChar('F');。这样只要芯片进异常,SWO 就会吐出一个 'F',你立刻就知道问题出在异常处理上,而不是代码跑飞。

3.7 知识体系:SWO 调试的核心逻辑

下面这张图是我自己整理的 SWO 调试知识框架,帮你快速建立全局认知:

SWO 调试知识体系框架图 SWO 调试知识体系 SWO 输出 硬件连接 SWCLK / SWDIO / SWO / GND 线长 < 10cm / 无上下拉 软件配置 使能 ITM / 设置 SWO 分频 解锁 ITM / 使能端口 数据采集 PC 采样 / 异常事件 printf 输出 / 时间戳 烧录失败定位:复位抓取 → 异常分析 → 修复 核心思路:硬件连通 → 软件使能 → 数据采集 → 异常定位,三步闭环

3.8 常见问题与避坑

最后聊几个我实际遇到过的问题,你碰到了可以直接对号入座:

  • SWO 没数据? 先查 SWO 引脚是否被复用成 GPIO。很多芯片的 SWO 引脚默认是 GPIO 功能,需要在代码里显式配置为 SWO。
  • 数据全是乱码? 波特率不匹配。用逻辑分析仪抓一下 SWO 引脚的波形,数一下脉冲宽度,反推实际波特率。
  • 只能抓到部分数据? ITM 端口没全使能。检查 *(volatile uint32_t *)0xE0000000 的值,确保你需要的端口位被置 1。
  • 调试器连不上芯片? 试试用 J-Link 的「强制连接」模式(-force 参数),有时候芯片处于低功耗模式,普通连接会被拒绝。
特别提醒: 不要在生产固件里长期开启 SWO 输出。虽然它不占 CPU 时间,但会额外消耗几十微安的电流。我见过有人把 SWO 开着的板子拿去量产,结果整批货的待机电流超标。调试完记得关掉。