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 用于分配一片连续的存储单元
第一部分 定义栈段,不初始化
1 Stack_Size EQU 0x00000400 2 3 AREA STACK, NOINIT, READWRITE, ALIGN=3 4 Stack_Mem SPACE Stack_Size 5 __initial_sp
上面的程序这样理解,定义了一个栈,栈名为STACK (AREA STACK),大小为Stack_Size(EQU理解为#define),只分配空间不做初始化或者初始化为 0;NOINIT,可读可写READWRITE;按 8 字节对齐: ALIGN=3;栈顶地址: __initial_sp ,SPACE表示分配一块连续的区域。
第二部分 定义堆段,不初始化
1 Heap_Size EQU 0x00000200 2 3 AREA HEAP, NOINIT, READWRITE, ALIGN=3 4 __heap_base 5 Heap_Mem SPACE Heap_Size 6 __heap_limit
堆名: HEAP
大小: Heap_Size
只分配空间不做初始化或者初始化为 0: NOINIT
可读可写: READWRITE:
按 8 字节对齐: ALIGN=3
堆起始地址: __heap_base
堆终止地址: __heap_limit
1 PRESERVE8 ;指示编译器 8 字节对齐(keil 编译器时需要加上) 2 THUMB ;指示编译器为 THUMB 指令
汇编基础2:
4. 伪指令: EXPORT
语法格式: EXPORT 标号{[WEAK]}
EXPORT 伪指令用于在程序中声明一个全局的标号,该标号可在其他的文件中引用。
EXPORT 可用 GLOBAL 代替。标号在程序中区分大小写, [WEAK]选项声明其他的同名标号优先于该标号被引用。
5. 伪指令: DCD
语法格式: DCD 表达式
DCD(或 DCDU) 伪指令用于分配一个或多个连续的字(32bit)存储单元并用伪指令中指定的表达式初始化。其中,表达式可以为程序标号或数字表达式。用 DCD 分配的字存储单元是字对齐的。(一片是指多少?我并没有查到相关资料,但是我看了公司大神们写的启动文件,备注的地址只占了4个字节,所以我理解成分配一个字的存储单元)
第三部分 定义复位段(中断向量表),并初始化
1 AREA RESET, DATA, READONLY 2 EXPORT __Vectors 3 EXPORT __Vectors_End 4 EXPORT __Vectors_Size 5 6 __Vectors DCD __initial_sp ; Top of Stack 7 DCD Reset_Handler ; Reset Handler 8 DCD NMI_Handler ; NMI Handler 9 DCD HardFault_Handler ; Hard Fault Handler 10 DCD MemManage_Handler ; MPU Fault Handler 11 DCD BusFault_Handler ; Bus Fault Handler 12 DCD UsageFault_Handler ; Usage Fault Handler 13 DCD 0 ; Reserved 14 DCD 0 ; Reserved 15 DCD 0 ; Reserved 16 DCD 0 ; Reserved 17 DCD SVC_Handler ; SVCall Handler 18 DCD DebugMon_Handler ; Debug Monitor Handler 19 DCD 0 ; Reserved 20 DCD PendSV_Handler ; PendSV Handler 21 DCD SysTick_Handler ; SysTick Handler 22 23 ; External Interrupts 24 DCD WWDG_IRQHandler ; Window Watchdog 25 DCD PVD_IRQHandler ; PVD through EXTI Line detect 26 DCD TAMPER_IRQHandler ; Tamper 27 DCD RTC_IRQHandler ; RTC 28 DCD FLASH_IRQHandler ; Flash 29 DCD RCC_IRQHandler ; RCC 30 DCD EXTI0_IRQHandler ; EXTI Line 0 31 DCD EXTI1_IRQHandler ; EXTI Line 1 32 DCD EXTI2_IRQHandler ; EXTI Line 2 33 DCD EXTI3_IRQHandler ; EXTI Line 3 34 DCD EXTI4_IRQHandler ; EXTI Line 4 35 DCD DMA1_Channel1_IRQHandler ; DMA1 Channel 1 36 DCD DMA1_Channel2_IRQHandler ; DMA1 Channel 2 37 DCD DMA1_Channel3_IRQHandler ; DMA1 Channel 3 38 DCD DMA1_Channel4_IRQHandler ; DMA1 Channel 4 39 DCD DMA1_Channel5_IRQHandler ; DMA1 Channel 5 40 DCD DMA1_Channel6_IRQHandler ; DMA1 Channel 6 41 DCD DMA1_Channel7_IRQHandler ; DMA1 Channel 7 42 DCD ADC1_2_IRQHandler ; ADC1 & ADC2 43 DCD USB_HP_CAN1_TX_IRQHandler ; USB High Priority or CAN1 TX 44 DCD USB_LP_CAN1_RX0_IRQHandler ; USB Low Priority or CAN1 RX0 45 DCD CAN1_RX1_IRQHandler ; CAN1 RX1 46 DCD CAN1_SCE_IRQHandler ; CAN1 SCE 47 DCD EXTI9_5_IRQHandler ; EXTI Line 9..5 48 DCD TIM1_BRK_IRQHandler ; TIM1 Break 49 DCD TIM1_UP_IRQHandler ; TIM1 Update 50 DCD TIM1_TRG_COM_IRQHandler ; TIM1 Trigger and Commutation 51 DCD TIM1_CC_IRQHandler ; TIM1 Capture Compare 52 DCD TIM2_IRQHandler ; TIM2 53 DCD TIM3_IRQHandler ; TIM3 54 DCD TIM4_IRQHandler ; TIM4 55 DCD I2C1_EV_IRQHandler ; I2C1 Event 56 DCD I2C1_ER_IRQHandler ; I2C1 Error 57 DCD I2C2_EV_IRQHandler ; I2C2 Event 58 DCD I2C2_ER_IRQHandler ; I2C2 Error 59 DCD SPI1_IRQHandler ; SPI1 60 DCD SPI2_IRQHandler ; SPI2 61 DCD USART1_IRQHandler ; USART1 62 DCD USART2_IRQHandler ; USART2 63 DCD USART3_IRQHandler ; USART3 64 DCD EXTI15_10_IRQHandler ; EXTI Line 15..10 65 DCD RTCAlarm_IRQHandler ; RTC Alarm through EXTI Line 66 DCD USBWakeUp_IRQHandler ; USB Wakeup from suspend 67 DCD TIM8_BRK_IRQHandler ; TIM8 Break 68 DCD TIM8_UP_IRQHandler ; TIM8 Update 69 DCD TIM8_TRG_COM_IRQHandler ; TIM8 Trigger and Commutation 70 DCD TIM8_CC_IRQHandler ; TIM8 Capture Compare 71 DCD ADC3_IRQHandler ; ADC3 72 DCD FSMC_IRQHandler ; FSMC 73 DCD SDIO_IRQHandler ; SDIO 74 DCD TIM5_IRQHandler ; TIM5 75 DCD SPI3_IRQHandler ; SPI3 76 DCD UART4_IRQHandler ; UART4 77 DCD UART5_IRQHandler ; UART5 78 DCD TIM6_IRQHandler ; TIM6 79 DCD TIM7_IRQHandler ; TIM7 80 DCD DMA2_Channel1_IRQHandler ; DMA2 Channel1 81 DCD DMA2_Channel2_IRQHandler ; DMA2 Channel2 82 DCD DMA2_Channel3_IRQHandler ; DMA2 Channel3 83 DCD DMA2_Channel4_5_IRQHandler ; DMA2 Channel4 & Channel5 84 __Vectors_End 85 86 __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
1 AREA |.text|, CODE, READONLY 2 3 ; Reset handler 4 Reset_Handler PROC 5 EXPORT Reset_Handler [WEAK] 6 IMPORT __main 7 IMPORT SystemInit 8 LDR R0, =SystemInit 9 BLX R0 10 LDR R0, =__main 11 BX R0 12 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语言函数都可以使用以上两步:
- 导入函数标号
- 调用这个函数
例如:
1 IMPORT SystemInit ;导入函数标号 2 LDR R0, =SystemInit ;2行和3行合起来,是调用函数的功能 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
1 NMI_Handler PROC ;定义一个名为NMI_Handler的子程序 2 EXPORT NMI_Handler [WEAK] ;外部声明 3 B . ;跳转到子程序的地址(这个函数里面通常写的是死循环,所以当出现异常时,就会卡死) 4 ENDP ;结束 5 HardFault_Handler\ 6 PROC 7 EXPORT HardFault_Handler [WEAK] 8 B . 9 ENDP 10 MemManage_Handler\ 11 PROC 12 EXPORT MemManage_Handler [WEAK] 13 B . 14 ENDP 15 BusFault_Handler\ 16 PROC 17 EXPORT BusFault_Handler [WEAK] 18 B . 19 ENDP 20 UsageFault_Handler\ 21 PROC 22 EXPORT UsageFault_Handler [WEAK] 23 B . 24 ENDP 25 SVC_Handler PROC 26 EXPORT SVC_Handler [WEAK] 27 B . 28 ENDP 29 DebugMon_Handler\ 30 PROC 31 EXPORT DebugMon_Handler [WEAK] 32 B . 33 ENDP 34 PendSV_Handler PROC 35 EXPORT PendSV_Handler [WEAK] 36 B . 37 ENDP 38 SysTick_Handler PROC 39 EXPORT SysTick_Handler [WEAK] 40 B . 41 ENDP
异常处理函数2
这个默认的异常处理函数处理所有外部中断 。
1 Default_Handler PROC 2 3 EXPORT WWDG_IRQHandler [WEAK] 4 EXPORT PVD_IRQHandler [WEAK] 5 EXPORT TAMPER_IRQHandler [WEAK] 6 EXPORT RTC_IRQHandler [WEAK] 7 EXPORT FLASH_IRQHandler [WEAK] 8 EXPORT RCC_IRQHandler [WEAK] 9 EXPORT EXTI0_IRQHandler [WEAK] 10 EXPORT EXTI1_IRQHandler [WEAK] 11 EXPORT EXTI2_IRQHandler [WEAK] 12 EXPORT EXTI3_IRQHandler [WEAK] 13 EXPORT EXTI4_IRQHandler [WEAK] 14 EXPORT DMA1_Channel1_IRQHandler [WEAK] 15 EXPORT DMA1_Channel2_IRQHandler [WEAK] 16 EXPORT DMA1_Channel3_IRQHandler [WEAK] 17 EXPORT DMA1_Channel4_IRQHandler [WEAK] 18 EXPORT DMA1_Channel5_IRQHandler [WEAK] 19 EXPORT DMA1_Channel6_IRQHandler [WEAK] 20 EXPORT DMA1_Channel7_IRQHandler [WEAK] 21 EXPORT ADC1_2_IRQHandler [WEAK] 22 EXPORT USB_HP_CAN1_TX_IRQHandler [WEAK] 23 EXPORT USB_LP_CAN1_RX0_IRQHandler [WEAK] 24 EXPORT CAN1_RX1_IRQHandler [WEAK] 25 EXPORT CAN1_SCE_IRQHandler [WEAK] 26 EXPORT EXTI9_5_IRQHandler [WEAK] 27 EXPORT TIM1_BRK_IRQHandler [WEAK] 28 EXPORT TIM1_UP_IRQHandler [WEAK] 29 EXPORT TIM1_TRG_COM_IRQHandler [WEAK] 30 EXPORT TIM1_CC_IRQHandler [WEAK] 31 EXPORT TIM2_IRQHandler [WEAK] 32 EXPORT TIM3_IRQHandler [WEAK] 33 EXPORT TIM4_IRQHandler [WEAK] 34 EXPORT I2C1_EV_IRQHandler [WEAK] 35 EXPORT I2C1_ER_IRQHandler [WEAK] 36 EXPORT I2C2_EV_IRQHandler [WEAK] 37 EXPORT I2C2_ER_IRQHandler [WEAK] 38 EXPORT SPI1_IRQHandler [WEAK] 39 EXPORT SPI2_IRQHandler [WEAK] 40 EXPORT USART1_IRQHandler [WEAK] 41 EXPORT USART2_IRQHandler [WEAK] 42 EXPORT USART3_IRQHandler [WEAK] 43 EXPORT EXTI15_10_IRQHandler [WEAK] 44 EXPORT RTCAlarm_IRQHandler [WEAK] 45 EXPORT USBWakeUp_IRQHandler [WEAK] 46 EXPORT TIM8_BRK_IRQHandler [WEAK] 47 EXPORT TIM8_UP_IRQHandler [WEAK] 48 EXPORT TIM8_TRG_COM_IRQHandler [WEAK] 49 EXPORT TIM8_CC_IRQHandler [WEAK] 50 EXPORT ADC3_IRQHandler [WEAK] 51 EXPORT FSMC_IRQHandler [WEAK] 52 EXPORT SDIO_IRQHandler [WEAK] 53 EXPORT TIM5_IRQHandler [WEAK] 54 EXPORT SPI3_IRQHandler [WEAK] 55 EXPORT UART4_IRQHandler [WEAK] 56 EXPORT UART5_IRQHandler [WEAK] 57 EXPORT TIM6_IRQHandler [WEAK] 58 EXPORT TIM7_IRQHandler [WEAK] 59 EXPORT DMA2_Channel1_IRQHandler [WEAK] 60 EXPORT DMA2_Channel2_IRQHandler [WEAK] 61 EXPORT DMA2_Channel3_IRQHandler [WEAK] 62 EXPORT DMA2_Channel4_5_IRQHandler [WEAK] 63 ;下面的全部异常处理函数标号都对应同一个地址, 这个地址也是 Default_Handler 的地址
64 WWDG_IRQHandler 65 PVD_IRQHandler 66 TAMPER_IRQHandler 67 RTC_IRQHandler 68 FLASH_IRQHandler 69 RCC_IRQHandler 70 EXTI0_IRQHandler 71 EXTI1_IRQHandler 72 EXTI2_IRQHandler 73 EXTI3_IRQHandler 74 EXTI4_IRQHandler 75 DMA1_Channel1_IRQHandler 76 DMA1_Channel2_IRQHandler 77 DMA1_Channel3_IRQHandler 78 DMA1_Channel4_IRQHandler 79 DMA1_Channel5_IRQHandler 80 DMA1_Channel6_IRQHandler 81 DMA1_Channel7_IRQHandler 82 ADC1_2_IRQHandler 83 USB_HP_CAN1_TX_IRQHandler 84 USB_LP_CAN1_RX0_IRQHandler 85 CAN1_RX1_IRQHandler 86 CAN1_SCE_IRQHandler 87 EXTI9_5_IRQHandler 88 TIM1_BRK_IRQHandler 89 TIM1_UP_IRQHandler 90 TIM1_TRG_COM_IRQHandler 91 TIM1_CC_IRQHandler 92 TIM2_IRQHandler 93 TIM3_IRQHandler 94 TIM4_IRQHandler 95 I2C1_EV_IRQHandler 96 I2C1_ER_IRQHandler 97 I2C2_EV_IRQHandler 98 I2C2_ER_IRQHandler 99 SPI1_IRQHandler 100 SPI2_IRQHandler 101 USART1_IRQHandler 102 USART2_IRQHandler 103 USART3_IRQHandler 104 EXTI15_10_IRQHandler 105 RTCAlarm_IRQHandler 106 USBWakeUp_IRQHandler 107 TIM8_BRK_IRQHandler 108 TIM8_UP_IRQHandler 109 TIM8_TRG_COM_IRQHandler 110 TIM8_CC_IRQHandler 111 ADC3_IRQHandler 112 FSMC_IRQHandler 113 SDIO_IRQHandler 114 TIM5_IRQHandler 115 SPI3_IRQHandler 116 UART4_IRQHandler 117 UART5_IRQHandler 118 TIM6_IRQHandler 119 TIM7_IRQHandler 120 DMA2_Channel1_IRQHandler 121 DMA2_Channel2_IRQHandler 122 DMA2_Channel3_IRQHandler 123 DMA2_Channel4_5_IRQHandler 124 B . 125 126 ENDP
先定义,当外部中断触发时,B . 表示跳进去执行。
汇编基础5:
由于前面只是定义了堆栈段并没有初始化,这里对堆栈段进行初始化。 就像定义了: int a; 初始化 a = 1;也可以像代码段一样定义的同时就初始化: int b = 2;
在_main中,会调用一下的程序,进行堆栈的初始化,进而为进入到C语言中的main函数做好准备。
下面代码中有个__MICROLIB,对应后面 MDK 截图的 Use MicroLIB,如果选了勾选了 Use MicroLIB, IF 就为真,否则为假
初始化堆栈段
1 IF :DEF:__MICROLIB 2 3 EXPORT __initial_sp 4 EXPORT __heap_base 5 EXPORT __heap_limit 6 7 ELSE 8 9 IMPORT __use_two_region_memory 10 EXPORT __user_initial_stackheap 11 12 __user_initial_stackheap 13 14 LDR R0, = Heap_Mem 15 LDR R1, =(Stack_Mem + Stack_Size) 16 LDR R2, = (Heap_Mem + Heap_Size) 17 LDR R3, = Stack_Mem 18 BX LR 19 20 ALIGN 21 22 ENDIF 23 24 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寄存器来进行重映射,修改。
后面,手把手写一下启动文件,并进行验证一下;写完启动文件之后写分散加载文件。