【第42期】调试进阶(一):IDE中的Register与Memory窗口

进入第九阶段:调试与底层透视

这是区分"代码民工"和"技术专家"最明显的标志。 新手遇到 Bug,第一反应是加 printf("Here 1\n"); 专家遇到 Bug,第一反应是挂上 J-Link,打开 RegisterMemory 窗口,直接看芯片的"五脏六腑"。

printf 是有延迟的、有侵入性的(会改变时序),而硬件调试器是上帝视角

让我们进入 第42期,学会像外科医生一样解剖 MCU。

抛弃 Printf,直接看外设的"裸体"

1. 为什么要看 Register (寄存器)?

你写了 HAL_GPIO_Init(GPIOA, &init_struct),把 PA5 配置为推挽输出。 但是灯就是不亮。 你开始怀疑:是时钟没开?是引脚复用没配对?还是速度等级不对?

如果你只看代码,你永远在猜。因为代码逻辑可能是对的,但库函数可能有 Bug,或者被后面的代码覆盖了配置。

正确的做法:

  1. 在 IDE (Keil/IAR/CubeIDE) 中进入 Debug 模式。

  2. 打开 System ViewerRegisters 窗口。

  3. 找到 GPIOA -> MODER 寄存器。

看什么?

  • 检查 PA5 对应的两个位是否是 01 (Output Mode)。

  • 如果是 00 (Input),说明你的初始化代码根本没生效(可能时钟没开,写不进去)。

  • 如果是 11 (Analog),说明被后面的 ADC 初始化覆盖了。

这就是"真理"。寄存器里的值,是芯片硬件当前真实的物理状态,它不会撒谎。


2. 实战:SFR (Special Function Register) 排查法

场景一:串口发不出数据
  • Printf 现象: 程序卡死在 HAL_UART_Transmit

  • Register 排查:

    1. USART1 -> CR1TE (Transmitter Enable) 位是不是 1?(检查是否使能)

    2. USART1 -> SR (状态寄存器):TC (Transmission Complete) 位是不是 1?

    3. 最关键的:RCC -> APB2ENR。USART1 的时钟使能位是不是 1?很多时候是因为你忘了开时钟,导致怎么写寄存器都写不进去(读出来全是 0)。

场景二:定时器时间不对
  • 现象: 设定 1秒中断,结果 0.5秒就中断了。

  • Register 排查:

    1. TIMx -> PSC (预分频器)。是不是 7199?(72MHz / 7200 = 10kHz)

    2. TIMx -> ARR (自动重装载)。是不是 9999?(10kHz / 10000 = 1Hz)

    3. 常见坑: PSC 是 16 位的,如果你填了 100000,它会溢出截断,导致频率变快。看寄存器一眼就能发现数值不对。


3. Memory 窗口:透视内存的真相

Watch 窗口(变量观察)很好用,但它只能看"变量的值"。 Memory 窗口 能让你看到"变量在内存里的布局"。这对于排查指针越界结构体对齐字节序问题是绝杀。

技巧一:检查结构体对齐 (Struct Alignment)

你定义了一个通信协议结构体:

struct {

uint8_t Head;

uint32_t Len;

} Packet;

你以为 Len 紧挨着 Head? 打开 Memory 窗口,输入 &Packet

  • 地址 0x20000000: AA (Head)

  • 地址 0x20000001: 00 (Padding/填充字节)

  • 地址 0x20000002: 00 (Padding/填充字节)

  • 地址 0x20000003: 00 (Padding/填充字节)

  • 地址 0x20000004: 64 00 00 00 (Len = 100)

你会发现中间有 3 个字节的空洞!如果你直接把这个结构体 memcpy 发给上位机,解析一定错位。 解决:__packed__attribute__((packed)),再看 Memory 窗口,空洞消失了。

技巧二:抓捕"栈溢出" (Stack Overflow)

程序莫名其妙死机,怀疑栈炸了?

  1. 在 Memory 窗口找到栈的地址(比如 0x2000 8000 附近)。

  2. 一般栈的末尾会有大量的 00 00 00 00(未使用的区域)。

  3. 程序跑一会儿,暂停。

  4. 如果你发现那些 00 全部变成了乱七八糟的数据,而且一直顶到了栈底(Stack Limit),说明栈爆了


4. 实时更新 (Live Watch)

Keil 和 IAR 必须暂停才能看内存吗?不。 J-Link 支持 Live Watch (非侵入式读取)

  • 原理: ARM Cortex-M 内核支持在 CPU 全速运行的同时,通过调试接口(DAP)偷偷读取内存,不影响 CPU 执行。

  • 用法: 勾选 Periodic Window Update

  • 场景: 观察 PID 控制中的 Current_Error 变量,你可以看到数值像示波器一样跳动,而不需要停下电机。


5. 总结归纳

别再用 printf 调试底层驱动了。

  • Register 窗口 告诉你:"我叫你做的事,你到底做了没?" (验证配置)

  • Memory 窗口 告诉你:"你存的数据,到底长什么样?" (验证对齐和越界)

当你习惯了看寄存器,你会发现你不再需要翻几百页的 Reference Manual 去找位的定义,因为 IDE 已经把每一位的含义(RW, BitName)都列在旁边了。

但是,有时候 Bug 很狡猾。 全局变量 g_State 莫名其妙从 0 变成了 1,但你搜遍全代码也没找到哪里改了它。 难道是野指针?还是 DMA 误写? 这时候,你需要一个**"监控摄像头"**,一旦有人改这个变量,立马报警暂停。

相关推荐
m0_748249542 小时前
Java 语言提供了八种基本类型【文123】
java·开发语言·python
boneStudent2 小时前
STM32工业HMI控制系统
stm32·单片机·嵌入式硬件
a程序小傲2 小时前
中国邮政Java面试被问:Netty的FastThreadLocal优化原理
java·服务器·开发语言·面试·职场和发展·github·哈希算法
淦。。。。2 小时前
题解:P14013 [POCamp 2023] 送钱 / The Generous Traveler
开发语言·c++·经验分享·学习·其他·娱乐·新浪微博
橙露2 小时前
C#在视觉检测中的优势:工业智能化转型的利器
开发语言·c#·视觉检测
醇氧2 小时前
java.lang.NumberFormatException: For input string: ““
java·开发语言·spring
人工小情绪2 小时前
Antigravity简介
ide·人工智能
利刃大大2 小时前
【ES6】变量与常量 && 模板字符串 && 对象 && 解构赋值 && 箭头函数 && 数组 && 扩展运算符 && Promise/Await/Async
开发语言·前端·javascript·es6
大猫会长2 小时前
postgreSQL中,RLS的using与with check
开发语言·前端·javascript