欢迎光临
我们一直在努力

第40期:Bootloader(二):跳转App的“惊险一跳”

在上一期(第39期),我们通过修改链接脚本(.ld)和 VTOR 寄存器,把 Flash 里的房产分好了,App 也能正确响应中断了。

现在,Bootloader 已经完成了固件搬运,校验也通过了。 到了最激动人心,也最容易“翻车”的时刻:移交控制权。

这不仅仅是一个函数调用,这是一次灵魂转移。Bootloader 必须把 CPU 的状态恢复到“出厂设置”,然后把接力棒交给 App。如果做得不干净,App 运行几秒钟就会莫名其妙地 HardFault。

让我们进入 第40期,完成这“惊险一跳”。

关中断、清外设、重设 MSP、汇编跳转 PC 的完整步骤

1. 为什么要“大扫除”?

很多新手的 Bootloader 跳转代码只有一行: ((void (*)(void))App_Addr)(); 然后发现 App 跑不起来。

原因: Bootloader 运行期间,可能开启了串口 DMA、开启了 SysTick 定时器、开启了 USB 中断。 当你跳转到 App 时,App 的 main 函数刚开始跑,还没有初始化自己的中断向量表(VTOR 还没改)。 就在这时,Bootloader 遗留的 SysTick 倒计时到了!触发中断! CPU 拿着旧的向量表(指向 Bootloader),却跑在新的环境里,或者干脆因为堆栈混乱,直接 HardFault。

专家法则: 在跳转之前,必须把单片机恢复到**“仿佛刚上电”**的纯净状态。


2. 准备工作:清理现场 (De-Init)

在执行跳转函数之前,必须按顺序执行以下操作:

  • 关闭所有外设时钟: 尤其是 DMA 和 定时器。

  • 关闭全局中断: __disable_irq()。虽然 App 启动后会重开,但跳转瞬间必须关掉。

  • 关闭 SysTick: 这是最著名的“杀手”。必须停掉它,并清除挂起标志。

  • 清除所有中断标志位: 防止跳转后立刻触发。

  • 实战代码:

    void Boot_DeInit(void) {     // 1. 关闭 USB, UART, SPI 等外设     HAL_UART_DeInit(&huart1);     HAL_PCD_DeInit(&hpcd_USB_OTG_FS);           // 2. 关闭 SysTick (致命杀手!)     SysTick->CTRL = 0;     SysTick->LOAD = 0;     SysTick->VAL  = 0;          // 3. 关闭全局中断     __disable_irq();          // 4. (可选) 将所有外设复位     // HAL_RCC_DeInit(); // ST的库函数,有时过于暴力,需慎用 }

    3. 核心动作:读取新世界的坐标

    App 程序的二进制文件(.bin)开头前 8 个字节,蕴含了最重要的信息:

    • 0x00 – 0x03: App 的 MSP 初始值 (栈顶地址)。

    • 0x04 – 0x07: App 的 Reset_Handler 地址 (代码入口)。

    Bootloader 必须把这两个值读出来,塞给 CPU 寄存器。


    4. 终极代码:Jump_To_App()

    这是可以直接复制到你工程里的标准跳转代码。

    // 定义一个函数指针类型 typedef void (*pFunction)(void);

    // 假设 App 存在这个地址 #define APP_START_ADDR  0x08004000 

    void Jump_To_App(void) {     uint32_t JumpAddress;     pFunction Jump_To_Application;

        // 1. 检查栈顶地址是否合法     // 栈必须在 RAM 范围内 (0x20000000 附近)     // *(__IO uint32_t*)APP_START_ADDR 取出的是 MSP 的值     if (((*(__IO uint32_t*)APP_START_ADDR) & 0x2FF00000) == 0x20000000)     {         // 2. 取出 Reset_Handler 地址 (偏移 4 字节)         JumpAddress = *(__IO uint32_t*) (APP_START_ADDR + 4);                  // 3. 将地址转换为函数指针         Jump_To_Application = (pFunction) JumpAddress;

            // 4. 【关键步骤】设置主堆栈指针 (MSP)         // 必须让 App 使用它自己定义的栈,而不是 Bootloader 的栈         __set_MSP(*(__IO uint32_t*) APP_START_ADDR);

            // 5. 【最后一步】跳转!         // 实际上就是把 PC 指针赋值为 JumpAddress         Jump_To_Application();     }     else     {         // App 地址非法(可能没烧录,或者固件损坏)         printf("Error: No valid App found!\\r\\n");     } }

    5. 常见死机原因分析 (Troubleshooting)

    如果你用了上面的代码还是死机,请排查以下三点:

  • SysTick 没关干净: App 刚运行几条指令,还没来得及 SCB->VTOR,SysTick 中断就来了。一定要清空 SysTick->CTRL。

  • App 忘记设置 VTOR: App 跑起来了,但一开中断,CPU 又跑回 Bootloader 的中断向量表了。见第39期。

  • Bootloader 使用了 FreeRTOS:

    • RTOS 会把堆栈指针切换为 PSP。

    • 而 __set_MSP 只改了 MSP。

    • 解决: 跳转前必须把 CPU 切回 特权级 (Privileged) 并使用 MSP。这通常需要写几行汇编来复位 CONTROL 寄存器。

  • // 如果跑了 RTOS,跳转前必须加这一句 __set_CONTROL(0);

    6. 总结

    Bootloader 跳转 App 的本质,就是模拟一次 “软件复位”。 我们手动帮 CPU 做了上电时做的那两件事:

  • 加载 MSP (__set_MSP)。

  • 加载 PC (Jump_To_Application())。

  • 只要现场清理得干净,App 就会以为自己是刚上电启动的,完全不知道 Bootloader 的存在。

    但是, 如果在升级过程中(擦写 Flash 时)突然断电了怎么办? App 区变成了写到一半的乱码。 再次上电,Bootloader 尝试跳转,App 直接死机,设备变砖。

    我们需要一套**“防变砖机制”**,也就是双分区备份。

    赞(0)
    未经允许不得转载:171主机测评 » 第40期:Bootloader(二):跳转App的“惊险一跳”
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址