浅谈MCU的启动

目前正在学习STM32F407芯片, 我们通过KEIL将代码生成Application.bin后,通过JFLASH烧录到0x08000000, 然后重新上电MCU就开始工作了。

那APPlication.bin烧录到FLASH后,程序是如何开始工作的?

我们找打开bin文件,着重关注前两个32字节, 由于我们MCU是小端存储,可以确定这个两个值是0x200112e0 和0x080001a1。

其实,这两个地址是特殊意义:0x200112e0代表初始化堆栈的地址,而0x080001a1程序执行的第一条指令。

确实,在STM32中,RAM的地始地址是0x20000000, ROM的地始地址是0x08000000,看起来好像是正常的。而且我们知道,当复位MCU时,程序第一条执行的指令是Reset_Handler。 难道0x080001a1就表示Reset_Handler的入口?

但是这里有问题必须得搞清楚,STM32F407是32位的MCU,指令地址应该是4的倍数才对,而此处的0x080001a1明显不是4的倍数。为何会这样呢?
为什么 0x080001A1 不是 4 的倍数?

  • 可能的原因:
    • 硬件设计问题:某些硬件设计可能会使用特殊的启动逻辑,但这非常罕见。
    • 启动代码错误:可能是启动代码写错了,或者在烧录过程中出现了错误。
    • 指令集特性:在某些情况下,ARM Cortex-M 支持从 Thumb 指令集启动,而 Thumb 指令集的地址是 2 字节对齐的。不过,这通常不会导致地址的最低位为 1。
  • 实际原因:
    在 ARM Cortex-M 架构中,程序计数器(PC)的初始值通常会加上 1,以指示处理器从 Thumb 指令集开始执行。这是因为 ARM Cortex-M 架构默认从 Thumb 指令集启动。
    因此,0x080001A1 实际上是一个有效的地址,表示程序从 0x080001A0 开始执行,但最低位被设置为 1,以指示处理器从 Thumb 指令集开始执行。

终于搞清楚了,由于设计原因,原来此处真正的值为 0x080001A0。 那 0x080001A0确实是Reset_Handler的入口地址吗?

两种确认方式:

  1. 使用map文件查看

    然后我们将找到的文件进行反汇编即可确认。

  2. 直接仿真程序

    我们从上面的仿真程序可以清楚的看到,Reset_Handler的入口地址就是0x080001A0。 从寄存器列表也可以发现,PC指针寄存器的值就是0x080001A0, 而SP堆栈寄存器的值就是0x200112e0。

知道了上面的工作原理,我们可以将程序随意烧到FLASH的地方,通过跳转就可以让程序起来了(甚至可以让程序加载到内存,然后再跳转从内存里执行 )。

// 定义跳转到 APP 的函数

复制代码
void JumpToApp(uint32_t app_address)
{
    typedef void (*APP_START)(void); // 定义函数指针类型
    uint32_t *p_stack = (uint32_t *)app_address; // APP 的起始地址
    uint32_t *p_reset = (uint32_t *)(app_address + 4); // APP 的复位向量地址

    // 检查 APP 的栈指针和复位向量是否有效
    if ((*p_stack != 0) && (*p_reset != 0))
    {
        // 关闭所有中断
        __disable_irq();

        // 设置 MSP 栈指针
        __set_MSP(*p_stack);
        // 设置 PC 指针到 APP 的入口地址
        ((APP_START)(*p_reset))();
	
    }
    else
    {
        // 如果无效,可以在调试中添加错误处理
        while (1); // 无限循环
    }
}