当时的嵌入式应用程序开发进程里,而且C言语成为了绝大部分场合的最佳挑选。如此一来main函数好像成为了天经地义的起点——由于C程序往往从main函数开端履行。但一个常常会被疏忽的问题是:微控制器(单片机)上电后,是怎么寻找到并履行main函数的呢?很显然微控制器无法从硬件上定位main函数的进口地址,由于运用C言语作为开发言语后,变量/函数的地址便由编译器在编译时自行分配,这样一来main函数的进口地址在微控制器的内部存储空间中不再是必定不变的。信任读者都能够答复这个问题,答案或许迥然不同,但必定都有个关键词,叫“发动文件”,用英文单词来描绘是“Bootloader”。
不管功能高低,结构简繁,价格贵贱,每一种微控制器(处理器)都有必要有发动文件,发动文件的效果就是担任履行微控制器从“复位”到“开端履行main函数”中心这段时刻(称为发动进程)一切必要进行的作业。最为常见的51,AVR或MSP430等微控制器当然也有对应发动文件,但开发环境往往主动完好地供给了这个发动文件,不需要开发人员再行干涉发动进程,只需要从main函数开端进行应用程序的规划即可。
论题转到STM32微控制器,不管是keil
uvision4仍是IAR EWARM开发环境,ST公司都供给了现成的直接可用的发动文件,程序开发人员能够直接引证发动文件后直接进行C应用程序的开发。这样能大大减小开发人员从其它微控制器渠道跳转至STM32渠道,也降低了习惯STM32微控制器的难度(关于上一代ARM的当家花旦ARM9,发动文件往往是榜首道难啃却又无法跨越的坎)。
相关于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的国际。