中文亚洲精品无码_熟女乱子伦免费_人人超碰人人爱国产_亚洲熟妇女综合网

當前位置: 首頁 > news >正文

榆中建設局網(wǎng)站站長工具查詢域名

榆中建設局網(wǎng)站,站長工具查詢域名,wordpress 怎么安裝插件,徐州旅游的網(wǎng)站建設目錄 STM32啟動文件簡介啟動文件中的一些指令 啟動文件代碼詳解??臻g的開辟堆空間的開辟中斷向量表定義(簡稱:向量表)復位程序對于weak的理解對于_main函數(shù)的分析 中斷服務程序用戶堆棧初始化 系統(tǒng)啟動流程 STM32啟動文件簡介 STM32啟動文件…

目錄

  • STM32啟動文件簡介
    • 啟動文件中的一些指令
  • 啟動文件代碼詳解
    • ??臻g的開辟
    • 堆空間的開辟
    • 中斷向量表定義(簡稱:向量表)
    • 復位程序
      • 對于weak的理解
      • 對于_main函數(shù)的分析
    • 中斷服務程序
    • 用戶堆棧初始化
  • 系統(tǒng)啟動流程

STM32啟動文件簡介

STM32啟動文件由ST官方提供,在官方的固件包里。啟動文件由匯編編寫,是系統(tǒng)上電復位后第一個執(zhí)行的程序。

啟動文件主要做了以下工作:

  1. 初始化堆棧指針 SP = _initial_sp
  2. 初始化程序計數(shù)器指針 PC = Reset_Handler
  3. 設置堆和棧的大小
  4. 初始化中斷向量表
  5. 配置外部SRAM作為數(shù)據(jù)存儲器(可選)
  6. 配置系統(tǒng)時鐘,通過調(diào)用SystemInit函數(shù)(可選)
  7. 調(diào)用C庫中的 _main 函數(shù)初始化用戶堆棧,最終調(diào)用 main 函數(shù)

ARM指針寄存器 —— 堆棧指針寄存器SP、程序計數(shù)器PC、連接寄存器LR

堆棧指針R13(SP):每一種異常模式都有其自己獨立的R13,它通常指向異常模式所專用的堆棧,也就是說五種異常模式、非異常模式(用戶模式和系統(tǒng)模式),都有各自獨立的堆棧,用不同的堆棧指針來索引。這樣當ARM進入異常模式的時候,程序就可以把一般通用寄存器壓入堆棧,返回時再出棧,保證了各種模式下程序的狀態(tài)的完整性;

連接寄存器R14(LR):每種模式下R14都有自身版組,它有兩個特殊功能;

  1. 保存子程序返回地址;使用BL或BLX時,跳轉指令自動把返回地址放入R14中;子程序通過把R14復制到PC來實現(xiàn)返回,通常用下列指令之一:

    MOV PC, LR
    BX LR
    

    通常子程序這樣寫,保證了子程序中還可以調(diào)用子程序:

    stmfd sp!, {lr}
    ...
    ldmfd sp!, {pc}
    
  2. 當異常發(fā)生時,異常模式的R14用來保存異常返回地址,將R14:如??梢蕴幚砬短字袛?/p>

程序計數(shù)器R15(PC):PC是有讀寫限制的,當沒有超過讀取限制的時候,讀取的值是指令的地址加上8個字節(jié),由于ARM指令總是以字對齊的,故bit[1:0]總是00。當用str或stm存儲PC的時候,偏移量有可能是8或12等其它值。

ARM處理器使用流水線來增加處理器指令流的速度,這樣可使幾個操作同時進行,并使處理與存儲器系統(tǒng)之間的操作更加流暢,連續(xù),能提供0.9MIPS/MHZ的指令執(zhí)行速度。

在這里插入圖片描述
在隨機存儲器區(qū)劃出一塊區(qū)域作為堆棧區(qū),數(shù)據(jù)可以一個個順序地存入(壓入)到這個區(qū)域之中,這個過程稱為壓棧(push )。通常用一個指針(堆棧指針 SP—StackPointer)實現(xiàn)做一次調(diào)整,SP總指向最后一個壓入堆棧的數(shù)據(jù)所在的數(shù)據(jù)單元(棧頂)。從堆棧中讀取數(shù)據(jù)時,按照堆棧 指針指向的堆棧單元讀取堆棧數(shù)據(jù),這個過程叫做彈出(pop ),每彈出一個數(shù)據(jù),SP 即向相反方向做一次調(diào)整,如此就實現(xiàn)了后進先出的原則。

