13. FSP库启动文件详解¶
本章配套视频介绍:
《15-FSP库启动文件详解》
https://www.bilibili.com/video/BV1C34y137uf/
13.1. 什么是启动文件¶
启动文件是系统上电复位后执行的第一个程序。主要做了以下工作:
初始化堆栈。
使能FPU(float-point unit,即浮点单元)。
定位中断向量表。
配置系统时钟。
启用CORTEX-M33栈监视器。
初始化C语言运行环境。
初始化变量SystemCoreClock,这个变量存放的是处理器时钟的频率。
初始化用于触发NVIC中断的ELC(Event Link Controller)事件。
初始化IO口
13.2. 启动文件代码讲解¶
13.2.1. 复位程序¶
void Reset_Handler (void)
{
/* 使用BSP对系统进行初始化. */
SystemInit();
/* Call user application. */
main();
while (1)
{
/* Infinite Loop. */
}
}
这是系统上电或复位后执行的第一个程序,使用BSP对系统进行初始化,随后通过main函数进入用户代码。 BSP负责使MCU从复位状态进入到用户的应用程序。在到达用户的应用程序之前,BSP设置栈、堆、时钟、中断、C语言运行环境和堆栈监视器。
13.2.2. 栈区初始化¶
/* Main stack */
static uint8_t g_main_stack[BSP_CFG_STACK_MAIN_BYTES + BSP_TZ_STACK_SEAL_SIZE]
BSP_ALIGN_VARIABLE(BSP_STACK_ALIGNMENT) //宏展开后为“__attribute__((aligned(8)))”
BSP_PLACE_IN_SECTION(BSP_SECTION_STACK); //宏展开后为“__attribute__((section( ".stack"))) __attribute__((__used__))”
栈是一种先进后出的内存结构,存放函数的参数值、返回值、局部变量等,在程序运行过程中实时加载和释放。 如果代码中使用的局部变量和函数嵌套较多,则需要增加栈区的大小,需要注意的是, 栈区分配大小不能超过RAM的大小。 宏“BSP_CFG_STACK_MAIN_BYTES”可以在FSP Configuration的“BSP”属性栏中的“RA Common”中通过修改“Main stack size”设置,默认为1KB(0x400 Byte)。
宏“BSP_TZ_STACK_SEAL_SIZE”用于封装栈顶,便于检测并阻止攻击者对栈的攻击。若使用TrustZone,则该宏为8,反之为0。
代码中的“BSP_ALIGN_VARIABLE(BSP_STACK_ALIGNMENT)” ,宏展开后为“__attribute__((aligned(8)))”,
“BSP_PLACE_IN_SECTION(BSP_SECTION_STACK)”,宏展开后为“__attribute__((section( “.stack”))) __attribute__((__used__))”
__attribute__可以设置类型,变量或函数的属性,下面将逐一解释修饰“g_main_stack”的属性。
__attribute__((aligned(8))):参数“aligned”指定被修饰对象的对齐方式(以字节为单位)。“aligned(8)”则意为栈区在分配时将 采用8字节对齐方式。
__attribute__((section( “.stack”))):参数“section”可以将变量定义到指定的输入段中。“section( “.stack”)”则意为将栈 放到名为“.stack”的输入段中。
__attribute__((__used__)):参数“__used__”告诉编译器,这个变量会被使用,即使没被调用也不会被编译器警告, 之所以要用这个属性修饰变量,是因为使用C或C++语言的用户一般不会直接操作栈, 绝大部分时候这个变量是不会被用户直接调用的。
13.2.3. 堆区初始化¶
/* Heap */
#if (BSP_CFG_HEAP_BYTES > 0) //若分配堆区大小为0则不进行初始化
BSP_DONT_REMOVE static uint8_t g_heap[BSP_CFG_HEAP_BYTES]
BSP_ALIGN_VARIABLE(BSP_STACK_ALIGNMENT) //宏展开后为“__attribute__((aligned(8)))”
BSP_PLACE_IN_SECTION(BSP_SECTION_HEAP); //宏展开后为“__attribute__((section(".heap"))) __attribute__((__used__))”
#endif
堆没有栈那样先进后出的顺序,用于动态内存分配,一般由程序员使用malloc和free进行分配和释放。 BSP_CFG_HEAP_BYTES用于配置堆区大小,当这个宏定义为0,则不对堆区进行初始化。 由于MCU中可用的片上SRAM相对较少,且缺乏内存保护,这意味着必须非常小心地控制堆的使用,以避免内存泄漏、溢出和试图过度分配。 因此默认堆区大小被设置为0。如果用户需要(例如一些C标准库函数需要使用堆), 可以在FSP Configuration中“BSP”属性栏的“RA Common”中通过修改“Heap size”来设置堆区大小。
代码中的BSP_DONT_REMOVE只有在使用IAR编译器的情况下才会被宏展开为“__root”,意为强制编译, 保证没有使用的函数或者变量也能够包含在目标代码中。其他环境下这个宏没有宏展开,可以忽略。
宏“BSP_ALIGN_VARIABLE(BSP_STACK_ALIGNMENT)”同样也修饰栈,这里的作用和上文的修饰栈的宏相同。
宏“BSP_PLACE_IN_SECTION(BSP_SECTION_HEAP)”展开后为“__attribute__((section(“.heap”))) __attribute__((__used__))”, 这个宏与上文的BSP_PLACE_IN_SECTION(BSP_SECTION_STACK)作用差不多,只是参数“section”不同,这里的“section(“.heap”)” 意为将堆放到名为“.heap”的输入段中。
13.2.4. 中断向量表初始化¶
/* Vector table. */
BSP_DONT_REMOVE const exc_ptr_t __Vectors[BSP_CORTEX_VECTOR_TABLE_ENTRIES]
BSP_PLACE_IN_SECTION(BSP_SECTION_FIXED_VECTORS) =
{
(exc_ptr_t) (&g_main_stack[0] + BSP_CFG_STACK_MAIN_BYTES), /* Initial Stack Pointer */
Reset_Handler, /* Reset Handler */
NMI_Handler, /* NMI Handler */
HardFault_Handler, /* Hard Fault Handler */
MemManage_Handler, /* MPU Fault Handler */
BusFault_Handler, /* Bus Fault Handler */
UsageFault_Handler, /* Usage Fault Handler */
SecureFault_Handler, /* Secure Fault Handler */
0, /* Reserved */
0, /* Reserved */
0, /* Reserved */
SVC_Handler, /* SVCall Handler */
DebugMon_Handler, /* Debug Monitor Handler */
0, /* Reserved */
PendSV_Handler, /* PendSV Handler */
SysTick_Handler, /* SysTick Handler */
};
宏“BSP_PLACE_IN_SECTION(BSP_SECTION_FIXED_VECTORS)”展开后为“__attribute__((section(“.fixed_vectors”))) __attribute__((__used__))”。 意为将表放到名为“.fixed_vectors”的输入段中,并且即使不被使用,编译器也不会警告。
13.2.5. SystemInit()¶
void SystemInit (void)
{
#if __FPU_USED
/* Enable the FPU only when it is used.
* Code taken from Section 7.1, Cortex-M4 TRM (DDI0439C) */
/* Set bits 20-23 (CP10 and CP11) to enable FPU. */
SCB->CPACR = (uint32_t) CP_MASK;
#endif
#if BSP_TZ_SECURE_BUILD
uint32_t * p_main_stack_top = (uint32_t *) __Vectors[0];
*p_main_stack_top = BSP_TZ_STACK_SEAL_VALUE;
#endif
.............. //由于篇幅所限,省略中间代码
/* Call Post C runtime initialization hook. */
R_BSP_WarmStart(BSP_WARM_START_POST_C);
/* Initialize ELC events that will be used to trigger NVIC interrupts. */
bsp_irq_cfg();
/* Call any BSP specific code. No arguments are needed so NULL is sent. */
bsp_init(NULL);
}
这是MCU进入Reset_Handler后执行的第一个函数,正如函数的字面意思,用于初始化MCU和运行环境, 运行完这段代码后将由main进入用户的hal_entry函数,由于代码较长,下面将分为几个部分对代码进行分析。
13.2.5.1. 使能FPU¶
#if __FPU_USED
/* Enable the FPU only when it is used.
* Code taken from Section 7.1, Cortex-M4 TRM (DDI0439C) */
/* Set bits 20-23 (CP10 and CP11) to enable FPU. */
SCB->CPACR = (uint32_t) CP_MASK;
#endif
FPU(Float-Point Unit)支持单精度加、减、乘、除、乘、累加、平方根运算。它还提供了定点和浮点数据格式以及浮点常量之间的转换的命令。
13.2.5.2. 封装栈顶¶
#if BSP_TZ_SECURE_BUILD
uint32_t * p_main_stack_top = (uint32_t *) __Vectors[0];
*p_main_stack_top = BSP_TZ_STACK_SEAL_VALUE;
#endif
这里获取栈顶指针,并将栈顶赋值为“BSP_TZ_STACK_SEAL_VALUE”,它的宏展开为0xFEF5EDA5,不能用作程序地址,因为地址范围从 0xE0000000到0xFFFFFFFF是不可执行的。这个过程被称为封栈(Sealing Stack),过程如图。如果有人针对栈进行攻击,那么这个地址 的值会被覆盖掉,这会被检测并阻止。
13.2.5.3. 设置中断向量表的基地址¶
#if !BSP_TZ_NONSECURE_BUILD
SCB->VTOR = (uint32_t) &__Vectors;
#endif
这里通过直接设置SCB->VTOR的值来设置中断向量表的基地址。在非安全项目中,这一步会被跳过。
13.2.5.4. 热启动回调函数¶
void R_BSP_WarmStart(bsp_warm_start_event_t event) __attribute__((weak));
void R_BSP_WarmStart (bsp_warm_start_event_t event)
{
if (BSP_WARM_START_RESET == event)
{
/* C runtime environment has not been setup so you cannot use globals. System clocks are not setup. */
}
if (BSP_WARM_START_POST_CLOCK == event)
{
/* C runtime environment has not been setup so you cannot use globals. Clocks have been initialized. */
}
else if (BSP_WARM_START_POST_C == event)
{
/* C runtime environment, system clocks, and pins are all setup. */
}
else
{
/* Do nothing */
}
}
这个函数会被调用三次,分别在时钟初始化前,时钟初始化后和C语言运行环境初始化后被调用,这个函数被声明了“weak”属性,因此其可以被用户重写。 在默认情况下,这个函数会在hal_entry.c中被重写。
void R_BSP_WarmStart(bsp_warm_start_event_t event)
{
if (BSP_WARM_START_RESET == event)
{
#if BSP_FEATURE_FLASH_LP_VERSION != 0 //因为RA6M5上没有这个功能,因此可以忽略这部分
/* Enable reading from data flash. */
R_FACI_LP->DFLCTL = 1U;
/* Would normally have to wait tDSTOP(6us) for data flash recovery. Placing the enable here, before clock and
* C runtime initialization, should negate the need for a delay since the initialization will typically take more than 6us. */
#endif
}
if (BSP_WARM_START_POST_C == event)
{
/* C runtime environment and system clocks are setup. */
/* Configure pins. */
R_IOPORT_Open (&g_ioport_ctrl, g_ioport.p_cfg);
}
}
默认情况下,这里只有在C语言运行环境初始化后,也就是函数传入参数为“BSP_WARM_START_POST_C”时,才会对用户在FSP Configuration中配置的引脚进行初始化,其他时候这里不会进行操作。
13.2.5.5. 时钟初始化¶
bsp_clock_init();
根据bsp_clock_cfg.h中的设置来设置所有系统时钟,这些配置来自于FSP Configuration中的Clocks选项卡。
13.2.5.6. 启用CORTEX-M33栈监视器¶
/* Use CM33 stack monitor. */
__set_MSPLIM(BSP_PRV_STACK_LIMIT);
设置主堆栈指针限制。没有ARMv8-M主扩展的设备(即Cortex-M23)缺乏不安全的堆栈指针限制寄存器,因此在非安全模式下此操作被忽略。
13.2.5.7. 初始化C语言运行环境¶
13.2.5.7.1. 初始化BSS区¶
/* Zero out BSS */
#if defined(__ARMCC_VERSION)
memset((uint8_t *) &Image$$BSS$$ZI$$Base, 0U, (uint32_t) &Image$$BSS$$ZI$$Length);
#elif defined(__GNUC__)
memset(&__bss_start__, 0U, ((uint32_t) &__bss_end__ - (uint32_t) &__bss_start__));
#elif defined(__ICCARM__)
memset((uint32_t *) __section_begin(".bss"), 0U, (uint32_t) __section_size(".bss"));
#endif
BSS(Block Start by Symbol)指用来存放程序中未初始化的全局变量和静态变量的一块内存区域,在这里BSS区所有数据都会被初始化为0。
13.2.5.7.2. 初始化data区¶
/* Copy initialized RAM data from ROM to RAM. */
#if defined(__ARMCC_VERSION)
memcpy((uint8_t *) &Image$$DATA$$Base, (uint8_t *) &Load$$DATA$$Base, (uint32_t) &Image$$DATA$$Length);
#elif defined(__GNUC__)
memcpy(&__data_start__, &__etext, ((uint32_t) &__data_end__ - (uint32_t) &__data_start__));
#elif defined(__ICCARM__)
memcpy((uint32_t *) __section_begin(".data"), (uint32_t *) __section_begin(".data_init"),
(uint32_t) __section_size(".data"));
data区是用来存放已初始化的全局变量,静态变量和常量的一块内存区域。在这段代码,数据会从ROM被拷贝到RAM的data区。
13.2.5.7.3. 调用全局对象的构造函数¶
#if defined(__ARMCC_VERSION)
int32_t count = Image$$INIT_ARRAY$$Limit - Image$$INIT_ARRAY$$Base;
for (int32_t i = 0; i < count; i++)
{
void (* p_init_func)(void) =
(void (*)(void))((uint32_t) &Image$$INIT_ARRAY$$Base + (uint32_t) Image$$INIT_ARRAY$$Base[i]);
p_init_func();
}
#elif defined(__GNUC__)
int32_t count = __init_array_end - __init_array_start;
for (int32_t i = 0; i < count; i++)
{
__init_array_start[i]();
}
#elif defined(__ICCARM__)
void const * pibase = __section_begin("SHT$$PREINIT_ARRAY");
void const * ilimit = __section_end("SHT$$INIT_ARRAY");
__call_ctors(pibase, ilimit);
#endif
RA系列MCU是支持使用C++语言进行开发的,这段代码用于调用C++的全局对象的构造函数。
13.2.5.8. 初始化SystemCoreClock的值¶
/* Initialize SystemCoreClock variable. */
SystemCoreClockUpdate();
初始化SystemCoreClock的值,这个值表示处理器时钟的频率,默认为200MHz。
13.2.5.9. 初始化ICU¶
/* Initialize ELC events that will be used to trigger NVIC interrupts. */
bsp_irq_cfg();
这个函数将初始化ICU(Interrupt Control Unit),即中断控制器,以便配置的ELC事件触发NVIC中断。