STM32F103C8T6 引导程序跳转机制笔记
适用场景:自定义 Bootloader 位于 Flash 起始 8KB( 0x08000000--0x08001FFF ),应用程序(APP)从 0x08002000 开始,芯片为 STM32F103C8T6(Flash 64KB、SRAM 20KB)。
关键内存与向量表
-
Flash(程序存储)
- 总大小:64KB( 0x08000000--0x0800FFFF )
- Bootloader: 0x08000000--0x08001FFF (8KB)
- APP: 0x08002000--0x0800FFFF (56KB)
-
SRAM(数据存储)
- 起始地址: 0x20000000
- 大小:20KB( 0x20000000--0x20004FFF )
-
中断向量表(Vector Table)
- 存放在程序镜像的开头(APP 从 0x08002000 开始)
-
0\] 位置:初始主栈指针 MSP(必须指向 SRAM,例如 0x2000xxxx )
-
SCB->VTOR (向量表偏移寄存器)
- 告诉 CPU 中断向量表当前在哪个地址
- 需按 0x200 对齐; 0x08002000 满足对齐要求
Memory Map 总览
如果有不懂的地方可以参考我的另一篇博客***STM32内存分配与堆栈***
plaintext
地址高 ───────────────────────────────────────────────────────────
0xE000E000 System Control Space(NVIC、SCB、SysTick 等)
└─ 核心控制寄存器区,软件不可搬移
0x40000000 外设寄存器空间(AHB/APB)
└─ GPIO、USART、TIM、RCC、FLASH 控制器等
0x20000000 SRAM(20KB:0x20000000--0x20004FFF)
├─ 堆(heap)通常自低地址向上增长
└─ 栈(stack)通常自高地址向下增长(由 MSP/PSP 指向)
0x08000000 主 Flash(64KB:0x08000000--0x0800FFFF)
├─ Bootloader(8KB):0x08000000--0x08001FFF
└─ APP(56KB): 0x08002000--0x0800FFFF
(系统内置 Bootloader 位于"System Memory"专有区域,不在主 Flash 范围)
地址低 ───────────────────────────────────────────────────────────
点击keil的小魔术棒也可以看到如何进行内存分配

