1. 嵌入式调试基础:从“灯不亮”到“系统稳”

做嵌入式这么多年,我最大的体会就是:调试能力决定了你能走多远。写代码谁都会,但出了问题能快速定位,这才是真本事。这一章,咱们就把调试的底子打好。

1.1 嵌入式系统调试到底在调什么?

说白了,调试就是让硬件和软件“对上话”。你想想看,一个单片机跑着你的代码,它到底在执行哪条指令?某个寄存器值对不对?外设有没有按预期工作?这些信息,调试工具就是帮你“看”到的。

我个人习惯把调试场景分成三类:

调试类型 典型问题 常用工具
逻辑/时序 GPIO 电平跳变不对、SPI 时钟相位错 逻辑分析仪、示波器
程序执行 跑飞、死循环、变量值异常 JTAG/SWD 调试器
通信/协议 I²C 无应答、UART 乱码 逻辑分析仪 + 串口助手

核心原则:先确认硬件没问题,再怀疑软件。我在项目中遇到过太多次,折腾半天代码,最后发现是虚焊。

1.2 三大调试工具,你得会用

1.2.1 逻辑分析仪——看“数字信号”的眼睛

逻辑分析仪只关心电平高低,不关心具体电压值。它最适合看时序关系。比如 SPI 的 CS、SCLK、MOSI 之间的先后顺序对不对。

  • 采样率:至少是信号频率的 4 倍。测 1MHz 的 SPI,采样率要 4MHz 以上。
  • 通道数:8 通道起步,16 通道更实用。我常用的就是 8 通道,够抓 I²C + UART 同时跑。
  • 触发条件:设置上升沿或下降沿触发,能帮你精准抓到异常时刻。

我的习惯:调试 I²C 时,我会同时抓 SCL 和 SDA,再设一个触发条件为“SCL 连续 9 个脉冲后 SDA 没拉低”。这样能快速定位从设备无应答的问题。

1.2.2 示波器——看“模拟信号”的利器

示波器能看电压波形,还能测噪声、过冲、振铃。这些逻辑分析仪干不了。

  • 带宽:测普通数字信号,100MHz 够用。测高速信号(比如 USB、以太网),至少 200MHz。
  • 采样率:1GS/s 是主流,抓单次信号够用。
  • 探头:10x 探头比 1x 探头更准,尤其测高频时。

注意:示波器探头的地线夹子要尽量短。地线太长会引入噪声,测出来的波形全是毛刺。我曾经因为这个,误判了一个电源纹波问题,折腾了两天。

1.2.3 JTAG/SWD 调试器——控制程序执行的“遥控器”

JTAG 和 SWD 是两种调试接口。JTAG 用 5 根线,SWD 只用 2 根(SWDIO + SWCLK)。现在主流 MCU 都支持 SWD,省引脚。

特性 JTAG SWD
引脚数 5(TMS, TCK, TDI, TDO, nTRST) 2(SWDIO, SWCLK)
速度 最高可达 50MHz 通常 4-10MHz
调试能力 支持多核、边界扫描 单核调试,够用
适用场景 FPGA、复杂 SoC ARM Cortex-M 系列 MCU

我个人建议:能用 SWD 就别用 JTAG。省下来的 3 个 GPIO,够你接一个按键加一个 LED 了。

1.3 调试环境搭建——别让工具拖后腿

环境搭不好,调试效率直接减半。我见过太多人,花半小时连不上调试器,最后发现是驱动没装。

1.3.1 硬件连接

  1. 调试器选型:J-Link、ST-Link、DAP-Link 都行。J-Link 最稳,但贵。ST-Link 便宜,够用。
  2. 接线:SWD 只需要 4 根线:SWDIO、SWCLK、GND、VCC(可选)。VCC 用来检测目标板电压,不是供电。
  3. 上拉/下拉:SWDIO 需要上拉(10kΩ 到 3.3V),SWCLK 需要下拉(10kΩ 到 GND)。很多开发板已经集成,但自己做板子时别忘了。

避坑指南:我曾经因为 SWD 线太长(超过 20cm),导致调试器经常连不上。后来换成 10cm 的排线,问题解决。SWD 信号频率高,线越短越好。

1.3.2 软件配置

  • IDE:Keil、IAR、STM32CubeIDE 都行。我个人习惯用 VS Code + Cortex-Debug 插件,轻量又灵活。
  • 驱动:调试器需要装驱动。J-Link 用 Segger 官方驱动,ST-Link 用 ST 官方驱动。别用 Windows 自动更新的,容易出问题。
  • 调试配置:设置目标芯片型号、调试接口(SWD/JTAG)、时钟频率。时钟频率别设太高,先设 1MHz,稳定后再往上调。

1.3.3 验证环境

搭好环境后,先跑一个最简单的程序:点灯。能正常进入调试、单步执行、查看变量,说明环境没问题。

// 点灯测试程序
#include "stm32f1xx_hal.h"

int main(void) {
    HAL_Init();
    __HAL_RCC_GPIOC_CLK_ENABLE();

    GPIO_InitTypeDef GPIO_InitStruct = {0};
    GPIO_InitStruct.Pin = GPIO_PIN_13;
    GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
    HAL_GPIO_Init(GPIOC, &GPIO_InitStruct);

    while (1) {
        HAL_GPIO_TogglePin(GPIOC, GPIO_PIN_13);
        HAL_Delay(500);  // 断点打在这里,看能不能停住
    }
}

小技巧:第一次连接调试器时,如果提示“No target connected”,先检查接线,再检查供电。很多开发板需要外部供电,调试器不供电。

1.4 本章知识体系

下面这张图,帮你把本章的核心逻辑串起来:

嵌入式调试基础——知识体系 调试目标:让软硬件“对上话” 逻辑/时序问题 程序执行问题 通信/协议问题 逻辑分析仪 示波器 JTAG/SWD 调试器 环境搭建:硬件连接 → 软件配置 → 验证测试 输出:稳定可靠的调试环境 核心思路:先硬件后软件,先工具后代码,先简单后复杂

调试不是玄学,是方法论。工具只是手段,关键是思路。这一章的内容,就是帮你把调试的“骨架”搭起来。后面几章,咱们会在这个骨架上填肉——具体怎么用逻辑分析仪抓 I²C、怎么用示波器测 PWM、怎么用调试器看堆栈……