本文主要阐述了stm32启动过程全面解析,包括启动过程的介绍、启动代码的陈列以及深入解析。
相对于arm上一代的主流arm7/arm9内核架构,新一代cortex内核架构的启动方式有了比较大的变化。arm7/arm9内核的控制器在复位后,cpu会从存储空间的绝对地址0x000000取出第一条指令执行复位中断服务程序的方式启动,即固定了复位后的起始地址为0x000000(pc = 0x000000)同时中断向量表的位置并不是固定的。而cortex-m3内核则正好相反,有3种情况:
1、 通过boot引脚设置可以将中断向量表定位于sram区,即起始地址为0x2000000,同时复位后pc指针位于0x2000000处;
2、 通过boot引脚设置可以将中断向量表定位于flash区,即起始地址为0x8000000,同时复位后pc指针位于0x8000000处;
3、 通过boot引脚设置可以将中断向量表定位于内置bootloader区,本文不对这种情况做论述;
cortex-m3内核规定,起始地址必须存放堆顶指针,而第二个地址则必须存放复位中断入口向量地址,这样在cortex-m3内核复位后,会自动从起始地址的下一个32位空间取出复位中断入口向量,跳转执行复位中断服务程序。对比arm7/arm9内核,cortex-m3内核则是固定了中断向量表的位置而起始地址是可变化的。
有了上述准备只是后,下面以stm32的2.02固件库提供的启动文件“stm32f10x_vector.s”为模板,对stm32的启动过程做一个简要而全面的解析。
程序清单一:
;文件“stm32f10x_vector.s”,其中注释为行号
data_in_extsram equ 0 ;1
stack_size equ 0x00000400 ;2
area stack, noinit, readwrite, align = 3 ;3
stack_mem space stack_size ;4
__initial_sp ;5
heap_size equ 0x00000400 ;6
area heap, noinit, readwrite, align = 3 ;7
__heap_base ;8
heap_mem space heap_size ;9
__heap_limit ;10
thumb ;11
preserve8 ;12
import nmiexception ;13
import hardfaultexception ;14
import memmanageexception ;15
import busfaultexception ;16
import usagefaultexception ;17
import svchandler ;18
import debugmonitor ;19
import pendsvc ;20
import systickhandler ;21
import wwdg_irqhandler ;22
import pvd_irqhandler ;23
import tamper_irqhandler ;24
import rtc_irqhandler ;25
import flash_irqhandler ;26
import rcc_irqhandler ;27
import exti0_irqhandler ;28
import exti1_irqhandler ;29
import exti2_irqhandler ;30
import exti3_irqhandler ;31
import exti4_irqhandler ;32
import dma1_channel1_irqhandler ;33
import dma1_channel2_irqhandler ;34
import dma1_channel3_irqhandler ;35
import dma1_channel4_irqhandler ;36
import dma1_channel5_irqhandler ;37
import dma1_channel6_irqhandler ;38
import dma1_channel7_irqhandler ;39
import adc1_2_irqhandler ;40
import usb_hp_can_tx_irqhandler ;41
import usb_lp_can_rx0_irqhandler ;42
import can_rx1_irqhandler ;43
import can_sce_irqhandler ;44
import exti9_5_irqhandler ;45
import tim1_brk_irqhandler ;46
import tim1_up_irqhandler ;47
import tim1_trg_com_irqhandler ;48
import tim1_cc_irqhandler ;49
import tim2_irqhandler ;50
import tim3_irqhandler ;51
import tim4_irqhandler ;52
import i2c1_ev_irqhandler ;53
import i2c1_er_irqhandler ;54
import i2c2_ev_irqhandler ;55
import i2c2_er_irqhandler ;56
import spi1_irqhandler ;57
import spi2_irqhandler ;58
import usart1_irqhandler ;59
import usart2_irqhandler ;60
import usart3_irqhandler ;61
import exti15_10_irqhandler ;62
import rtcalarm_irqhandler ;63
import usbwakeup_irqhandler ;64
import tim8_brk_irqhandler ;65
import tim8_up_irqhandler ;66
import tim8_trg_com_irqhandler ;67
import tim8_cc_irqhandler ;68
import adc3_irqhandler ;69
import fsmc_irqhandler ;70
import sdio_irqhandler ;71
import tim5_irqhandler ;72
import spi3_irqhandler ;73
import uart4_irqhandler ;74
import uart5_irqhandler ;75
import tim6_irqhandler ;76
import tim7_irqhandler ;77
import dma2_channel1_irqhandler ;78
import dma2_channel2_irqhandler ;79
import dma2_channel3_irqhandler ;80
import dma2_channel4_5_irqhandler ;81
area reset, data, readonly ;82
export __vectors ;83
__vectors ;84
dcd __initial_sp ;85
dcd reset_handler ;86
dcd nmiexception ;87
dcd hardfaultexception ;88
dcd memmanageexception ;89
dcd busfaultexception ;90
dcd usagefaultexception ;91
dcd 0 ;92
dcd 0 ;93
dcd 0 ;94
dcd 0 ;95
dcd svchandler ;96
dcd debugmonitor ;97
dcd 0 ;98
dcd pendsvc ;99
dcd systickhandler ;100
dcd wwdg_irqhandler ;101
dcd pvd_irqhandler ;102
dcd tamper_irqhandler ;103
dcd rtc_irqhandler ;104
dcd flash_irqhandler ;105
dcd rcc_irqhandler ;106
dcd exti0_irqhandler ;107
dcd exti1_irqhandler ;108
dcd exti2_irqhandler ;109
dcd exti3_irqhandler ;110
dcd exti4_irqhandler ;111
dcd dma1_channel1_irqhandler ;112
dcd dma1_channel2_irqhandler ;113
dcd dma1_channel3_irqhandler ;114
dcd dma1_channel4_irqhandler ;115
dcd dma1_channel5_irqhandler ;116
dcd dma1_channel6_irqhandler ;117
dcd dma1_channel7_irqhandler ;118
dcd adc1_2_irqhandler ;119
dcd usb_hp_can_tx_irqhandler ;120
dcd usb_lp_can_rx0_irqhandler ;121
dcd can_rx1_irqhandler ;122
dcd can_sce_irqhandler ;123
dcd exti9_5_irqhandler ;124
dcd tim1_brk_irqhandler ;125
dcd tim1_up_irqhandler ;126
dcd tim1_trg_com_irqhandler ;127
dcd tim1_cc_irqhandler ;128
dcd tim2_irqhandler ;129
dcd tim3_irqhandler ;130
dcd tim4_irqhandler ;131
dcd i2c1_ev_irqhandler ;132
dcd i2c1_er_irqhandler ;133
dcd i2c2_ev_irqhandler ;134
dcd i2c2_er_irqhandler ;135
dcd spi1_irqhandler ;136
dcd spi2_irqhandler ;137
dcd usart1_irqhandler ;138
dcd usart2_irqhandler ;139
dcd usart3_irqhandler ;140
dcd exti15_10_irqhandler ;141
dcd rtcalarm_irqhandler ;142
dcd usbwakeup_irqhandler ;143
dcd tim8_brk_irqhandler ;144
dcd tim8_up_irqhandler ;145
dcd tim8_trg_com_irqhandler ;146
dcd tim8_cc_irqhandler ;147
dcd adc3_irqhandler ;148
dcd fsmc_irqhandler ;149
dcd sdio_irqhandler ;150
dcd tim5_irqhandler ;151
dcd spi3_irqhandler ;152
dcd uart4_irqhandler ;153
dcd uart5_irqhandler ;154
dcd tim6_irqhandler ;155
dcd tim7_irqhandler ;156
dcd dma2_channel1_irqhandler ;157
dcd dma2_channel2_irqhandler ;158
dcd dma2_channel3_irqhandler ;159
dcd dma2_channel4_5_irqhandler ;160
area |.text|, code, readonly ;161
reset_handler proc ;162
export reset_handler ;163
if data_in_extsram == 1 ;164
ldr r0,= 0x00000114 ;165
ldr r1,= 0x40021014 ;166
str r0,[r1] ;167
ldr r0,= 0x000001e0 ;168
ldr r1,= 0x40021018 ;169
str r0,[r1] ;170
ldr r0,= 0x44bb44bb ;171
ldr r1,= 0x40011400 ;172
str r0,[r1] ;173
ldr r0,= 0xbbbbbbbb ;174
ldr r1,= 0x40011404 ;175
str r0,[r1] ;176
ldr r0,= 0xb44444bb ;177
ldr r1,= 0x40011800 ;178
str r0,[r1] ;179
ldr r0,= 0xbbbbbbbb ;180
ldr r1,= 0x40011804 ;181
str r0,[r1] ;182
ldr r0,= 0x44bbbbbb ;183
ldr r1,= 0x40011c00 ;184
str r0,[r1] ;185
ldr r0,= 0xbbbb4444 ;186
ldr r1,= 0x40011c04 ;187
str r0,[r1] ;188
ldr r0,= 0x44bbbbbb ;189
ldr r1,= 0x40012000 ;190
str r0,[r1] ;191
ldr r0,= 0x44444b44 ;192
ldr r1,= 0x40012004 ;193
str r0,[r1] ;194
ldr r0,= 0x00001011 ;195
ldr r1,= 0xa0000010 ;196
str r0,[r1] ;197
ldr r0,= 0x00000200 ;198
ldr r1,= 0xa0000014 ;199
str r0,[r1] ;200
endif ;201
import __main ;202
ldr r0, =__main ;203
bx r0 ;204
endp ;205
align ;206
if :def:__microlib ;207
export __initial_sp ;208
export __heap_base ;209
export __heap_limit ;210
else ;211
import __use_two_region_memory ;212
export __user_initial_stackheap ;213
__user_initial_stackheap ;214
ldr r0, = heap_mem ;215
ldr r1, = (stack_mem + stack_size) ;216
ldr r2, = (heap_mem + heap_size) ;217
ldr r3, = stack_mem ;218
bx lr ;219
align ;220
endif ;221
end ;222
endif ;223
end ;224
如程序清单一,stm32的启动代码一共224行,使用了汇编语言编写,这其中的主要原因下文将会给出交代。现在从第一行开始分析:
? 第1行:定义是否使用外部sram,为1则使用,为0则表示不使用。此语行若用c语言表达则等价于:
#define data_in_extsram 0
? 第2行:定义栈空间大小为0x00000400个字节,即1kbyte。此语行亦等价于:
#define stack_size 0x00000400
? 第3行:伪指令area,表示
? 第4行:开辟一段大小为stack_size的内存空间作为栈。
? 第5行:标号__initial_sp,表示栈空间顶地址。
? 第6行:定义堆空间大小为0x00000400个字节,也为1kbyte。
? 第7行:伪指令area
? 第8行:标号__heap_base,表示堆空间起始地址。
? 第9行:开辟一段大小为heap_size的内存空间作为堆。
? 第10行:标号__heap_limit,表示堆空间结束地址。
? 第11行:告诉编译器使用thumb指令集。
? 第12行:告诉编译器以8字节对齐。
? 第13—81行:import指令,指示后续符号是在外部文件定义的(类似c语言中的全局变量声明),而下文可能会使用到这些符号。
? 第82行:定义只读数据段,实际上是在code区(假设stm32从flash启动,则此中断向量表起始地址即为0x8000000)
? 第83行:将标号__vectors声明为全局标号,这样外部文件就可以使用这个标号。
? 第84行:标号__vectors,表示中断向量表入口地址。
? 第85—160行:建立中断向量表。
? 第161行:
? 第162行:复位中断服务程序,proc…endp结构表示程序的开始和结束。
? 第163行:声明复位中断向量reset_handler为全局属性,这样外部文件就可以调用此复位中断服务。
? 第164行:if…endif为预编译结构,判断是否使用外部sram,在第1行中已定义为“不使用”。
? 第165—201行:此部分代码的作用是设置fsmc总线以支持sram,因不使用外部sram因此此部分代码不会被编译。
? 第202行:声明__main标号。
? 第203—204行:跳转__main地址执行。
? 第207行:if…else…endif结构,判断是否使用def:__microlib(此处为不使用)。
? 第208—210行:若使用def:__microlib,则将__initial_sp,__heap_base,__heap_limit亦即栈顶地址,堆始末地址赋予全局属性,使外部程序可以使用。
? 第212行:定义全局标号__use_two_region_memory。
? 第213行:声明全局标号__user_initial_stackheap,这样外程序也可调用此标号。
? 第214行:标号__user_initial_stackheap,表示用户堆栈初始化程序入口。
? 第215—218行:分别保存栈顶指针和栈大小,堆始地址和堆大小至r0,r1,r2,r3寄存器。
? 第224行:程序完毕。
以上便是stm32的启动代码的完整解析,接下来对几个小地方做解释:
1、 area指令:伪指令,用于定义代码段或数据段,后跟属性标号。其中比较重要的一个标号为“readonly”或者“readwrite”,其中 “readonly”表示该段为只读属性,联系到stm32的内部存储介质,可知具有只读属性的段保存于flash区,即0x8000000地址后。而 “readonly”表示该段为“可读写”属性,可知“可读写”段保存于sram区,即0x2000000地址后。由此可以从第3、7行代码知道,堆栈段位于sram空间。从第82行可知,中断向量表放置与flash区,而这也是整片启动代码中最先被放进flash区的数据。因此可以得到一条重要的信息:0x8000000地址存放的是栈顶地址__initial_sp,0x8000004地址存放的是复位中断向量 reset_handler(stm32使用32位总线,因此存储空间为4字节对齐)。
2、 dcd指令:作用是开辟一段空间,其意义等价于c语言中的地址符“&”。因此从第84行开始建立的中断向量表则类似于使用c语言定义了一个指针数组,其每一个成员都是一个函数指针,分别指向各个中断服务函数。
3、 标号:前文多处使用了“标号”一词。标号主要用于表示一片内存空间的某个位置,等价于c语言中的“地址”概念。地址仅仅表示存储空间的一个位置,从c语言的角度来看,变量的地址,数组的地址或是函数的入口地址在本质上并无区别。
4、 第202行中的__main标号并不表示c程序中的main函数入口地址,因此第204行也并不是跳转至main函数开始执行c程序。__main标号表示c/c++标准实时库函数里的一个初始化子程序__main的入口地址。该程序的一个主要作用是初始化堆栈(对于程序清单一来说则是跳转 __user_initial_stackheap标号进行初始化堆栈的),并初始化映像文件,最后跳转c程序中的main函数。这就解释了为何所有的c 程序必须有一个main函数作为程序的起点——因为这是由c/c++标准实时库所规定的——并且不能更改,因为c/c++标准实时库并不对外界开发源代码。因此,实际上在用户可见的前提下,程序在第204行后就跳转至.c文件中的main函数,开始执行c程序了。
至此可以总结一下stm32的启动文件和启动过程。首先对栈和堆的大小进行定义,并在代码区的起始处建立中断向量表,其第一个表项是栈顶地址,第二个表项是复位中断服务入口地址。然后在复位中断服务程序中跳转¬¬c/c++标准实时库的__main函数,完成用户堆栈等的初始化后,跳转.c文件中的 main函数开始执行c程序。假设stm32被设置为从内部flash启动(这也是最常见的一种情况),中断向量表起始地位为0x8000000,则栈顶地址存放于0x8000000处,而复位中断服务入口地址存放于0x8000004处。当stm32遇到复位信号后,则从0x80000004处取出复位中断服务入口地址,继而执行复位中断服务程序,然后跳转__main函数,最后进入mian函数,来到c的世界。
基于瑞萨电子的通用型微控制器的简介
LTpowerCAD设计工具的回路补偿特性的介绍
意法半导体物联网解决及方案
SERDES的引脚数量和通道优势
ROHM旗下蓝碧石半导体微控制器入门套件“SK-AD01”开始网售电容式开关系统的导入更轻松!
专家揭秘:STM32启动过程全解
e络盟播客节目《创新专家》第三集上线,HIOKI剖析电池设计问题
刀片电池的优缺点,刀片电池寿命一般多长时间
烽火通信连续三年在中国移动蝶形光缆集采项目中排名前三
戴尔易安信利用全方位存储产品组合打造现代化数据中心
LLC为何要工作在感性区域?
PCB四层板抄板方法
三星预计将全面投资于大型OLED面板
3亿台!基本预示着2021年将是真正意义上的“鸿蒙元年”
教你如何进行Xilinx SerDes调试
英特尔10纳米处理器一度跳票遭质疑,被指英特尔正收缩其半导体制造业务
插座零线带电的原因是什么
私有区块链对企业的未来会产生什么影响
DaimlerTrucks宣布将在美国市场出售2级自动驾驶卡车
如何利用物联网技术和5G技术解决城市问题