4. 实现应用程序入口地址的跳转

任务目标

在Bootloader中实现从固件跳转到应用程序的功能。核心是通过函数指针调用应用程序的复位向量,在跳转前正确设置主栈指针(MSP)和程序计数器(PC),确保应用程序从复位向量处开始执行。

实现原理

ARM Cortex-M系列处理器上电后从向量表偏移0x00处加载MSP,从偏移0x04处加载PC(即复位向量地址)。Bootloader跳转前需完成:关闭全局中断、确认应用程序向量表地址合法、将向量表基址重映射至应用地址、设置MSP为向量表首字、通过函数指针跳转至复位向量。

关键完整代码

// app_jump.h
#ifndef APP_JUMP_H
#define APP_JUMP_H

#include <stdint.h>

/* 应用程序起始地址(需与链接脚本匹配) */
#define APP_START_ADDR   0x08008000U

/* 跳转前关闭中断的宏(Cortex-M专用) */
#define DISABLE_INTERRUPTS()  __asm volatile ("cpsid i")

/* 跳转函数声明 */
void jump_to_app(uint32_t app_addr);

#endif /* APP_JUMP_H */
// app_jump.c
#include "app_jump.h"

/* 应用程序向量表结构 */
typedef void (*pFunction)(void);

/**
 * @brief  跳转到应用程序
 * @param  app_addr: 应用程序起始地址(向量表基址)
 * @note   跳转前需确保app_addr已按256字节对齐(Cortex-M向量表对齐要求)
 */
void jump_to_app(uint32_t app_addr)
{
    uint32_t app_msp;
    pFunction app_reset_handler;

    /* 1. 检查地址合法性:向量表首字(MSP)必须在RAM地址范围 */
    app_msp = *(volatile uint32_t *)app_addr;
    if ((app_msp & 0x2FFE0000U) != 0x20000000U) {
        return;  /* MSP不在RAM区,拒绝跳转 */
    }

    /* 2. 关闭全局中断 */
    DISABLE_INTERRUPTS();

    /* 3. 设置应用程序向量表基址(SCB->VTOR) */
    /* 注意:若MCU不支持VTOR(如Cortex-M0非M0+),需跳过此步 */
    SCB->VTOR = app_addr;

    /* 4. 设置主栈指针为向量表首字 */
    __set_MSP(app_msp);

    /* 5. 获取复位向量地址并跳转 */
    app_reset_handler = (pFunction)(*(volatile uint32_t *)(app_addr + 4U));
    app_reset_handler();

    /* 不应执行到这里 */
    while (1) {
    }
}

集成与调用

在Bootloader主流程中,当收到跳转命令或校验通过后调用:

// main.c 片段
#include "app_jump.h"

int main(void)
{
    /* ... Bootloader初始化、外设配置、固件校验 ... */

    /* 执行跳转 */
    jump_to_app(APP_START_ADDR);

    /* 跳转失败时不会返回,若返回则在此处理错误 */
    while (1) {
    }
}

编译与链接配置

Bootloader工程需将应用程序起始地址配置为0x08008000,并在链接脚本中设置:

/* Bootloader链接脚本(.ld)关键片段 */
FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 32K
/* 应用程序从0x08008000开始,Bootloader占用前32KB */

应用程序工程需配置:

/* 应用程序链接脚本(.ld)关键片段 */
FLASH (rx) : ORIGIN = 0x08008000, LENGTH = 480K
/* 向量表偏移:在system_stm32xxx.c中设置 */
#define VECT_TAB_OFFSET  0x08008000U

验证方法

  1. 在jump_to_app()中跳转前设置断点,确认app_msp值等于应用程序链接脚本中定义的_estack地址(通常为0x20020000或类似值)。
  2. 单步执行app_reset_handler()调用,观察PC寄存器跳转至应用程序复位向量地址(应为0x08008004处存储的值)。
  3. 在应用程序的main()函数入口设置断点,若程序能停在断点处,说明跳转成功。
  4. 检查应用程序中的全局变量初始化是否正常(若跳转前未正确设置MSP,首次变量访问将触发HardFault)。

常见问题

  • 跳转后HardFault:检查MSP是否设置正确;确认VTOR设置前已关闭中断;检查应用程序向量表是否确实在app_addr处。
  • 中断不工作:确认跳转前已重新使能中断(在应用程序的SystemInit中),且应用程序的向量表偏移已正确配置。
  • Cortex-M0无VTOR:M0内核不支持VTOR重映射,需在应用程序启动代码中手动复制向量表至SRAM并重映射,或使用固定地址跳转方案。