STM32学习笔记#2-Keil调试
解决方案应包括以下步骤:1. 在Keil中设置断点,单步执行代码。2. 使用外设查看器检查GPIO寄存器的状态。3. 观察led_on和led_off执行时的寄存器、电信号波形变化。4. 对比预期与实际值,发现GPIOC的设置错误。5. 检查代码中的GPIO_WriteBit参数,确认是否正确。同时,可能需要解释如何利用观察窗口或内存窗口直接查看变量和寄存器的值,以及如何利用逻辑分析仪查看引脚电平
我在学习怎么编写代码时,无论是Keil还是VScode环境,其实非常忽略Keil中调试(debug)的学习,只会一味的复现各种例程,出了bug人脑加ai纠错,在真实开发环境中这必然是寸步难行的。以最简单的STM32标准库点亮LED灯为例,型号为STM32F407VET6,LED的GPIO口为:
| LED0 | LED1 | LED2 |
| PE5 | PE6 | PC13 |


一、例程:LED三灯同时亮灭
给出例程之后,我将led_on()和led_off()函数中GPIO_WriteBit(GPIOC, GPIO_Pin_13, Bit_RESET);与GPIO_WriteBit(GPIOC, GPIO_Pin_13, Bit_SET);互换,导致led2与其余两个led产生相反的亮灭关系。
#include "stm32f4xx.h"
/**
* @brief 延时函数,用于产生一定的延时。
*
* 该函数通过嵌套循环和空操作指令来实现延时。外层循环控制延时的大致时间,内层循环进一步细化延时。
* 每个内层循环中执行了5次空操作指令,以增加延时的精度。
* 延时时间 = 外层循环次数 * 内层循环次数 * 空操作指令次数__NOP() * 指令周期
* 每个指令周期约为5.95ns(1 / 168MHz),是因为STM32F407的主频为168MHz。
* 延时时间 = 1000 * 10000 * 5 * 5.95ns = 297.5ms
*
*/
static void delay()
{
// 外层循环,控制延时的大致时间
for(uint32_t i = 0; i < 1000; i++)
{
// 内层循环,进一步细化延时
for(uint32_t j = 0; j < 10000; j++)
{
// 执行空操作指令,增加延时精度
__NOP();
__NOP();
__NOP();
__NOP();
__NOP();
}
}
}
//该函数用于初始化STM32F407开发板上的LED引脚。根据提供的电路图,它使能了GPIOE和GPIOC的时钟,并配置了相应的引脚为输出模式。
static void leds_init(void)
{
// 使能GPIOE和GPIOC的时钟
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOE | RCC_AHB1Periph_GPIOC, ENABLE);
GPIO_InitTypeDef GPIO_InitStruct;
// 配置GPIOE的引脚5和6
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_5 | GPIO_Pin_6 ;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_OUT;//表示输出模式。
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;//G表示输出速度为50MHz
GPIO_InitStruct.GPIO_OType = GPIO_OType_PP;//表示推挽输出。
GPIO_InitStruct.GPIO_PuPd = GPIO_PuPd_NOPULL;//表示不使用上拉或下拉电阻。
GPIO_Init(GPIOE, &GPIO_InitStruct);
// 配置GPIOC的引脚13
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_13;
GPIO_Init(GPIOC, &GPIO_InitStruct);
}
//该函数用于点亮STM32F407开发板上的LED。根据提供的电路图,它将GPIOE的引脚5和6以及GPIOC的引脚13设置为低电平,从而点亮对应的LED。
static void led_on()
{
// 将GPIOE的引脚5和6设置为低电平,点亮对应的LED
GPIO_WriteBit(GPIOE, GPIO_Pin_5 | GPIO_Pin_6, Bit_RESET);
// 将GPIOC的引脚13设置为低电平,点亮对应的LED
GPIO_WriteBit(GPIOC, GPIO_Pin_13, Bit_SET);
}
// 该函数用于关闭STM32F407开发板上的LED。根据提供的电路图,它将GPIOE的引脚5和6以及GPIOC的引脚13设置为高电平,从而关闭对应的LED。
static void led_off()
{
GPIO_WriteBit(GPIOE, GPIO_Pin_5 | GPIO_Pin_6, Bit_SET);
GPIO_WriteBit(GPIOC, GPIO_Pin_13, Bit_RESET) ;
}
int main(void)
{
// 初始化LED引脚
leds_init();
while (1)
{
delay();
led_on();
delay();
led_off();
}
}
二、调试核心操作步骤
1. 进入调试模式

点击 **Start/Stop Debug Session (Ctrl+F5)**。
2. 设置断点

在代码中,行56、行63左部点击,设置以下断点:
-
led_on()函数内的GPIO_WriteBit调用处。 -
led_off()函数内的GPIO_WriteBit调用处。
![]()
同时断点的其余操作都可以尝试玩一下。
3. 单步执行并观察寄存器
-
点击 Run (F5) 触发第一个断点(
led_on())。
-
查看GPIOE和GPIOC的ODR寄存器:Peripherals →System Viewer Windows → GPIO → GPIOE 和 GPIOC 窗口。

