單片機STM32的啟動文件詳解–學習筆記

  • 2019 年 12 月 27 日
  • 筆記

  啟動文件簡介

  啟動文件由彙編編寫,是系統上電複位後第一個執行的程式。主要做了以下工作:

  1、初始化堆棧指針SP=_initial_sp

  2、初始化PC 指針=Reset_Handler

  3、初始化中斷向量表

  4、配置系統時鐘

  5、調用C 庫函數_main 初始化用戶堆棧,從而最終調用main 函數去到C 的世界

  查找ARM 彙編指令

  在講解啟動程式碼的時候,會涉及到ARM 的彙編指令和Cortex 內核的指令,剩下的ARM的彙編指令我們可以在MDK->Help->Uvision Help 中搜索到,以EQU 為例,檢索如下:

  圖1 ARM 彙編指令索引

  檢索出來的結果會有很多,我們只需要看Assembler User Guide 這部分即可。下面列出了啟動文件中使用到的ARM 彙編指令,該列表的指令全部從ARM Development Tools這個幫助文檔裡面檢索而來。其中編譯器相關的指令WEAK 和ALIGN 為了方便也放在同一個表格了。

  啟動文件程式碼講解

  1. Stack—棧

  開闢棧的大小為0X00000400(1KB),名字為STACK,NOINIT 即不初始化,可讀可寫,8(2^3)位元組對齊。

  棧的作用是用於局部變數,函數調用,函數形參等的開銷,棧的大小不能超過內部SRAM 的大小。如果編寫的程式比較大,定義的局部變數很多,那麼就需要修改棧的大小。如果某一天,你寫的程式出現了莫名奇怪的錯誤,並進入了硬fault 的時候,這時你就要考慮下是不是棧不夠大,溢出了。

  EQU:宏定義的偽指令,相當於等於,類似與C 中的define。

  AREA:告訴彙編器彙編一個新的程式碼段或者數據段。STACK 表示段名,這個可以任意命名;NOINIT 表示不初始化;READWRITE 表示可讀可寫,ALIGN=3,表示按照2^3對齊,即8 位元組對齊。

  SPACE:用於分配一定大小的記憶體空間,單位為位元組。這裡指定大小等於Stack_Size。標號__initial_sp 緊挨著SPACE 語句放置,表示棧的結束地址,即棧頂地址,棧是由高向低生長的。

  2. Heap 堆

  開闢堆的大小為0X00000200(512 位元組),名字為HEAP,NOINIT 即不初始化,可讀可寫,8(2^3)位元組對齊。__heap_base 表示對的起始地址,__heap_limit 表示堆的結束地址。堆是由低向高生長的,跟棧的生長方向相反。

  堆主要用來動態記憶體的分配,像malloc()函數申請的記憶體就在堆上面。這個在STM32裡面用的比較少。

  1 PRESERVE8

  2 THUMB

  PRESERVE8:指定當前文件的堆棧按照8 位元組對齊。

  THUMB:表示後面指令兼容THUMB 指令。THUBM是ARM以前的指令集,16bit,現在Cortex-M系列的都使用THUMB-2 指令集,THUMB-2 是32 位的,兼容16 位和32 位的指令,是THUMB 的超集。

  3. 向量表

  1 AREA RESET, DATA, READONLY

  2 EXPORT __Vectors

  3 EXPORT __Vectors_End

  4 EXPORT __Vectors_Size

  定義一個數據段,名字為RESET, 可讀。並聲明 __Vectors 、__Vectors_End 和__Vectors_Size 這三個標號具有全局屬性,可供外部的文件調用。

  EXPORT:聲明一個標號可被外部的文件使用,使標號具有全局屬性。如果是IAR 編譯器,則使用的是GLOBAL 這個指令。

  當內核響應了一個發生的異常後,對應的異常服務常式(ESR)就會執行。為了決定 ESR的入口地址, 內核使用了―向量表查表機制‖。這裡使用一張向量表。向量表其實是一個WORD( 32 位整數)數組,每個下標對應一種異常,該下標元素的值則是該 ESR 的入口地址。向量表在地址空間中的位置是可以設置的,通過 NVIC 中的一個重定位暫存器來指出向量表的地址。在複位後,該暫存器的值為 0。因此,在地址 0 (即FLASH 地址0)處必須包含一張向量表,用於初始時的異常分配。要注意的是這裡有個另類: 0 號類型並不是什麼入口地址,而是給出了複位後 MSP 的初值。

  向量表

  __Vectors 為向量表起始地址,__Vectors_End 為向量表結束地址,兩個相減即可算出向量表大小。

  向量表從FLASH 的0 地址開始放置,以4 個位元組為一個單位,地址0 存放的是棧頂地址,0X04 存放的是複位程式的地址,以此類推。從程式碼上看,向量表中存放的都是中斷服務函數的函數名,可我們知道C 語言中的函數名就是一個地址。

  DCD:分配一個或者多個以字為單位的記憶體,以四位元組對齊,並要求初始化這些記憶體。在向量表中,DCD 分配了一堆記憶體,並且以ESR 的入口地址初始化它們。

  4. 複位程式

  複位子程式是系統上電後第一個執行的程式,調用SystemInit 函數初始化系統時鐘,然後調用C 庫函數_mian,最終調用main 函數去到C 的世界。

  WEAK:表示弱定義,如果外部文件優先定義了該標號則首先引用該標號,如果外部文件沒有聲明也不會出錯。這裡表示複位子程式可以由用戶在其他文件重新實現,這裡並不是唯一的。

  IMPORT:表示該標號來自外部文件,跟C 語言中的EXTERN 關鍵字類似。這裡表示SystemInit 和__main 這兩個函數均來自外部的文件。

  SystemInit()是一個標準的庫函數,在system_stm32f10x.c 這個庫文件總定義。主要作用是配置系統時鐘,這裡調用這個函數之後,單片機的系統時鐘配被配置為72M。

  __main 是一個標準的C 庫函數,主要作用是初始化用戶堆棧,並在函數的最後調用main 函數去到C 的世界。這就是為什麼我們寫的程式都有一個main 函數的原因。

  LDR、BLX、BX 是CM4 內核的指令,具體作用見下表:

  5. 中斷服務程式

  在啟動文件裡面已經幫我們寫好所有中斷的中斷服務函數,跟我們平時寫的中斷服務函數不一樣的就是這些函數都是空的,真正的中斷復服務程式需要我們在外部的C 文件裡面重新實現,這裡只是提前佔了一個位置而已。

  如果我們在使用某個外設的時候,開啟了某個中斷,但是又忘記編寫配套的中斷服務程式或者函數名寫錯,那當中斷來臨的時,程式就會跳轉到啟動文件預先寫好的空的中斷

  服務程式中,並且在這個空函數中無線循環,即程式就死在這裡。

  B:跳轉到一個標號。這裡跳轉到一個『.』,即表示無線循環。

  6. 用戶堆棧初始化

  1 ALIGN

  ALIGN:對指令或者數據存放的地址進行對齊,後面會跟一個立即數。預設表示4 位元組對齊。

  首先判斷是否定義了__MICROLIB ,如果定義了這個宏則賦予標號__initial_sp(棧頂地址)、__heap_base(堆起始地址)、__heap_limit(堆結束地址)全局屬性,可供外部文件調用。有關這個宏我們在KEIL 裡面配置,具體見下圖 。然後堆棧的初始化就由C 庫函數_main 來完成。

  如果沒有定義__MICROLIB , 則才用雙段存儲器模式, 且聲明標號__user_initial_stackheap 具有全局屬性,讓用戶自己來初始化堆棧。

  IF,ELSE,ENDIF:彙編的條件分支語句,跟C 語言的if ,else 類似

  END:文件結束