我在学习怎么编写代码时,无论是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. 单步执行并观察寄存器
  1. 点击 Run (F5) 触发第一个断点(led_on())。

    1. 查看GPIOE和GPIOC的ODR寄存器Peripherals →System Viewer Windows → GPIO → GPIOEGPIOC 窗口。

  2. 预期结果:GPIOE.ODRBit5Bit6 为 **0**(低电平,LED亮),GPIOC.ODRBit13 应为 **0**。可以把框图拖出悬浮使用。

  3. 实际结果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 位对应一个引脚的复用功能选择,这里值为 0 表示没有配置引脚的复用功能(若配置为普通 GPIO 则通常如此)。

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 位对应一个引脚的复用功能选择,这里值为 0 表示没有配置引脚的复用功能(若配置为普通 GPIO 则通常如此)。

AFRH 0x0
4. 逻辑分析仪验证波形(注意需硬件配合)
  1. 打开 **Logic Analyzer**(View → Analysis Windows → Logic Analyzer)。

  2. 添加监控引脚:

    1. GPIOE.5(正常LED),输入PORTE.5

    2. GPIOC.13(异常LED),输入PORTC.13

  3. 全速运行,观察波形:

    1. GPIOE.5 周期性变化(高→低→高…)。

    2. GPIOC.13 波形与 GPIOE.5 完全反相,确认逻辑错误。

三、修复方法

  1. 修正 led_on()led_off()GPIOC 的控制逻辑:

    1. 重新编译并调试,观察寄存器与波形是否一致。

    四、调试总结

    调试工具

    作用

    关键发现

    断点与单步执行

    定位代码执行流程

    发现GPIOC的写入值异常

    GPIO外设窗口

    实时监控寄存器状态

    GPIOC.ODR.Bit13电平与预期相反

    逻辑分析仪

    可视化引脚电平变化

    波形反相确认逻辑错误

    五、避免类似错误的技巧

    1. 代码规范

      • 使用宏定义统一LED控制逻辑(#3笔记会编写关于面向对象编程思想中会详细说明):

        #define LED_ON(port, pin) GPIO_WriteBit(port, pin, Bit_RESET) 
        #define LED_OFF(port, pin) GPIO_WriteBit(port, pin, Bit_SET)
    2. 代码审查code review

      1. 对比同一功能的代码段(如led_on()中的多个GPIO操作),确保逻辑一致。

    3. 单元测试

      1. 可用VScode单独测试每个LED的控制函数,提前发现问题。


    总结

    解决方案应包括以下步骤:

    1. 在Keil中设置断点,单步执行代码。

    2. 使用外设查看器检查GPIO寄存器的状态。

    3. 观察led_on和led_off执行时的寄存器、电信号波形变化。

    4. 对比预期与实际值,发现GPIOC的设置错误。

    5. 检查代码中的GPIO_WriteBit参数,确认是否正确。

    同时,可能需要解释如何利用观察窗口或内存窗口直接查看变量和寄存器的值,以及如何利用逻辑分析仪查看引脚电平变化波形,从而辅助确认问题所在。

    Logo

    智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

    更多推荐