(扩展-IAP首要用于产品出厂后运用程序的更新效果,考虑到出厂时要先烧写IAP 再烧写APP运用程序要烧写2次添加工人劳动力基础上写了“STM32 IAP+APP ==>双剑合一”链接稍后发)
一、在进入主题之前咱们先了解一些必要的基础知识—-stm32系列芯片的品种和类型:
startup_stm32f10x_hd.s 大容量的STM32F101xx,STM32F102xx,STM32F103xx
startup_stm32f10x_hd_vl.s 大容量的STM32F100xx
startup_stm32f10x_ld.s 小容量的STM32F101xx,STM32F102xx,STM32F103xx
startup_stm32f10x_ld_vl.s 小容量的STM32F100xx
startup_stm32f10x_md.s 中容量的STM32F101xx,STM32F102xx,STM32F103xx
startup_stm32f10x_md_vl.s 中容量的STM32F100xx (我项目顶用的是此款芯片 stm32f100CB)
startup_stm32f10x_xl.s FLASH在512K到1024K字节的STM32F101xx,STM32F102xx,STM32F103xx
vl:超值型产品,stm32f100系列
xl:超高密度产品,stm32f101/103系列
ld:低密度产品,FLASH小于64K
md:中等密度产品,FLASH=64 or 128
hd:高密度产品,FLASH大于128
二、在拿到ST公司官方的IAP 程序后 咱们要考虑几点:
1.ST 官方IAP是什么针对什么芯片类型的,咱们要用的又是什么芯片类型;
2.咱们要用官方IAP合适咱们芯片的程序晋级运用,要在原有的基础上做那些改动;
(我的资源里有官方IAP源码:http://download.csdn.net/detail/yx_l128125/6445811)
初略看了一下IAP源码后,现在咱们能够答复一下上面的2个问题了:
1.官网刚下载的IAP针对的是stm32f103c8芯片的,所以他的发动代码文件挑选的是startup_stm32f10x_md.s,而我的芯片是stm32f100cb,所以我的发动代码文件挑选的是 startup_stm32f10x_md_lv.s
2 .第二个问题就是今日咱们要做详细剖析才干答复的问题了;
(1).知道了IAP官方源码的芯片和咱们要用芯片的差异,首要咱们要在源码的基础上做芯片级的改动;
A.首要改动编译器keil的芯片类型上咱们要改成咱们的芯片类型—STM32F100CB;
B.在keil的options for targer 选项C/C++/PREPROMCESSOR symbols的Define栏里界说,把有关STM32F10X_MD的宏界说改成:STM32F10X_MD_VL
- /*UncommentthelinebelowaccordingtothetargetSTM32deviceusedinyour
- application
- */
- #if!defined(STM32F10X_LD)&&!defined(STM32F10X_LD_VL)&&!defined(STM32F10X_MD)&&!defined(STM32F10X_MD_VL)&&!defined(STM32F10X_HD)&&!defined(STM32F10X_HD_VL)&&!defined(STM32F10X_XL)&&!defined(STM32F10X_CL)
- /*#defineSTM32F10X_LD*//*!
- /*#defineSTM32F10X_LD_VL*//*!
- /*#defineSTM32F10X_MD*//*!
- #defineSTM32F10X_MD_VL/*!
- /*#defineSTM32F10X_HD*//*!
- /*#defineSTM32F10X_HD_VL*//*!
- /*#defineSTM32F10X_XL*//*!
- /*#defineSTM32F10X_CL*//*!
- #endif
- /*#defineSTM32F10X_LD_VL*//*!
上面代码说的是假如没有界说 STM32F10X_MD_VL, 则宏界说STM32F10X_MD_VL
C.外部时钟问价在stm32f10x.h 根据实践修正,原文是 说假如没有宏界说外部时钟HES_VALUE的值,可是宏界说了stm32f10x_cl 则外部时钟设置为25MHZ, 不然外部时钟都设置为8MHZ; 我用的外部晶振是8MHZ的所以不用修正这部分代码;
- #if!definedHSE_VALUE
- #ifdefSTM32F10X_CL
- #defineHSE_VALUE((uint32_t)25000000)//ValueoftheExternaloscillatorinHz
de”class=”plain”>#else #defineHSE_VALUE((uint32_t)8000000)//ValueoftheExternaloscillatorinHz#endif/*STM32F10X_CL*/#endif/*HSE_VALUE*/
D.做体系主频时钟的更改
- #ifdefined(STM32F10X_LD_VL)||(definedSTM32F10X_MD_VL)||(definedSTM32F10X_HD_VL)
- /*#defineSYSCLK_FREQ_HSEHSE_VALUE*/
- #defineSYSCLK_FREQ_24MHz24000000
- #else
- /*#defineSYSCLK_FREQ_HSEHSE_VALUE*/
- #defineSYSCLK_FREQ_24MHz24000000
- /*#defineSYSCLK_FREQ_36MHz36000000*/
- /*#defineSYSCLK_FREQ_48MHz48000000*/
- /*#defineSYSCLK_FREQ_56MHz56000000*/
- /*#defineSYSCLK_FREQ_72MHz72000000*/
- #endif
E.下面是要害部分操作了,在说这部分操作前咱们先来说一下内存映射:
从上图咱们看出几个要害部分:
1.内部flash 是从0x0800 0000开端 到0x0801 FFFF 结束, 0x0801FFFF-0x0800 0000= 0x20000 =128k 128也就是flash的巨细;
2.SRAM的开端地址是 0x2000 0000 ;
咱们要把咱们的在线晋级程序IAP放到FLASH里以0x0800 0000 开端的方位, 运用程序放APP放到以0x08003000开端的方位,中止向量表也放在0x0800 3000开端的方位;如图
所以咱们需求先查看一下misc.h文件中的中止向量表的初始方位宏界说为 NVIC_VectTab_Flash 0x0800 0000
那么要就要设置编译器keil 中的 options for target 的target选项中的 IROM1地址 为0x0800 0000 巨细为 0x20000即128K;
IRAM1地址为0x2000 0000 巨细为0x2000;
(提示:这一项IROM1 地址 即为当时程序下载到flash的地址的开端方位)
下面咱们来剖析一下修正后的IAP代码:
- /*******************************************************************************
- *@函数称号main
- *@函数说明主函数
- *@输入参数无
- *@输出参数无
- *@回来参数无
- *******************************************************************************/
- intmain(void)
- {
- //Flash解锁
- FLASH_Unlock();
- //装备PA15管脚
- KEY_Configuration();
- //装备串口1
- IAP_Init();
- //PA15是否为低电平
- if(GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_15)==0x00)
- {
- //履行IAP驱动程序更新Flash程序
- SerialPutString(“\r\n======================================================================”);
- SerialPutString(“\r\n=(C)COPYRIGHT2011Lierda=”);
- SerialPutString(“\r\n==”);
- SerialPutString(“\r\n=In-ApplicationProgrammingApplication(Version1.0.0)=”);
- SerialPutString(“\r\n==”);
- SerialPutString(“\r\n=Bywuguoyan=”);
- SerialPutString(“\r\n======================================================================”);
- SerialPutString(“\r\n\r\n”);
- Main_Menu();
- }
- //不然履行用户程序
- else
- {
- //判别用途是否现已下载了用户程序,由于正常状况下此地址是栈地址
- //若没有这一句话,即便没有下载程序也会进入而导致跑飞。
- if(((*(__IOuint32_t*)ApplicationAddress)&0x2FFE0000)==0x20000000)
- {
- SerialPutString(“ExecuteuserProgram\r\n\n”);
- //跳转至用户代码
- JumpAddress=*(__IOuint32_t*)(ApplicationAddress+4);
- Jump_To_Application=(pFunction)JumpAddress;
- //初始化用户程序的仓库指针
- __set_MSP(*(__IOuint32_t*)ApplicationAddress);
- Jump_To_Application();
- }
- else
- {
- SerialPutString(“nouserProgram\r\n\n”);
- }
- }
这儿要点说一下几句经典且非常重要的代码:
榜首句: if (((*(__IO uint32_t*)ApplicationAddress) & 0x2FFE0000 ) == 0x20000000) //判别栈定地址值是否在0x2000 0000 – 0x 2000 2000之间
怎样了解呢? (1),在程序里#define ApplicationAddress 0x8003000 ,*(__IO uint32_t*)ApplicationAddress) 即取0x8003000开端到0x8003003 的4个字节的值, 由于咱们的运用程序APP中设置把中止向量表放置在0x08003000 开端的方位;而中止向量表里榜首个放的就是栈顶地址的值
也就是说,这句话即经过判别栈顶地址值是否正确(是否在0x2000 0000 – 0x 2000 2000之间) 来判别是否运用程序现已下载了,由于运用程序的发动文件刚开端就去初始化化栈空间,假如栈顶值对了,说运用程现已下载了发动文件的初始化也履行了;
第二句: JumpAddress = *(__IO uint32_t*) (ApplicationAddress + 4); [ common.c文件第18行界说了: pFunction Jump_To_Application;]
ApplicationAddress + 4 即为0x0800 3004 ,里边放的是中止向量表的第二项“复位地址” JumpAddress = *(__IO uint32_t*) (ApplicationAddress + 4); 之后此刻JumpAddress
第三句: Jump_To_Application = (pFunction) JumpAddress;
startup_stm32f10x_md_lv.文件中别号typedef void (*pFunction)(void); 这个看上去有点古怪;正常榜首个整型变量 typedef int a; 就是给整型界说一个别号 a
void (*pFunction)(void); 是声明一个函数指针,加上一个typedef 之后 pFunction只不过是类型void (*)(void) 的一个别号;例如:
- pFunctiona1,a2,a3;
- voidfun(void)
- {
- ……
- }
- a1=fun;
所以,Jump_To_Application = (pFunction) JumpAddress; 此刻Jump_To_Application指向了复位函数地点的地址;
第四 、五句:__set_MSP(*(__IO uint32_t*) ApplicationAddress); \\设置主函数栈指针
Jump_To_Application(); \\履行复位函数
咱们看一下发动文件startup_stm32f10x_md_vl。s 中的发动代码,更简略了解
移植后的IAP代码在我的资源(假如是stm32f100cb的芯片能够直接用):http://download.csdn.net/detail/yx_l128125/6475219
三、咱们来简略看下发动文件中的发动代码,剖析一下这更有利于咱们对IAP的了解: (下面这篇文章写的非常好,有木有!)
下文来自于:http://blog.sina.com.cn/s/blog_69bcf45201019djx.html
解析STM32的发动进程
解析STM32的发动进程
当时的嵌入式运用程序开发进程里,而且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”,其间注释为行号
DA
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
IMP
IMP
IMP
IMP
IMP
IMP
IMP
IMP
IMP
IMP
IMP
IMP
IMP
IMP
IMP
IMP
IMP
IMP
IMP
IMP
IMP
IMP
IMP
IMP
IMP
IMP
IMP
IMP
IMP
IMP
IMP
IMP
IMP
IMP
IMP
IMP
IMP
IMP
IMP
IMP
IMP
IMP
IMP
IMP
IMP
IMP
IMP
IMP
IMP
IMP
IMP
IMP
IMP
IMP
IMP
IMP
IMP
IMP
IMP
IMP
IMP
IMP
IMP
IMP
IMP
IMP
IMP
IMP
IMP
AREA RESET, DA
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|, CO
Reset_Handler PROC;162
EXPORT Reset_Handler;163
IF DA
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
IMP
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
IMP
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 DA
?第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行:IMP
?第82行:界说只读数据段,实践上是在CO
?第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:__M%&&&&&%ROLIB,则将__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的国际。