STM32學習記錄18 IAP(2)
一、在進入主題之前我們先了解一些必要的基礎知識----stm32系列芯片的種類和型號:
本文引用地址:http://www.ex-cimer.com/article/201611/316194.htmstartup_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字節(jié)的STM32F101xx,STM32F102xx,STM32F103xx
cl:互聯(lián)型產(chǎn)品,stm32f105/107系列
vl:超值型產(chǎn)品,stm32f100系列
xl:超高密度產(chǎn)品,stm32f101/103系列
ld:低密度產(chǎn)品,F(xiàn)LASH小于64K
md:中等密度產(chǎn)品,F(xiàn)LASH=64 or 128
hd:高密度產(chǎn)品,F(xiàn)LASH大于128
二、在拿到ST公司官方的IAP 程序后 我們要思考幾點:
1.ST 官方IAP是什么針對什么芯片型號的,我們要用的又是什么芯片型號;
2.我們要用官方IAP適合我們芯片的程序升級使用,要在原有的基礎上做那些改變;
(我的資源里有官方IAP源碼:http://download.csdn.net/detail/yx_l128125/6445811)
初略看了一下IAP源碼后,現(xiàn)在我們可以回答一下上面的2個問題了:
1.官網(wǎng)剛下載的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 依據(jù)實際修改,原文是 說如果沒有宏定義外部時鐘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.做系統(tǒng)主頻時鐘的更改
- #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
從上圖我們看出幾個關鍵部分:
1.內(nèi)部flash 是從0x0800 0000開始 到0x0801 FFFF 結(jié)束, 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代碼:
- /*******************************************************************************
- *@函數(shù)名稱main
- *@函數(shù)說明主函數(shù)
- *@輸入?yún)?shù)無
- *@輸出參數(shù)無
- *@返回參數(shù)無
- *******************************************************************************/
- intmain(void)
- {
- //Flash解鎖
- FLASH_Unlock();
- //配置PA15管腳
- KEY_Configuration();
- //配置串口1
- IAP_Init();
- //PA15是否為低電平
- if(GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_15)==0x00)
- {
- //執(zhí)行IAP驅(qū)動程序更新Flash程序
- SerialPutString("rn======================================================================");
- SerialPutString("rn=(C)COPYRIGHT2011Lierda=");
- SerialPutString("rn==");
- SerialPutString("rn=In-ApplicationProgrammingApplication(Version1.0.0)=");
- SerialPutString("rn==");
- SerialPutString("rn=Bywuguoyan=");
- SerialPutString("rn======================================================================");
- SerialPutString("rnrn");
- Main_Menu();
- }
- //否則執(zhí)行用戶程序
- else
- {
- //判斷用處是否已經(jīng)下載了用戶程序,因為正常情況下此地址是棧地址
- //若沒有這一句話,即使沒有下載程序也會進入而導致跑飛。
- if(((*(__IOuint32_t*)ApplicationAddress)&0x2FFE0000)==0x20000000)
- {
- SerialPutString("ExecuteuserProgramrnn");
- //跳轉(zhuǎn)至用戶代碼
- JumpAddress=*(__IOuint32_t*)(ApplicationAddress+4);
- Jump_To_Application=(pFunction)JumpAddress;
- //初始化用戶程序的堆棧指針
- __set_MSP(*(__IOuint32_t*)ApplicationAddress);
- Jump_To_Application();
- }
- else
- {
- SerialPutString("nouserProgramrnn");
- }
- }
這里重點說一下幾句經(jīng)典且非常重要的代碼:
第一句: if (((*(__IO uint32_t*)ApplicationAddress) & 0x2FFE0000 ) == 0x20000000) //判斷棧定地址值是否在0x2000 0000 - 0x 2000 2000之間
怎么理解呢? (1),在程序里#define ApplicationAddress 0x8003000 ,*(__IO uint32_t*)ApplicationAddress) 即取0x8003000開始到0x8003003 的4個字節(jié)的值, 因為我們的應用程序APP中設置把中斷向量表放置在0x08003000 開始的位置;而中斷向量表里第一個放的就是棧頂?shù)刂返闹?/p>
也就是說,這句話即通過判斷棧頂?shù)刂分凳欠裾_(是否在0x2000 0000 - 0x 2000 2000之間) 來判斷是否應用程序已經(jīng)下載了,因為應用程序的啟動文件剛開始就去初始化化??臻g,如果棧頂值對了,說應用程已經(jīng)下載了啟動文件的初始化也執(zhí)行了;
第二句: 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); 是聲明一個函數(shù)指針,加上一個typedef 之后 pFunction只不過是類型void (*)(void) 的一個別名;例如:
- pFunctiona1,a2,a3;
- voidfun(void)
- {
- ......
- }
- a1=fun;
所以,Jump_To_Application = (pFunction) JumpAddress; 此時Jump_To_Application指向了復位函數(shù)所在的地址;
第四 、五句:__set_MSP(*(__IO uint32_t*) ApplicationAddress); \設置主函數(shù)棧指針
Jump_To_Application(); \執(zhí)行復位函數(shù)
我們看一下啟動文件startup_stm32f10x_md_vl。s 中的啟動代碼,更容易理解
移植后的IAP代碼在我的資源(如果是stm32f100cb的芯片可以直接用):http://download.csdn.net/detail/yx_l128125/6475219
三、我們來簡單看下啟動文件中的啟動代碼,分析一下這更有利于我們對IAP的理解: (下面這篇文章寫的非常好,有木有?。?/p>
下文來自于:http://blog.sina.com.cn/s/blog_69bcf45201019djx.html
解析STM32的啟動過程
解析STM32的啟動過程
當前的嵌入式應用程序開發(fā)過程里,并且C語言成為了絕大部分場合的最佳選擇。如此一來main函數(shù)似乎成為了理所當然的起點——因為C程序往往從main函數(shù)開始執(zhí)行。但一個經(jīng)常會被忽略的問題是:微控制器(單片機)上電后,是如何尋找到并執(zhí)行main函數(shù)的呢?很顯然微控制器無法從硬件上定位main函數(shù)的入口地址,因為使用C語言作為開發(fā)語言后,變量/函數(shù)的地址便由編譯器在編譯時自行分配,這樣一來main函數(shù)的入口地址在微控制器的內(nèi)部存儲空間中不再是絕對不變的。相信讀者都可以回答這個問題,答案也許大同小異,但肯定都有個關鍵詞,叫“啟動文件”,用英文單詞來描述是“Bootloader”。
無論性能高下,結(jié)構(gòu)簡繁,價格貴賤,每一種微控制器(處理器)都必須有啟動文件,啟動文件的作用便是負責執(zhí)行微控制器從“復位”到“開始執(zhí)行main函數(shù)”中間這段時間(稱為啟動過程)所必須進行的工作。最為常見的51,AVR或MSP430等微控制器當然也有對應啟動文件,但開發(fā)環(huán)境往往自動完整地提供了這個啟動文件,不需要開發(fā)人員再行干預啟動過程,只需要從main函數(shù)開始進行應用程序的設計即可。
話題轉(zhuǎn)到STM32微控制器,無論是keil
uvision4還是IAR EWARM開發(fā)環(huán)境,ST公司都提供了現(xiàn)成的直接可用的啟動文件,程序開發(fā)人員可以直接引用啟動文件后直接進行C應用程序的開發(fā)。這樣能大大減小開發(fā)人員從其它微控制器平臺跳轉(zhuǎn)至STM32平臺,也降低了適應STM32微控制器的難度(對于上一代ARM的當家花旦ARM9,啟動文件往往是第一道難啃卻又無法逾越的坎)。
相對于ARM上一代的主流ARM7/ARM9內(nèi)核架構(gòu),新一代Cortex內(nèi)核架構(gòu)的啟動方式有了比較大的變化。ARM7/ARM9內(nèi)核的控制器在復位后,CPU會從存儲空間的絕對地址0x000000取出第一條指令執(zhí)行復位中斷服務程序的方式啟動,即固定了復位后的起始地址為0x000000(PC = 0x000000)同時中斷向量表的位置并不是固定的。而Cortex-M3內(nèi)核則正好相反,有3種情況:
1、通過boot引腳設置可以將中斷向量表定位于SRAM區(qū),即起始地址為0x2000000,同時復位后PC指針位于0x2000000處;
2、通過boot引腳設置可以將中斷向量表定位于FLASH區(qū),即起始地址為0x8000000,同時復位后PC指針位于0x8000000處;
3、通過boot引腳設置可以將中斷向量表定位于內(nèi)置Bootloader區(qū),本文不對這種情況做論述;
而Cortex-M3內(nèi)核規(guī)定,起始地址必須存放堆頂指針,而第二個地址則必須存放復位中斷入口向量地址,這樣在Cortex-M3內(nèi)核復位后,會自動從起始地址的下一個32位空間取出復位中斷入口向量,跳轉(zhuǎn)執(zhí)行復位中斷服務程序。對比ARM7/ARM9內(nèi)核,Cortex-M3內(nèi)核則是固定了中斷向量表的位置而起始地址是可變化的。
有了上述準備只是后,下面以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行,使用了匯編語言編寫,這其中的主要原因下文將會給出交代?,F(xiàn)在從第一行開始分析:
?第1行:定義是否使用外部SRAM,為1則使用,為0則表示不使用。此語行若用C語言表達則等價于:
#define DA
?第2行:定義??臻g大小為0x00000400個字節(jié),即1Kbyte。此語行亦等價于:
#define Stack_Size 0x00000400
?第3行:偽指令AREA,表示
?第4行:開辟一段大小為Stack_Size的內(nèi)存空間作為棧。
?第5行:標號__initial_sp,表示??臻g頂?shù)刂贰?br />?第6行:定義堆空間大小為0x00000400個字節(jié),也為1Kbyte。
?第7行:偽指令AREA,表示
?第8行:標號__heap_base,表示堆空間起始地址。
?第9行:開辟一段大小為Heap_Size的內(nèi)存空間作為堆。
?第10行:標號__heap_limit,表示堆空間結(jié)束地址。
?第11行:告訴編譯器使用THUMB指令集。
?第12行:告訴編譯器以8字節(jié)對齊。
?第13—81行:IMP
?第82行:定義只讀數(shù)據(jù)段,實際上是在CO
?第83行:將標號__Vectors聲明為全局標號,這樣外部文件就可以使用這個標號。
?第84行:標號__Vectors,表示中斷向量表入口地址。
?第85—160行:建立中斷向量表。
?第161行:
?第162行:復位中斷服務程序,PROC…ENDP結(jié)構(gòu)表示程序的開始和結(jié)束。
?第163行:聲明復位中斷向量Reset_Handler為全局屬性,這樣外部文件就可以調(diào)用此復位中斷服務。
?第164行:IF…ENDIF為預編譯結(jié)構(gòu),判斷是否使用外部SRAM,在第1行中已定義為“不使用”。
?第165—201行:此部分代碼的作用是設置FSMC總線以支持SRAM,因不使用外部SRAM因此此部分代碼不會被編譯。
?第202行:聲明__main標號。
?第203—204行:跳轉(zhuǎn)__main地址執(zhí)行。
?第207行:IF…ELSE…ENDIF結(jié)構(gòu),判斷是否使用DEF:__MICROLIB(此處為不使用)。
?第208—210行:若使用DEF:__MICROLIB,則將__initial_sp,__heap_base,__heap_limit亦即棧頂?shù)刂?,堆始末地址賦予全局屬性,使外部程序可以使用。
?第212行:定義全局標號__use_two_region_memory。
?第213行:聲明全局標號__user_initial_stackheap,這樣外程序也可調(diào)用此標號。
?第214行:標號__user_initial_stackheap,表示用戶堆棧初始化程序入口。
?第215—218行:分別保存棧頂指針和棧大小,堆始地址和堆大小至R0,R1,R2,R3寄存器。
?第224行:程序完畢。
以上便是STM32的啟動代碼的完整解析,接下來對幾個小地方做解釋:
1、AREA指令:偽指令,用于定義代碼段或數(shù)據(jù)段,后跟屬性標號。其中比較重要的一個標號為“READONLY”或者“READWRITE”,其中“READONLY”表示該段為只讀屬性,聯(lián)系到STM32的內(nèi)部存儲介質(zhì),可知具有只讀屬性的段保存于FLASH區(qū),即0x8000000地址后。而“READONLY”表示該段為“可讀寫”屬性,可知“可讀寫”段保存于SRAM區(qū),即0x2000000地址后。由此可以從第3、7行代碼知道,堆棧段位于SRAM空間。從第82行可知,中斷向量表放置與FLASH區(qū),而這也是整片啟動代碼中最先被放進FLASH區(qū)的數(shù)據(jù)。因此可以得到一條重要的信息:0x8000000地址存放的是棧頂?shù)刂穇_initial_sp,0x8000004地址存放的是復位中斷向量Reset_Handler(STM32使用32位總線,因此存儲空間為4字節(jié)對齊)。
2、DCD指令:作用是開辟一段空間,其意義等價于C語言中的地址符“&”。因此從第84行開始建立的中斷向量表則類似于使用C語言定義了一個指針數(shù)組,其每一個成員都是一個函數(shù)指針,分別指向各個中斷服務函數(shù)。
3、標號:前文多處使用了“標號”一詞。標號主要用于表示一片內(nèi)存空間的某個位置,等價于C語言中的“地址”概念。地址僅僅表示存儲空間的一個位置,從C語言的角度來看,變量的地址,數(shù)組的地址或是函數(shù)的入口地址在本質(zhì)上并無區(qū)別。
4、第202行中的__main標號并不表示C程序中的main函數(shù)入口地址,因此第204行也并不是跳轉(zhuǎn)至main函數(shù)開始執(zhí)行C程序。__main標號表示C/C++標準實時庫函數(shù)里的一個初始化子程序__main的入口地址。該程序的一個主要作用是初始化堆棧(對于程序清單一來說則是跳轉(zhuǎn)__user_initial_stackheap標號進行初始化堆棧的),并初始化映像文件,最后跳轉(zhuǎn)C程序中的main函數(shù)。這就解釋了為何所有的C程序必須有一個main函數(shù)作為程序的起點——因為這是由C/C++標準實時庫所規(guī)定的——并且不能更改,因為C/C++標準實時庫并不對外界開發(fā)源代碼。因此,實際上在用戶可見的前提下,程序在第204行后就跳轉(zhuǎn)至.c文件中的main函數(shù),開始執(zhí)行C程序了。
至此可以總結(jié)一下STM32的啟動文件和啟動過程。首先對棧和堆的大小進行定義,并在代碼區(qū)的起始處建立中斷向量表,其第一個表項是棧頂?shù)刂罚诙€表項是復位中斷服務入口地址。然后在復位中斷服務程序中跳轉(zhuǎn)??C/C++標準實時庫的__main函數(shù),完成用戶堆棧等的初始化后,跳轉(zhuǎn).c文件中的main函數(shù)開始執(zhí)行C程序。假設STM32被設置為從內(nèi)部FLASH啟動(這也是最常見的一種情況),中斷向量表起始地位為0x8000000,則棧頂?shù)刂反娣庞?x8000000處,而復位中斷服務入口地址存放于0x8000004處。當STM32遇到復位信號后,則從0x80000004處取出復位中斷服務入口地址,繼而執(zhí)行復位中斷服務程序,然后跳轉(zhuǎn)__main函數(shù),最后進入mian函數(shù),來到C的世界。
評論