-
-
预期结果:
GPIOE.ODR的Bit5和Bit6为 **0**(低电平,LED亮),GPIOC.ODR的Bit13应为 **0**。可以把框图拖出悬浮使用。
-
实际结果:
GPIOC.ODR.Bit13显示为 **1**(高电平,LED灭),与预期相反。
寄存器功能与数值解析,查询《STM32F4xx参考手册_V4(中文版)》可得下表结果
GPIOE寄存器值=LED0、LED1亮
|
寄存器 |
功能描述 |
值 |
含义分析 |
|
MODER |
模式寄存器(输入/输出) |
0x00001400 |
对于引脚 5(GPIO_Pin_5),对应 MODER 的第 10 - 11 位。这里的值表明引脚 5 和 6 被配置为输出模式(GPIO_Mode_OUT) |
|
OTYPER |
输出类型(推挽/开漏) |
0x0 |
所有引脚为推挽输出 |
|
OSPEEDR |
输出速度 |
0x00002800 |
所有引脚高速(50MHz) |
|
PUPDR |
上拉/下拉电阻 |
0x0 |
无上拉/下拉 |
|
IDR |
输入数据寄存器 |
0x0000FC87 |
表明引脚 5 和 6 的上下拉配置符合代码中无上下拉的设置 |
|
ODR |
输出数据寄存器 |
0x0 |
所有引脚输出低电平 |
| BSRR | 位设置 / 复位寄存器 | 0x0 | 写 1 到对应位可设置(置 1)或复位(清 0)引脚输出状态,写 0 无作用 |
| LCKR | 锁定寄存器 | 0x0 | 用于锁定引脚配置,防止意外修改 |
| AFRL | 复用功能低位寄存器 | 0x0 |
用于配置引脚的复用功能,每 4 位对应一个引脚的复用功能选择,这里值为 |
| AFRH | 复用功能高位寄存器 | 0x0 |
GPIOC寄存器值=LED2灭
|
寄存器 |
值 |
含义分析 |
|
MODER |
0x40000000 |
GPIO_Pin_13被配置为输出模式 |
|
OTYPER |
0x0 |
所有引脚为推挽输出 |
|
OSPEEDR |
0x08000000 |
引脚 13 的输出速度配置 |
|
PUPDR |
0x0 |
无上拉/下拉 |
|
IDR |
0x00005FC0 |
输入数据(具体值依赖外部电路) |
|
ODR |
0x0 |
所有引脚输出低电平 |
| BSRR | 0x0 | 写 1 到对应位可设置(置 1)或复位(清 0)引脚输出状态,写 0 无作用 |
| LCKR | 0x0 | 用于锁定引脚配置,防止意外修改 |
| AFRL | 0x0 |
用于配置引脚的复用功能,每 4 位对应一个引脚的复用功能选择,这里值为 |
| AFRH | 0x0 |
4. 逻辑分析仪验证波形(注意需硬件配合)
-
打开 **Logic Analyzer**(View → Analysis Windows → Logic Analyzer)。

-
添加监控引脚:
-
GPIOE.5(正常LED),输入PORTE.5 -
GPIOC.13(异常LED),输入PORTC.13
-
-
全速运行,观察波形:
-
GPIOE.5周期性变化(高→低→高…)。 -
GPIOC.13波形与GPIOE.5完全反相,确认逻辑错误。
-
三、修复方法
-
修正
led_on()和led_off()中GPIOC的控制逻辑: -
重新编译并调试,观察寄存器与波形是否一致。
四、调试总结
|
调试工具 |
作用 |
关键发现 |
|
断点与单步执行 |
定位代码执行流程 |
发现 |
|
GPIO外设窗口 |
实时监控寄存器状态 |
|
|
逻辑分析仪 |
可视化引脚电平变化 |
波形反相确认逻辑错误 |
五、避免类似错误的技巧
-
代码规范:
-
使用宏定义统一LED控制逻辑(#3笔记会编写关于面向对象编程思想中会详细说明):
#define LED_ON(port, pin) GPIO_WriteBit(port, pin, Bit_RESET) #define LED_OFF(port, pin) GPIO_WriteBit(port, pin, Bit_SET)
-
-
代码审查code review:
-
对比同一功能的代码段(如
led_on()中的多个GPIO操作),确保逻辑一致。
-
-
单元测试:
-
可用VScode单独测试每个LED的控制函数,提前发现问题。
-
总结
解决方案应包括以下步骤:
1. 在Keil中设置断点,单步执行代码。
2. 使用外设查看器检查GPIO寄存器的状态。
3. 观察led_on和led_off执行时的寄存器、电信号波形变化。
4. 对比预期与实际值,发现GPIOC的设置错误。
5. 检查代码中的GPIO_WriteBit参数,确认是否正确。
同时,可能需要解释如何利用观察窗口或内存窗口直接查看变量和寄存器的值,以及如何利用逻辑分析仪查看引脚电平变化波形,从而辅助确认问题所在。
更多推荐



所有评论(0)