赞
踩
前面搞定了栈顶的问题,接着下来,可以从复位矢量表里,看到如下:
__Vectors DCD __initial_sp ; Top of Stack
DCD Reset_Handler ; Reset Handler
DCD NMI_Handler ; NMI Handler
从向量表里看到第二项,就是复位函数的入口地址,Reset_Handler是复位向量的过程函数,它被编译之后,可以看到它表示的值是:
Reset_Handler 0x0800019d Thumb Code 8 startup_stm32f40_41xxx.o(.text)
前面说过CPU会在复位之后,就加载Reset_Handler到PC寄存器,也就是加载地址0x0800019d,然后就会执行复位过程函数Reset_Handler,这时就开始运行复位过程函数的代码了。复位过程函数到底做了些什么工作,才可以进入C语言的入口函数main执行呢?要回答这个问题,我们先来看复位过程函数的代码,如下:
Reset_Handler PROC
EXPORT Reset_Handler [WEAK]
IMPORT SystemInit
IMPORT __main
LDR R0, =SystemInit
BLX R0
LDR R0, =__main
BX R0
ENDP
在上面这段代码里,使用PROC/ENDP宏指令来声明一个子过程,然后使用EXPORT指令来声明本过程Reset_Handler为弱声明,也就是后面的[WEAK]。为什么要使用WEAK指令?其实是为了方便用户来自定义自己的函数。比如用户使用默认的Reset_Handler过程函数不能满足自己的要求,需要自己定义一个过程函数来替换Reset_Handler。如果使用旧的方法,就是使用宏定义来判断,但这里使用WEAK指令之后,就可以不使用宏来实现了。编译器会直接判断编译所有文件时,是否会发现用户自定义的Reset_Handler函数,如果有就会使用用户定义的Reset_Handler函数,而不是调用这里声明为WEAK的Reset_Handler函数。如果用户没有定义,就调用默认的Reset_Handler函数。这跟C++里虚函数有点像,当派生类有相同函数名称时,就会调用派生类的函数,如果没有就调用基类的函数。因此在向量表里的中断函数,很多都是使用WEAK指令声明的,所以都会有这个作用,如果用户自己没有定义相应中断函数,就会调用默认的中断函数,这样就方便大家写中断函数的代码,不必要把所有的中断函数都写一遍,只需要写自己关心的中断函数即可,这样大大减轻了开发人员的工作量,同时代码更加标准化,更方便移植和复用。
接着下来可以看到下面这行代码:
IMPORT SystemInit
这里使用IMPORT指令,说明后面的函数SystemInit在别的地方声明的,在这里它是在system_stm32f4xx.c文件里定义的,通过IMPORT指令才可以在startup_stm32f40_41xxx.s文件里调用它,否则它在连接时找不到这个函数,就会编译出错。
同理,IMPORT __main代码,也是声明_main函数是在别的模块里声明和定义的,所以采用IMPORT指令。
接着会运行这行代码:
LDR R0, =SystemInit
LDR指令是将存储器地址所指地址处连续的4个字节(1个字)的数据传送到目的寄存器中。在这里是把SystemInit的地址加载到R0寄存器。
BLX R0
BLX reg是跳转到由寄存器reg给出的地址,并根据REG的LSB切换处理器;状态还要把转移前的下条指令地址保存到LR。这行代码就是实现调用SystemInit函数运行,运行之后跳到下一条指令执行。
LDR R0, =__main
BX R0
这两行语句,与前面运行SystemInit函数是一样的,在这里调用_main函数运行,这个函数里会更进一步地初始化C语言运行环境,然后调用C语言的入口函数main来运行,这样就调用C语言编写的代码了。
到这里,就完成复位函数的代码分析,理解整个复位函数为什么要这么写,以及怎么样执行每一步。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。