第三章:裸机调试基础
各位同学,今天咱们聊聊裸机调试。说白了,就是没有操作系统的情况下,怎么跟你的单片机“对话”。
我刚开始做航天嵌入式那会儿,最怕的就是程序跑飞了,但不知道飞哪儿去了。后来老工程师教了我几招,今天全部分享给你们。
3.1 GPIO点灯调试法
这招看起来土,但真管用。我到现在还经常用。
原理很简单:用LED的亮灭来表示程序执行到了哪里。你想想看,一个灯能表达的信息其实不少。
核心思路:把GPIO当作“示波器探头”,用眼睛看电平变化。
具体怎么做?我一般这样用:
- 单灯指示:程序跑到关键位置,点亮LED。比如初始化完成、进入主循环、发生错误。
- 多灯组合:用多个LED表示状态码。比如红灯+绿灯表示模式1,红灯+蓝灯表示模式2。
- 闪烁频率:快闪表示紧急情况,慢闪表示正常运行。
举个实际例子。我在项目中遇到过一个问题:某型飞控的传感器初始化偶尔失败。用串口打印吧,失败时串口还没初始化好。怎么办?
我就在初始化函数里加了个GPIO翻转:
// 传感器初始化函数
void sensor_init(void) {
// 第一步:上电
GPIO_SetPin(HIGH); // 灯亮
delay_ms(10);
GPIO_SetPin(LOW); // 灯灭
// 第二步:复位
GPIO_SetPin(HIGH);
delay_ms(5);
GPIO_SetPin(LOW);
// 第三步:读取ID
uint8_t id = read_reg(0x00);
if (id != EXPECTED_ID) {
// 失败!灯快速闪烁
while(1) {
GPIO_Toggle();
delay_ms(100);
}
}
}
小技巧:我习惯把调试用的GPIO放在不用的引脚上,比如JTAG接口的空闲引脚。这样不影响正常功能。
3.2 串口打印调试
GPIO点灯虽然直观,但信息量有限。串口打印就不一样了,你能看到具体的数据和状态。
嗯,这里要注意:串口调试有个前提——你的串口驱动必须稳定可靠。否则你看到的可能是假象。
我一般这样用串口:
- 关键变量打印:在程序的关键节点,打印当前变量的值。
- 状态机跟踪:打印当前状态和触发事件。
- 错误信息输出:发生异常时,打印错误码和上下文。
举个例子,我曾经调试一个步进电机控制程序,电机总是走不准。用串口一打印,发现是速度计算时发生了整数溢出:
void motor_step(void) {
static uint32_t step_count = 0;
step_count++;
// 打印当前步数
debug_printf("Step: %lu\n", step_count);
// 计算位置
uint32_t position = step_count * STEP_ANGLE; // 这里溢出了!
debug_printf("Position: %lu\n", position);
}
看到打印结果,我立刻发现问题:step_count到一定值后,position突然变小了。这就是典型的整数溢出。
避坑指南:我曾经在串口中断里调用printf,结果导致死循环。为什么?因为printf本身可能触发中断,造成递归调用。记住:中断服务函数里尽量别用打印函数。
3.3 断点调试技巧
断点调试是最强大的调试手段,但也是最容易被滥用的。
我见过很多新手,一上来就全程序设断点,然后单步执行。结果呢?跑了一整天,啥也没发现。
我个人习惯这样用断点:
- 条件断点:只在特定条件满足时停下。比如变量等于某个值,或者计数器达到阈值。
- 数据断点:监控某个内存地址的读写。当数据被意外修改时,立刻停下。
- 函数断点:在函数入口或出口设断点,观察参数和返回值。
举个例子,我调试过一个CAN总线通信问题。数据偶尔会丢包,但很难复现。我就在发送函数里设了个条件断点:
void can_send(uint32_t id, uint8_t* data, uint8_t len) {
// 条件断点:只在ID为0x123时停下
if (id == 0x123) {
// 在这里设断点
debug_printf("Sending ID 0x123\n");
}
// 正常发送逻辑
// ...
}
这样,只有特定ID的数据发送时才会停下,大大缩小了排查范围。
核心原则:断点不是越多越好,而是要精准。一个精心设置的条件断点,胜过一百个普通断点。
3.4 三种方法的对比
这三种方法各有优劣,我整理了个表格,方便你们对比:
| 方法 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| GPIO点灯 | 简单可靠,不依赖外设 | 信息量少,只能表达状态 | 初始化阶段、中断服务、实时性要求高的场景 |
| 串口打印 | 信息丰富,可输出变量值 | 依赖串口驱动,可能影响时序 | 逻辑调试、状态跟踪、数据分析 |
| 断点调试 | 最强大,可观察任意变量 | 会暂停程序,不适合实时系统 | 复杂逻辑分析、内存问题排查 |
我个人建议:三种方法结合使用。先用GPIO点灯快速定位问题范围,再用串口打印获取详细信息,最后用断点深入分析。
我的经验:在航天项目中,我通常会在硬件上预留一个调试LED和串口接口。即使产品交付后,这些接口也能用于现场排错。记住:调试接口不是浪费,是保险。
好了,这一章就到这里。下一章咱们聊聊中断调试,那可是个容易出坑的地方。