我对STM32启动过程的理解
从*.s文件看程序启动过程
如果没记错的话,C语言中我们看代码一定都是从main()看起吧?因为“main()函数是程序的入口”,大家都这么说。但实际上如果我们从硬件上电复位看起,它并不是直接来到main()的——对于stm32单片机来说它至少还要把工程文件中对应的.s文件的汇编代码跑完才跳转到我们的用户程序。
参考资料
这几个链接的文章基本讲得差不多了。
*.s文件干了什么
以startup_stm32f10x_hd.s为例,可以看到单片机从复位(Reset引脚拉低、或重新上电)到进入用户main()函数之前执行了以下操作:
- 指定堆、栈的起始地址和大小(字节数),但未初始化它们(这是C标准库函数__main()做事情);
- 建立中断向量(含栈顶指针)表:Flash中从0x08000000开始,DCD表示分配1个4字节空间;第1个4字节(0x08000000-0x08000003)放栈顶指针值,第2个4字节(0x08000004-0x08000007)放Reset_Handler()中断函数地址;
- 以上步骤是编译阶段完成的,然后生成.bin文件;
- .bin文件里面第1个4字节(0x00000000-0x00000003)为栈顶指针值,第2个4字节(0x00000004-0x00000007)为Reset_Handler()中断函数地址;后面还依次存放了各个中断函数的地址;
- 烧到Flash(0x08000000),单片机复位后首先把0x08000000+0x00000000=0x08000000的栈顶指针值复制到SP寄存器,再把0x08000000+0x00000004=0x08000004的Reset_Handler()地址复制到CP寄存器。这两个地址是分别和.bin文件中的偏移位置对应的;
- 在Reset_Handler()中先调用SystemInit函数,配置单片机时钟分频倍频系数等,得到主频;然后返回到.s文件再调用__main()函数——这个是C标准库中的函数,包括初始化堆栈;然后再跳转到用户的main(),不再返回到.s文件。
就这样进入到用户程序了。
Cortex M3核和STM32单片机启动顺序的关系
Coter M3 CPU希望从0x00000000获取复位向量(Reset_Handler地址),但STM32使用一种特殊机制,使得CPU从0x00000000获取的是栈顶指针,然后从0x00000004获得真正的复位向量(Reset_Handler地址)。但是呢,这个0x00000000和0x00000004并不是真正的地址,它们真正指向的地址由BOOT0、BOOT1引脚配置来指定(给出地址和实际地址之间的转换称为“映射”、给出地址此时称为“别名”)(如下图,截自中文版F10x参考手册和参考博客):
- SRAM启动时,
Cortex M3看到的地址 | 实际被STM32指向的地址 | 里面的值(地址值)(个人猜测,待验证) |
---|---|---|
0x00000000 | 0x20000000 | 0x200026B0 |
0x00000004 | 0x20000004 | 0x2000019D |
- Flash启动时,
Cortex M3看到的地址 | 实际被STM32指向的地址 | 里面的值(地址值) |
---|---|---|
0x00000000 | 0x08000000 | 0x200026B0 |
0x00000004 | 0x08000004 | 0x8000019D |
栈总是在SRAM中的,所以启动模式不会改变它在整个单片机存储器中的位置(不会从SRAM跑到Flash)。
看到这里可能有一个问题:
这个“别名”默认就是指向外面的其他地址吗?会不会一不小心真的把这个地址当做真实地址来用了?
比如:0x00000000会不会在某种情况下既不映射到0x20000000也不映射到0x08000000,而是真的就是0x00000000?
这个问题的答案可以在数据手册的《存储器映像》一章里找到:
从下图可以看到——整个存储器的0x00000000-(0x08000000-1)范围被划为Aliased to Flash or system memory depending on BOOT pins,说明这一块本来就一定会映射到外面其他区域,并不会被错误使用为实际地址。所以不用担心会有数据错误地存放到0x00000000这样的范围里面。