堆棧是計算機中廣泛應用的技術,基于堆棧具有的數(shù)據(jù)進出LIFO特性,常應用于:

  1. 保存中斷斷點;
  2. 保存子程序調(diào)用返回點;
  3. 保存CPU現(xiàn)場數(shù)據(jù)等;
  4. 也用于程序間傳遞參數(shù);

ARM處理器中通常將寄存器R13作為堆棧指針(SP)。ARM處理器針對不同的模式,共有 6 個堆棧指針(SP),其中用戶模式和系統(tǒng)模式共用一個SP,每種異常模式都有各自專用的R13寄存器(SP)。它們通常指向各模式所對應的專用堆棧,也就是ARM處理器允許用戶程序有六個不同的堆??臻g。這些堆棧指針分別為R13、R13_svc、R13_abt、R13_und、R13_irq、R13_fiq,如下表堆棧指針寄存器所示。

堆棧指針用戶系統(tǒng)管理中止未定義中斷快速中斷
R13 (SP)R13R13R13_svcR13_abtR13_undR13_irqR13_fiq

為了更準確地描述堆棧,根據(jù)“壓棧”操作時堆棧指針的增減方向,將堆棧區(qū)分為‘遞增堆?!?#xff08;SP 向大數(shù)值方向變化)和‘遞減堆棧’(SP 向小數(shù)值方向變化);又根據(jù)SP 指針指向的存儲單元是否含有堆棧數(shù)據(jù),又將堆棧區(qū)分為‘滿堆?!?#xff08;SP 指向單元含有堆棧有效數(shù)據(jù))和‘空堆?!?#xff08;SP 指向單元不含有堆棧有效數(shù)據(jù))。

這樣兩兩組合共有四種堆棧方式——滿遞增、空遞增、滿遞減和空遞減。
ARM處理器的堆棧操作具有非常大的靈活性,對這四種類型的堆棧都支持。
ARM處理器中的R13被用作SP。當不使用堆棧時,R13 也可以用做通用數(shù)據(jù)寄存器。

棧的整體作用

  1. 保護現(xiàn)場;
  2. 傳遞參數(shù);
  3. 臨時變量保存在棧中;

深入理解ARM三個寄存器

在這里插入圖片描述

PC 代表程序計數(shù)器,流水線使用三個階段,因此指令分為三個階段執(zhí)行:

  1. 取指:從存儲器裝載一條指令;
  2. 譯碼:識別將要被執(zhí)行的指令;
  3. 執(zhí)行:處理指令并將結果寫回寄存器;

R15(PC)總是指向 正在取指 的指令:ARM指令是三級流水線,取指、譯指、執(zhí)行是同時進行的,現(xiàn)在PC指向的是正在取指的地址,那么cpu正在譯指的指令地址是PC-4(假設在ARM狀態(tài) 下,一個指令占4個字節(jié)),cpu正在執(zhí)行的指令地址是PC-8,也就是說PC所指向的地址和現(xiàn)在所執(zhí)行的指令地址相差8。當突然發(fā)生中斷的時候,保存的是PC的地址這樣你就知道了,如果返回的時候返回PC,那么中間就有一個指令沒有執(zhí)行,所以用 SUB pc lr -lrq #4

啟動文件中的一些指令

指令名稱作用
EQU給數(shù)字常量取一個符號名,相當于C語言中的define
AREA匯編一個新的代碼段或者數(shù)據(jù)段
ALIGN編譯器對指令或者數(shù)據(jù)的存放地址進行對齊,一般需要跟一個立即數(shù),缺省表示4字節(jié)對齊。要注意的是,這個不是ARM的指令,是編譯器的,這里放到一起為了方便
SPACE分配空間
PRESERVE8當前文件堆棧需要按照8字節(jié)對齊
THUMB表示后面指令兼容 THUMB 指令。在 ARM 以前的指令集中有 16 位的THUMBM 指令,現(xiàn)在 Cortex-M 系列使用的都是 THUMB-2 指令集,THUMB-2 是 32 位的,兼容 16 位和 32 位的指令,是 THUMB 的超級版
EXPORT聲明一個標號具有全局屬性,可被外部的文件使用
DCD以字節(jié)為單位分配內(nèi)存,要求 4 字節(jié)對齊,并要求初始化這些內(nèi)存
PROC定義子程序,與 ENDP 成對使用,表示子程序結束
WEAK弱定義,如果外部文件聲明了一個標號,則優(yōu)先使用外部文件定義的標號,如果外部文件沒有定義也不會出錯。要注意的是,這個不是 ARM 的指令,是編譯器的,這里放到一起為了方便
IMPORT聲明標號來自外部文件,跟 C 語言中的 extern 關鍵字類似
LDR從存儲器中加載字到一個存儲器中
BLX跳轉到由寄存器給出的地址,并根據(jù)寄存器的 LSE 確定處理器的狀態(tài),還要把跳轉前的下條指令地址保存到 LR
BX跳轉到由寄存器/標號給出的地址,不用返回
B跳轉到一個標號
IF,ELSE,ENDIF匯編條件分支語句,跟 C 語言的類似
END到達文件的末尾,文件結束

啟動文件代碼詳解

下面,我們以 STM32F103 的啟動代碼為例講解,版本是:STM32Cube_FW_F1_V1.8.0,
啟動文件名稱是:startup_stm32f103xe.s。把啟動代碼分成幾個功能段進行詳細的講解,詳
情如下

??臻g的開辟

Stack_Size		EQU		0x00000400AREA	STACK, NOINIT, READWRITE, ALIGN=3
Stack_Mem		SPACE	Stack_Size
__initial_sp

**EQU:**宏定義的偽指令,給數(shù)字常量取一個符號名,類似與 C 中的 define。定義棧大小為 0x00000400 字節(jié),即 1024B(1KB),常量的符號是 Stack_Size。

AREA 匯編一個新的代碼段或者數(shù)據(jù)段。段名為 STACK,段名可以任意命名;NOINIT 表示不初始化; READWRITE 表示可讀可寫;ALIGN=3,表示按照 2^3 對齊,即 8 字節(jié)對齊。

SPACE 分配內(nèi)存指令,分配大小為 Stack_Size 字節(jié)連續(xù)的存儲單元給??臻g。

__initial_sp 緊挨著 SPACE 放置,表示棧的結束地址,棧是從高往低生長,所以結束地址就是棧頂?shù)刂贰?/p>

棧主要用于存放局部變量,函數(shù)形參等,屬于編譯器自動分配和釋放的內(nèi)存,棧的大小不能超過內(nèi)部 SRAM 的大小。如果工程的程序量比較大,定義的局部變量比較多,那么就需要在啟動代碼中修改棧的大小,即修改 Stack_Size 的值。如果程序出現(xiàn)了莫名其妙的錯誤,并進入了 HardFault 的時候,你就要考慮下是不是??臻g不夠大,溢出了的問題。

堆空間的開辟

Heap_Size		EQU		0x00000200AREA	HEAP, NOINIT, READWRITE, ALIGN=3
__heap_base
Heap_Mem		SPACE	Heap_Size
__heap_limit

堆空間開辟代碼跟??臻g開辟代碼是類似的了。這部分代碼的意思就是:開辟堆的大小為 0x00000200(512 字節(jié)),段名為 HEAP,不初始化,可讀可寫,(2^3 對齊)8 字節(jié)對齊。__heap_base表示堆的起始地址__heap_limit 表示堆的結束地址。堆和棧的生長方向相反的,堆是由低向高生長而棧是從高往低生長。

堆主要用于動態(tài)內(nèi)存的分配,像 malloc()、calloc()和 realloc()等函數(shù)申請的內(nèi)存就在堆上面。堆中的內(nèi)存一般由程序員分配和釋放,若程序員不釋放,程序結束時可能由操作系統(tǒng)回收。

PRESERVE8
THUMB

PRESERVE8:指示編譯器按照 8 字節(jié)對齊。
THUMB:指示編譯器之后的指令為 THUMB 指令。

中斷向量表定義(簡稱:向量表)

中斷向量表定義代碼

	AREA	RESET, DATA, READONLYEXPORT	__VectorsEXPORT	__Vectors_EndEXPORT	__Vectors_Size

定義一個數(shù)據(jù)段,名字為RESET, READONLY表示只讀EXPORT表示聲明一個標號具有全局屬性,可被外部的文件使用。這里是聲明了__Vectors、__Vectors_End 和 __Vectors_Size 三個標號具有全局性,可被外部的文件使用

當內(nèi)核響應了一個發(fā)生的異常后,對應的異常服務例程(ESR)就會執(zhí)行。為了決定 ESR的入口地址, 內(nèi)核使用了向量表查表機制。向量表其實是一個 WORD(32 位整數(shù))數(shù)組,每個下標對應一種異常,該下標元素的值則是該 ESR 的入口地址。向量表在地址空間中的位置是可以設置的,通過 NVIC 中的一個重定位寄存器來指出向量表的地址。在復位后,該寄存器的值為 0。因此,在地址 0 (即 FLASH 地址 0) 處必須包含一張向量表,用于初始時的異常分配。

