目前正在学习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的入口地址吗?
两种确认方式:
-
使用map文件查看
然后我们将找到的文件进行反汇编即可确认。 -
直接仿真程序
我们从上面的仿真程序可以清楚的看到,Reset_Handler的入口地址就是0x080001A0。 从寄存器列表也可以发现,PC指针寄存器的值就是0x080001A0, 而SP堆栈寄存器的值就是0x200112e0。
知道了上面的工作原理,我们可以将程序随意烧到FLASH的地方,通过跳转就可以让程序起来了(甚至可以让程序加载到内存,然后再跳转从内存里执行)。
// 定义跳转到 APP 的函数
/* by 01022.hk - online tools website : 01022.hk/zh/keyboardcode.html */
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); // 无限循环
}
}