3. 读取按键控制启动模式

读取按键控制启动模式 学习卡片

任务目标

在Bootloader启动阶段读取按键状态,根据按键是否按下决定进入正常启动模式(跳转至应用程序)或下载模式(等待主机通过串口发送固件)。本案例以STM32F103系列MCU为例,使用标准外设库实现最小可复现的按键检测与模式分支逻辑。

硬件连接与依赖

  • 按键接在PA0,按下为低电平(需外部上拉或使用内部上拉)
  • LED接在PC13(板载LED),用于指示当前模式
  • 依赖:STM32标准外设库(提供GPIO、RCC初始化函数)

关键完整代码


#include "stm32f10x.h"
#include "stm32f10x_gpio.h"
#include "stm32f10x_rcc.h"

/* 按键与LED引脚定义 */
#define KEY_PORT     GPIOA
#define KEY_PIN      GPIO_Pin_0
#define LED_PORT     GPIOC
#define LED_PIN      GPIO_Pin_13

/* 模式定义 */
#define BOOT_MODE_NORMAL   0x00
#define BOOT_MODE_DOWNLOAD 0x01

/* 简单延时(约10ms @ 72MHz) */
static void Delay_Short(void) {
    volatile uint32_t i;
    for (i = 0; i < 720000; i++);
}

/* GPIO初始化 */
static void GPIO_Config(void) {
    GPIO_InitTypeDef GPIO_InitStructure;

    /* 使能GPIOA和GPIOC时钟 */
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_GPIOC, ENABLE);

    /* PA0按键输入,上拉(按键按下接地) */
    GPIO_InitStructure.GPIO_Pin = KEY_PIN;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
    GPIO_Init(KEY_PORT, &GPIO_InitStructure);

    /* PC13 LED输出 */
    GPIO_InitStructure.GPIO_Pin = LED_PIN;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_2MHz;
    GPIO_Init(LED_PORT, &GPIO_InitStructure);
}

/* 检测按键是否按下(消抖后返回) */
static uint8_t Key_IsPressed(void) {
    if (GPIO_ReadInputDataBit(KEY_PORT, KEY_PIN) == Bit_RESET) {
        Delay_Short();          /* 消抖延时 */
        if (GPIO_ReadInputDataBit(KEY_PORT, KEY_PIN) == Bit_RESET) {
            return 1;           /* 确认按下 */
        }
    }
    return 0;
}

/* 跳转到应用程序(假设APP起始地址为0x08008000) */
typedef void (*pFunction)(void);
static void Jump_To_App(void) {
    uint32_t app_addr = 0x08008000;
    pFunction app_entry;

    /* 检查栈顶地址是否合法(RAM范围内) */
    if ((*(volatile uint32_t *)app_addr & 0x2FFE0000) == 0x20000000) {
        app_entry = (pFunction)(*(volatile uint32_t *)(app_addr + 4));
        /* 设置主栈指针 */
        __set_MSP(*(volatile uint32_t *)app_addr);
        app_entry();            /* 跳转至APP的Reset_Handler */
    }
    while (1);                  /* 跳转失败则停留 */
}

/* 下载模式处理(此处仅指示,实际固件接收由外部实现) */
static void Enter_Download_Mode(void) {
    GPIO_SetBits(LED_PORT, LED_PIN);    /* LED亮,指示下载模式 */
    while (1) {
        /* 实际工程中在此处调用串口接收/Flash写入逻辑 */
    }
}

int main(void) {
    GPIO_Config();

    if (Key_IsPressed()) {
        /* 按键按下 → 下载模式 */
        Enter_Download_Mode();
    } else {
        /* 按键未按下 → 正常启动 */
        GPIO_ResetBits(LED_PORT, LED_PIN);  /* LED灭,指示正常模式 */
        Jump_To_App();
    }

    /* 不应到达此处 */
    while (1);
}

编译与运行

  1. 将上述代码加入STM32F103标准外设库工程,启动文件选择startup_stm32f10x_md.s(中等容量)
  2. 链接脚本中设置FLASH起始地址为0x08000000,Bootloader占用前32KB(0x08000000~0x08007FFF),应用程序烧录在0x08008000起始地址
  3. 编译生成Bootloader的HEX文件,通过ST-Link烧录至0x08000000
  4. 将应用程序(其向量表偏移已设为0x08008000)烧录至0x08008000

验证方法与判定标准

  • 正常启动验证:不按按键,直接上电或复位。观察PC13 LED保持熄灭,且应用程序开始运行(如应用程序有LED闪烁或串口输出,应能观察到)
  • 下载模式验证:按住PA0按键不放,同时上电或复位。观察PC13 LED点亮并保持,程序停留在下载模式循环中,不会跳转至应用程序
  • 消抖功能验证:快速按动按键后复位,若按键按下时间过短(<10ms),系统应判定为未按下并进入正常启动,说明消抖逻辑生效

常见问题

  • 跳转后程序跑飞:确认应用程序的向量表已偏移(在APP工程中设置VECT_TAB_OFFSET = 0x8000),且APP的链接起始地址与Bootloader跳转地址一致
  • 按键检测不稳定:检查PA0外部是否接上拉电阻(内部上拉已使能,但若外部有强下拉会干扰);消抖延时可根据实际按键机械特性调整
  • LED不亮:确认PC13在板载电路中的有效电平(部分开发板LED为低电平点亮,需反转逻辑)