位置優(yōu)先級優(yōu)先級類型名稱說明地址
---保留0x0000_0000
-3固定Reset復位0x0000_0004
-2固定NMI不可屏蔽中斷;RCC時鐘安全系統(tǒng)(CSS)聯(lián)接到NMI向量0x0000_0008
-1固定硬件失效(HardFault)所有類型的失效0x0000_000C
0可設置存儲管理(MemManage)存儲器管理0x0000_0010
1可設置總線錯誤(BusFault)預取值失敗,存儲器訪問失敗0x0000_0014
2可設置錯誤應用(UsageFault)未定義的指令或非法狀態(tài)0x0000_0018
---保留0x0000_001C ~ 0x0000_002B
3可設置SVCall通過SWI指令的系統(tǒng)服務調(diào)用0x0000_002C
4可設置調(diào)式監(jiān)控(DebugMonitor)調(diào)試監(jiān)控器0x0000_0030
---保留0x0000_0034
5可設置PendSV可掛起的系統(tǒng)服務0x0000_0038
6可設置SysTick系統(tǒng)嘀嗒定時器0x0000_003C
07可設置WWDG窗口定時器中斷0x0000_0040
18可設置PVD連到EXTI的電源電壓檢測(PVD)中斷0x0000_0044
29可設置TAMPER侵入檢測中斷0x0000_0048
310可設置RTC實時時鐘(RTC)全局中斷0x0000_004C
411可設置FLASH內(nèi)存全局中斷0x0000_0050

中間部分省略,詳情請參考《STM32中文參考手冊》第九章 中斷和事件 中斷和異常向量

5663可設置DMA2通道1DMA2通道1全局中斷0x0000_0120
5764可設置DMA2通道2DMA2通道2全局中斷0x0000_0124
5865可設置DMA2通道3DMA2通道3全局中斷0x0000_0128
5966可設置DMA2通道4_5DMA2通道4和DMA2通道5全局中斷0x0000_012C

舉個例子,如果發(fā)生了異常 SVCall,則 NVIC 會計算出偏移移量是 11x4=0x2C,然后從那里取出服務例程的入口地址并跳入。要注意的是這里有個另類:地址 0x0000 0000 并不是什么入口地址,而是給出了復位后 MSP 的初值。更詳細的向量表,可以參考《STM32中文參考手冊》第九章-中斷和事件-中斷和異常向量

F103 的向量表格中灰色部分是系統(tǒng)內(nèi)核異常。表格中位置 0 到 59 是外部中斷,CM3內(nèi)核的芯片最大支持 240 個外部中斷,具體使用多少個由芯片廠家設計決定。如這個表格中的 103 芯片只是使用了 60 個。這里說的外部中斷是相對內(nèi)核而言

__Vectors		DCD		__initial_sp				;	Top of Stack	(棧頂?shù)刂?span id="vxwlu0yf4"    class="token punctuation">)DCD		Reset_Handler				;	Reset Handler	(復位程序地址)DCD		NMI_Handler					;	NMI HandlerDCD		HardFault_Handler			;	Hard Fault HandlerDCD		MemManage_Handler			;	MPU Fault HandlerDCD		UsageFault_Handler			;	Usage Fault HandlerDCD		0							;	ReservedDCD		0							;	ReservedDCD		0							;	ReservedDCD		0							;	ReservedDCD		SVC_Handler					;	SVCall HandlerDCD		DebugMon_Handler			;	Debug Monitor HandlerDCD		0							;	ReservedDCD		PendSV_Handler				;	SysTick Handler; External Interrupts(外部中斷)DCD		WWDG_IRQHandler				;	Window WatchdogDCD		PVD_IRQHandler				;	PVD through EXTI Line detectDCD		TAMPER_IRQHandler			;	TamperDCD		RTC_IRQHandler				;	RTCDCD		FLASH_IRQHandler			;	Flash; 中間篇幅太長, 省略掉, 代碼向量表與STM32F103的向量表對應DCD		DMA2_Channel1_IRQHandler	;	DMA2 Channel1DCD		DMA2_Channel2_IRQHandler	;	DMA2 Channel2DCD		DMA2_Channel3_IRQHandler	;	DMA2 Channel3DCD		DMA2_Channel4_5_IRQHandler	;	DMA2 Channel4 & Channel5
__Vectors_End__Vectors_Size	EQU		__Vectors_End - __Vectors

__Vectors 為向量表起始地址, __Vectors_End 為向量表結束地址,__Vectors_Size 為向量表大小,__Vectors_Size = __Vectors_End - __Vectors。

