嵌入式调试与故障排查
调试工具链
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 优先级分组配置 |