2. UDS Bootloader安全访问
任务目标
实现一个最小可复现的Bootloader启动流程,包含三个核心阶段:硬件初始化、跳转条件判断(是否进入应用程序)、以及跳转到应用程序执行。本案例以STM32F103系列为目标平台,使用标准外设库编写,通过串口打印状态信息辅助验证。
环境与依赖
- 硬件:STM32F103最小系统板(或兼容开发板),板载LED(PC13),USART1(PA9/PA10)
- 软件:Keil MDK-ARM 5.x 或 IAR EWARM,STM32标准外设库 V3.5
- 烧录工具:ST-Link / J-Link,配合STM32 ST-LINK Utility或Keil内置下载
实现方案
Bootloader与应用程序分别存放在不同的Flash区域。Bootloader位于0x08000000起始地址,应用程序起始地址设为0x08008000(偏移32KB)。启动流程如下:
- 硬件初始化:时钟、LED、串口、按键(PA0,用于强制进入Bootloader等待模式)
- 跳转条件判断:上电后检测应用程序区首个字是否为有效栈顶地址(范围在0x20000000~0x20010000内),且PA0按键未按下;若满足则跳转,否则停留在Bootloader并打印提示
- 应用程序启动:通过函数指针跳转到应用程序复位向量
关键完整代码
// main.c - Bootloader启动流程实现
#include "stm32f10x.h"
#include <stdio.h>
// 应用程序起始地址(偏移32KB)
#define APP_START_ADDR 0x08008000
// 应用程序栈顶地址有效范围(STM32F103内置SRAM 20KB~64KB)
#define SRAM_BASE 0x20000000
#define SRAM_END 0x20010000
// 函数指针类型:指向应用程序复位向量
typedef void (*pFunction)(void);
// 串口发送一个字符(重定向printf)
int fputc(int ch, FILE *f) {
while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET);
USART_SendData(USART1, (uint8_t)ch);
return ch;
}
// 硬件初始化:时钟、LED、串口、按键
static void Hardware_Init(void) {
GPIO_InitTypeDef GPIO_InitStructure;
USART_InitTypeDef USART_InitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
// 使能时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_GPIOC |
RCC_APB2Periph_USART1 | RCC_APB2Periph_AFIO, ENABLE);
// LED(PC13)推挽输出
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_2MHz;
GPIO_Init(GPIOC, &GPIO_InitStructure);
GPIO_SetBits(GPIOC, GPIO_Pin_13); // 默认熄灭
// 按键PA0下拉输入(按下为高电平)
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPD;
GPIO_Init(GPIOA, &GPIO_InitStructure);
// USART1: PA9(TX) PA10(RX), 115200-8-N-1
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_Init(GPIOA, &GPIO_InitStructure);
USART_InitStructure.USART_BaudRate = 115200;
USART_InitStructure.USART_WordLength = USART_WordLength_8b;
USART_InitStructure.USART_StopBits = USART_StopBits_1;
USART_InitStructure.USART_Parity = USART_Parity_No;
USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
USART_InitStructure.USART_Mode = USART_Mode_RX | USART_Mode_TX;
USART_Init(USART1, &USART_InitStructure);
USART_Cmd(USART1, ENABLE);
}
// 跳转条件判断:返回1则跳转,返回0则停留在Bootloader
static uint8_t Check_JumpCondition(void) {
uint32_t app_stack = *(volatile uint32_t *)APP_START_ADDR;
// 条件1:PA0未按下(低电平)
if (GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_0) == SET) {
return 0; // 按键按下,强制停留在Bootloader
}
// 条件2:应用程序区栈顶地址有效
if ((app_stack < SRAM_BASE) || (app_stack >= SRAM_END)) {
return 0; // 栈顶无效,无有效应用程序
}
return 1;
}
// 跳转到应用程序
static void Jump_To_Application(void) {
uint32_t app_entry = *(volatile uint32_t *)(APP_START_ADDR + 4);
pFunction jump_func = (pFunction)app_entry;
printf("Jump to App @0x%08X\r\n", (unsigned int)app_entry);
// 关闭全局中断
__disable_irq();
// 设置主栈指针
__set_MSP(*(volatile uint32_t *)APP_START_ADDR);
// 跳转
jump_func();
}
int main(void) {
Hardware_Init();
printf("\r\n=== Bootloader v1.0 ===\r\n");
// 闪烁LED 3次,提示进入Bootloader
for (int i = 0; i < 3; i++) {
GPIO_ResetBits(GPIOC, GPIO_Pin_13);
for (volatile uint32_t d = 0; d < 200000; d++);
GPIO_SetBits(GPIOC, GPIO_Pin_13);
for (volatile uint32_t d = 0; d < 200000; d++);
}
if (Check_JumpCondition()) {
printf("Valid App found, jumping...\r\n");
Jump_To_Application();
} else {
printf("Stay in Bootloader (key pressed or no valid app)\r\n");
// 停留在Bootloader:LED常亮指示
GPIO_ResetBits(GPIOC, GPIO_Pin_13);
while (1);
}
}
工程配置要点
- 在Keil中设置IROM1起始地址为
0x08000000,大小为0x8000(32KB),用于Bootloader - 应用程序工程需设置IROM1起始地址为
0x08008000,大小根据实际调整 - 两个工程需分别编译烧录:先烧录Bootloader到0x08000000,再烧录应用程序到0x08008000
- 若使用标准外设库,需在工程中添加
stm32f10x_gpio.c、stm32f10x_usart.c、stm32f10x_rcc.c源文件
验证方法
- 无应用程序场景:仅烧录Bootloader,上电后串口输出
Stay in Bootloader,LED常亮,程序不跳转 - 有效应用程序场景:在0x08008000处烧录一个简单的LED闪烁程序(其工程IROM1起始地址设为0x08008000),上电后串口输出
Valid App found, jumping...,随后应用程序开始运行(LED按应用逻辑闪烁) - 强制停留场景:上电时按住PA0按键,即使存在有效应用程序,串口输出
Stay in Bootloader,不跳转
通过判定标准:上述三种场景的行为与预期完全一致,串口输出正确,LED状态符合描述,即判定Bootloader启动流程实现正确。
常见问题
- 跳转后程序跑飞:确认应用程序工程的中断向量表已重映射(在应用程序的SystemInit或main开头调用
NVIC_SetVectorTable(NVIC_VectTab_FLASH, 0x8000)) - 串口无输出:检查USART1引脚是否与板载调试串口冲突,部分开发板需要断开ST-Link的TX/RX跳线
- 栈顶地址判断失败:确认应用程序确实烧录在0x08008000处,且其首字为有效的SRAM地址(由链接脚本自动生成)