STM32-bootloader引导程序跳转机制笔记

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 设置可能不生效或行为不稳定。

烧录与启动流程推荐

  1. 使用调试器烧录 Bootloader 到 0x08000000 (8KB 内)。
  2. 将 APP 烧录到 0x08002000 (通过 Keil 的 IROM1 设置或自定义 scatter)。
  3. 复位芯片: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 重定位与中断重新开启是避免卡死和异常的关键。

应用程序app的分区跳转和升级app可以为你的小创意或者毕设增加亮点,想要stm32f103c8t6的bootloader模板工程可以点赞加收藏下方评论。

相关推荐
飞睿科技3 小时前
【芯片选型指南】乐鑫ESP32-C61核心能力解析:为何它在Wi-Fi 6物联网赛道中优势独具?
科技·嵌入式硬件·物联网·智能家居
智者知已应修善业3 小时前
【c语言蓝桥杯计算卡片题】2023-2-12
c语言·c++·经验分享·笔记·算法·蓝桥杯
一叶知秋063 小时前
英集芯-IP5385 IIC通信异常原因深入分析及解决方案 21
单片机·嵌入式硬件
亿道电子Emdoor4 小时前
【Arm】Encountered an improper argument
arm开发·stm32·单片机
Elias不吃糖5 小时前
NebulaChat项目构建笔记
linux·c++·笔记·多线程
BreezeJuvenile5 小时前
嵌入式系统-实验三——串口通信实验
stm32·单片机·串口通信·标准库·嵌入式系统实验
d111111111d5 小时前
STM32外设学习-串口数据包笔记-(程序)
笔记·stm32·单片机·嵌入式硬件·学习
----云烟----5 小时前
MCU单片机驱动WS2812
单片机·嵌入式硬件
ACP广源盛139246256735 小时前
GSV6127E#ACP#Type-C/DisplayPort 1.4/HDMI 2.0 到 MIPI CSI-2 混合转换器(带嵌入式 MCU)
单片机·嵌入式硬件·音视频