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

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

任务说明

本案例实现一个按键控制的启动模式选择器:系统上电后检测指定GPIO引脚上的按键状态,若按键被按下(低电平)则进入下载模式(等待主机通过串口发送固件),否则跳转到应用模式(执行用户应用程序)。该逻辑是Bootloader中最基础的分支决策部分。

硬件连接

  • 按键接PA0引脚,另一端接地(按下为低电平)
  • 板载LED接PC13,用于指示当前所处模式
  • 串口1(PA9/PA10)用于下载模式通信

完整代码

// 文件: boot_selector.c
// 依赖: STM32F103标准外设库, 系统时钟已初始化为72MHz

#include "stm32f10x.h"
#include <string.h>

#define APP_ADDRESS   0x08003000  // 应用程序起始地址(跳过Bootloader占用的12KB)
#define KEY_GPIO_CLK   RCC_APB2Periph_GPIOA
#define KEY_GPIO       GPIOA
#define KEY_PIN        GPIO_Pin_0
#define LED_GPIO_CLK   RCC_APB2Periph_GPIOC
#define LED_GPIO       GPIOC
#define LED_PIN        GPIO_Pin_13

// 按键消抖延时(简单软件延时)
static void delay_ms(volatile uint32_t ms) {
    while (ms--) {
        for (volatile uint32_t i = 0; i < 7200; i++);
    }
}

// GPIO初始化
static void gpio_init(void) {
    GPIO_InitTypeDef gpio;
    
    RCC_APB2PeriphClockCmd(KEY_GPIO_CLK | LED_GPIO_CLK, ENABLE);
    
    // PA0上拉输入(按键接地,按下为低)
    gpio.GPIO_Pin = KEY_PIN;
    gpio.GPIO_Mode = GPIO_Mode_IPU;
    GPIO_Init(KEY_GPIO, &gpio);
    
    // PC13推挽输出(LED低电平点亮)
    gpio.GPIO_Pin = LED_PIN;
    gpio.GPIO_Mode = GPIO_Mode_Out_PP;
    gpio.GPIO_Speed = GPIO_Speed_2MHz;
    GPIO_Init(LED_GPIO, &gpio);
}

// 检测按键是否按下(带消抖)
static uint8_t key_pressed(void) {
    if (GPIO_ReadInputDataBit(KEY_GPIO, KEY_PIN) == Bit_RESET) {
        delay_ms(20);  // 消抖
        if (GPIO_ReadInputDataBit(KEY_GPIO, KEY_PIN) == Bit_RESET) {
            return 1;
        }
    }
    return 0;
}

// 跳转到应用程序
static void jump_to_app(void) {
    typedef void (*pFunction)(void);
    uint32_t app_sp = *(volatile uint32_t*)APP_ADDRESS;      // 取应用栈顶地址
    uint32_t app_pc = *(volatile uint32_t*)(APP_ADDRESS + 4); // 取应用复位向量
    
    if ((app_sp & 0xFFF00000) != 0x20000000) {
        return;  // 栈顶地址非法,停留在Bootloader
    }
    
    pFunction jump = (pFunction)app_pc;
    __set_MSP(app_sp);  // 设置主栈指针
    jump();             // 跳转执行
}

// 下载模式处理(此处仅演示模式进入,实际接收逻辑由主机工具配合)
static void download_mode(void) {
    GPIO_SetBits(LED_GPIO, LED_PIN);  // LED灭,指示下载模式
    // 实际项目中在此处初始化串口并等待接收固件数据
    // 本案例仅演示模式选择,串口接收逻辑省略
    while (1);  // 停留在下载模式
}

int main(void) {
    gpio_init();
    
    // 上电延时,等待电源稳定
    delay_ms(100);
    
    if (key_pressed()) {
        // 按键按下 → 下载模式
        GPIO_ResetBits(LED_GPIO, LED_PIN);  // LED亮
        download_mode();
    } else {
        // 按键未按下 → 应用模式
        GPIO_SetBits(LED_GPIO, LED_PIN);    // LED灭
        jump_to_app();
    }
    
    // 若跳转失败(应用地址无效),则停留在此处
    while (1);
}

编译与烧录

# 使用arm-none-eabi工具链编译(以Makefile方式为例)
arm-none-eabi-gcc -c -mcpu=cortex-m3 -mthumb boot_selector.c -o boot_selector.o
arm-none-eabi-ld -T linker.ld boot_selector.o -o boot_selector.elf
arm-none-eabi-objcopy -O ihex boot_selector.elf boot_selector.hex

# 通过ST-Link烧录到0x08000000地址
st-flash write boot_selector.hex 0x08000000

链接脚本中需设置FLASH起始地址为0x08000000,长度12KB(0x3000),应用程序烧录到0x08003000之后。

验证方法

  1. 下载模式验证:按住PA0按键不放,同时给板上电(或按复位键)。观察PC13 LED点亮,程序停留在下载模式(可用调试器查看PC指针停在download_mode的while循环内)。
  2. 应用模式验证:不按按键,直接上电或复位。若0x08003000处已烧录有效应用程序,则LED熄灭且程序跳转执行应用代码;若该地址无有效程序(全0xFF),则程序不会跳转,最终停在while(1),LED保持熄灭。
  3. 判定标准:两种操作下LED状态与程序停留位置符合上述描述,即表示按键控制启动模式功能正常。

关键说明

  • 跳转前必须检查应用栈顶地址是否落在SRAM区间(0x20000000起始),防止非法地址导致硬件错误。
  • 按键消抖采用20ms软件延时,实际产品中可改用定时器实现非阻塞消抖。
  • 下载模式中的串口接收逻辑不属于本案例范围,实际项目中在download_mode()内初始化USART并实现YModem或自定义协议即可。