100C8的startup.S

ba5ag.kai at gmail.com 2014-04-24

100C8上电后的第一条代码在startup.S里,准确地说,我打算用

STM32F10x_StdPeriph_Lib_V3.5.0/Libraries/CMSIS/CM3/DeviceSupport/ST/STM32F10x/startup/TrueSTUDIO/startup_stm32f10x_md_vl.s

为了方便,我在Makefile里是这样写的:

AS = $(GNUARM)/bin/arm-none-eabi-as
FLAG = -mcpu=cortex-m3 -mthumb -Wall

startup.o: $(STM)/Libraries/CMSIS/CM3/DeviceSupport/ST/STM32F10x/startup/TrueSTUDIO/startup_stm32f10x_md_vl.s
        $(AS) $(FLAGS) -c $< -o $@

就是说,不管具体的文件名叫什么,编译出来的.o都是startup.o。

我们来看看这个startup.s。

.syntax unified
.cpu cortex-m3
.fpu softvfp
.thumb

首先,Cortex-m3 为了兼容 thumb 指令和 thumb2 指令,使这两种指令可以使用统一的格式,引入了一种叫做"统一汇编语言 UAL "的语法机制,第一行说的就是这个。后面的应该不需要解释了,其义自明。

.global g_pfnVectors
.global Default_Handler

.global 使得连接程序(ld)能够识别后面的符号,所以这两行就是说明这两个符号是全局的,ld可以找过来的,一般也就意味着下面要出现这两个符号的定义了。

.word   _sidata
.word   _sdata
.word   _edata
.word   _sbss
.word   _ebss

.word就是在这个地方放一个值。相当于在这里定义一个数据变量。这5个变量依次是:

这些段是由链接器确定后,将相应的地址填入这些变量。

.equ  BootRAM, 0xF108F85F

这句定义一个符号BootRAM的值,在后面会用到。

.section    .text.Reset_Handler
.weak   Reset_Handler
.type   Reset_Handler, %function

接下去的段是代码的Reset_Handle,这就是重启(启动)时进入的第一段程序。.weak说明这个代码是弱链接的,如果另外地方有同样名字的非弱链接的函数,那么这个代码会被链接器丢掉。.type说明这个名字“Reset_Handle”是一个函数,可以被外部调用。

Reset_Handler:
/* Copy the data segment initializers from flash to SRAM */
  movs  r1, #0
  b LoopCopyDataInit
CopyDataInit:
    ldr r3, =_sidata
    ldr r3, [r3, r1]
    str r3, [r0, r1]
    adds    r1, r1, #4
LoopCopyDataInit:
    ldr r0, =_sdata
    ldr r3, =_edata
    adds    r2, r0, r1
    cmp r2, r3
    bcc CopyDataInit

上电启动的第一步,是把全局变量的初始值拷贝到SRAM中的全局变量中去。这段代码可能是从C语言编译过来的,所以用了一点优化小tricky,把循环的一部分代码前后颠倒了,所以看着怪怪的。基本上,它做的事情就是把_sidata开始的flash中的内容,逐字拷贝到_sdata开始的地址中,直到_edata为止(_edata是最后一个要拷贝的地址)。这里r1是索引,从0开始,每次加4;r3是源地址,r0是目的地址。但是在比较是否循环结束的地方,r3又用来表示结束地址,所以循环的每一轮都需要重新装载r3。

    ldr r2, =_sbss
    b   LoopFillZerobss
/* Zero fill the bss segment. */
FillZerobss:
    movs    r3, #0
    str r3, [r2], #4
LoopFillZerobss:
    ldr r3, = _ebss
    cmp r2, r3
    bcc FillZerobss

这段和前面的类似,一段循环把bss段全部填充为0,就相当于给没有初始值的全局变量填充了0值。

/* Call the clock system intitialization function.*/
  bl  SystemInit 
/* Call static constructors */
  bl __libc_init_array  
/* Call the application's entry point.*/
    bl  main
    bx  lr

依次调用三个函数:SystemInit__libc_init_arraymain。也就是说,在调用应用程序的main之前,还有两个函数要先跑一下的。我们后面再展开来看那两个函数。还要注意的一个细节时,在调用SystemInit之前,堆栈指针还没有初始化,时钟频率(PLL)也还没有设置过。堆栈指针没有初始化就可以调用其他子程序,是因为ARM的BL指令是把返回地址保存在RL寄存器里而不是推堆栈的。另外中断也没有全局关闭,那是因为上电默认全局关闭。

最后的这句bx lr,会使得这段代码回到调用它的地方。但是这是一个中断服务程序,上电就到这里来了,所以实际上会重新回到这段代码开头的地方...所以main不能结束啊。

.size   Reset_Handler, .-Reset_Handler

接下来:

.section    .text.Default_Handler,"ax",%progbits
Default_Handler:
Infinite_Loop:
    b   Infinite_Loop
    .size   Default_Handler, .-Default_Handler

这是一个默认中断处理程序,所有应用程序不管的中断向量都填入了这个程序,它只是一个死循环。

.section    .isr_vector,"a",%progbits
    .type   g_pfnVectors, %object
    .size   g_pfnVectors, .-g_pfnVectors
g_pfnVectors:
    .word   _estack
    .word   Reset_Handler
    .word   NMI_Handler

这是Cortex-M3的最小的中断向量表,这个表从g_pfnVectores开始放,第一个是保留的,第二个就是前面的那个Reset_Handler了。接下去每个中断都已经定义了一个中断处理程序(少数直接填0)。最后一个是:

.word BootRAM          /* @0x01CC. This is for boot in RAM mode for 
                     STM32F10x Medium Value Line Density devices. */

就是前面定义的那个符号BootRAM,它的值要填在这里。之所以不在这里直接写一个数值,是因为这样定义了符号之后,一容易让人理解这个是什么,二容易让人找到在哪里修改它。

接下来,是一排这样的代码:

  .weak  NMI_Handler
  .thumb_set 
NMI_Handler,Default_Handler

这是用弱符号的方式,把这些中断向量都定义为那个死循环的默认处理程序。因为是弱符号,所以将来你自己写的中断处理程序就会直接替换它们。

把它编译之后,得到了startup.o。为了验证,再用arm-none-eabi-objdump -d startup.o把它dump 出来看看,和.s对照一下。