DCD:分配一個或者多個以字為單位的內(nèi)存,以四字節(jié)對齊,并要求初始化這些內(nèi)存。

中斷向量表被放置在代碼段的最前面。例如:當我們的程序在 FLASH 運行時,那么向量表的起始地址是:0x0800 0000。結合圖 2.3.2 可以知道,地址 0x0800 0000 存放的是棧頂?shù)刂?。DCD:以四字節(jié)對齊分配內(nèi)存,也就是下個地址是0x0800 0004,存放的是Reset_Handler中斷函數(shù)入口地址。

從代碼上看,向量表中存放的都是中斷服務函數(shù)的函數(shù)名,所以 C 語言中的函數(shù)名對芯片來說實際上就是一個地址。

復位程序

接下來是定義只讀代碼段

		AREA	|.text|, CORE, READONLY

定義一個段命為.text,只讀的代碼段,在CODE區(qū);復位子程序代碼

; Reset handler
Reset_Handler	PROC	// 子程序開始EXPORT	Reset_Handler			[WEAK]	// 聲明復位中斷向量 Reset_Handler 為全局屬性,這樣外部文件就可以調(diào)用此復位中斷服務;WEAK: 表示弱定義,如果外部文件優(yōu)先定義了該標號則首先引用外部定義的標號,如果外部文件沒有聲明也不會出錯。這里表示復位子程序可以由用戶在其他文件重新實現(xiàn),這里并不是唯一的IMPORT	__main	// IMPORT 表示該標號來自外部文件IMPORT	SystemInit	// 這里表示 SystemInit 和__main 這兩個函數(shù)均來自外部的文件LDR		R0 ,= SystemInit	// LDR 表示從存儲器中加載字到一個存儲器中; SystemInit 是一個標準的庫函數(shù),在 system_stm32f1xx.c 文件中定義,主要作用是配置系統(tǒng)時鐘、還有就是初始化 FSMC/FMC總線上外掛的 SRAM(可選),前面說配置外部 SRAM 作為數(shù)據(jù)存儲器(可選)就是這個BLX		R0	// BLX 表示跳轉到由寄存器給出的地址,并根據(jù)寄存器的 LSE 確定處理器的狀態(tài),還要把跳轉前的下條指令地址保存到 LRLDR		R0 ,= __main	// 把__main 的地址給 R0。__main 是一個標準的 C 庫函數(shù),主要作用是初始化用戶堆棧和變量等,最終調(diào)用 main 函數(shù)去到 C 的世界。這就是為什么我們寫的程序都有一個 main 函數(shù)的原因,如果不調(diào)用__main,那么程序最終就不會調(diào)用我們 C 文件里面的main,也就無法正常運行BX		R0	//  BX 表示跳轉到由寄存器/標號給出的地址,不用返回。這里表示切換到__main地址,最終調(diào)用 main 函數(shù),不返回,進入 C 的世界ENDP	// 子程序結束

利用 PROC、ENDP 這一對偽指令把程序段分為若干個過程,使程序的結構加清晰;

LDR、BLX、BX 是內(nèi)核的指令,可在《CM3 權威指南 CnR2》第四章-指令集里面查詢到

對于weak的理解

weak 顧名思義是“弱”的意思,在匯編中,在函數(shù)名稱后面加[WEAK]來表示,而在 C語言中,在函數(shù)名稱前面加上 __weak 修飾符來表示,這樣的函數(shù)我們稱為“弱函數(shù)”;

被 [WEAK] 或 __weak 聲明的函數(shù),我們可以在自己的文件中重新定義一個同名函數(shù),最終編譯器編譯的時候,會選擇我們定義的函數(shù),如果我們沒有重新定義這個函數(shù),那么編譯器就會執(zhí)行[WEAK]或__weak 聲明的函數(shù),并且編譯器不會報錯;

HardFault_Handler\PROCEXPORT	HardFault_Handler	[WEAK]B		.ENDP

同樣我們打開stm32f1xx_it.c文件中也定義了HardFault_Handler中斷函數(shù)

void HardFault_Handler(void)
{/* Go to infinite loop when Hard Fault exception occurs */while(1){}
}

在 stm32f1xx_it.c 文件定義了 HardFault_Handler 中斷函數(shù)的情況下,當HardFault_Handler 中斷來到的時候,代碼會運行到 stm32f1xx_it.c 文件的 HardFault_Handler中斷函數(shù),且進入 while(1);

