在接触ARM Cortex M嵌入式开发的初期,很多人直接使用芯片厂商提供的IDE进行编程。新建程序后,开发者只需在main()函数中插入自己的程序。IDE会自动生成其他所需的文件,其中就包括了在main()函数之前的初始化步骤。但是很多时候厂商提供的驱动并不能满足开发者的需求或者厂商的驱动过于复杂。这时候就要求开发者自己深入了解ARM Cortex M架构的基本初始化过程来进行自定义开发。
以STM32F41和STM32CubeIDE2为例,如果我们让main()
为空循环。最终IDE生成的程序也有700字节。那这700字节的程序都做了些什么呢?
#include <stdint.h>
int main(void)
{
while(1)
{
}
}
编译输出
arm-none-eabi-size stm32_mini.elf
text data bss dec hex filename
724 12 1572 2308 904 stm32_mini.elf
内存分布(Memory Layout)
在讨论初始化之前先要简单介绍一下程序在MCU片上闪存(Flash)存储方式。一般基于ARM Cortex M架构的MCU都会有片上闪存。这个闪存会有一个由芯片厂商自己定义分配好的地址。例如STM32F4片上闪存起始地址为0x08000000
。编译好的程序通过调试接口(JTAG或SWD)烧录入闪存。闪存地址起始处存放的是中断向量表(Interrupt Vector Table)。中断向量表每行为32bits的地址,总长度由芯片中断数决定。ARM Cortex M架构规定了中断向量表前16行的内容。第一行为入口函数地址。第二行为栈指针地址。
结合上面STM32F4的例子,我们可以读出闪存地址0x08000000
的数据。
0x08000000: 0x08000208
如果我们打开编译器生成的stm32_mini.map文件我们可以看到在地址0x08000208
处存放的是Reset_Handler()
也就是我们要寻找的入口函数。
.text.Reset_Handler
0x0000000008000208 0x50 Core/Startup/startup_stm32f401retx.o