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);
}
编译与运行
- 将上述代码加入STM32F103标准外设库工程,启动文件选择
startup_stm32f10x_md.s(中等容量) - 链接脚本中设置FLASH起始地址为
0x08000000,Bootloader占用前32KB(0x08000000~0x08007FFF),应用程序烧录在0x08008000起始地址 - 编译生成Bootloader的HEX文件,通过ST-Link烧录至0x08000000
- 将应用程序(其向量表偏移已设为0x08008000)烧录至0x08008000
验证方法与判定标准
- 正常启动验证:不按按键,直接上电或复位。观察PC13 LED保持熄灭,且应用程序开始运行(如应用程序有LED闪烁或串口输出,应能观察到)
- 下载模式验证:按住PA0按键不放,同时上电或复位。观察PC13 LED点亮并保持,程序停留在下载模式循环中,不会跳转至应用程序
- 消抖功能验证:快速按动按键后复位,若按键按下时间过短(<10ms),系统应判定为未按下并进入正常启动,说明消抖逻辑生效
常见问题
- 跳转后程序跑飞:确认应用程序的向量表已偏移(在APP工程中设置
VECT_TAB_OFFSET = 0x8000),且APP的链接起始地址与Bootloader跳转地址一致 - 按键检测不稳定:检查PA0外部是否接上拉电阻(内部上拉已使能,但若外部有强下拉会干扰);消抖延时可根据实际按键机械特性调整
- LED不亮:确认PC13在板载电路中的有效电平(部分开发板LED为低电平点亮,需反转逻辑)