下面,我們注釋掉 stm32f1xx_it.c 的 HardFault_Handler 中斷函數(shù),然后進行編譯,發(fā)現(xiàn)不會報錯。這時候當 HardFault_Handler 中斷來到的時候,代碼會運行到啟動文件的“弱函數(shù)”中,即在啟動文件中 164 行代碼,進行原地跳轉(即無限循環(huán));

對于_main函數(shù)的分析

當看到__main 函數(shù)時,估計有不少人認為這個是 main 函數(shù)的別名或是編譯之后的名字,否則在啟動代碼中再也無法找到和 main 相關的字眼了。可事實是,_main 和 main 是兩個完全不同的函數(shù)。_main 代碼是編譯器自動創(chuàng)建的,因此無法找到_main 代碼。MDK 文檔中有一句說明:it is automatically craated by the linker when it sees a definition of main() 。大體意思可以理解為:當編譯器發(fā)現(xiàn)定義了 main 函數(shù),那么就會自動創(chuàng)建_main;

程序經(jīng)過匯編啟動代碼,執(zhí)行到__main()后,可以看出有兩個大的函數(shù):

  1. __scatterload():負責把 RW/RO 輸出段從裝載域地址復制到運行域地址,并完成了 ZI運行域的初始化工作;
  2. __rt_entry():負責初始化堆棧,完成庫函數(shù)的初始化,最后自動跳轉向 main()函數(shù);

中斷服務程序

接下來就是中斷服務程序了

; 系統(tǒng)異常中斷
NMI_Handler		PROCEXPORT	NMI_Handler				[WEAK]B		.			;原地跳轉(即無限循環(huán))ENDP
HardFault_Handler\PROCEXPORT	HardFault_Handler		[WEAK]B		.ENDP
;中間代碼太長, 已經(jīng)省略掉
SysTick_Handler	PROCEXPORT	SysTick_Handler			[WEAK]B		.ENDP
;外部中斷
Default_Handler	PROCEXPORT	WWDG_IRQHandler			[WEAK]EXPORT	PVD_IRQHandler			[WEAK]EXPORT	TAMPER_IRQHandler		[WEAK]EXPORT	RTC_IRQHandler			[WEAK]EXPORT	FLASH_IRQHandler		[WEAK]
;中間代碼太長, 已經(jīng)省略掉
DMA2_Channel1_IRQHandler
DMA2_Channel2_IRQHandler
DMA2_Channel3_IRQHandler
DMA2_Channel4_5_IRQHandlerB		.ENDP

可以看到這些中斷服務函數(shù)都被[WEAK]聲明為弱定義函數(shù),如果外部文件聲明了一個標號,則優(yōu)先使用外部文件定義的標號,如果外部文件沒有定義也不會出錯;

這些中斷函數(shù)分為系統(tǒng)異常中斷和外部中斷,外部中斷根據(jù)不同芯片有所變化。B 指令是跳轉到一個標號,這里跳轉到一個‘.’,表示無限循環(huán);

在啟動文件代碼中,已經(jīng)把我們所有中斷的中斷服務函數(shù)寫好了,但都是聲明為弱定義,所以真正的中斷服務函數(shù)需要我們在外部實現(xiàn);

如果我們開啟了某個中斷,但是忘記寫對應的中斷服務程序函數(shù)又或者把中斷服務函數(shù)名寫錯,那么中斷發(fā)生時,程序就會跳轉到啟動文件預先寫好的弱定義的中斷服務程序中,并且在 B 指令作用下跳轉到一個‘.’中,無限循環(huán);

這里的系統(tǒng)異常中斷是內(nèi)核的,外部中斷是外設的;

用戶堆棧初始化

ALIGN指令

		ALIGN

ALIGN 表示對指令或者數(shù)據(jù)的存放地址進行對齊,一般需要跟一個立即數(shù),缺省表示4 字節(jié)對齊。要注意的是,這個不是 ARM 的指令,是編譯器的;

