第4章 变量与寄存器观察:Watch窗口、Memory窗口、Peripherals与System Viewer
调试的时候,最怕什么?
怕程序跑飞了,还不知道错在哪。怕变量值不对,却看不到。怕寄存器配错了,外设不干活。
这一章,我就带你把这几个窗口玩透。Watch窗口、Memory窗口、Peripherals窗口、System Viewer,这四个工具用好了,定位问题能快一倍。
4.1 Watch窗口:变量的“实时监控器”
Watch窗口,说白了就是给你盯着变量用的。程序跑起来,变量怎么变,它实时刷新。
4.1.1 添加变量到Watch
操作很简单:
- 在代码里选中变量名,右键 → Add to Watch。
- 或者直接在Watch窗口的空白行双击,手动输入变量名。
我个人习惯把关键变量都扔进去。比如电机控制里的目标速度、实际速度、PID误差值。一眼就能看出逻辑对不对。
4.1.2 变量类型与格式
Watch窗口里,你可以右键变量,选择显示格式:
| 格式 | 适用场景 |
|---|---|
| 十进制 | 普通计数、延时参数 |
| 十六进制 | 寄存器值、地址、标志位 |
| 二进制 | 位操作、状态标志 |
| 浮点数 | ADC采样值、PID输出 |
嗯,这里要注意:如果你看的是uint32_t类型的寄存器值,用十六进制最直观。看ADC采样结果,用十进制或浮点数。
4.1.3 实时更新与断点配合
Watch窗口有两种刷新模式:
- 运行中刷新:程序全速跑,Watch窗口定期更新。适合观察趋势。
- 断点刷新:程序停在断点处,Watch窗口显示那一刻的值。适合精确分析。
我曾经遇到一个Bug:电机转速忽快忽慢。我把速度变量加到Watch,全速跑着观察,发现目标速度没变,但实际速度在跳。后来在PID计算函数里设断点,Watch显示误差值突然变大——原来是传感器采样偶尔丢数据。你看,Watch窗口配合断点,问题一下就锁定了。
4.2 Memory窗口:直接看内存,不绕弯子
Watch窗口看变量,Memory窗口看内存。有时候变量值看起来不对,但内存里数据是好的——那可能是变量类型声明错了。反过来,内存数据乱了,那问题就大了。
4.2.1 打开Memory窗口
View → Memory Windows → Memory 1(或2、3、4)。你可以同时开4个窗口,看不同地址区域。
4.2.2 查看指定地址
在Address栏输入地址:
0x20000000— SRAM起始地址0x08000000— Flash起始地址&变量名— 直接取变量地址
我调试时经常这么干:在Watch窗口看到变量值异常,立刻切到Memory窗口,输入&那个变量,看看内存里到底存了什么。有一次发现数组越界,就是靠这个办法——Watch里数组最后一个元素正常,但Memory里相邻地址的数据被覆盖了。
4.2.3 数据格式与显示
Memory窗口支持多种显示格式:
| 格式 | 说明 |
|---|---|
| 8位十六进制 | 每个字节显示2个十六进制字符 |
| 16位十六进制 | 每2个字节合并显示 |
| 32位十六进制 | 每4个字节合并显示 |
| ASCII | 右侧显示对应字符,适合看字符串 |
我个人习惯用32位十六进制看寄存器区域,用8位十六进制+ASCII看缓冲区数据。比如调试串口接收时,Memory窗口里能看到一帧完整的报文,哪个字节对了、哪个错了,一目了然。
4.3 Peripherals窗口:外设寄存器的“可视化面板”
Peripherals窗口,是Keil里我最喜欢的工具之一。它把外设寄存器按功能分组,用图形化界面展示。你不用去翻参考手册查寄存器地址,直接看这个窗口就行。
4.3.1 打开Peripherals窗口
View → Peripherals → 选择外设(比如GPIO、USART、TIM)。
举个例子,打开GPIOA的Peripherals窗口,你会看到:
- 每个引脚的模式(输入、输出、复用、模拟)
- 输出数据寄存器(ODR)的值
- 输入数据寄存器(IDR)的值
- 上下拉配置
而且,这些值会随着程序运行实时变化。你按下按键,对应引脚的IDR位立刻从1变成0。这种直观反馈,比看代码快多了。
4.3.2 修改寄存器值
Peripherals窗口里,有些寄存器可以直接修改。比如你想强制让某个GPIO引脚输出高电平,直接在ODR对应的位上点一下,改成1。程序下次运行到那里时,会读到这个新值。
但要注意:
4.3.3 实战案例:串口不发送数据
我记得有一次,串口怎么都不发数据。代码检查了好几遍,没问题。打开Peripherals窗口看USART寄存器:
- CR1寄存器:UE位=1,使能了
- CR1寄存器:TE位=1,发送使能了
- DR寄存器:有数据待发送
- SR寄存器:TXE位=0 —— 等等,TXE是0?
TXE位为0,表示发送数据寄存器非空,但数据还没发出去。再一看,波特率寄存器算出来的值不对。原来是时钟配置错了,USART的时钟频率比预期低了一半。波特率自然也不对。
你看,Peripherals窗口把寄存器状态全摆在你面前,问题根本藏不住。
4.4 System Viewer:系统级的外设总览
System Viewer,是Peripherals窗口的“升级版”。它把整个芯片的外设、中断、时钟树都列出来,以树形结构展示。
4.4.1 打开System Viewer
View → System Viewer。你会看到一个窗口,左侧是外设列表,右侧是详细信息。
4.4.2 主要功能
- 外设寄存器查看:和Peripherals窗口类似,但信息更全。
- 中断状态查看:哪些中断被使能了,哪些中断正在挂起,一目了然。
- 时钟配置查看:HSE、HSI、PLL、系统时钟、外设时钟,当前值是多少。
我个人习惯在调试开始时,先打开System Viewer看一眼时钟树。确认系统时钟跑在目标频率上,外设时钟也正确使能了。这一步能避免很多低级错误。
4.4.3 与Peripherals窗口的对比
| 特性 | Peripherals窗口 | System Viewer |
|---|---|---|
| 展示方式 | 图形化面板 | 树形结构 |
| 信息密度 | 中等 | 高 |
| 修改寄存器 | 支持 | 支持 |
| 中断查看 | 有限 | 完整 |
| 时钟查看 | 无 | 有 |
简单说:日常调试用Peripherals窗口,够快够直观。需要系统级分析时,切到System Viewer。
4.5 四个窗口的配合使用
这四个窗口不是孤立的。我调试时,经常这样配合:
- Watch窗口盯着关键变量,看逻辑对不对。
- Memory窗口验证内存数据,看有没有越界或覆盖。
- Peripherals窗口检查外设状态,看寄存器配没配对。
- System Viewer做系统级确认,看时钟和中断。
举个例子:调试I2C通信时,Watch窗口看发送缓冲区的数据,Memory窗口看接收缓冲区的内容,Peripherals窗口看I2C的状态寄存器(忙、应答、停止位),System Viewer确认I2C时钟是否使能。四个窗口一配合,问题基本跑不掉。
4.6 本章知识结构图
下面这张图,帮你理清四个窗口的关系和适用场景:
4.7 避坑指南
最后,分享几个我踩过的坑:
- Watch窗口变量不更新:检查一下是不是优化等级太高了。O0优化下变量肯定能看,O2优化下有些局部变量可能被优化掉。我建议调试时先用O0。
- Memory窗口地址输错了:输入
&变量名时,注意变量作用域。局部变量在函数退出后,地址可能被回收。用全局变量或静态变量更稳妥。 - Peripherals窗口值不变:可能是外设时钟没使能。去System Viewer看一眼时钟树,确认外设时钟是打开的。
- System Viewer里中断挂起但没响应:检查中断优先级分组。有时候优先级配错了,高优先级中断一直占着,低优先级中断永远进不去。
嗯,这些坑我都亲自踩过。现在调试时,这四个窗口已经成了我的标准配置。你也试试看,用熟了之后,定位问题的速度能快不少。