ARM Cortex-M底层技术(2)—启动代码详解
杂谈
工作了一天,脑袋比较乱。一直想把底层的知识写成一个系列,希望可以坚持下去。为什么要写底层的东西呢?首先,工作用到了这部分内容,最近和内部Flash打交道比较多,自然而然会接触到一些底层的东西;第二,近些年来Cortex-M阵营各厂商(ST、Nordic、ATMEL……)对新产品的迭代速度越来越快,以及微控制器应用普及程度的加深,越来越多的开发者把更多精力投注在应用层开发上,花在对底层技术上的时间越来越少,更深层次的原因是走嵌入式底层没有做互联网上层赚钱。希望自己可以把嵌入式ARM Cortex-M(M0/M0+/M3/M4/M7/M23/M33)底层技术写下去,加油!(长文,慎入,谢谢)
一. STM32的启动代码分析
当前,STM32因其丰富的学习资料,已经成为了80%嵌入式工作者入门学习的首选,当然我也不例外,主要是因为在学生时代,没钱买更好的开发板。工作之后,你会发现老板更抠门,产品的核心芯片一代比一代便宜。废话不多说,直接上知识点。
1.1 启动代码的概念
问题1. 什么是启动代码
启动代码是系统上电或者复位后运行的第一段代码,是进入C 语言的main 函数之前需要执行的那段汇编代码。或者说用户程序运行之前对系统硬件及软件环境进行必要的初始化并在最后使程序跳转到用户程序。
问题2. 启动代码主要干了什么
启动代码直接面对ARM 处理器内核及硬件控制器进行编程,所执行的操作与具体的目标系统紧密相关。C语言程序的运行需要具备一定的条件,如分配好外部数据空间、设置初始堆栈指针、配置时钟、设置中断向量入口、设置初始程序计数器(指向main())等。对于 Cortex-M系列的芯片而言,启动代码大同小异,故我挑选其中一个进行分析。ARM Cortex-M系列MCU的启动代码的主要做3件事:
- 初始化并正确放置异常/中断向量表;
- 分散加载;
- 初始化C语言运行环境(初始化堆栈以及C Library、浮点等)
1.2 启动代码详解
汇编基础1:
1. 伪指令: EQU
语法格式:名称 EQU 表达式{,类型}
EQU 伪指令用于为程序中的常量、标号等定义一个等效的字符名称,类似于 C 语言的#define,所以这下能理解了吧。
2. 伪指令: AREA
语法格式: AREA 段名{, 属性 1}{, 属性 2}……
AREA 命令指示汇编程序汇编一个新的代码段或数据段。理解:段是独立的、指定的、不可见的代码或数据块,它们由链接程序处理。
段名: 可以为段选择任何段名。但是,以一个数字开始的名称必须包含在竖杠号内,否则会产生一个缺失段名错误。例如, |1_DataArea|。
有些名称是习惯性的名称。例如: |.text|用于表示由 C 编译程序产生的代码段,或用于以某种方式与 C 库关联的代码段。
属性字段表示该代码段(或数据段)的相关属性,多个属性用逗号分隔。 常用的属性如下:
——CODE 属性:用于定义代码段,默认为 READONLY。
——DATA 属性:用于定义数据段,默认为 READWRITE。
——READONLY 属性:指定本段为只读,代码段默认为 READONLY。
——READWRITE 属性:指定本段为可读可写,数据段的默认属性为 READWRITE。
——ALIGN 属性:使用方式为 ALIGN 表达式。在默认时, ELF(可执行连接文件)的代码段和数据段是按字对齐的,表达式的取值范围为 0~31,相应的对齐方式为 2 表达式次方。
如:ALIGN=3表示8字节对齐。
——NOINIT 属性: 表示数据段是未初始化的或初始化为零。
一个汇编语言程序至少要包含一个段,当程序太长时,也可以将程序分为多个代码段和数据段。
3. 伪指令: SPACE 用于分配一片连续的存储单元
第一部分 定义栈段,不初始化
Stack_Size EQU 0x00000400 AREA STACK, NOINIT, READWRITE, ALIGN=
Stack_Mem SPACE Stack_Size
__initial_sp
上面的程序这样理解,定义了一个栈,栈名为STACK (AREA STACK),大小为Stack_Size(EQU理解为#define),只分配空间不做初始化或者初始化为 0;NOINIT,可读可写READWRITE;按 8 字节对齐: ALIGN=3;栈顶地址: __initial_sp ,SPACE表示分配一块连续的区域。
第二部分 定义堆段,不初始化
Heap_Size EQU 0x00000200 AREA HEAP, NOINIT, READWRITE, ALIGN=
__heap_base
Heap_Mem SPACE Heap_Size
__heap_limit
堆名: HEAP
大小: Heap_Size
只分配空间不做初始化或者初始化为 0: NOINIT
可读可写: READWRITE:
按 8 字节对齐: ALIGN=3
堆起始地址: __heap_base
堆终止地址: __heap_limit
PRESERVE8 ;指示编译器 字节对齐(keil 编译器时需要加上)
THUMB ;指示编译器为 THUMB 指令
汇编基础2:
4. 伪指令: EXPORT
语法格式: EXPORT 标号{[WEAK]}
EXPORT 伪指令用于在程序中声明一个全局的标号,该标号可在其他的文件中引用。
EXPORT 可用 GLOBAL 代替。标号在程序中区分大小写, [WEAK]选项声明其他的同名标号优先于该标号被引用。
5. 伪指令: DCD
语法格式: DCD 表达式
DCD(或 DCDU) 伪指令用于分配一个或多个连续的字(32bit)存储单元并用伪指令中指定的表达式初始化。其中,表达式可以为程序标号或数字表达式。用 DCD 分配的字存储单元是字对齐的。(一片是指多少?我并没有查到相关资料,但是我看了公司大神们写的启动文件,备注的地址只占了4个字节,所以我理解成分配一个字的存储单元)
第三部分 定义复位段(中断向量表),并初始化
AREA RESET, DATA, READONLY
EXPORT __Vectors
EXPORT __Vectors_End
EXPORT __Vectors_Size __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 ; Reserved
DCD ; Reserved
DCD ; Reserved
DCD ; Reserved
DCD SVC_Handler ; SVCall Handler
DCD DebugMon_Handler ; Debug Monitor Handler
DCD ; 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 detect
DCD TAMPER_IRQHandler ; Tamper
DCD RTC_IRQHandler ; RTC
DCD FLASH_IRQHandler ; Flash
DCD RCC_IRQHandler ; RCC
DCD EXTI0_IRQHandler ; EXTI Line
DCD EXTI1_IRQHandler ; EXTI Line
DCD EXTI2_IRQHandler ; EXTI Line
DCD EXTI3_IRQHandler ; EXTI Line
DCD EXTI4_IRQHandler ; EXTI Line
DCD DMA1_Channel1_IRQHandler ; DMA1 Channel
DCD DMA1_Channel2_IRQHandler ; DMA1 Channel
DCD DMA1_Channel3_IRQHandler ; DMA1 Channel
DCD DMA1_Channel4_IRQHandler ; DMA1 Channel
DCD DMA1_Channel5_IRQHandler ; DMA1 Channel
DCD DMA1_Channel6_IRQHandler ; DMA1 Channel
DCD DMA1_Channel7_IRQHandler ; DMA1 Channel
DCD ADC1_2_IRQHandler ; ADC1 & ADC2
DCD USB_HP_CAN1_TX_IRQHandler ; USB High Priority or CAN1 TX
DCD USB_LP_CAN1_RX0_IRQHandler ; USB Low Priority or CAN1 RX0
DCD CAN1_RX1_IRQHandler ; CAN1 RX1
DCD CAN1_SCE_IRQHandler ; CAN1 SCE
DCD EXTI9_5_IRQHandler ; EXTI Line ..
DCD TIM1_BRK_IRQHandler ; TIM1 Break
DCD TIM1_UP_IRQHandler ; TIM1 Update
DCD TIM1_TRG_COM_IRQHandler ; TIM1 Trigger and Commutation
DCD TIM1_CC_IRQHandler ; TIM1 Capture Compare
DCD TIM2_IRQHandler ; TIM2
DCD TIM3_IRQHandler ; TIM3
DCD TIM4_IRQHandler ; TIM4
DCD I2C1_EV_IRQHandler ; I2C1 Event
DCD I2C1_ER_IRQHandler ; I2C1 Error
DCD I2C2_EV_IRQHandler ; I2C2 Event
DCD I2C2_ER_IRQHandler ; I2C2 Error
DCD SPI1_IRQHandler ; SPI1
DCD SPI2_IRQHandler ; SPI2
DCD USART1_IRQHandler ; USART1
DCD USART2_IRQHandler ; USART2
DCD USART3_IRQHandler ; USART3
DCD EXTI15_10_IRQHandler ; EXTI Line ..
DCD RTCAlarm_IRQHandler ; RTC Alarm through EXTI Line
DCD USBWakeUp_IRQHandler ; USB Wakeup from suspend
DCD TIM8_BRK_IRQHandler ; TIM8 Break
DCD TIM8_UP_IRQHandler ; TIM8 Update
DCD TIM8_TRG_COM_IRQHandler ; TIM8 Trigger and Commutation
DCD TIM8_CC_IRQHandler ; TIM8 Capture Compare
DCD ADC3_IRQHandler ; ADC3
DCD FSMC_IRQHandler ; FSMC
DCD SDIO_IRQHandler ; SDIO
DCD TIM5_IRQHandler ; TIM5
DCD SPI3_IRQHandler ; SPI3
DCD UART4_IRQHandler ; UART4
DCD UART5_IRQHandler ; UART5
DCD TIM6_IRQHandler ; TIM6
DCD TIM7_IRQHandler ; TIM7
DCD DMA2_Channel1_IRQHandler ; DMA2 Channel1
DCD DMA2_Channel2_IRQHandler ; DMA2 Channel2
DCD DMA2_Channel3_IRQHandler ; DMA2 Channel3
DCD DMA2_Channel4_5_IRQHandler ; DMA2 Channel4 & Channel5
__Vectors_End __Vectors_Size EQU __Vectors_End - __Vectors
段名: RESET(根据前面的套路,发现AREA的第一个属性表示段名)
大小: __Vectors_Size(大小肯定要表示出来,其实堆栈是直接给出的,RESET段是先分配,后计算得到的)
数据段: DATA
只读: READONLY
按字节对齐: 默认 ALIGN
向量表起始地址: __Vectors(标号)
向量表终止地址: __Vectors_End
注意:SPACE 和 DCD有什么区别?
1. SPACE和DCD的功能类似,SPACE申请一片内存空间,DCD申请一个或多个字(32bit)的内存空间。
2. SPACE和DCD的区别在于,SPACE申请空间但不赋初值,DCD申请一个字的空间,并赋初值。
参考资料:https://blog.csdn.net/inurlcn/article/details/20691233#reply
汇编基础3:
6. 过程定义伪指令: PROC、 ENDP
语法格式: <过程名> PROC [类型]
……
RET
<过程名> ENDP
过程就是子程序,即定义一个子程序。一个过程可以被其它程序所调用(用 CALL 指令),过程的最后一条指令一般是返回指令(RET)。
7. 伪指令: IMPORT
语法格式: IMPORT 标号 {[WEAK]}
IMPORT 伪指令用于通知编译器要使用的标号在其他源文件中定义。
[WEAK]选项表示弱定义,如果编译器发现在别处定义了同名的函数,则在链接时用别处的地址进行链接,如果其它地方没有定义,编译器也不报错,以此处地址进行链接。
例如:对NMI_Handler的定义有两处,如下图,首先使用的是C语言的定义的,而不是汇编定义的。
1,汇编定义,后面加【weak】
2, C语言定义 在stm32f10x_it.c中
8. 伪指令: LDR
语法格式: LDR{执行条件,如 EQ、 NE 等} register,=expr/label_expr
大范围的地址读取伪指令 LDR 用于加载 32 位的立即数或一个地址值到指定寄存器,在汇编编译源程序时, LDR 伪指令被编译器替换成一条合适的指令。
9. Thumb 跳转指令: B、 BL、 BX
语法格式: B{执行条件,如 EQ、 NE 等} label
带链接 BL{执行条件,如 EQ、 NE 等} label
带状态切换 BX{执行条件,如 EQ、 NE 等} label
AREA |.text|, CODE, READONLY ; Reset handler
Reset_Handler PROC
EXPORT Reset_Handler [WEAK]
IMPORT __main
IMPORT SystemInit
LDR R0, =SystemInit
BLX R0
LDR R0, =__main
BX R0
ENDP
段名: .text
代码段: CODE
只读: READONLY
按字节对齐: 默认 ALIGN
代码段起始地址: Reset_Handler
更详细的来说一下这段代码,
这部分可以称作Reset_Handler实体,是芯片上电经过厂商BOOTROM后,用户最开始可控的地方。
- 第一行,申请一个名为.text的代码段,该代码段的属性是只读的,其他没写,认为认为是默认的;
- 第三行,注释
- 第四行,Reset_Handler是标号,定义同一个名为Reset_Handler的子程序(代码段)
- 第五行,声明一下Reset_Handler程序可以在外部使用,[WEAK]表示没有找到其他地方的定义时,然后连接器使用此处定义的Reset_Handler程序
- 第六行和第七行,在Reset_Handler函数中导入SystemInit 和__main ,这两个标号在其他文件,在链接的时候需要到其他文件去寻找
- 第八行,把SystemInit 的地址加载到寄存器R0
- 第九行,程序跳转到R0 中的地址执行程序,如果在SystemInit中配置了时钟,之后系统的时钟就被设置成我们配置的了。
- 第十行把_main 的地址加载到寄存器R0。
第十一行程序跳转到R0 中的地址执行程序,执行完毕之后就去到我们熟知的C 世界。
- 第十二行表示子程序的结束。
因为默认的标准的启动代码主要工作是在Reset_Handler里面完成的,调用函数一般也会再这里。我们可以发现,在启动代码的汇编语言里调用C语言函数都可以使用以上两步:
- 导入函数标号
- 调用这个函数
例如:
IMPORT SystemInit ;导入函数标号
LDR R0, =SystemInit ;2行和3行合起来,是调用函数的功能
BLX R0
当然这里有几点注意事项,这里不是所有函数都可以在汇编语言中调用的,因为此时__main还没有运行,C语言运行环境还没有被完整搭建起来,堆栈也没有初始化完成,所以要注意:
(1)调用的C函数参数不能超过4个,不用可以,但用的话不能超过4个参数,原因是在Cortex-M体系MCU中,函数的1-4个形参会压进R0-R3这4个通用寄存器(Cortex-M系列MCU,M0也好、M3也好、M4也好都只有16个通用寄存器,内部寄存器结构去参照ARM官方的白皮书)如果有第五个参数,这个参数会被压栈,但因为此时__main还没有运行,堆栈没有被初始化所以此时如果函数有超过4个以上的参数,会导致程序跑飞;
(2)不要把需要调用的函数写到__main之后,因为没有意义,程序不会跑到那里;
参考网址:https://blog.csdn.net/weixin_39118482/article/details/79632734
汇编基础4:
10. 内置变量: {PC} 或“.” 当前指令地址
11. 汇编语句格式规范:
ARM 汇编中,所有标号必须在一行的顶格书写,其后面不要添加“:”,但所有指令均不能顶格书写。
ARM 汇编器对标识符大小写敏感,书写标号及指令时字母大小写要一致, 在 ARM 汇编程序中,一个 ARM 指令、伪指令、寄存器名可以全部为大写字母,也可以全部为小写字母,但不要大小写混合使用。
异常处理函数 1
NMI_Handler PROC ;定义一个名为NMI_Handler的子程序
EXPORT NMI_Handler [WEAK] ;外部声明
B . ;跳转到子程序的地址(这个函数里面通常写的是死循环,所以当出现异常时,就会卡死)
ENDP ;结束
HardFault_Handler\
PROC
EXPORT HardFault_Handler [WEAK]
B .
ENDP
MemManage_Handler\
PROC
EXPORT MemManage_Handler [WEAK]
B .
ENDP
BusFault_Handler\
PROC
EXPORT BusFault_Handler [WEAK]
B .
ENDP
UsageFault_Handler\
PROC
EXPORT UsageFault_Handler [WEAK]
B .
ENDP
SVC_Handler PROC
EXPORT SVC_Handler [WEAK]
B .
ENDP
DebugMon_Handler\
PROC
EXPORT DebugMon_Handler [WEAK]
B .
ENDP
PendSV_Handler PROC
EXPORT PendSV_Handler [WEAK]
B .
ENDP
SysTick_Handler PROC
EXPORT SysTick_Handler [WEAK]
B .
ENDP
异常处理函数2
这个默认的异常处理函数处理所有外部中断 。
Default_Handler PROC EXPORT WWDG_IRQHandler [WEAK]
EXPORT PVD_IRQHandler [WEAK]
EXPORT TAMPER_IRQHandler [WEAK]
EXPORT RTC_IRQHandler [WEAK]
EXPORT FLASH_IRQHandler [WEAK]
EXPORT RCC_IRQHandler [WEAK]
EXPORT EXTI0_IRQHandler [WEAK]
EXPORT EXTI1_IRQHandler [WEAK]
EXPORT EXTI2_IRQHandler [WEAK]
EXPORT EXTI3_IRQHandler [WEAK]
EXPORT EXTI4_IRQHandler [WEAK]
EXPORT DMA1_Channel1_IRQHandler [WEAK]
EXPORT DMA1_Channel2_IRQHandler [WEAK]
EXPORT DMA1_Channel3_IRQHandler [WEAK]
EXPORT DMA1_Channel4_IRQHandler [WEAK]
EXPORT DMA1_Channel5_IRQHandler [WEAK]
EXPORT DMA1_Channel6_IRQHandler [WEAK]
EXPORT DMA1_Channel7_IRQHandler [WEAK]
EXPORT ADC1_2_IRQHandler [WEAK]
EXPORT USB_HP_CAN1_TX_IRQHandler [WEAK]
EXPORT USB_LP_CAN1_RX0_IRQHandler [WEAK]
EXPORT CAN1_RX1_IRQHandler [WEAK]
EXPORT CAN1_SCE_IRQHandler [WEAK]
EXPORT EXTI9_5_IRQHandler [WEAK]
EXPORT TIM1_BRK_IRQHandler [WEAK]
EXPORT TIM1_UP_IRQHandler [WEAK]
EXPORT TIM1_TRG_COM_IRQHandler [WEAK]
EXPORT TIM1_CC_IRQHandler [WEAK]
EXPORT TIM2_IRQHandler [WEAK]
EXPORT TIM3_IRQHandler [WEAK]
EXPORT TIM4_IRQHandler [WEAK]
EXPORT I2C1_EV_IRQHandler [WEAK]
EXPORT I2C1_ER_IRQHandler [WEAK]
EXPORT I2C2_EV_IRQHandler [WEAK]
EXPORT I2C2_ER_IRQHandler [WEAK]
EXPORT SPI1_IRQHandler [WEAK]
EXPORT SPI2_IRQHandler [WEAK]
EXPORT USART1_IRQHandler [WEAK]
EXPORT USART2_IRQHandler [WEAK]
EXPORT USART3_IRQHandler [WEAK]
EXPORT EXTI15_10_IRQHandler [WEAK]
EXPORT RTCAlarm_IRQHandler [WEAK]
EXPORT USBWakeUp_IRQHandler [WEAK]
EXPORT TIM8_BRK_IRQHandler [WEAK]
EXPORT TIM8_UP_IRQHandler [WEAK]
EXPORT TIM8_TRG_COM_IRQHandler [WEAK]
EXPORT TIM8_CC_IRQHandler [WEAK]
EXPORT ADC3_IRQHandler [WEAK]
EXPORT FSMC_IRQHandler [WEAK]
EXPORT SDIO_IRQHandler [WEAK]
EXPORT TIM5_IRQHandler [WEAK]
EXPORT SPI3_IRQHandler [WEAK]
EXPORT UART4_IRQHandler [WEAK]
EXPORT UART5_IRQHandler [WEAK]
EXPORT TIM6_IRQHandler [WEAK]
EXPORT TIM7_IRQHandler [WEAK]
EXPORT DMA2_Channel1_IRQHandler [WEAK]
EXPORT DMA2_Channel2_IRQHandler [WEAK]
EXPORT DMA2_Channel3_IRQHandler [WEAK]
EXPORT DMA2_Channel4_5_IRQHandler [WEAK]
;下面的全部异常处理函数标号都对应同一个地址, 这个地址也是 Default_Handler 的地址
WWDG_IRQHandler
PVD_IRQHandler
TAMPER_IRQHandler
RTC_IRQHandler
FLASH_IRQHandler
RCC_IRQHandler
EXTI0_IRQHandler
EXTI1_IRQHandler
EXTI2_IRQHandler
EXTI3_IRQHandler
EXTI4_IRQHandler
DMA1_Channel1_IRQHandler
DMA1_Channel2_IRQHandler
DMA1_Channel3_IRQHandler
DMA1_Channel4_IRQHandler
DMA1_Channel5_IRQHandler
DMA1_Channel6_IRQHandler
DMA1_Channel7_IRQHandler
ADC1_2_IRQHandler
USB_HP_CAN1_TX_IRQHandler
USB_LP_CAN1_RX0_IRQHandler
CAN1_RX1_IRQHandler
CAN1_SCE_IRQHandler
EXTI9_5_IRQHandler
TIM1_BRK_IRQHandler
TIM1_UP_IRQHandler
TIM1_TRG_COM_IRQHandler
TIM1_CC_IRQHandler
TIM2_IRQHandler
TIM3_IRQHandler
TIM4_IRQHandler
I2C1_EV_IRQHandler
I2C1_ER_IRQHandler
I2C2_EV_IRQHandler
I2C2_ER_IRQHandler
SPI1_IRQHandler
SPI2_IRQHandler
USART1_IRQHandler
USART2_IRQHandler
USART3_IRQHandler
EXTI15_10_IRQHandler
RTCAlarm_IRQHandler
USBWakeUp_IRQHandler
TIM8_BRK_IRQHandler
TIM8_UP_IRQHandler
TIM8_TRG_COM_IRQHandler
TIM8_CC_IRQHandler
ADC3_IRQHandler
FSMC_IRQHandler
SDIO_IRQHandler
TIM5_IRQHandler
SPI3_IRQHandler
UART4_IRQHandler
UART5_IRQHandler
TIM6_IRQHandler
TIM7_IRQHandler
DMA2_Channel1_IRQHandler
DMA2_Channel2_IRQHandler
DMA2_Channel3_IRQHandler
DMA2_Channel4_5_IRQHandler
B . ENDP
先定义,当外部中断触发时,B . 表示跳进去执行。
汇编基础5:
由于前面只是定义了堆栈段并没有初始化,这里对堆栈段进行初始化。 就像定义了: int a; 初始化 a = 1;也可以像代码段一样定义的同时就初始化: int b = 2;
在_main中,会调用一下的程序,进行堆栈的初始化,进而为进入到C语言中的main函数做好准备。
下面代码中有个__MICROLIB,对应后面 MDK 截图的 Use MicroLIB,如果选了勾选了 Use MicroLIB, IF 就为真,否则为假
初始化堆栈段
IF :DEF:__MICROLIB EXPORT __initial_sp
EXPORT __heap_base
EXPORT __heap_limit ELSE IMPORT __use_two_region_memory
EXPORT __user_initial_stackheap __user_initial_stackheap LDR R0, = Heap_Mem
LDR R1, =(Stack_Mem + Stack_Size)
LDR R2, = (Heap_Mem + Heap_Size)
LDR R3, = Stack_Mem
BX LR ALIGN ENDIF END
microlib是缺省C库的备选库。它旨在与需要装入到极少量内存中的深层嵌入式应用程序配合使用。这些应用程序不在操作系统中运行。microlib进行了高度优化以使代码变得很小。它的功能比缺省C库少,并且根本不具备某些ISOC特性。某些库函数的运行速度也比较慢,例如,memcpy()。不管使用与否,都可以,但是启动时稍微有点区别。
- 启动流程1(使用标准库,不使用Microlib)如下图:
- 启动流程2(使用Microlib)如下图:
假设STM32被设置为从内部FLASH启动(这也是最常见的一种情况),中断向量表起始地位为0x8000000,则栈顶地址存放于0x8000000处(大部分是这个地址),而复位中断服务入口地址存放于0x8000004处(复位地址在栈顶地址4字节后)。当STM32遇到复位信号后,则从0x80000004处取出复位中断服务入口地址,继而执行复位中断服务程序,然后跳转__main函数,最后进入mian函数,来到C的世界。
解释一下一个小细节,绝大部分ARM-M协议的芯片,复位之后先进入厂商boot,此时所有的用户均无法接入处理器;厂商boot主要负责芯片最初级的初始化,加密及
对MCU进行一些差异性设置等,BOOT完成后,会把主动权交给用户,也就是启动代码;启动代码(执行汇编语言不需要此启动代码),在启动文件中,会设置MSP(主堆栈指针)和PC(程序计数器)的值,MSP的地址默认是0x00000000,PC的地址默认是0x00000004,这两个地址可以通过CORTEX-M中的VTOR寄存器来进行重映射,修改。
后面,手把手写一下启动文件,并进行验证一下;写完启动文件之后写分散加载文件。
ARM Cortex-M底层技术(2)—启动代码详解的更多相关文章
- cortex m0启动代码详解
转自:http://www.cnblogs.com/mddblog/p/4920063.html 阅读目录 概述 1.堆栈空间定义 2.存放中断向量表 3. 复位中断函数(Reset_Handler) ...
- Qt开发技术:QCharts(三)QCharts样条曲线图介绍、Demo以及代码详解
若该文为原创文章,未经允许不得转载原博主博客地址:https://blog.csdn.net/qq21497936原博主博客导航:https://blog.csdn.net/qq21497936/ar ...
- 第14章 启动文件详解—零死角玩转STM32-F429系列
第14章 启动文件详解 全套200集视频教程和1000页PDF教程请到秉火论坛下载:www.firebbs.cn 野火视频教程优酷观看网址:http://i.youku.com/firege ...
- 【STM32H7教程】第13章 STM32H7启动过程详解
完整教程下载地址:http://forum.armfly.com/forum.php?mod=viewthread&tid=86980 第13章 STM32H7启动过程详解 本章教 ...
- php缓存技术——memcache常用函数详解
php缓存技术——memcache常用函数详解 2016-04-07 aileen PHP编程 Memcache函数库是在PECL(PHP Extension Community Library)中, ...
- (转)Linux 开机引导和启动过程详解
Linux 开机引导和启动过程详解 编译自:https://opensource.com/article/17/2/linux-boot-and-startup作者: David Both 原创:LC ...
- Android之Activity启动流程详解(基于api28)
前言 Activity作为Android四大组件之一,他的启动绝对没有那么简单.这里涉及到了系统服务进程,启动过程细节很多,这里我只展示主体流程.activity的启动流程随着版本的更替,代码细节一直 ...
- Linux启动过程详解(inittab、rc.sysinit、rcX.d、rc.local)
启动第一步--加载BIOS 当你打开计算机电源,计算机会首先加载BIOS信息,BIOS信息是如此的重要,以至于计算机必须在最开始就找到它.这是因为BIOS中包含了CPU的相关信息.设备启动顺序信息.硬 ...
- Linux启动过程详解
Linux启动过程详解 附上两张图,加深记忆 图1: 图2: 第一张图比较简洁明了,下面对第一张图的步骤进行详解: 加载BIOS 当你打开计算机电源,计算机会首先加载BIOS信息,BIOS信息是如此的 ...
随机推荐
- sh_01_判断年龄
sh_01_判断年龄 # 1. 定义一个整数变量记录年龄 age = 15 # 2. 判断是否满了18岁 if age >= 18: # 3. 如果满了18岁,可以进网吧嗨皮 print(&qu ...
- 一本通&&洛谷——P1120 小木棍 [数据加强版]——题解
题目传送 一道特别毒瘤能提醒人不要忘记剪枝的题. 首先不要忘了管理员的话.忘把长度大于50的木棍过滤掉真的坑了不少人(包括我). 显然是一道DFS题 .考虑剪枝. 找找搜索要面临的维度.状态:原始木棍 ...
- [CSP-S模拟测试]:方程的解(小学奥数)
题目描述 给出一个二元一次方程$ax+by=c$,其中$x$.$y$是未知数,求它的正整数解的数量. 输入格式 第一行一个整数$T$,表示有$T$组数据.接下来$T$行,每行$3$个整数$a$.$b$ ...
- Gradle 学习笔记
配置 Gradle 的环境变量 export GRADLE_HOME=/opt/software/java/gradle-3.1 export PATH=\(PATH:\)GRADLE_HOME/bi ...
- mktime夏令时处理
https://www.cnblogs.com/dongzhiquan/archive/2011/11/05/2237075.html 我们的最终目的是把字符串格式的时间转换为内部使用的“日历时间”, ...
- 用vuex实现购物车功能
效果图 展示目录结构 product组件(纯静态代码) cart组件(纯静态代码) info组件(纯静态代码) 完成以上的三个组件,现在要开始调用这些组件,在App.vue中调用 如果你的姿势正确的话 ...
- Delphi XE2 之 FireMonkey 入门(17) - 特效
刚打开 XE2 时, 就从 Tool Palette 窗口的 Effects 组中发现洋洋洒洒的六十多个特效... 每个特效分别对应一个类, 分别来自 FMX.Effects 和 FMX.Filter ...
- Jmeter之用户参数和用户定义的变量
在调试脚本的时候,可以使用前置处理器中的用户参数组件进行数据的提供,在该数据中可以使用固定值也可以使用变量值. 如果是固定不变的一些配置项,不需要多个值的时候,也可以使用用户已定义的变量组件. 一.界 ...
- kNN算法实例(约会对象喜好预测和手写识别)
import numpy as np import operator import random import os def file2matrix(filePath):#从文本中提取特征矩阵和标签 ...
- Vue入门---安装及常用指令介绍
1.安装 BootCDN----官网https://www.bootcdn.cn/ <script src="https://cdn.bootcss.com/vue/2.6.10/vu ...