mbed_boot.c中总体上介绍了启动流程
* For ARMCC:
* ==========
*
* Reset (TARGET)
* -> SystemInit (TARGET)
* -> __main (LIBC)
* -> __rt_entry (MBED: rtos/mbed_boot.c)
* -> __user_setup_stackheap (LIBC)
* -> mbed_set_stack_heap (MBED: rtos/mbed_boot.c)
* -> mbed_cpy_nvic (MBED: rtos/mbed_boot.c)
* -> mbed_sdk_init (TARGET)
* -> _platform_post_stackheap_init (RTX)
* -> osKernelInitialize (RTX)
* -> mbed_start_main (MBED: rtos/mbed_boot.c)
* -> osThreadNew (RTX)
* -> pre_main(MBED: rtos/mbed_boot.c)
* -> __rt_lib_init (LIBC)
* -> $Sub$$main (MBED: rtos/mbed_boot.c)
* -> mbed_main (MBED: rtos/mbed_boot.c)
* -> main (APP)
* -> osKernelStart (RTX)
*
* In addition to the above, libc will use functions defined by RTX: __user_perthread_libspace, _mutex_initialize,
* _mutex_acquire, _mutex_release, _mutex_free for details consult: ARM C and C++ Libraries and Floating-Point
* Support User Guide.
*
MBED项目中用C代码实现放在BIN文件最开始位置的中断向量表等内容的RESET section值得借鉴。
一般来说,启动阶段的代码由汇编实现,包含了终端向量表的数据设置,以及复位后第1个指令到调用LIBC主入口__main,在__main里完成数据的初始,并调用
__rt_entry,在
__rt_entry里再完成堆栈初始化、LIBC库初始后,调用main函数。
一个简单的示例如下:
AREA RESET, DATA, READONLY
EXPORT __Vectors
__Vectors DCD __initial_sp ; Top of Stack BIN文件前4个字节为栈的初始化地址
DCD Reset_Handler ; Reset Handler BIN文件第2个4字节是复位后的第1条指令的位置
DCD NMI_Handler ; NMI Handler 之后为各个中断对应处理函数的第1条指令位置
DCD HardFault_Handler ; Hard Fault Handler
DCD 0 ; Reserved
DCD 0 ; Reserved
DCD 0 ; Reserved
DCD 0 ; Reserved
DCD 0 ; Reserved
DCD 0 ; Reserved
DCD 0 ; Reserved
DCD SVC_Handler ; SVCall Handler
DCD 0 ; Reserved
DCD 0 ; Reserved
DCD PendSV_Handler ; PendSV Handler
DCD SysTick_Handler ; SysTick Handler
; External Interrupts
DCD xxxx
DCD xxxx
DCD xxxx
DCD xxxx
DCD xxxx
DCD xxxx
DCD xxxx
DCD xxxx
DCD xxxx
DCD xxxx
DCD xxxx
DCD xxxx
__Vectors_End
__Vectors_Size EQU __Vectors_End - __Vectors
AREA |.text|, CODE, READONLY
; Reset Handler
Reset_Handler PROC
EXPORT Reset_Handler [WEAK]
IMPORT SystemInit
IMPORT __main
LDR R0, =SystemInit 调用系统初始化函数,因为还没有初始化C库,不要使用C库里的函数
BLX R0
LDR R0, =__main 初始化数据去,初始化C库后调用应用main函数
BX R0
ENDP
; Dummy Exception Handlers (infinite loops which can be modified)
NMI_Handler PROC
EXPORT NMI_Handler [WEAK]
B .
ENDP
HardFault_Handler\
PROC
EXPORT HardFault_Handler [WEAK]
B .
ENDP
SVC_Handler PROC
EXPORT SVC_Handler [WEAK]
B .
ENDP
PendSV_Handler PROC
EXPORT PendSV_Handler [WEAK]
B .
ENDP
SysTick_Handler PROC
EXPORT SysTick_Handler [WEAK]
B .
ENDP
在mbed os项目里的startup_NUC_472.c里,使用__attribute__ ((section("RESET")))语法,将本文件的数据和代码定义为RESET section。首先定义个const uint32_t __vector_handlers[] 数组,该数组里存放的歌中断函数的地址。相当于前面汇编代码开始处的中断向量表。
再看看中断向量表中的函数名,使用了弱符号和别名机制,在用户没有定义强符号的同名中断函数时,将使用弱符号对应的函数。举个例
项目自身宏定义
#define WEAK __attribute__ ((weak))
#define ALIAS(f) __attribute__ ((weak, alias(#f)))
#define WEAK_ALIAS_FUNC(FUN, FUN_ALIAS) void FUN(void) __attribute__ ((weak, alias(#FUN_ALIAS)));
WEAK_ALIAS_FUNC(SC4_IRQHandler, Default_Handler)
const uint32_t __vector_handlers[] {
xxx
(uint32_t) SC4_IRQHandler, 根据前面的别名定义,开发人员没有定义
SC4_IRQHandler,将连接
Default_Handler函数
xxx
}
缺省的死循环函数
void Default_Handler(void)
{
while (1);
}
复位后第1条指令执行的函数地址
void Reset_Handler(void)
{
运行环境必要的硬件初始化
Xxxx
调用LIBC库的主入口
__main();
}
启动C代码里,再次利用了弱符号特性。因为C库里一些函数或变量是定义为弱符号的,便于应用开发中重新按照系统的要去重新实现。其中__rt_entry(void)就可以重新实现。mBed OS里重新实现了__rt_entry(void)如下。
/* Called by the C library */
void __rt_entry (void) {
__user_setup_stackheap();
mbed_set_stack_heap();
/* Copy the vector table to RAM only if uVisor is not in use. */
#if !(defined(FEATURE_UVISOR) &&defined(TARGET_UVISOR_SUPPORTED))
mbed_cpy_nvic();
#endif
mbed_sdk_init();
_platform_post_stackheap_init();
mbed_start_main();
}
C语言的弱、强符号特性,平常都很少使用。现在看来,用于实现函数的重载,是非常好的用法。
小贴士:可以定义多个弱符号,链接时会选择代码SIZE长的符号链接。