关注下面的ROM和RAM起始位置和区域大小
为什么要检查"栈指针必须落在 SRAM"
- ARM Cortex-M 的固件格式规定:镜像开头的第一个 32 位值就是该固件的初始栈指针(MSP)
- 栈需要在可读写的 RAM 中工作,因此这个值必须落在芯片的 SRAM 地址范围(F103C8 为 0x20000000--0x20004FFF )
F103C8的RAM是20kB所以一般肯定是会指向栈顶0x20004FFF,因为栈是向低地址生长,这样也会更安全 - 如果把 MSP 设为 0x20000000 (RAM 起点的低地址),第一次入栈就会越界到更低的地址(不在 SRAM),立刻触发错误(通常 HardFault)
- 检查意义:快速判断指定地址上是否真的烧写了"像样的"APP
- 合法示例: 0x20001234
- 非法示例: 0x00000000 、 0xFFFFFFFF (通常是空闪存)或落在 Flash 的地址(不可作为栈)
示例检查(两种写法,任选其一):
c
uint32_t msp = *(uint32_t*)APP_ADDRESS;
// 掩码法:判断起始段是否为 0x2000...
bool sp_ok = ((msp & 0x2FFE0000) == 0x20000000);
// 或范围法:判断落在 20KB SRAM 区间
bool sp_ok2 = (msp >= 0x20000000U && msp < 0x20005000U);
跳转前为何要做这么多"清场"操作
从 Bootloader 切换到 APP,本质上是一次"小型上下文切换"。目标是让 APP 进入时的环境与"芯片刚复位"尽可能一致,避免把 Bootloader 的状态"泄露"给 APP。
- 关闭全局中断 __disable_irq() 防止在切换过程中,Bootloader 的中断(如 SysTick)突然触发,引发混乱。
- 停用与清理 SysTick HAL_DeInit() 后, SysTick->CTRL/LOAD/VAL = 0 Bootloader 和 APP 都会配置各自的 SysTick;先关闭 Bootloader 的,避免互相干扰。
- 清除 NVIC 状态(使能位与挂起位) 遍历 NVIC->ICER[i] = 0xFFFFFFFF; NVIC->ICPR[i] = 0xFFFFFFFF; 确保没有残留的中断在 APP 进入后意外触发。
- 重定位向量表 SCB->VTOR = APP_ADDRESS 让所有中断从 APP 的向量表进入,而不是仍指向 Bootloader 的表。
- 切换主栈指针 __set_MSP((uint32_t)APP_ADDRESS) 按 APP 向量表指定的初始栈指针重置 MSP。
- 获取复位入口并跳转 reset = (uint32_t)(APP_ADDRESS + 4); 转为函数指针并调用。
- 重新打开中断 __enable_irq() 让 APP 初始化后的 SysTick、中断能正常工作;这是解决 HAL_Delay() 卡住的关键。
这些步骤缺一不可;省略任一步都可能导致不可预测的问题(如 HardFault、延时卡死、外设异常等)。
跳转代码示例
c
typedef void (* pFunction)(void);
void JumptoApplicaitoin(void){
uint32_t app_sp = *(__IO uint32_t*)APP_ADDRESS;
if((app_sp & 0x2FFE0000) == 0x20000000){
//关闭现有中断
__disable_irq();
//停用 HAL 和系统节拍
HAL_DeInit();
SysTick->CTRL = 0;
SysTick->LOAD = 0;
SysTick->VAL = 0;
//清除 NVIC 状态
for (uint32_t i = 0; i < 8; i++) {
NVIC->ICER[i] = 0xFFFFFFFF;
NVIC->ICPR[i] = 0xFFFFFFFF;
}
//重定位中断向量
SCB->VTOR = APP_ADDRESS;
//取 APP 的复位入口
pFunction app_reset_handler = (pFunction)(*(__IO uint32_t*)(APP_ADDRESS + 4));
//设置主栈指针: __set_MSP(*(uint32_t*)APP_ADDRESS) ,按 APP 的向量表重置栈。
__set_MSP(app_sp);
//再打开中断
__enable_irq();
//调用复位入口
app_reset_handler();
}
}
要点:
- APP_ADDRESS 对齐到 0x200 (如 0x08002000 )
- __enable_irq() 放在跳转前,保证 APP 的 SysTick/中断可用
APP 工程链接设置(Keil/MDK 示例)
-
IROM1 Start: 0x08002000
-
IROM1 Size: 0x0000E000 (= 64KB - 8KB)
-
IRAM1 Start: 0x20000000
-
IRAM1 Size: 0x00005000 (20KB)
校验方法(强烈建议):
-
查看 .map 文件或 .htm / .axf 导出信息,确认
- 向量表地址: 0x08002000
-
0\] MSP 在 0x20000000--0x20004FFF
常见坑点与后果
- 未设置 SCB->VTOR 中断仍指向 Bootloader 的表,APP 执行期间会跑到 Bootloader 的中断函数,造成异常。
- 跳转前未重新开启中断 APP 的 HAL_Delay() 依赖 SysTick 中断,关闭后会卡死,表现为 LED 不再翻转。
- Bootloader 与 APP 链接范围重叠 互相覆盖,随机崩溃或不可预测行为。
- APP_ADDRESS 未按 0x200 对齐 VTOR 设置可能不生效或行为不稳定。
烧录与启动流程推荐
- 使用调试器烧录 Bootloader 到 0x08000000 (8KB 内)。
- 将 APP 烧录到 0x08002000 (通过 Keil 的 IROM1 设置或自定义 scatter)。
- 复位芯片:Bootloader 自检后直接跳到 APP 运行;无 APP 则闪灯。
调试建议
- 在 Bootloader 中打印/或用 LED 标识:
- 进入 Bootloader、准备跳转、跳转失败等关键路径
- 在 APP 的 main() 入口临时加一行 __enable_irq();
- 观察是否恢复 HAL_Delay() ,帮助定位中断未开启问题
- 用 .map 文件确认向量表位置与入口地址
- 确认 [0] MSP 在 RAM、[1] Reset_Handler 在 Flash 且最低位=1(Thumb)
IAP(在线升级)扩展思路
- 层次化设计:Bootloader 负责通信与写 Flash,APP 专注业务逻辑
- 只擦/写 APP 区(1KB 页擦除),保护 Bootloader 区
- 在 Bootloader 中做固件校验(CRC/签名)与回滚策略
- 合理的接口与协议(UART/CAN/USB/SD 等),避免断电砖机
结论
- "栈指针必须落在 SRAM 才算有效 APP"是一个高价值、自检快速的安全检查。
- 跳转前的一系列"清场"操作是为了给 APP 提供接近"复位启动"的干净环境,确保稳定性。
- 正确的内存布局与链接配置是基础:Bootloader 8KB 起始,APP 从 0x08002000 。
"栈指针必须落在 SRAM 才算有效 APP"是一个高价值、自检快速的安全检查。 - 跳转前的一系列"清场"操作是为了给 APP 提供接近"复位启动"的干净环境,确保稳定性。
- 正确的内存布局与链接配置是基础:Bootloader 8KB 起始,APP 从 0x08002000 。
- 实践中, SCB->VTOR 重定位与中断重新开启是避免卡死和异常的关键。