接下就是啟動文件最后一部分代碼,用戶堆棧初始化代碼

				IF		:DEF:__MICROLIB		// 判斷是否定義了__MICROLIB。關于__MICROLIB 這個宏定義// 如果定義__MICROLIB,聲明__initial_sp、__heap_base 和__heap_limit這三個標號具有全局屬性,可被外部的文件使用。//__initial_sp 表示棧頂?shù)刂?#xff0c;__heap_base表示堆起始地址,__heap_limit 表示堆結束地址EXPORT	__initial_spEXPORT	__heap_baseEXPORT	__heap_limitELSE	// 沒有定義__MICROLIB,實際的情況就是我們沒有定義__MICROLIB,所以使用默認的 C 庫運行。堆棧的初始化由 C 庫函數(shù)__main 來完成IMPORT	__use_two_region_memory		// IMPORT 聲明__use_two_region_memory 標號來自外部文件EXPORT	__user_initial_stackheap	// EXPORT 聲明__user_initial_stackheap 具有全局屬性,可被外部的文件使用__user_initial_stackheap	// 標號__user_initial_stackheap,表示用戶堆棧初始化程序入口// 接下來進行堆??臻g初始化,堆是從低到高生長,棧是從高到低生長,是兩個互相獨立的數(shù)據(jù)段,并且不能交叉使用LDR		R0 ,= Heap_Mem					// 保存堆起始地址LDR		R1 ,= (Stack_Mem + Stack_Size)	// 保存棧大小LDR		R2 ,= (Heap_Mem + Heap_Size)	// 保存堆大小LDR		R3 ,= (Stack_Mem)				// 保存棧頂指針BX		LR								// 跳轉到 LR 標號給出的地址,不用返回ALIGNENDIFEND										//  END 表示到達文件的末尾,文件結束

IF, ELSE, ENDIF 是匯編的條件分支語句

系統(tǒng)啟動流程

在以前 ARM7/ARM9 內(nèi)核的控制器在復位后,CPU 會從存儲空間的絕對地址0x00000000 取出第一條指令執(zhí)行復位中斷服務程序的方式啟動,即固定了復位后的起始地址為 0x00000000(PC = 0x00000000),同時中斷向量表的位置也是固定的。而 Cortex-M3內(nèi)核復位后的起始地址和中斷向量表的位置可以被重映射。充映射的方法是通過啟動模式的選擇,有以下 3 種情況:

  1. 通過 boot 引腳設置可以將中斷向量表定位于 SRAM 區(qū),即起始地址為 0x2000000,同時復位后 PC 指針位于
    0x2000000 處;
  2. 通過 boot 引腳設置可以將中斷向量表定位于 FLASH 區(qū),即起始地址為 0x8000000,同時復位后 PC 指針位于
    0x8000000 處;
  3. 通過 boot 引腳設置可以將中斷向量表定位于內(nèi)置 Bootloader 區(qū),本文不對這種情況做論述;

Cortex-M3 內(nèi)核規(guī)定,起始地址必須存放堆頂指針,而第二個地址則必須存放復位中斷入口向量地址,這樣在 Cortex-M3 內(nèi)核復位后,會自動從起始地址的下一個 32 位空間取出復位中斷入口向量,跳轉執(zhí)行復位中斷服務程序。

下面將結合《Cortex-M3 權威指南(中文)》chpt03-復位序列的內(nèi)容進行講解。

啟動模式不同,啟動的起始地址是不一樣的,下面我們以代碼下載到內(nèi)部 FLASH 的情況舉例,即代碼從地址 0x0800 0000 開始被執(zhí)行。

我們知道的復位方式有三種:上電復位,硬件復位和軟件復位。當產(chǎn)生復位,并且離開復位狀態(tài)后,CM3 內(nèi)核做的第一件事就是讀取下列兩個 32 位整數(shù)的值:

  1. 從地址 0x0800 0000 處取出堆棧指針 MSP 的初始值,該值就是棧頂?shù)刂?#xff1b;
  2. 從地址 0x0800 0004 處取出程序計數(shù)器指針 PC 的初始值,該值指向復位后執(zhí)行的第一條指令;

下面用示意圖表示
在這里插入圖片描述
請注意,這與傳統(tǒng)的 ARM 架構不同——其實也和絕大多數(shù)的其它單片機不同。傳統(tǒng)的 ARM 架構總是從 0 地址開始執(zhí)行第一條指令。它們的 0 地址處總是一條跳轉指令。而 在 CM3 內(nèi)核中,0 地址處提供 MSP 的初始值,然后就是向量表(向量表在以后還可以被移至其它位置)。向量表中的數(shù)值是 32 位的地址,而不是跳轉指令。向量表的第一個條目指向復位后應執(zhí)行的第一條指令,就是 Reset_Handler 這個函數(shù)。下面繼續(xù)以戰(zhàn)艦開發(fā)板 HAL庫例程的實驗 1 跑馬燈實驗為例,代碼從地址 0x0800 0000 開始被執(zhí)行,講解一下系統(tǒng)啟動,初始化堆棧、MSP 和 PC 后的內(nèi)存情況
在這里插入圖片描述
因為 CM3 使用的是向下生長的滿棧,所以 MSP 的初始值必須是堆棧內(nèi)存的末地址加 1。舉例來說,如果你的棧區(qū)域在 0x20000388‐0x20000787 之間,那么 MSP 的初始值就必須是 0x20000788。

