FSP庫啟動文件詳解
13.1. 什么是啟動文件
啟動文件是系統上電復位后執行的第一個程序。 主要做了以下工作:
- 初始化堆棧。
- 使能FPU(float-point unit,即浮點單元)。
- 定位中斷向量表。
- 配置系統時鐘。
- 啟用CORTEX-M33棧監視器。
- 初始化C語言運行環境。
- 初始化變量SystemCoreClock,這個變量存放的是處理器時鐘的頻率。
- 初始化用于觸發NVIC中斷的ELC(Event Link Controller)事件。
- 初始化IO口
13.2. 啟動文件代碼講解
13.2.1. 復位程序
14-0:復位程序
void Reset_Handler (void)
{
/* 使用BSP對系統進行初始化. */
SystemInit();
/* Call user application. */
main();
while (1)
{
/* Infinite Loop. */
}
}
這是系統上電或復位后執行的第一個程序,使用BSP對系統進行初始化,隨后通過main函數進入用戶代碼。 BSP負責使MCU從復位狀態進入到用戶的應用程序。 在到達用戶的應用程序之前,BSP設置棧、堆、時鐘、中斷、C語言運行環境和堆棧監視器。
13.2.2. 棧區初始化
14-1:棧區初始化
/* 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 B)。
宏“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. 堆區初始化
14-2:堆區初始化
/* 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. 中斷向量表初始化
14-3:中斷向量表初始化
/* 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. 系統初始化()
14-4: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
14-5:使能 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. 封裝棧頂
14-6:封裝棧頂
#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. 設置中斷向量表的基地址
14-7:設置中斷向量表的基地址
#if !BSP_TZ_NONSECURE_BUILD
SCB->VTOR = (uint32_t) &__Vectors;
#endif
這里通過直接設置SCB->VTOR的值來設置中斷向量表的基地址。 在非安全項目中,這一步會被跳過。
13.2.5.4. 熱啟動回調函數
14-8:函數聲明
void R_BSP_WarmStart(bsp_warm_start_event_t event) __attribute__((weak));
14-9:熱啟動回調函數
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中被重寫。
14-10:hal_entry中被重寫的函數
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. 時鐘初始化
14-11:時鐘初始化
bsp_clock_init();
根據bsp_clock_cfg.h中的設置來設置所有系統時鐘,這些配置來自于FSP Configuration中的Clocks選項卡。
13.2.5.6. 啟用CORTEX-M33棧監視器
14-12:啟用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區
14-13: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區
14-14: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. 調用全局對象的構造函數
14-15:調用全局或靜態對象的構造函數
#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的值
14-16:初始化SystemCoreClock的值
/* Initialize SystemCoreClock variable. */
SystemCoreClockUpdate();
初始化SystemCoreClock的值,這個值表示處理器時鐘的頻率,默認為200MHz。
13.2.5.9. 初始化ICU
14-17:初始化 ICU
/* Initialize ELC events that will be used to trigger NVIC interrupts. */
bsp_irq_cfg();
這個函數將初始化ICU(Interrupt Control Unit),即中斷控制器,以便配置的ELC事件觸發NVIC中斷。
-
mcu
+關注
關注
146文章
17168瀏覽量
351408 -
中斷
+關注
關注
5文章
898瀏覽量
41524 -
代碼
+關注
關注
30文章
4791瀏覽量
68677 -
FSP
+關注
關注
0文章
34瀏覽量
7143 -
啟動文件
+關注
關注
0文章
15瀏覽量
2258
發布評論請先 登錄
相關推薦
評論