ARM的启动过程通常包括以下部分:
- 外部硬件reset
- 根据BOOT MODE进入不同的启动入口
- 向量表定义
- 地址重映射及中断向量表的转移
- 堆栈初始化
- 设置系统时钟频率
- 中断寄存器的初始化
- 进入C应用程序
1. 外部硬件reset
按压reset button后,系统复位。
2. 根据BOOT MODE进入不同的启动入口
BOOT MODE是由PCB设计决定的,根据BOOT0和BOOT1引脚的电压高低决定进入哪种模式:
3. 向量表定义
Cortex-M4复位后首先从默认向量表处读取SP初始值和PC初始值。为了让Cortex-M4复位后立即有可用的复位向量,必须将向量表存储在ROM中,然后在初始化过程可选地将向量表地址映射到其它区域。 向量表的定义如下:
__Vectors DCD __initial_sp ; Top of Stack
DCD Reset_Handler ; Reset Handler
DCD NMI_Handler ; NMI Handler
DCD HardFault_Handler ; Hard Fault Handler
DCD MemManage_Handler ; MPU Fault Handler
DCD BusFault_Handler ; Bus Fault Handler
DCD UsageFault_Handler ; Usage Fault Handler
DCD 0 ; Reserved
DCD 0 ; Reserved
DCD 0 ; Reserved
DCD 0 ; Reserved
DCD SVC_Handler ; SVCall Handler
DCD DebugMon_Handler ; Debug Monitor Handler
DCD 0 ; Reserved
DCD PendSV_Handler ; PendSV Handler
DCD SysTick_Handler ; SysTick Handler
; External Interrupts
DCD WDT_IRQHandler ; 0: Watchdog Timer
DCD RTC_IRQHandler ; 1: Real Time Clock
DCD TIM0_IRQHandler ; 2: Timer0 / Timer1
DCD TIM2_IRQHandler ; 3: Timer2 / Timer3
DCD MCIA_IRQHandler ; 4: MCIa
DCD MCIB_IRQHandler ; 5: MCIb
DCD UART0_IRQHandler ; 6: UART0 - DUT FPGA
DCD UART1_IRQHandler ; 7: UART1 - DUT FPGA
DCD UART2_IRQHandler ; 8: UART2 - DUT FPGA
DCD UART4_IRQHandler ; 9: UART4 - not connected
DCD AACI_IRQHandler ; 10: AACI / AC97
DCD CLCD_IRQHandler ; 11: CLCD Combined Interrupt
DCD ENET_IRQHandler ; 12: Ethernet
DCD USBDC_IRQHandler ; 13: USB Device
DCD USBHC_IRQHandler ; 14: USB Host Controller
DCD CHLCD_IRQHandler ; 15: Character LCD
DCD FLEXRAY_IRQHandler ; 16: Flexray
DCD CAN_IRQHandler ; 17: CAN
DCD LIN_IRQHandler ; 18: LIN
DCD I2C_IRQHandler ; 19: I2C ADC/DAC
DCD 0 ; 20: Reserved
DCD 0 ; 21: Reserved
DCD 0 ; 22: Reserved
DCD 0 ; 23: Reserved
DCD 0 ; 24: Reserved
DCD 0 ; 25: Reserved
DCD 0 ; 26: Reserved
DCD 0 ; 27: Reserved
DCD CPU_CLCD_IRQHandler ; 28: Reserved - CPU FPGA CLCD
DCD 0 ; 29: Reserved - CPU FPGA
DCD UART3_IRQHandler ; 30: UART3 - CPU FPGA
DCD SPI_IRQHandler ; 31: SPI Touchscreen - CPU FPGA
__Vectors_End
上图中,复位后,从地址0x00000000处取出0x20001BB0放入R13(MSP)中,是栈顶指针。从0x00000004处取出值0x08000569放入R15(PC)中, 0x08000569最低为为1,代表为thumb指令,0x08000568为将要执行指令的地址,该地址就是Reset_Hnadler处理函数的入口地址,可参加下面代码:
Reset_Handler PROC
EXPORT Reset_Handler [WEAK]
IMPORT SystemInit
IMPORT check_update_requirement
IMPORT __main
LDR R0, =SystemInit
BLX R0
LDR R0, =check_update_requirement
BLX R0
cbz R0, RunUserCode
LDR R0, =__main
BX R0
ENDP
进入Reset_Handler后,首先进入SystemInit,做系统环境的初始化(clock,ram,Flash等控制器)。 然后进入__main,这是系统函数,直接跳转到 __scatterload,__scatterload 执行代码和数据复制以及 ZI 数据的清零。 根据分散加载文件,拷贝RW数据到RAM,在RAM空间里建立ZI的数据空间,建立运行时的映像存储器映射.然后跳转到 __rt_entry(运行时的入口)则负责初始化 C 库。还设置应用程序的栈和堆,初始化库函数及其静态数据。这时应用程序的堆栈建立了,跳转到main()函数,运行用户代码。
4. ARM系统启动过程
4.1. 地址划分
boot_memory_base.h
#ifndef BOOT_MEMORY_BASE_H
#define BOOT_MEMORY_BASE_H
#define BOOT_ROM_BASE 0x00000000
#define BOOT_ROM_LIMIT 0x1000
#define ROM_CODE_LIMIT 0x10000 //64K
#define BOOT_ROM_DATABASE 0x20010000 // 8K
#define BOOT_ROM_DATALIMIT 0x2000
#define BOOT_HEADER_SIZE 24 //refer to boot_type.h
#define BOOT_RAM_CODEBASE 0x20012000 // 4K
#define BOOT_RAM_DATABASE 0x20013000 // 4K
#define UART_DOWNLOAD_CODEBASE 0x20012000
#define UART_DOWNLOAD_DATABASE 0x20013000
#define RAM_BASE 0x1fff0000
#define RAM_TOP 0x20032000
#define FLASH_CACHE_BASE 0x10000000
#define FLASH_IMAGE_OFFSET 0x3000
#define RETENTION_MEM_BASE 0x20028000
#define RETENTION_MEM_SIZE 0x2000
#define CACHE_MEM_BASE 0x2002A000
#define CACHE_MEM_SIZE 0x8000
#endif
4.2. Level-1 Boot
第一阶段BootLoader在ROM中,ROM起始地址放中断向量表,往后依次放入代码段和数据段,总大小为12KB,其中,数据段的执行域在RAM中。如图橙色部分。
typedef struct{
uint32_t art_flag;
uint8_t *target_address;
uint16_t ram_header_length;
uint16_t length;
uint32_t bootram_crc;
entry_point_t entry_point;
uint32_t crp_value;
}boot_header_t;
int main()
{
uint32_t crp_flag;
flash_init();
flash_read(CRP_FLASH_OFFSET,sizeof(uint32_t),(uint8_t *)(&crp_flag)); // first 4 bytes is crp flag
if(crp_flag != CRP_VALID_FLAG)
{
// function io set for swd
sysc_cmp_gpioa_en_2_setf(0);
sysc_cmp_gpioa_en_3_setf(0);
#ifdef BOOT_ROM_DEBUG
LOG(LOG_LVL_INFO,"crp_invalid SWD out\n");
#endif
}
#ifdef BOOT_ROM_DEBUG
LOG(LOG_LVL_INFO,"enter main......\n");
#endif
boot_stat.rx_done= false;
boot_mode.boot_source = (boot_option_t)sysc_awo_i_boot_mode_getf();
#ifdef FPGA_SET_BOOTMODE
boot_mode.boot_source = BOOT_FROM_FLASH;
#endif
boot_mode_set(boot_mode.boot_source);
while(1)
{
boot_header_rx_start();
while(boot_stat.rx_done==false);
if(boot_stat.crc_valid == true)
break;
else
boot_stat.rx_done = false;
}
boot_header.entry_point();
return 1;
}
4.3. Level-2 Boot
二级BootLoader从UART或者flash加载到RAM中运行,所以它的加载域和执行域是RAM中的相同位置。因此,MDK project的链接文件如下:
#! armcc -E
#include "..\boot_memory_base.h"
LOAD_BOOTRAM BOOT_RAM_CODEBASE - BOOT_HEADER_SIZE { ; load region size_region
BOOT_RAM_EXEC +0 { ; load address = execution address
*(boot_header_area,+First) ;
*(+RO,+RW,+ZI)
}
}
开始的24 bytes存放的是如下内容:
const boot_info_t boot_info BOOT_HEADER_ATTRIBUTE = {
.boot_header = {
.art_flag = FLASH_VALID_FLAG,
.target_address = (uint8_t *)BOOT_RAM_CODEBASE,
.ram_header_length = sizeof(boot_info_t)-sizeof(boot_header_t),
.length = 0xffff,
.bootram_crc = TO_BE_FILLED,
.entry_point = boot_ram_entry,
.crp_value = crp_flag,
},
.sys_nvds_offset = 0x1000,
.sys_nvds_len = 0x1000,
.sys_nvds_backup = 1,
.rfu1 = TO_BE_FILLED,
.rfu2 = TO_BE_FILLED,
};
Image 的Image Entry point : boot_ram_entry Flash开始的12KB存放二级boot的image文件 二级boot生成的image文件格式如下: 其中,boot_info_t的长度为0x28。该image文件还需做一下格式转换。
- 获取img文件总长度lSize。
- Seek到offset为10处,并写入lSize,其实是设置boot_header_t中的length:
.boot_header = { .art_flag = FLASH_VALID_FLAG, .target_address = (uint8_t *)BOOT_RAM_CODEBASE, .ram_header_length = sizeof(boot_info_t)-sizeof(boot_header_t), .length = 0xffff, .bootram_crc = TO_BE_FILLED, .entry_point = boot_ram_entry, .crp_value = crp_flag, },
- 分配buffer,大小为image_size – sizeof(boot_info_t).
- 从OFFSET为0x28处开始读取整个image内容到buffer中。
- 计算CRC值。
- 把CRC值写入boot_header_t中:
.boot_header = { .art_flag = FLASH_VALID_FLAG, .target_address = (uint8_t *)BOOT_RAM_CODEBASE, .ram_header_length = sizeof(boot_info_t)-sizeof(boot_header_t), .length = 0xffff, .bootram_crc = TO_BE_FILLED, .entry_point = boot_ram_entry, .crp_value = crp_flag, },
至此,生成了修改以后的boot_ram.bin。该image会在后面生成flash image时,放入flash的0x0-0x1000的位置。 一级boot的最后,会引导进入boot_ram_entry()函数。下面来看看该函数的流程。 在调用Reset_Handler函数时,会传递参数进去,所以Reset_Handler函数中要用R4寄存器。
Reset_Handler PROC EXPORT Reset_Handler [WEAK] IMPORT SystemInit IMPORT __main LDR R4, =SystemInit BLX R4 LDR R4, =__main BX R4 ENDP
至此,已经引导进入user main函数。