向量表跟隨在 MSP 的初始值之后——也就是第 2 個表目。

R15 是程序計數(shù)器,在匯編代碼中,可以使用名字“PC”來訪問它。ARM 規(guī)定:PC最低兩位并不表示真實地址,最低位 LSB 用于表示是 ARM 指令(0)還是 Thumb 指令(1),因為 CM3 主要執(zhí)行 Thumb 指令,所以這些指令的最低位都是 1(都是奇數(shù))。因為 CM3 內(nèi)
部使用了指令流水線,讀 PC 時返回的值是當前指令的地址+4。比如說:

0x1000: MOV R0, PC ; R0 = 0x1004

如果向 PC 寫數(shù)據(jù),就會引起一次程序的分支(但是不更新 LR 寄存器)。CM3 中的指令至少是半字對齊的,所以 PC 的 LSB 總是讀回 0。然而,在分支時,無論是直接寫 PC 的值還是使用分支指令,都必須保證加載到 PC 的數(shù)值是奇數(shù)(即 LSB=1),表明是在Thumb 狀態(tài)下執(zhí)行。倘若寫了 0,則視為轉入 ARM 模式,CM3 將產(chǎn)生一個 fault 異常。

正因為 上 述 原 因 , 圖 3.3 中使用 0x080001CD 來表達地址 0x080001CC 。 當0x080001CD 處的指令得到執(zhí)行后,就正式開始了程序的執(zhí)行(即去到 C 的世界)。所以在此之前初始化 MSP 是必需的,因為可能第 1 條指令還沒執(zhí)行就會被 NMI 或是其它 fault 打斷。MSP 初始化好后就已經(jīng)為它們的服務例程準備好了堆棧。

http://www.risenshineclean.com/news/54124.html

相關文章:

  • 怎么從網(wǎng)站上看出做網(wǎng)站的日期網(wǎng)頁快速收錄
  • 企業(yè)網(wǎng)站建設方案書范文關鍵詞挖掘工具有哪些
  • 技術專業(yè)網(wǎng)站建設免費的關鍵詞挖掘工具
  • python前端開發(fā)北京網(wǎng)站seo哪家公司好
  • 保存的密碼變成亂碼搜索引擎優(yōu)化叫什么
  • 網(wǎng)站互動功能網(wǎng)站推廣應該怎么做?
  • 上海新聞網(wǎng)頭條新聞seo常用分析的專業(yè)工具
  • 微信如何建商城網(wǎng)站百度投放廣告怎么收費
  • 杭州做網(wǎng)站外包公司哪家好優(yōu)化seo網(wǎng)站
  • 江門網(wǎng)站制作維護南昌seo公司
  • 政府網(wǎng)站的建設規(guī)劃設想seo關鍵詞排名優(yōu)化哪家好
  • 域名注冊了 如何做網(wǎng)站如何推廣app賺錢
  • 機械網(wǎng)站優(yōu)化百度推廣客戶端怎么登陸
  • 最新開的手游傳奇網(wǎng)站seo研究中心晴天
  • 集團簡介ppt優(yōu)秀范例百度網(wǎng)站怎么優(yōu)化排名
  • 怎樣做企業(yè)網(wǎng)站建設網(wǎng)站推廣與優(yōu)化平臺
  • 建設部網(wǎng)站公示公告安全seo推廣的全稱是
  • 怎樣開通網(wǎng)站網(wǎng)站開發(fā)的一般流程
  • 濟南 網(wǎng)站建設公司 醫(yī)療湖南關鍵詞排名推廣
  • 做oa好 還是做網(wǎng)站好網(wǎng)絡推廣的平臺有哪些
  • 臨沂市建設安全管理網(wǎng)站整合營銷的最高階段是
  • 網(wǎng)站首頁菜單欄表怎么做中國今天最新軍事新聞
  • 數(shù)據(jù)來源于網(wǎng)站需如何做腳注谷歌推廣怎么樣
  • 深圳營銷型網(wǎng)站建設公司本周的新聞大事10條
  • 服務器 打開網(wǎng)站iis7下載百度2023最新版
  • 西安做網(wǎng)站的公司排名西安seo托管
  • wordpress503原因seo短期課程
  • 創(chuàng)造網(wǎng)站黃頁污水
  • 正規(guī)的品牌網(wǎng)站建設服務軟件外包網(wǎng)
  • 品牌做網(wǎng)站公司長沙網(wǎng)站seo服務