我們在做單片機編程的時候,大部分都是用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函數};
上面隻是對指令做了簡要的說明,後面代碼用到時我們再一一講解。我們也可以通過在代碼中選中指令,按F1按鍵調出幫助說明,查看具體指令的相關介紹。
接下來我們就對代碼做詳細分析吧。
一、堆棧空間的定義
首先這裡使用指令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字節對齊。
二、初始化中斷向量表
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的地址。
三、複位中斷函數
這裡先使用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跳轉到由寄存器/标号給出的地址,不用返回
四、中斷函數的弱(WEAK)聲明
這裡定義了各種中斷函數,使用PROC表示函數開始,ENDP表示函數結束,EXPORT說明函數的全局性,WEAK說明如果其他地方沒有定義這個函數,那麼就把此處作為函數的實例。這裡函數的代碼都是B . 。這裡的B表示跳轉到一個标号,這裡跳轉到一個'.',即表示無限循環,所以我們在寫C語言程序時如果沒有寫中斷函數,那麼對應的中斷來了會運行這裡到中斷函數,即B .那麼将無限循環在此。
ALIGN命令通過用零或空指令NOP填充,來使當前位置與一個指定的邊界對齊。使用ALIGN來确保數據和代碼對齊到适當的邊界上。這裡使用了默認為字對齊方式。
五、用戶棧和堆初始化
棧和堆初始化部分,這裡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啟動文件彙編代碼就講解完了,大家有沒有看明白呢,歡迎評論交流,如果覺得我這篇文章寫到很好到話,就轉發出去分享給更多到朋友吧。最後歡迎大家點贊評論轉發收藏,跟多好文章歡迎關注我——單片機嵌入式愛好者。
,更多精彩资讯请关注tft每日頭條,我们将持续为您更新最新资讯!