久久久久久久999_99精品久久精品一区二区爱城_成人欧美一区二区三区在线播放_国产精品日本一区二区不卡视频_国产午夜视频_欧美精品在线观看免费

 找回密碼
 立即注冊

QQ登錄

只需一步,快速開始

搜索
查看: 13404|回復: 5
打印 上一主題 下一主題
收起左側

STM32在MAIN函數之前做了什么事

[復制鏈接]
跳轉到指定樓層
樓主
ID:241309 發表于 2017-10-20 15:55 | 只看該作者 回帖獎勵 |倒序瀏覽 |閱讀模式
在學習STM32的時候了解了在MAIN函數之前程序干了什么,其中有一些內容是參考別人的。

main函數之前執行的啟動程序:
在執行main函數之前還有一些引導的程序,以開發板的型號為例,啟動程序為startup_stm32f10x_cl.s:
開始的部分是聲明的內容
【1】MODULE 控制指令是用來標記 modules 源碼的開始,后邊的 ?cstartup 是模塊的名字,此文檔的最后的 END 表明模塊的結束,與前面的MODULE對應。
【2】SECTION 指令是聲明段,一個段不能同時包含 public symbol 和 pubweak symbol ,模塊只有在相同的名字的模塊沒有被鏈接進來的時候才會被鏈接進來。
    語法格式:SECTION section:type [flag] [(align)]
        align,是用于指定地址對齊到 2^align,他的取值是 0 到 30
        flag,取值NOROOT、ROOT、REORDER、NOREORDER,默認是ROOT,NOROOT表示如果這個段中的符號沒有被引用,將會被連接器舍棄,即可被優化。ROOT表示不可被優化。REORDER表示開始一個新的名字是 section 的段(section),NOREORDER表示開始一個新的名字為 section 的片段(fragment),多個片段組成一個段(section)。
        type,memory 的類型,取值是 CODE、CONST、DATA
        section,段的名字
【3】EXTERN 用導入其他模塊的 symbol(符號)。
【4】PUBLIC 導出 symbol(符號)。
【1】DATA 表示下邊中的標簽是 32 位的標簽,THUMB 表示下邊的標簽是 16 位的標簽,所謂的標簽是 地址的別名,不占用代碼空間,給編譯器看的。
【2】 DCD 是數據定義或者 重定位指令,為的是定義一個值,或者保留 memory,DCD 別名是 DC32,用于聲明一個 32 位的常量,這部分是中斷向量表的內容,需要注意的是,他們的順序不能改變,此部分會放到 flash 的最開始部分,當系統啟動的時候會加載前一個地址,第一個地址是 C 程序的棧的棧頂地址,第二個地址是向量表的開始地址,中斷發生時會根據向量表的首地址和偏移量來找到程序的入口。
【3】sfe 指令作用是返回棧的結尾,因為棧的增長方向是反方向的。
【1】THUMB 表明下邊是 thumb 指令。
【2】Reset_Handler 在開機或者復位的時候執行
    R0 = SystemInit
    跳轉到 SystemInit 函數,并將處理器切換到 thumb 狀態
    R0 = __iar_program_start
    跳轉到 __iar_program_start 函數,狀態也是切換到 thumb 狀態
【3】此處的 __iar_program_start 在程序中找不到是因為它已經被封裝到了 IAR 自帶的C庫啟動代碼中了,當我們編譯的時候,在項目屬性的 linker,library中勾選了 Automatic runtime library ,就告訴了編譯器用庫中的 __iar_program_start ,具體實現了什么,我們可以查看 IAR 工具為我們提供的源碼,具體路徑在IAR 安裝目錄下的 arm\src\lib\thumb ,我們可以看到有的文件分別的提供了 匯編代碼和 c 代碼。
其中的 cmain.s 文件中有
可以看到在執行過__iar_program_start函數之后就進入了main函數。
分析啟動過程:
當STM32復位后,首先從地址0x0000_0000處取出棧頂地址放入MSP(主堆棧指針)寄存器中(即寄存器R13)。接著從地址0x0000_0004處取出復位向量地址放入PC寄存器中(即寄存器R15),再接著程序就從復位處理函數處(Reset_Handler)開始執行。
對比兩張圖片可以發現,R13所指向的地址與0x0000_0000地址的內容相等;R15所指向的地址與0x0000_0004地址的內容相等。
接下來我們分析Reset_Handler做了哪些事:
我們可以看到,只是執行了兩個函數SystemInit和__iar_program_start,通過單步跟蹤發現在SystemInit里進行清理中斷,時鐘初始化,Flash讀取初始化等操作。
而在__iar_program_start里實際上只執行了__low_level_init和__iar_data_init2兩個函數,因為__low_level_init里只是將R0賦0x1,所以再將R0和0x0進行比較結果不相等(CMP R0, #0x0),__iar_init$$done這個函數沒有得到執行。
系統復位時,Cortex-M3從代碼區偏移0x0000'0000處獲取棧頂地址,用來初始化MSP(主堆棧指針)寄存器的值。接下來從代碼區偏移0x0000'0004獲取第一個指令的跳轉地址。這些地址,是CM3要求放置中斷向量表的地方。
這里是一個程序的啟動區的反匯編:
__vector_table:
  08004000  2600      
  08004002  2000      
  08004004  7E1D      (注解:下面的文字老是說 IAP 很奇怪 不是 IAR么)
  08004006  0800      (轉發人注解:我知道 IAP 意思是“在應用編程”。)
這個程序是由IAP程序來啟動的,IAP程序獲取0x0800'4000處的MSP值(0x20002600),并設置為MSP的值,即主堆棧最大范圍是0x2000'0000~0x2000'25FF。接下來IAP程序獲取0x0800'4004處的Reset_Handler的地址(0x0800'7E1D),并跳轉到Reset_Handler()執行。
IAP在這里完全是模仿了Cortex-M3的復位序列,也就是說,在沒有IAP的系統上,CM3只能從0x0800'0000獲取MSP,從0x0800'0004獲取第一條指令所處地址。而IAP就存在在0x0800'0000這個地址上,IAP的啟動,已經消耗掉了這個復位序列,所以IAP要啟動UserApp程序的時候,也是完全模仿Cortex-M3的復位序列的。
接下來我們看看復位后第一句指令——Reset_Handler()函數里有什么。
若我們使用的是ST公司標準外設庫,那么已經有了現成的Reset_Handler,不過他是弱定義——PUBWEAK,可以被我們重寫的同名函數覆蓋。一般來說,我們使用的都是ST提供的Reset_Handler,在V3.4版本的庫中,可以在startup_stm32f10x_xx.s中找到這個函數:
        PUBWEAK Reset_Handler
        SECTION .text:CODE:REORDER(2)
Reset_Handler
        LDR     R0, =SystemInit
        BLX     R0
        LDR     R0, =__iar_program_start
        BX      R0
看來ST沒有做太多的事,他只調用了自家庫提供的SystemInit函數進行系統時鐘、Flash讀取的初始化,并把大權交給了__iar_program_start這個IAR提供的“內部函數”了,我們就跟緊這個__iar_program_start跳轉,看看IAR做了什么,上面一段代碼的反匯編如下:
       Reset_Handler:
__iar_section$$root:
  08007E1C  4801      LDR          R0, [PC, #0x4]; LDR     R0, =SystemInit
  08007E1E  4780      BLX          R0;BLX     R0
  08007E20  4801      LDR          R0, [PC, #0x4];LDR R0, =__iar_program_start
  08007E22  4700      BX           R0;BX      R0
  08007E24  6C69      
  08007E26  0800      
  08007E28  7D8D      
  08007E2A  0800     
細心的觀眾會發現地址是0x0800'7E1C,比我們查到的0x0800'7E1D差了1,這是ARM家族的遺留問題,因為ARM處理器的指令至少是半字對齊的(16位THUMB指令集 or 32位ARM指令集),所以PC指針的LSB是常為0的,為了充分利用寄存器,ARM公司給PC的LSB了一個重要的使命,那就是在執行分支跳轉時,PC的LSB=1,表示使用THUMB模式,LSB=0,表示使用ARM模式,但在最新的Cortex-M3內核上,只使用了THUMB-2指令集挑大梁,所以這一位要常保持1,所以我們查到的地址是0x0800'7E1D(C=1100,D=1101),放心,我們的CM3內核會忽略掉LSB(除非為0,那么會引起一個fault),從而正確跳轉到0x0800'7E1C。
從0x0800'7E20處的加載指令,我們可以算出__iar_program_start所處的位置,就是當前PC指針(0x0800'7E24),再加上4,即0x0800'7E28處的所指向的地址——0x0800'7D8D(0x0800'7D8C),我們跟緊著跳轉,__iar_program_start果然在這里:
__iar_program_start:
  08007D8C  F000F88C  BL           __low_level_init
  08007D90  2800      CMP          R0, #0x0
  08007D92  D001      BEQ          __iar_init$$done
  08007D94  F7FFFFDE  BL           __iar_data_init2
  08007D98  2000      MOVS         R0, #0x0
  08007D9A  F7FDFC49  BL          main
我們看到IAR提供了__low_level_init這個函數進行了“底層”的初始化,進一步跟蹤,我們可以查到__low_level_init這個函數做了些什么,是不是我們想象中的不可告人。
__low_level_init:
  08007EA8  2001      MOVS         R0, #0x1
  08007EAA  4770      BX           LR
__low_level_init出乎想象的簡單,只是往R0寄存器寫入了1,就立即執行"BX LR"回到調用處了,接下來,__iar_program_start檢查了R0是否為0,為0,則執行__iar_init$$done,若不是0,就執行__iar_data_init2。__iar_init$$done這個函數很簡單,只有2句話,第一句是把R0清零,第二句就直接"BL main",跳轉到main()函數了。不過既然__low_level_init已經往R0寫入了1,那么我們還是得走下遠路——看看__iar_data_init2做了些什么,雖然距離main只有一步之遙,不過這中間隱藏了編譯器的思想,我們得耐心看下去。
__iar_data_init2:
  08007D54  B510      PUSH         {R4,LR}
  08007D56  4804      LDR          R0, [PC, #0x10]
  08007D58  4C04      LDR          R4, [PC, #0x10]
  08007D5A  E002      B            0x8007D62
  08007D5C  F8501B04  LDR          R1, [R0], #0x4
  08007D60  4788      BLX          R1
  08007D62  42A0      CMP          R0, R4
  08007D64  D1FA      BNE          0x8007D5C
  08007D66  BD10      POP          {R4,PC}
  08007D68  7C78      
  08007D6A  0800      
  08007D6C  7C9C     
  08007D6E  0800     
看來IAR遲遲不執行main()函數,就是為了執行__iar_data_init2,我們來分析分析IAR都干了些什么壞事~
首先壓R4,LR入棧,然后加載0x0800'7C78至R0,0x0800'7C9C至R4,馬上跳轉到0x0800'7D62執行R0,R4的比較,結果若是相等,則彈出R4,PC,然后立即進入main()。不過IAR請君入甕是自不會那么快放我們出來的——結果不相等,跳轉到0x0800'7D5C執行,在這里,把R0指向的地址——0x0800'7C78中的值——0x0800'7D71加載到R1,并且R0中的值自加4,更新為0x0800'7C7C,并跳轉到R1指向的地址處執行,這里是另一個IAR函數:__iar_zero_init2:
__iar_zero_init2:
  08007D70  2300      MOVS         R3, #0x0
  08007D72  E005      B            0x8007D80
  08007D74  F8501B04  LDR          R1, [R0], #0x4
  08007D78  F8413B04  STR          R3, [R1], #0x4
  08007D7C  1F12      SUBS         R2, R2, #0x4
  08007D7E  D1FB      BNE          0x8007D78
  08007D80  F8502B04  LDR          R2, [R0], #0x4
  08007D84  2A00      CMP          R2, #0x0
  08007D86  D1F5      BNE          0x8007D74
  08007D88  4770      BX           LR
  08007D8A  0000      MOVS         R0, R0
__iar_data_init2還沒執行完畢,就跳轉到了這個__iar_zero_inti2,且看我們慢慢分析這個幫兇——__iar_zero_inti2做了什么。
__iar_zero_inti2將R3寄存器清零,立即跳轉到0x0800'7D80執行'LDR          R2, [R0], #0x4',這句指令與剛才在__iar_data_init2見到的'LDR          R1, [R0], #0x4'很類似,都為“后索引”。這回,將R0指向的地址——0x0800'7C7C中的值——0x0000'02F4加載到R2寄存器,然后R0中的值自加4,更新為0x0800'7C80。接下來的指令檢查了R2是否為0,顯然這個函數沒那么簡單想放我我們,R2的值為2F4,我們又被帶到了0x0800'7D74處,隨后4條指令做了如下的事情:
1、將R0指向的地址——0x0800'7C80中的值——0x2000'27D4加載到R1寄存器,然后R0中的值自加4,更新為0x0800'7C84。
2、將R1指向的地址——0x2000'27D4中的值——改寫為R3寄存器的值——0,然后R1中的值自加4,更新為0x2000'27D8。
3、R2自減4
4、檢查R2是否為0,不為0,跳轉到第二條執行。不為,則執行下一條。
這簡直就是一個循環!——C語言的循環for(r2=0x2F4;r2-=4;r!=0){...},我們看看循環中做了什么。
第一條指令把一個地址加載到了R1——0x2000'27D4 是一個RAM地址,以這個為起點,在循環中,對長度為2F4的RAM空間進行了清零的操作。那為什么IAR要做這個事情呢?消除什么記錄么?用Jlink查看這片內存區域,可以發現這片區域是我們定義的全局變量的所在地。也就是說,IAR在每次系統復位后,都會自動將我們定義的全局變量清零0。
清零完畢后,接下來的指令"LDR          R2, [R0], #0x4"將R0指向的地址——0x0800'7C84中的值——0加載到R2寄存器,然后R0中的值自加4,更新為0x0800'7C88。隨后檢查R2是否為0,這里R2為0,執行'BX LR'返回到__iar_data_init2函數,若是不為0,我們可以發現又會跳轉至“4指令”處進行一個循環清零的操作。
讀到這里,我們應該可以猜到IAR的意圖了:__iar_data_init2一開始加載了0x0800'7C78至R0,0x0800'7C9C至R4,[R0,R4]就是一段啟動代碼區,在這個區域內保存了要“處理”的所有地址與信息——執行的函數地址或者參數,實際上,這片區域也有一個名字,叫做:Region$$Table$$Base。在這個區域內,程序以R0為索引,R4為上限,當R0=R4,__iar_data_init2執行完畢,跳轉至main()函數。
好了,保持我們這個猜想,繼續跟蹤我們的PC指針——我們回到了__iar_data_init2函數中,第一件事就是比較R0,R4的值,可惜的是,仍然不相等,我們又被帶到了0x0800'7D5C,至此,我們應該能看出這是一個__iar_data_init2的“主循環”,這也驗證了我們對IAR意圖的猜想~
  __iar_data_init2中的“主循環”:
  08007D5C  F8501B04  LDR          R1, [R0], #0x4
  08007D60  4788      BLX          R1
  08007D62  42A0      CMP          R0, R4
我們可以等價寫為:for(r0=0x0800'7C78,r4=0x0800'7C9C;r0!=r4;r0+=4){...}
此時,我們的R0為0x0800'7C88,經過“指令1”,R0變為0x0800'7C8C,R1為0x0800'7C55。我們來看看,7C55處,IAR又要執行何種操作。
__iar_copy_init2:
  08007C54  B418      PUSH         {R3,R4}
  08007C56  E009      B            0x8007C6C
  08007C58  F8501B04  LDR          R1, [R0], #0x4
  08007C5C  F8502B04  LDR          R2, [R0], #0x4
  08007C60  F8514B04  LDR          R4, [R1], #0x4
  08007C64  F8424B04  STR          R4, [R2], #0x4
  08007C68  1F1B      SUBS         R3, R3, #0x4
  08007C6A  D1F9      BNE          0x8007C60
  08007C6C  F8503B04  LDR          R3, [R0], #0x4
  08007C70  2B00      CMP          R3, #0x0
  08007C72  D1F1      BNE          0x8007C58
  08007C74  BC12      POP          {R1,R4}
  08007C76  4770      BX           LR
這是一個名為__iar_copy_init2的函數,他執行了什么"copy"操作呢?
首先壓R3,R4入棧,然后跳轉到0x0800'7C6C,從R0——Region$$Table$$Base中取出參數0x238放入R3,接下來的指令大家應該都熟悉了,0x238不為0,所以我們被帶至7C58處,再次從Region$$Table$$Base中取出參數0x0800'7F14放入R1,從Region$$Table$$Base取出參數0x2000'2AC8放入R2處。細心的觀眾應該能察覺這和__iar_zero_init2中取參數的幾乎一樣:先取出大小,隨后取出了地址——只不過這里多出了1個地址,沒錯這就是"copy",隨后的指令
  08007C60  F8514B04  LDR          R4, [R1], #0x4
  08007C64  F8424B04  STR          R4, [R2], #0x4
  08007C68  1F1B      SUBS         R3, R3, #0x4
  08007C6A  D1F9      BNE          0x8007C60
則是另一個“4指令”,指令1將R1指向地址的數據讀到R4,指令2將R2指向地址的數據改寫為R4的數據,指令3、4是完成一個循環。
說到這里大家都應該明白了——這就是一個"copy"的操作,從Flash地址0x0800'7F14起,將長度0x238的數據拷貝到RAM地址0x2000'2AC8中。
通過Jlink,我們可以看到這片區域是我們定義的并且已初始化的全局變量。也就是說,每次復位后,IAR在此處進行全局變量的初始化。
在這“4指令”執行完畢后,再次從Region$$Table$$Base中取出參數,為0,比較之后條件符合,函數返回__iar_data_init2。
此時的R0已經為0x0800'7C9C與R4相等,__iar_data_init2終于完成它的使命。
  08007D98  2000      MOVS         R0, #0x0
  08007D9A  F7FDFC49  BL           main
將R0清零以后,IAR放棄主動權,把PC指針交給了用戶程序的入口——main()。
但請注意,這里使用的是BL指令進行main跳轉,也就是說,main函數只是IAR手中的一個子程序,若是main函數執行到了結尾,接下來則會執行exit等IAR提供的“退出”函數。這些函數,等待下回分解~
總之,IAR在啟動main()函數以前,執行了Reset_Handler,調用SystemInit()(ST庫提供)進行時鐘,Flash讀取初始化,并轉入__iar_program_start中執行__low_level_init與__iar_data_init2,并在__iar_data_init2中,先后調用__iar_zero_init2與__iar_copy_init2對全局變量、全局已初始化變量進行相應的初始化操作。最后,調用main()函數執行。
這就是IAR在啟動main()函數之前做的事情,它并沒有那么神秘,只要花些時間,就可以跟跟蹤分析出這個過程。
STM32的啟動分析
一、STM32的復位序列
當STM32產生復位后,做的第一件事就是讀取下列兩個32位整數的值:
1、從地址0x0000,0000處取出MSP(主堆棧指針)的初始值放入MSP寄存器中;
2、從地址0x0000,0004處取出復位向量放入PC寄存器中,然后從PC中存取的地
址出取指并開始執行。
圖1:復位序列
請注意,這與傳統的ARM架構以及其他的單片機完全不同,他們復位后一般是從
0x0000,0000地址處取出第一條指令并執行,而一般0x0000,0000都是一條跳轉指令。而在STM32中,在0地址處提供的是MSP的初始值,然后緊跟著就是向量表(上電復位時向量表是被默認放在0x04地址處,但是通過修改向量表偏移量寄存器(VTOR)可以將其定義在其他位置)。另外,向量表中的數值是32位的地址,而不是跳轉指令,系統會自動將該數值存入PC寄存器中后從該32為地址指向的地址出開始執行,這有點像指針的指針。
圖2:初始化MSP及PC的初始化的一個范例因為SMT32使用的是向下生長的滿棧,
所以MSP初始值必須是堆棧內存的末地址加1。舉例來說,如果你的堆棧區域在
0x20007C00-0x20007FFF之間,那么MSP的初始值就必須是0x20008000。 向量表跟隨在MSP的初始化之后——也就是第2個標目。要注意因為STM32是在Thumb態下執行,所以向量表中每個數值必須把LSB(最低權重位)置1.正是因為這個原因,圖2中就是用0x101來表示0x100.當0x100處的指令得到執行后,就正是開始了程序的執行。在這之前MSP是必須的,因為可能第1條指令還沒來得及執行,就發生了NMI(不可屏蔽中斷)或者其他的Fault,MSP初始化好后就已經為他們的服務例程準備好了堆棧。
二、STM323種啟動模式
在STM32中,可以通過BOOT[1:0]引腳選擇三種不同的啟動模式,如表1:
表1:STM32的三種啟動模式 根據選定的啟動模式,主閃存存儲器、系統存儲器或SRAM可以按照以下方式訪問:
1、 從主閃存存儲器啟動:
主閃存存儲器被映射到啟動空間(0x0000,0000),但仍然能夠在原有的地址(0x8000,0000)訪問它,即閃存存儲器的內容可以在兩個地
址區域訪問,0x0000,0000或者0x8000,0000.
2、從系統存儲器啟動:
系統存儲器被映射到啟動空間(0x0000,0000),但仍然能夠在原有的地址(0x1FFF,F000)訪問它。
3、 從內置SRAM啟動:
只能在0x2000,0000開始的地址區訪問SARM。當從內置的SRAM中啟動,在應用程序的初始化代碼中,必須使用NVIC的異常表和偏移寄存器,從新映射向量表到SRAM之中。
《stm32權威指南》中有這樣的說法,待理解。

完整的Word格式文檔51黑下載地址(共12頁):
STM32在main函數之前執行的啟動程序.docx (392.57 KB, 下載次數: 45)


評分

參與人數 1黑幣 +50 收起 理由
admin + 50 共享資料的黑幣獎勵!

查看全部評分

分享到:  QQ好友和群QQ好友和群 QQ空間QQ空間 騰訊微博騰訊微博 騰訊朋友騰訊朋友
收藏收藏3 分享淘帖 頂 踩
回復

使用道具 舉報

沙發
ID:1 發表于 2017-10-21 00:27 | 只看該作者
好資料,51黑有你更精彩!!!
回復

使用道具 舉報

板凳
ID:324843 發表于 2018-5-8 10:44 | 只看該作者
資料不錯, 可惜沒有積分下載個全的
回復

使用道具 舉報

地板
ID:222998 發表于 2018-10-22 10:52 | 只看該作者
樓主厲害了  分析的很詳細   學習了
回復

使用道具 舉報

5#
ID:372307 發表于 2018-10-22 20:32 | 只看該作者
學習了
回復

使用道具 舉報

6#
ID:474500 發表于 2019-2-1 10:08 | 只看該作者
感謝樓主,可惜沒有積分下載
回復

使用道具 舉報

您需要登錄后才可以回帖 登錄 | 立即注冊

本版積分規則

小黑屋|51黑電子論壇 |51黑電子論壇6群 QQ 管理員QQ:125739409;技術交流QQ群281945664

Powered by 單片機教程網

快速回復 返回頂部 返回列表
主站蜘蛛池模板: 亚洲精品久久久久中文字幕欢迎你 | 99精品久久99久久久久 | 911影院| 亚洲成人中文字幕 | 在线免费观看a级片 | 国产精品欧美一区二区三区不卡 | 国产精品一区一区三区 | 超碰免费在线观看 | 亚洲精品久久久久久久久久吃药 | 国产免国产免费 | 日韩一二区 | 99re99| 欧美日韩亚洲视频 | 亚洲午夜av久久乱码 | 超级碰在线 | 亚洲成人免费视频 | 拍拍无遮挡人做人爱视频免费观看 | 亚洲免费在线观看av | 9191av | 欧美 日韩 国产 成人 在线 91 | 一级毛片视频在线 | 日本精品一区二区 | 日本不卡一区 | 亚洲精品1 | 91看片在线观看 | 亚洲精品在线视频 | 四虎永久影院 | 99热精品6 | 日韩一区二区久久 | 亚洲成人网在线 | 日本在线你懂的 | 国产亚洲一区二区三区 | 一区二区视频在线 | 日韩一级免费看 | 国产成人免费视频 | 狠狠躁天天躁夜夜躁婷婷老牛影视 | 天堂一区二区三区 | 91视频亚洲 | 麻豆视频在线看 | 日韩色图视频 | 区一区二区三在线观看 |