转载——cortex-M4与cortex-A7内核启动流程分析
文章目录
- 一.概述
- 二.cortex-M4内核启动流程
-
- 1.在代码最开始进行定义中断向量表
- 2.编写中断服务函数(重点分析Reset_Handler复位中断函数)
- 3.分析SystemInit()函数
- 4. 启动过程总结
- 三.cortex-A7内核启动流程
-
- 1.定义中断向量表
- 2.Reset_Handler中断服务函数
- 3.IRQ_Handler中断服务函数
- 三.总结
一.概述
在系统上电时需要先执行一段引导程序,也称bootloader程序,来完成对系统运行环境的初始化工作。就和 PC 上的 BIOS程序一样,bootloader 就相当于 BIOS。
基于ARM架构的处理器其内核启动流程大同小异,本文就基于cortex-M4内核的MCU以及基于cortex-A7内核的MPU,对其启动流程做简要分析。
二.cortex-M4内核启动流程
在我们进行单片机编程的时候,通常没有考虑过自己写bootloader程序,直接编写main函数就能运行。那是因为半导体厂商给我们写好了bootloader,通常不需要我们修改。以STM32F4系列MCU为例,可以看到在我们的工程中存在startup_stm32f40_41xxx.s这个文件,这个文件是使用汇编写的,也就是bootloader程序。
在单片机上电时,首先指向的就是startup_stm32f40_41xxx.s这个文件,因为在这个文件中完成了对系统的初始化工作。下面具体看看做了哪些事情。在这个文件上方给出了这样一段描述:
This module performs:
;* - Set the initial SP
;* - Set the initial PC == Reset_Handler
;* - Set the vector table entries with the exceptions ISR address
;* - Configure the system clock and the external SRAM mounted on
;* STM324xG-EVAL board to be used as data memory (optional,
;* to be enabled by user)
;* - Branches to __main in the C library (which eventually
;* calls main()).
;* After Reset the CortexM4 processor is in Thread mode,
;* priority is Privileged, and the Stack is set to Main.
从上面的描述中可以大概看出初始化工作做了哪些事情:包括堆栈初始化、SP初始化、PC初始化、系统时钟初始化并最终跳转到main函数。
1.在代码最开始进行定义中断向量表
__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 WWDG_IRQHandler ; Window WatchDog
DCD PVD_IRQHandler ; PVD through EXTI Line detection
DCD TAMP_STAMP_IRQHandler ; Tamper and TimeStamps through the EXTI line
DCD RTC_WKUP_IRQHandler ; RTC Wakeup through the EXTI line
DCD FLASH_IRQHandler ; FLASH
DCD RCC_IRQHandler ; RCC
DCD EXTI0_IRQHandler ; EXTI Line0
DCD EXTI1_IRQHandler ; EXTI Line1
DCD EXTI2_IRQHandler ; EXTI Line2
..................
..................
..................
2.编写中断服务函数(重点分析Reset_Handler复位中断函数)
; Reset handler
Reset_Handler PROC
EXPORT Reset_Handler [WEAK]
IMPORT SystemInit
IMPORT __main
LDR R0, =SystemInit
BLX R0
LDR R0, =__main
BX R0
ENDP
可以看出,在Reset handler中断服务函数中主要完成了两件事:
(1)调用SystemInit进行系统初始化
(2)跳转到mian处执行并不再跳回(因为BX R0并未保存返回地址至LR)
3.分析SystemInit()函数
SystemInit()系统初始化函数主要做了一下几件事情:
(1)设置是否开启FPU
(2)进行系统时钟初始化配置
(3)设置中断向量表偏移地址(ARM处理器默认中断向量表地址为:0x00000000,这里FLASH起始地址是0x08000000,因此设置偏移地址为0x08000000)
void SystemInit(void)
{
#if (__FPU_PRESENT == 1) && (__FPU_USED == 1)
SCB->CPACR |= ((3UL << 10*2)|(3UL << 11*2));
#endif
RCC->CR |= (uint32_t)0x00000001;
RCC->CFGR = 0x00000000;
RCC->CR &= (uint32_t)0xFEF6FFFF;
RCC->PLLCFGR = 0x24003010;
RCC->CR &= (uint32_t)0xFFFBFFFF;
RCC->CIR = 0x00000000;
#if defined (DATA_IN_ExtSRAM) || defined (DATA_IN_ExtSDRAM)
SystemInit_ExtMemCtl();
#endif
SetSysClock();
#ifdef VECT_TAB_SRAM
SCB->VTOR = SRAM_BASE | VECT_TAB_OFFSET;
#else
SCB->VTOR = FLASH_BASE | VECT_TAB_OFFSET;
#endif
}
4. 启动过程总结
<1>初始化堆栈、SP指针、令PC指向Reset_Handler(也就是最先执行复位中断服务函数)
<2>定义中断向量表(后面在中断发送时会根据中断向量表找到中断服务函数的入口地址)
<3>由于最开始PC指针指向了Reset_Handler中断,因此先执行Reset_Handler中断服务函数(进行初始化工作:包括初始化时钟,FPU,设置VTOR中断向量表偏移地址等)
<4>在Reset_Handler服务函数中SystemInit执行完返回并跳转到mian函数(采用BX跳转,不会返回了),进入用户程序执行,至此,系统启动,boot程序作用终结。
<5>通常main函数是while死循环执行的方式,那么当中断来的时候怎样执行呢?别忘了我们在bootloader程序的一开始就定义了中断向量表,将各中断都注册了(包括外部中断和内部中断),并且有系统或用户实现其中断服务函数。当有中断来临时系统会自动跳转到中断向量表,并根据中断号查找中断向量表获得中断服务子程序的入口地址,并跳转指向,中断指向完成后跳回main函数继续指向。
三.cortex-A7内核启动流程
通常ARM的A系列处理器会基于linux系统做开发,由专门的bootloader程序做引导。常用的例如U-BOOT。这里我们不分析U-BOOT,只是按照上面的MCU裸机开发的方式分析cortex-A7内核启动流程。具体过程和上面大同小异。
1.定义中断向量表
_start:
ldr pc, =Reset_Handler
ldr pc, =Undefined_Handler
ldr pc, =SVC_Handler
ldr pc, =PrefAbort_Handler
ldr pc, =DataAbort_Handler
ldr pc, =NotUsed_Handler
ldr pc, =IRQ_Handler
ldr pc, =FIQ_Handler
可以看出Cortex-A中断向量表有8个中断,比上面的M4内核中断少了很多,而且我们常见的中断都没有了,这是为什么?这是因为Cortex-A内核有9种运行模式,其中断系统也更加复杂,因此中间又封装了一层,将所有中断分为了以上几类,我们常用的一些中断都在IRQ_Handler这一大类里面。在上面的表中我们重点关注Reset_Handler和IRQ_Handler。因为Reset_Handler完成了系统初始化,IRQ_Handler是用户级中断。
2.Reset_Handler中断服务函数
Reset_Handler:
MCR{cond} p15,<opc1>,<Rt>,<CRn>,<CRm>,<opc2>
MRC p15,0,r0,c0,c0,0
mrc p15, 0, r0, c1, c0, 0
bic r0, r0, #(0x1 << 12)
bic r0, r0, #(0x1 << 2)
bic r0, r0, #0x2
bic r0, r0, #(0x1 << 11)
bic r0, r0, #0x1
mcr p15, 0, r0, c1, c0, 0
MCR{ cond} p15,<opc1>,<Rt>,<CRn>,<CRm>,<opc2>
MRC p15,0,r0,c12,c0,0
MCR p15,0,r0,c12,c0,0
ldr r0, =0X87800000
dsb
isb
mcr p15, 0, r0, c12, c0, 0
dsb
isb
mrs r0, cpsr
bic r0, r0, #0x1f
orr r0, r0, #0x12
msr cpsr, r0
ldr sp, =0x80600000
mrs r0, cpsr
bic r0, r0, #0x1f
orr r0, r0, #0x1f
msr cpsr, r0
ldr sp, =0x80400000
mrs r0, cpsr
bic r0, r0, #0x1f
orr r0, r0, #0x13
msr cpsr, r0
ldr sp, =0X80200000
b main
可以看出,系统初始化工作也是在Reset_Handler复位中断中完成的,并且在完成系统初始化之后就会跳转到main函数执行,且不会再返回。
3.IRQ_Handler中断服务函数
IRQ_Handler:
111 push {lr}
112 push {r0-r3, r12}
113
114 mrs r0, spsr
115 push {r0}
117 mrc p15, 4, r1, c15, c0, 0
121 add r1, r1, #0X2000
122 ldr r0, [r1, #0XC]
126 push { r0, r1}
128 cps #0x13
129
130 push { lr}
131 ldr r2, =system_irqhandler
132 blx r2
134 pop { lr}
135 cps #0x12
136 pop { r0, r1}
137 str r0, [r1, #0X10]
138
139 pop { r0}
140 msr spsr_cxsf, r0
141
142 pop { r0-r3, r12}
143 pop { lr}
144 subs pc, lr, #4
可以看出IRQ_Handler中断服务函数总体上完成了三件事,在发送通用级用户中断时,系统会在中断向量表中找到IRQ_Handler中断服务函数的入口地址并指向上述代码:上述代码时是汇编写的,大概流程是:1.保护现场----->2.获取更具体的中断号------>3.调用更具体的中断服务函数system_irqhandler,这个函数是有参数的,就是中断号,保存在R0寄存器中---->4.指定具体的中断服务程序,完成后返回------>5.恢复现场,返回main继续执行。
三.总结
总体来看cortex-M4与cortex-A7的启动流程基本一致:都是定义中断向量表---->执行复位中断服务Reset_Handler并在此中完成系统初始化----->跳转至main函数。
对于后面中断的处理过程也基本一致:都是查找中断向量表获取中断服务函数入口地址---->执行中断服务函数(保护现场、具体指向、恢复现场)------>指向完返回main。M4与A7的唯一不同可能就是对于系统中断的封装上,M4直接将所有中断都列在了中断向量表中。而A7将所有中断分为了两级,其中我们常见的中断都放在IRQ_Handler中,中断发生时先执行IRQ_Handler,在IRQ_Handler中再具体指向中断处理。