Skip to content

嵌入式调试与故障排查

调试工具链

GDB + OpenOCD

bash
# 安装工具链
sudo apt install openocd gdb-multiarch

# 启动 OpenOCD(以 STM32H7 + ST-Link 为例)
openocd -f interface/stlink.cfg -f target/stm32h7x.cfg

# 连接 GDB
gdb-multiarch build/firmware.elf
(gdb) target remote localhost:3333
(gdb) monitor reset halt
(gdb) load                    # 烧录固件
(gdb) monitor reset run       # 运行

# 常用 GDB 命令
(gdb) break main              # 设置断点
(gdb) break modbus.c:125      # 文件行号断点
(gdb) watch g_soc             # 数据观察点(变量被修改时停止)
(gdb) info registers          # 查看寄存器
(gdb) x/10x 0x20000000        # 查看内存(16进制)
(gdb) backtrace               # 调用栈
(gdb) info threads            # FreeRTOS 线程(需要 FreeRTOS-aware 插件)

VS Code + Cortex-Debug

json
// .vscode/launch.json
{
    "version": "0.2.0",
    "configurations": [
        {
            "name": "Debug STM32H7",
            "type": "cortex-debug",
            "request": "launch",
            "servertype": "openocd",
            "configFiles": [
                "interface/stlink.cfg",
                "target/stm32h7x.cfg"
            ],
            "executable": "${workspaceFolder}/build/firmware.elf",
            "runToEntryPoint": "main",
            "svdFile": "${workspaceFolder}/STM32H743xx.svd",
            "rtos": "FreeRTOS",
            "preLaunchTask": "build"
        }
    ]
}

串口调试输出

c
// 重定向 printf 到 UART(STM32 HAL)
#include <stdio.h>

// 重写 _write 函数
int _write(int file, char *ptr, int len) {
    HAL_UART_Transmit(&huart1, (uint8_t*)ptr, len, HAL_MAX_DELAY);
    return len;
}

// 带时间戳的日志宏
#define LOG_DEBUG(fmt, ...) \
    printf("[%8lu][DBG] " fmt "\r\n", HAL_GetTick(), ##__VA_ARGS__)
#define LOG_INFO(fmt, ...) \
    printf("[%8lu][INF] " fmt "\r\n", HAL_GetTick(), ##__VA_ARGS__)
#define LOG_ERROR(fmt, ...) \
    printf("[%8lu][ERR] " fmt "\r\n", HAL_GetTick(), ##__VA_ARGS__)

// 使用示例
LOG_INFO("Modbus read: voltage=%.2fV, current=%.2fA", voltage, current);
LOG_ERROR("Modbus timeout, retry=%d", retry_count);

硬件故障分析

HardFault 分析

c
// HardFault 处理程序(保存现场)
void HardFault_Handler(void) {
    __asm volatile (
        "TST LR, #4\n"
        "ITE EQ\n"
        "MRSEQ R0, MSP\n"
        "MRSNE R0, PSP\n"
        "B HardFault_HandlerC\n"
    );
}

// C 语言处理函数
void HardFault_HandlerC(uint32_t *stack_frame) {
    // 从栈帧中提取寄存器值
    uint32_t r0  = stack_frame[0];
    uint32_t r1  = stack_frame[1];
    uint32_t r2  = stack_frame[2];
    uint32_t r3  = stack_frame[3];
    uint32_t r12 = stack_frame[4];
    uint32_t lr  = stack_frame[5];  // 链接寄存器(返回地址)
    uint32_t pc  = stack_frame[6];  // 程序计数器(故障地址)
    uint32_t psr = stack_frame[7];

    // 读取故障状态寄存器
    uint32_t cfsr = SCB->CFSR;   // 配置故障状态
    uint32_t hfsr = SCB->HFSR;   // 硬故障状态
    uint32_t mmfar = SCB->MMFAR; // 内存管理故障地址
    uint32_t bfar = SCB->BFAR;   // 总线故障地址

    printf("=== HardFault ===\n");
    printf("PC=0x%08lX LR=0x%08lX\n", pc, lr);
    printf("CFSR=0x%08lX HFSR=0x%08lX\n", cfsr, hfsr);

    // 分析 CFSR
    if (cfsr & 0x0001) printf("IACCVIOL: 指令访问违规\n");
    if (cfsr & 0x0002) printf("DACCVIOL: 数据访问违规,地址=0x%08lX\n", mmfar);
    if (cfsr & 0x0100) printf("IBUSERR: 指令总线错误\n");
    if (cfsr & 0x0200) printf("PRECISERR: 精确数据总线错误,地址=0x%08lX\n", bfar);
    if (cfsr & 0x0400) printf("IMPRECISERR: 非精确数据总线错误\n");
    if (cfsr & 0x10000) printf("UNDEFINSTR: 未定义指令\n");
    if (cfsr & 0x20000) printf("INVSTATE: 无效状态\n");

    // 保存到 Flash 并重启
    SaveCrashDump(stack_frame, cfsr, hfsr);
    NVIC_SystemReset();
}

栈溢出检测

c
// FreeRTOS 栈溢出钩子
void vApplicationStackOverflowHook(TaskHandle_t xTask, char *pcTaskName) {
    printf("STACK OVERFLOW in task: %s\n", pcTaskName);
    // 记录到 Flash
    SaveCrashInfo("StackOverflow", pcTaskName);
    // 重启
    NVIC_SystemReset();
}

// 在 FreeRTOSConfig.h 中启用
// #define configCHECK_FOR_STACK_OVERFLOW 2

嵌入式 Linux 调试

bash
# 查看内核日志
dmesg -w                          # 实时内核日志
dmesg | grep -i "error\|fault"    # 过滤错误

# 查看进程状态
ps aux
top -b -n 1

# 查看内存使用
cat /proc/meminfo
cat /proc/buddyinfo               # 内存碎片情况

# 查看 CPU 使用
cat /proc/stat
mpstat 1 5                        # 每秒采样 5 次

# 串口调试
strace -p <pid>                   # 跟踪系统调用
ltrace -p <pid>                   # 跟踪库调用

# 内存泄漏检测
valgrind --leak-check=full ./myapp

# 性能分析
perf record -g ./myapp
perf report

# 查看 GPIO 状态
cat /sys/kernel/debug/gpio

# 查看 I2C 设备
i2cdetect -y 1

# 查看 SPI 设备
ls /dev/spidev*

常见问题速查

问题可能原因排查方法
程序跑飞/复位栈溢出、HardFault启用 HardFault 处理,检查栈大小
串口乱码波特率不匹配、时钟配置错误用示波器测量实际波特率
CAN 无法通信终端电阻缺失、波特率不匹配检查总线电阻,用 CAN 分析仪抓包
Flash 写入失败未擦除、写保护检查擦除操作,确认写保护状态
任务死锁互斥锁顺序不一致使用 FreeRTOS 调试器查看任务状态
内存不足堆/栈配置过小监控 xPortGetFreeHeapSize()
看门狗复位任务阻塞、中断风暴检查任务执行时间,降低中断频率
时序问题中断优先级配置错误检查 NVIC 优先级分组配置

褚成志的IoT笔记