玩转STM32(14)运行第一行代码

前面学习了选择从那里进行加载代码,接着下来,我们将要了解CPU是怎么样运行编译的代码。通过前面的学习,我们知道通过编译器的编译,会生成可运行的代码,然后通过JLINK下载到STM32的FLASH里,再通过配置CPU的引导管脚,实现选择内部FLASH来加载代码。但是CPU是怎么样来加载代码,并运行的呢?

 

其实不同类型的CPU运行代码的方式不一样,STM32的运行方式是这样的,CPU 将从地址 0x0000 0000 获取栈顶值,然后从始于 0x0000 0004 的自举存储器开始执行代码。前面已经把0x800 0000的FLASH地址配置为启动的存储器,因此就会从这里偏移位置0取得栈顶值,从偏移位置4取得第一行执行代码。根据这个要求,那么每个项目工程的启动文件,必须是这样设置才可以运行。来看工程里的startup_stm32f40_41xxx.s文件,这个文件就是STM32F407运行的第一个文件,它编译出来的代码,就是被第一行执行的,因此它是采用汇编语言来编写的。也许有人问为什么不采用C语言来编写,非要使用汇编语言来编写?其实还真不能使用C语言来编写,因为CPU运行时,根本没有具备C语言的运行环境,所以不能直接运行C语言编译出来的代码,必须通过汇编语言来建立C语言的运行环境才可以运行C语言编译出来的代码。这个汇编语言文件是库文件里的一部分,它的目录路径是

Libraries\CMSIS\Device\ST\STM32F4xx\Source\Templates\arm\startup_stm32f40_41xxx.s。

这个文件里的内容比较多,并且是汇编语言,要看懂它,需要下些功夫才可以。前面说到CPU会从偏移位置0处来加载栈顶值,那么在这个汇编文件里是怎么样实现把栈顶的值放到偏移位置0的呢?现在来打开这个文件,查看到如下图:

玩转STM32(14)运行第一行代码_第1张图片

可以从这里看到__initial_sp变量放在第一个位置,意味着这个变量的值会放在这个区域的第一个位置,这样确保了栈顶值保存在这段代码布局的首位置了。但是你也许问编译器怎么样知道这段代码一定会放在首位置,而不是放置在存储器的后面位置呢?如果没有的特别的声明,编译器还真可能把它放置在任何位置上的,并不是首位置。关键之处,是项目里还有一个编译连接的说明文件,它叫做project.sct,这个文件主要用来指定ARM连接器在生成映像文件时如何分配RO,RW,ZI等数据的存放地址,在我的工程里采用默认的文件,内容如下:

; *************************************************************

; *** Scatter-Loading Description File generated by uVision ***

; *************************************************************

 

LR_IROM1 0x08000000 0x00080000  {    ; load region size_region

  ER_IROM1 0x08000000 0x00080000  {  ; load address = execution address

   *.o (RESET, +First)

   *(InRoot$$Sections)

   .ANY (+RO)

  }

  RW_IRAM1 0x20000000 0x00020000  {  ; RW data

   .ANY (+RW +ZI)

  }

}

在这里看到,加载代码和执行代码的地址,就是前面设置FLASH的地址,紧接下来会看到下面这行:

*.o (RESET, +First)

这行的意思就是说,把所有目标文件.o结尾的代码,都放置在这里,但是要把标识为RESET段的代码放置在最前面,因此这里明确地说明RESET的代码放在首位置,就是偏移0的位置。那么回过头来看一下启动的汇编文件,如下:

玩转STM32(14)运行第一行代码_第2张图片

在这里看到AREA后面紧跟着一个RESET名称,也就是说这段代码区域就被命名为RESET了,当编译器看到这个名称,就会把这段代码放置在存储器的首位置,这样CPU就会从这里获得栈顶值,就可以开始加载代码进行运行了,从偏移4的位置,也就是Reset_Handler的值,把它放到CPU的执行指令寄存器,就进入代码运行了。

https://blog.csdn.net/caimouse/article/details/51749579

玩转STM32(14)运行第一行代码_第3张图片

你可能感兴趣的:(玩转STM32)