Stm32單片機開發KEIL啟動文件彙編語言詳解

文章目錄

  1. 簡介
  2. 啟動文件彙編代碼相關指令
  3. 堆棧空間的定義
  4. 初始化中斷向量表
  5. 復位中斷函數
  6. 中斷函數的弱(WEAK)聲明
  7. 用戶棧和堆初始化

簡介

我們在做單片機編程的時候,大部分都是用KEIL自帶的啟動文件來使程序進入C語言main函數,然後進行C語言編程開發的工作。那麼這個啟動文件到底做了什麼呢?相信朋友們肯定和我一樣好奇,想弄明白啟動文件到底都幹了些什麼。那麼本文就來介紹下,本文介紹stm32啟動文件彙編代碼,對應文件名startup_stm32f10x_hd.s。其他Cortex-M3內核的單片機都是大同小異的。

其實啟動文件存在的目的就是構建可以供C語言代碼運行的工作環境,比如傳遞參數時需要的棧空間初始化,動態分配內存時的堆初始化,一些初始化為0的變量空間的初始化等等。如果這些沒有配置好,無法達到C語言代碼運行的工作環境,那麼後面的C語言代碼執行的結果就是不對的,也會導致總個系統無法工作。所以啟動文件很重要,也正是因為我們覺得它重要,所以才想搞懂它。

startup_stm32f10x_hd.s啟動文件中的彙編代碼主要做了下面5個工作。

1.堆棧空間的定義;

2.初始化中斷向量表;

3.復位中斷函數(Reset_Handler){系統初始化,然後進入main函數};

5.用戶棧和堆初始化

再介紹這5個部分的詳細代碼前,這裡已經先總結了啟動文件中用到的彙編代碼與編譯器相關的指令,下面就先來介紹下這些指令。

啟動文件彙編代碼相關指令

啟動文件代碼主要由ARM指令代碼和與編譯器相關的彙編指令組成,下表羅列了啟動文件中用到的相關彙編指令。

Stm32單片機開發KEIL啟動文件彙編語言詳解

上面只是對指令做了簡要的說明,後面代碼用到時我們再一一講解。我們也可以通過在代碼中選中指令,按F1按鍵調出幫助說明,查看具體指令的相關介紹。

Stm32單片機開發KEIL啟動文件彙編語言詳解

接下來我們就對代碼做詳細分析吧。

一、堆棧空間的定義

Stm32單片機開發KEIL啟動文件彙編語言詳解

首先這裡使用指令EQU定義了一個數值常量符號Stack_Size指明棧大小為0x00000400即1K,這個值可以根據實際需求更改。使用QEU定義常數,類似與C語言的#define定義常數。

然後這裡又使用指令AREA定義了一個未初始化,可以讀寫並要求8字節邊界對齊的段,段名,為STACK。這裡可以為段選擇任何段名。但是,以一個數字開始的名稱必須包含在豎槓號內,否則會產生一個缺失段名錯誤。例如, |1_DataArea|。有些名稱是習慣性的名稱。例如,|.text| 用於表示由 C 編譯程序產生的代碼段,或用於以某種方式與 C 庫關聯的代碼段。這裡的NOINIT表示數據段是未初始化的或初始化為零。其只包含零初始化的空間保留命令 SPACE 或 DCB, DCD, DCDU, DCQ, DCQU, DCW 或 DCWU 。可以決定在鏈接時 AREA 是未初始化的,還是零初始化的,後面一條指令是SPACE,所以這裡要初始化為0,READWRITE指明段可以讀寫,ALIGN=3指明段要在2^3=8字節邊界上對齊。

再然後使用SPACE定義了一個初始化為0的存儲塊Stack_Mem,可以理解為存儲塊是歸別到段裡的。標號__initial_sp緊挨著SPACE語句放置,表示棧的結束地址,即棧頂地址,棧是由高向低生長的。

同樣的堆也是這樣定義的,只是這裡先是指明瞭堆開始__heap_base(堆起始地址),再指明堆存儲塊,最後指明__heap_limit(堆終止地址)。堆是由低向高生長的,跟棧的生長方向相反。堆主要用來動態內存的分配,像malloc()函數申請的內存就在堆上面。這裡堆默認大小為0x00000200即512字節,一般的程序中我們很少用到malloc函數,所以這裡也就不做過多更改,如果要使用malloc函數,需要將此處堆大小定義的值根據需求改大。

後面的PRESERVE8,指明當前文件的堆棧按照8字節對齊。

二、初始化中斷向量表

Stm32單片機開發KEIL啟動文件彙編語言詳解

THUMB指示彙編器將隨後的指令解釋為16位的Thumb指令。Cortex-M3使用的是Thumb-2指令集,是一種介於Thumb指令集和ARM指令集。ARM指令集全部是32位的,Thumb指令集全部是16位的,Thumb-2指令集是即有部分16位Thumb指令的也有部分32位的ARM指令。

