STM32:啟動過程


前言

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

  CPU從地址偏移量為0的地址處開始執行;該地址燒錄的代碼必須是xx.s啟動文件,使用匯編語言編寫;

  上電之后,具體步驟截圖如下;執行完以下步驟之后單片機就可以開始使用外設,運行邏輯代碼了;

  

  另外,MDK並沒有將啟動文件的所有配置開源,比如一部分的配置由__main閉源執行;

  我們只能配置開源的一部分啟動文件的參數;本文我們了解一下完整的啟動流程及其原理;

1 boot的啟動方式

   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 startup_stm32h743xx.s的堆棧

  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 startup_stm32h743xx.s的中斷向量表

;**************中斷向量表*****************************************************************
                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            ;__main為編譯器自動生成的函數,主要是解析代碼段table的數據,然后跳轉到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 main函數的初始化

  在配置外設之前,單片機首先還需要配置一下工作環境;主要涉及到以下幾個c文件的函數;具體的話也不要求精確分析,輪廓了解一下;

  4.1 stm32h7xx_hal.c

    主要是IO電壓的配置函數,boot引腳的啟動配置,以及systick的hal庫函數;

/* stm32h7xx_hal.h: line528: Peripheral Control functions  systick使用和配置,電壓配置,boot配置等******************/
void HAL_IncTick(void);                        //SysTick_Handler()中斷服務函數調用的函數,裝載systick為1ms(HAL_TICK_FREQ_1KHZ),
void HAL_Delay(__IO uint32_t Delay);          //systick時鍾延時,單位ms;
uint32_t HAL_GetTick(void);                
uint32_t HAL_GetTickPrio(void);
HAL_StatusTypeDef HAL_SetTickFreq(HAL_TickFreqTypeDef Freq);
HAL_TickFreqTypeDef HAL_GetTickFreq(void);
void HAL_SuspendTick(void);                    //掛起systick
void HAL_ResumeTick(void);                     //恢復systick
void HAL_SYSCFG_VREFBUF_VoltageScalingConfig(uint32_t VoltageScaling);    //line575:配置IO口輸出的電壓范圍

  4.2 stm32h7xx_hal_rcc.c

    主要是rcc相關的晶振使能,以及總線時鍾的配置;以及一些查看配置參數的函數;

/*stm32h7xx_hal_rc.h    line2814*/
void HAL_RCC_DeInit(void);//復位RCC默認配置;使用HSI;關閉HSE,PLL;根據時鍾樹可知外設及總線為HSI-64Mhz
HAL_StatusTypeDef HAL_RCC_OscConfig(RCC_OscInitTypeDef *RCC_OscInitStruct);//配置晶振是否使能,以及PLL1不為sysclk時PLL1的參數配置;
HAL_StatusTypeDef HAL_RCC_ClockConfig(RCC_ClkInitTypeDef *RCC_ClkInitStruct, uint32_t FLatency);//配置CPU(sysclk),AHB,APB..分頻系數;

  4.3 stm32h7xx_hal_cortex.c

    主要是cortex內核的外設NVIC和MPU的配置函數;以及一些systick的使用函數;之后再分析內核外設的時候一起講把;

    不知道為什么在stm32h7xx_hal.c已經有systick的函數了,這里又有,沒有將他們放一個文件里;先留着疑問;

5 小結

  1)對於啟動配置而言一直以來只使用過flash啟動;感覺其他的具體也沒試過,等需要的時候看看安富萊的視頻把;

      之前由於代碼配置錯誤導致芯片不能flash下載的時候,試過把boot引腳切換到ISP下載模式,然后使用SWD下載;

      當然下載之后是跑不起來的,然后再重新把boot引腳改為flash啟動,此時芯片可以重新flash下載;

  2)對於堆棧,注意一下,我們平常使用的局部數組什么的不要超出棧的內存大小,超出的話程序卡死,然后就跑不動了;

      平常說的push和pop應該只針對棧而言;如果堆也能push和pop,針對一整塊動態內存,怎么搞呢?從低地址到高地址搞;

  3)至於中斷向量表,主要是從偏移量0地址處開始依次分配內存,用來存儲各種中斷服務程序入口地址;

    一些內核級別的中斷程序的弱聲明,這些程序除了Reset_Handler中斷的弱函數執行了操作外,其他的都是循壞自己,使用時需要在用戶程序中重新編寫;

   后面還有外設中斷服務程序的弱聲明,這些外設中斷服務程序執行都是不斷遞歸循壞自己;使用時需要在用戶程序中重新編寫;

  4)啟動文件本質來說,除了分配中斷向量表空間之外,只主動執行了一個Reset_Handler一個函數,

   Reset_Handler函數跳轉到system_init函數執行,然后跳轉到MDK的__main函數執行;其他的函數都是弱聲明,不斷遞歸調用自己,使用的話需要重新寫函數;


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM