1. 程式人生 > 實用技巧 >STM32:啟動過程

STM32:啟動過程

前言

  MDK並沒有將所有的啟動檔案的所有配置開源,我們我們只能配置一部分啟動檔案的引數;本文我們瞭解一下這一部分可以配置的引數;

  上電之後,CPU首先根據boot引腳選擇儲存器重對映區域,將該區域的地址重對映為地址偏移量為0;

  CPU從地址偏移量為0的地址處開始執行;一開始執行的燒錄程式碼是xx.s啟動檔案,使用匯編語言編寫;

1 啟動方式

  1.1 對於STM32的F0和F4開發板而言,一共有3種啟動方式;以下的儲存空間大小和地址是以大容量F1晶片作為參考的;不同晶片有一些差別;

    注意下面儲存空間大小的單位都是byte,這是因為記憶體的資料線是8bit的;記憶體的資料線和微控制器的資料線是不同的匯流排;

boot[0:1] 啟動地址 儲存空間 啟動方式 事項
[0:x]

0x0800 0000-

0x0807 FFFF

512K

bytes

Flash啟動

使用JTAG或SWD下載

較為常用;

[1:0]

0x1FFF F000-

0x1FFF F7FF

2K

bytes

系統儲存器啟動

使用串列埠下載,程式碼是通過bootloader搬運到flash中;

該區域記憶體儲了廠家燒錄的bootloader程式(即ISP程式),需要配合st提供的下載軟體;

[1:1]

0x2000 0000-

0x2001 0000

64K

bytes

內嵌SRAM啟動

使用JTAG或SWD下載,僅支援除錯模式;較為少用;

沒有掉電儲存程式的功能,需要新增巨集定義VECT_TAB_SRAM,配合指令碼檔案使用;

  1.2 對於STM32H7x3 而言,只有一個boot引腳,配合BOOT_ADD0/BOOT_ADD1 的組合配置,可以讓系統從兩個不同的區域啟動;

boot引腳 啟動地址 預設值 預設值啟動方式 事項
0 BOOT_ADD0[15:0] 0x0800 Flash啟動

啟動地址高16位由BOOT_ADD0暫存器決定,低16位為0x0000;

BOOT_ADD0暫存器可以修改,修改後掉電不丟失;

0x0000 0000 到 0x3FFF 0000 的儲存器地址都可以設定;

1 BOOT_ADD1[15:0] 0x1FF0 系統儲存器啟動

2 堆疊
  2.1 堆疊的彙編程式

 1 ;********************** 目的是分配資料段用來做"棧"**********************
2 ; 表示即將宣告資料段STACK用來做"棧",不用填入初始資料,可讀寫,首地址按照2^3對齊(8位元組對齊); 3 Stack_Size EQU 0x00001000 4 AREA STACK, NOINIT, READWRITE, ALIGN=3 5 Stack_Mem SPACE Stack_Size 6 __initial_sp 7 8 ; **********************目的是分配資料段用來做"堆"********************** 9 Heap_Size EQU 0x0000800 10 AREA HEAP, NOINIT, READWRITE, ALIGN=3 11 __heap_base 12 Heap_Mem SPACE Heap_Size 13 __heap_limit

  2.2那麼什麼是堆疊呢?總結了一下,大概概括成了下面的表格形式;

STACK棧

主要用來儲存區域性變數,變數的記憶體塊;棧空間是從頂層開始向下生長的;

對於多級呼叫的函式入口地址,需要使用棧空間配合連線暫存器來儲存主調函式的地址;

Stack_Mem 表示棧的首地址,應該是給microLIB庫使用的;
__initial_sp 棧標號,表示棧頂地址;也就是棧的記憶體最大地址;
HEAP堆

動態分配時使用的記憶體塊;

如果程式中沒有使用到動態記憶體分配的話,編譯器是不會編譯出"堆"空間的;



__heap_base 堆標號,表示"堆"的起始地址
Heap_Mem 表示堆的首地址,應該是給microLIB庫使用的;
__heap_limit 堆標號,表示"堆"的結束地址

  2.3至於堆疊標號Stack_Mem和Stack_Size大概是標識堆疊地址給microLIB庫使用的把,應該沒什麼用;如下所示

;MDK針對嵌入式推出了microLIB小型庫,在功能上是用來替代C標準庫的;
;下面程式碼的功能主要是決定要不要啟用microLIB庫;以下程式碼比較不重要;
                 IF      :DEF:__MICROLIB                
                 EXPORT  __initial_sp
                 EXPORT  __heap_base
                 EXPORT  __heap_limit                
                 ELSE             
                 IMPORT  __use_two_region_memory
                 EXPORT  __user_initial_stackheap               
__user_initial_stackheap
                 LDR     R0, =  Heap_Mem
                 LDR     R1, =(Stack_Mem + Stack_Size)
                 LDR     R2, = (Heap_Mem +  Heap_Size)
                 LDR     R3, = Stack_Mem
                 BX      LR
                 ALIGN
                 ENDIF

3 啟動檔案

;**************中斷向量表*****************************************************************
                AREA    RESET, DATA, READONLY   ; 即將宣告一個記憶體區域RESET,是資料段,只讀;
                EXPORT  __Vectors               ; 宣告"標識__Vectors"可以被外部檔案呼叫
                EXPORT  __Vectors_End           ; 宣告"標識__Vectors_End"可以被外部檔案呼叫
                EXPORT  __Vectors_Size          ; 宣告"標識__Vectors_Size"可以被外部檔案呼叫

__Vectors       DCD     __initial_sp            ; 分配4位元組記憶體,初始化為棧頂地址,該記憶體開始的地址標識為"__Vectors"
                DCD     Reset_Handler           ; 分配4位元組記憶體,放入復位中斷服務函式的"地址標識Reset Handler"
                DCD     NMI_Handler             ; 
                ;.....
                DCD     0                       ;                                                             
                DCD     WAKEUP_PIN_IRQHandler   ; 
__Vectors_End                                   ; 記憶體結束的地址標識為"__Vectors_End"

__Vectors_Size  EQU  __Vectors_End - __Vectors  ; 宣告標識"__Vectors_Size"用來表示中斷向量表的大小

;**************彙編程式碼段:上電覆位中斷服務程式舉例***************************************************************
                AREA    |.text|, CODE, READONLY ; 即將宣告一個記憶體區域.text,是程式碼段,只讀;

Reset_Handler    PROC                           ;偽指令PROC和ENDP用來宣告一個程式,當前程式段標識為"Reset_Handler"
                 EXPORT  Reset_Handler   [WEAK] ;聲明當前程式可被外部函式呼叫,為弱函式
        IMPORT  SystemInit                      ;引入檔案外部宣告的函式SystemInit()
        IMPORT  __main                          ;引入檔案外部宣告的函式__main()
                 LDR     R0, =SystemInit        ;將32bit函式入口地址放入R0暫存器中?
                 BLX     R0                     ;跳轉到R0暫存器內的地址去執行?
                 LDR     R0, =__main            ;
                 BX      R0                     ;
                 ENDP                           ;

;**************彙編程式碼段:NMI中斷服務程式舉例***************************************************************
NMI_Handler     PROC                            ;
                EXPORT  NMI_Handler     [WEAK]  ;這裡匯入了中斷服務程式,如果外部檔案寫了,那此處的弱函式就不使用了
                B       .                       ;B. 表示在這裡跳轉當前程式? 就是跳轉到前面匯入的程式
                ENDP                            ;
  3.1 偽指令     相當於前處理器指令,編譯器在編譯時會進行替換,並不佔用記憶體空間;
EQU 宣告常數,彙編的常數單位不是bit,而是byte
AREA 表示即將分配一個數據段或程式碼段;需要跟分配記憶體的SPACE指令一起使用;
EXPORT 宣告為可被外部檔案引用,主要提供給連結器用於連線庫檔案;
IMPORT 引入外部檔案宣告
WEAK 弱宣告,如果外部檔案有相同的宣告,則編譯外部檔案的宣告,不編譯弱宣告
ALIGN 宣告編譯器需要對指令或資料的存放地址進行對齊;預設預設值為32bit對齊
PRESERVE8 當前檔案的堆疊需按照8位元組對齊,也就是64bit資料線對齊;
THUMB 表示接下來的指令都是thumb指令集,cm3,cm7採用的是thumb-2指令集,

  3.2彙編指令

SPACE 分配一段記憶體空間,並初始化這些記憶體空間為0; 需要跟宣告記憶體屬性的AREA偽指令一起使用;
DCD Define Constant Double-words;分配4位元組的記憶體空間用來儲存一個32bit的資料;並初始化這些記憶體空間;
LDR Load word;載入指令,將32bit資料存入目的暫存器中;
B Branch,跳轉到目標地址執行,執行指令集為ARM指令集
BX Branch with Exchange;跳轉到目的地址執行,並且指令集從ARM指令集切換到thumb指令集;
BLX

Branch with Link and Exchange;thumb-2相容許多thumb指令,但是cortex-m3不支援當前指令;

  3.3 註釋   
零散1 [地址]:地址加了[],用來表示取出地址內的資料;
零散2 彙編指令中的常數都是地址,自然數的格式為#常數;
4 小結   1)對於啟動配置而言一直以來只使用過flash啟動;感覺其他的具體也沒試過,等需要的時候看看安富萊的視訊把;   之前由於程式碼配置錯誤導致晶片不能flash下載的時候,試過把boot引腳切換到ISP下載模式,然後使用SWD下載;   當然下載之後是跑不起來的,然後再重新把boot引腳改為flash啟動,此時晶片可以重新flash下載;不曉得為啥,也可行就是;   2)對於堆疊,注意一下,我們平常使用的區域性陣列什麼的不要超出棧的記憶體大小,超出的話程式卡死,然後就跑不動了;   平常說的push和pop應該只針對棧而言;如果堆也能push和pop,針對一整塊動態記憶體,怎麼搞呢?   3)至於中斷向量表,主要是從偏移量0地址處開始依次分配記憶體,用來儲存各種中斷服務程式入口地址;   一些核心級別的中斷程式的弱宣告,這些程式除了Reset_Handler中斷的弱函式執行了操作外,其他的都是循壞自己,使用時需要在使用者程式中重新編寫;   後面還有外設中斷服務程式的弱宣告,這些外設中斷服務程式執行都是不斷遞迴循壞自己;使用時需要在使用者程式中重新編寫;