後面定義一個只讀數據段RESET,用於保存中斷向量表,和三個標號__Vectors(向量表開始)、__Vectors_End(向量表結束)和__Vectors_Size(向量表大小)並使用EXPORT指明其具有全局性。這樣可以使在其他文件中訪問此文件中的這三標號。

DCD 命令分配一個或多個字的存儲器,在四個字節的邊界上對齊,並定義存儲器的運行時初值。

__Vectors DCD __initial_sp ; Top of Stack

這裡就指示了段的開始為向量表的開始,標號__Vectors(向量表開始)編譯器會根據不同單片機為其指定值,比如stm32單片機就是0x08000000,然後我們定義的RESET段就被分在了0x08000000開始的地址處,其結束位置就是從0x08000000開始依次加4個字節,因為這裡每個DCD命令佔存儲器4個字節,這樣一直到__Vectors_End(向量表結束),__Vectors_Size(向量表大小)就是這個RESET段所佔大小。比如復位的時候,復位中斷來了,就從這個段的第二個存儲地址0x08000004處對應的值0x08000144作為復位函數Reset_Handler的地址。

三、復位中斷函數

Stm32單片機開發KEIL啟動文件彙編語言詳解

這裡先使用AREA定義了一個只讀代碼段。這裡的標號Reset_Handler就代表了復位函數的入口地址(函數名),使用PROC標記函數入口,使用ENDP標記函數結束。

EXPORT Reset_Handler [WEAK]

這裡EXPORT聲明Reset_Handler是一個全局性的。WEAK表示其他地方沒有定義Reset_Handler函數時,就將此處作為Reset_Handler函數的實例。IMPORT用於指示如果在當前彙編代碼中未找到其引用,則不導入該名稱,很顯然,下面有用到__main和SystemInit。

從上那些代碼我就就知道,程序上電後,從0x08000000地址處加載SP,上電覆位從0x08000004處加載PC,0x08000004處的地址就是復位函數的地址,然後復位函數里面先調用SystemInit函數來初始化系統的各種時鐘,再調用__main函數(由編譯器實現)。

復位函數中用到的Thumb-2指令介紹如下:

LDR從存儲器中加載字到一個寄存器中

BL跳轉到由寄存器/標號給出的地址,並把跳轉前的下條指令地址保存到LR

BLX跳轉到由寄存器給出的地址,並根據寄存器的LSE確定處理器的狀態,還要把跳轉前的下條指令地址保存到LR

BX跳轉到由寄存器/標號給出的地址,不用返回

Stm32單片機開發KEIL啟動文件彙編語言詳解

這裡定義了各種中斷函數,使用PROC表示函數開始,ENDP表示函數結束,EXPORT說明函數的全局性,WEAK說明如果其他地方沒有定義這個函數,那麼就把此處作為函數的實例。這裡函數的代碼都是B . 。這裡的B表示跳轉到一個標號,這裡跳轉到一個'.',即表示無限循環,所以我們在寫C語言程序時如果沒有寫中斷函數,那麼對應的中斷來了會運行這裡到中斷函數,即B .那麼將無限循環在此。

Stm32單片機開發KEIL啟動文件彙編語言詳解

ALIGN命令通過用零或空指令NOP填充,來使當前位置與一個指定的邊界對齊。使用ALIGN來確保數據和代碼對齊到適當的邊界上。這裡使用了默認為字對齊方式。

五、用戶棧和堆初始化

Stm32單片機開發KEIL啟動文件彙編語言詳解

棧和堆初始化部分,這裡IF、ELSE、ENDIF是條件編譯。

先判斷是否定義了__MICROLIB ,如果定義了則賦予標號__initial_sp(棧頂地址)、__heap_base(堆起始地址)、__heap_limit(堆結束地址)全局屬性,可供外部文件調用,這樣我們使用到molloc函數申請的空間就是從這裡有關堆的兩個標號之間的內存中申請的。如果沒有定義(實際的默認情況就是我們沒定義__MICROLIB)則通過 IMPORT __use_two_region_memory 表明使用雙段模式,即一部分儲存區用於棧空間,其他的存儲區用於堆空間,堆區空間可以為0,但是,這樣就不能調用malloc()內存分配函數。然後使用__user_initial_stackheap標號處的代碼用於初始化用戶堆棧,這部分由編譯器提供的__main來調用。

END 命令指示彙編器,已到達一個源文件的末尾。

關於STM32單片機的Keil啟動文件彙編代碼就講解完了,大家有沒有看明白呢,歡迎評論交流,如果覺得我這篇文章寫到很好到話,就轉發出去分享給更多到朋友吧。最後歡迎大家點贊評論轉發收藏,跟多好文章歡迎關注我——單片機嵌入式愛好者


分享到:


相關文章: