源碼分析寫了一堆,但是每次都分析不完就鴿了,氣死我了。也思考了很久究竟寫一些怎么樣的文章。
我想通了,要寫一個合集,精通Cortex-M3!
基本上你加上幾個實戰(zhàn)的項目就穩(wěn)了。
其次就是廣泛性,M3會了,M0難嗎?M0+呢?不言而喻了。我準備從一個工程入手,一行一行的代碼,多方權(quán)威資料查詢寫出這個合集,首先這篇是匯編的啟動文件,要求對讀者的要求是較高的,建議細細閱讀。
精通ARM Cortex-M意味著對ARM Cortex-M內(nèi)核及相關(guān)微控制器有深入全面的理解和高超的開發(fā)技能。具體需要:
1. 深入理解ARM Cortex-M內(nèi)核架構(gòu),包括核心模塊如內(nèi)存保護單元、中斷控制器、定時器等,以及指令系統(tǒng)和編程模型。
2. 熟悉各系列ARM Cortex-M內(nèi)核如M0/M3/M4/M7的具體特征與差異,尤其是外設配置與編程方法。
3.掌握主流MCU廠商基于ARMCortex-M內(nèi)核的微控制器產(chǎn)品,如STM32、Kinetis、EFM32等的詳細特性和使用方法。
4. 精通CMSIS標準,熟練使用其提供的寄存器訪問函數(shù)、外設驅(qū)動、啟動文件、調(diào)試工具等,快速開發(fā)MCU應用程序。
5. 具有豐富的開發(fā)經(jīng)驗,能夠熟練完成MCU資源配置、時鐘選擇、中斷服務程序編寫、外設驅(qū)動開發(fā)等,并對各種復雜問題進行快速解決。
6. 具備一定的創(chuàng)新能力,能基于對內(nèi)核和MCU的深入理解進行功能優(yōu)化與改進,開發(fā)出技術(shù)水平更高的程序。
ARM官網(wǎng)找的圖
這個就是M3內(nèi)核的特性
一個和別的內(nèi)核的對比圖
Corstone-101,官網(wǎng)看見的
我真服了,全網(wǎng)找不到這個開發(fā)板啥樣子??
Corstone-101是Arm推出的一款入門級的MCU開發(fā)板,目的是幫助工程師輕松學習 Arm Cortex-M系列MCU。
Corstone-101開發(fā)板的主要特性:-
- MCU:采用Arm Cortex-M3內(nèi)核的STM32F103RB芯片
-集成CMSIS-DAP debug probe,支持無縫調(diào)試- 板載各類外設和接口:LCD顯示屏、LED、按鍵、USB轉(zhuǎn)串口、MicroSD卡槽等
- Arm Mbed OS預裝,可直接進行MCU軟件開發(fā)
-免費開源的課程教材,包括從零開始快速入門stm32的開發(fā)環(huán)境搭建,到深入學習stm32的定時器、串口、DMA等。
-低成本,入門友好,非常適合Cortex-M內(nèi)核和MCU開發(fā)入門Corstone-101的主要特點是:
1)集成度高:MCU、Debug probe、豐富外設、Mbed OS軟件平臺均集成在一塊開發(fā)板上,使用非常方便。
2)教學優(yōu)勢:提供極為詳盡的中文視頻教程和文檔,可以零基礎快速學習stm32和MCU開發(fā)。
3) 低成本:Corstone-101開發(fā)板定價非常低,大約250元人民幣,非常適合入門體驗。
4) Mbed OS支持:基于Mbed OS,可以使用其豐富的庫函數(shù)包以及在線編譯環(huán)境快速開發(fā)應用程序,降低學習門檻。
250這個價格,我還是覺得不夠低成本哈。
倒是和我說的差不多
本來今天就是要說這個CMSIS的,但是這個已經(jīng)是抽象的高層了,所以先寫匯編。
CMSIS是ARM Cortex-M系列內(nèi)核的設備抽象層,全稱為Cortex Microcontroller Software Interface Standard。它定義了一套標準的軟件接口,用于開發(fā)基于ARM Cortex-M內(nèi)核的MCU設備的固件。主要包含:
- 設備寄存器及外設訪問函數(shù)庫:提供標準的API訪問MCU內(nèi)核寄存器和外設,屏蔽不同芯片的寄存器差異。
- 啟動文件和鏈接腳本:提供標準的MCU啟動和內(nèi)存布局文件,用于引導程序運行和鏈接。
- 中斷服務例程:提供標準的中斷服務例程入口定義,用于編寫設備特定的中斷服務程序。
- 調(diào)試寄存器訪問宏:標準宏定義訪問用于MCU調(diào)試的寄存器。
- 標準外設驅(qū)動庫:為常用外設提供跨MCU通用的驅(qū)動庫,如GPIO、USART等。
CMSIS的主要目的是降低基于Cortex-M內(nèi)核MCU的開發(fā)復雜度,屏蔽芯片差異,實現(xiàn)跨MCU的代碼可重用性。
開發(fā)者可以專注于應用程序本身的開發(fā),而不需過多關(guān)注具體MCU的配置細節(jié)。目前,主流的MCU廠商如ST、NXP、TI等都提供了基于CMSIS標準的固件庫和例程,使用這些CMSIS標準的固件庫可以輕松配置和訪問MCU資源,開發(fā)出高質(zhì)量的程序。
CMSIS本質(zhì)上是一個標準的固件庫和軟件接口。它提供:
1. 標準的寄存器外設訪問函數(shù)庫,相當于一個硬件抽象層,屏蔽不同MCU的寄存器差異,實現(xiàn)統(tǒng)一的訪問接口。
2. 標準的啟動文件、鏈接腳本和外設驅(qū)動,支持MCU啟動、內(nèi)存配置和常用外設的使用。
3. 標準的中斷服務例程入口定義和調(diào)試宏定義。
4. 其他工具和示例程序等。
所以,CMSIS為MCU開發(fā)提供了一系列標準化和統(tǒng)一的固件基礎設施,開發(fā)者可以直接基于CMSIS開發(fā)應用程序,而不需過多關(guān)注MCU本身的配置細節(jié)。這大大降低了基于ARM Cortex-M內(nèi)核MCU開發(fā)的難度,實現(xiàn)了跨MCU的代碼可重用性。但是,CMSIS本身并不是一個完整的MCU固件庫。它僅定義了一系列標準,提供基本的訪問接口和工具,但具體的 periperal drivers(外設驅(qū)動)和board support package(板級支持包)還需要芯片廠商及開發(fā)板廠商基于CMSIS標準開發(fā)和提供。所以,CMSIS給MCU開發(fā)帶來的主要價值在于:
1. 標準化:定義一套行業(yè)標準的軟件接口和規(guī)范。
2. 抽象化:提供硬件抽象層,屏蔽MCU差異,實現(xiàn)統(tǒng)一編程模型。
3. 可重用性:基于標準接口開發(fā)的代碼可以重用于不同的MCU,降低開發(fā)成本。
ARM Cortex-M3內(nèi)核適合使用CMSIS版本3.0及以上版本。CMSIS最初發(fā)布于2008年,當時的版本為1.0,主要支持ARM Cortex-M0和M3內(nèi)核。隨著CMSIS標準的不斷發(fā)展和Cortex-M內(nèi)核系列的增加,CMSIS也在發(fā)布新版本以支持更多內(nèi)核和增加更多功能。主要版本發(fā)布情況如下:
- CMSIS 1.0:2008年,支持Cortex-M0和M3內(nèi)核。
- CMSIS 2.0:2010年,增加對Cortex-M4內(nèi)核的支持。- CMSIS 3.0:2012年,統(tǒng)一設備抽象層,增加debug和RTOS功能。
- CMSIS 3.1:2013年,添加CMSIS-DAP接口和新增Cortex-M7內(nèi)核支持。- CMSIS 3.2:2014年,新增配置 wizard 工具和ARM Compiler 6支持。
- CMSIS 5.0:2016年,重構(gòu)代碼結(jié)構(gòu),支持基于CMSIS-Core的Middleware組件。
- CMSIS 5.1:2017年,新增CMSIS-Driver概念,擴充外設驅(qū)動支持。所以,對于ARM Cortex-M3內(nèi)核,CMSIS 3.0版本及以后版本均有很好的支持,提供設備抽象層、debug工具、標準外設驅(qū)動等,可以很好地滿足基于Cortex-M3內(nèi)核MCU開發(fā)的需求。
我們來學學32里面的CMSIS里面的啟動文件
startup_stm32f10x_cl.s是STM32F10x系列MCU的啟動文件,由ARM官方提供。啟動文件主要完成以下工作:
1. 設置堆棧指針(MSP),為中斷服務程序和中斷向量表預留堆棧空間。
2.檢查復位源,并清除相應的復位標志。
3.設置中斷向量表偏移地址,指向__Vectors段。
4.如果需要,設置中斷向量表在RAM中的拷貝。
5. 如果需要,設置FPU寄存器為默認值。
6. 調(diào)用SystemInit()函數(shù)初始化系統(tǒng)時鐘和外設。
7.調(diào)用__main()函數(shù)進入C代碼。啟動文件一般由匯編語言編寫,需要做的工作主要是CPU及外設的最低層初始化配置,為后續(xù)C代碼的運行做好準備。
看這個代碼
Stack_SizeEQU0x00000400
這行定義了堆棧的大小為0x400字節(jié),即1024字節(jié)。
AREA STACK, NOINIT, READWRITE, ALIGN=3
這行指令定義了一個名為STACK的無初值段,屬性為可讀寫,并且地址對齊到4字節(jié)。這個段用于定義堆棧空間。
Stack_Mem SPACE Stack_Size
這行在STACK段內(nèi)分配了0x400字節(jié)的存儲空間,作為MCU的堆棧空間。
__initial_sp
這行將當前位置的值定義為__initial_sp標號,該標號的值為堆棧底地址,即堆棧指針SP的初始值。
所以,這段匯編代碼完成了以下工作:
1. 定義了1024字節(jié)的堆棧存儲空間Stack_Mem。
2.使用__initial_sp標號來指向這個堆棧空間的底部,這就是SP寄存器的復位值。
3.啟動文件會將MSP(主堆棧指針)初始化為__initial_sp的值,這樣堆棧空間Stack_Mem就關(guān)聯(lián)到主堆棧,為中斷服務程序的執(zhí)行做好準備。
4.該堆棧空間也會在復位后由C代碼繼續(xù)使用,以存儲函數(shù)調(diào)用的返回信息等。
該段代碼定義的堆棧空間Stack_Mem所在的區(qū)域是MCU的內(nèi)存空間。
MCU的內(nèi)存空間一般可以分為以下幾個區(qū)域:
1.FLASH:程序存儲區(qū)域,用于存儲程序代碼。
2.SRAM:靜態(tài)RAM,用于數(shù)據(jù)存儲和堆棧空間。
3.HEAP:動態(tài)內(nèi)存分配區(qū)域,一般也在SRAM中,用于malloc/free管理。
4.STACK:函數(shù)調(diào)用堆棧空間,用于存儲函數(shù)調(diào)用的返回地址、參數(shù)等信息,一般在SRAM頂部。
所以,對于Startup文件來說,在SRAM中預留一段空間作為堆棧區(qū)域,這個區(qū)域就是用來作為中斷服務程序和系統(tǒng)調(diào)用等的堆棧空間,用于保存CPU運行現(xiàn)場等信息,這個區(qū)域就是我們這段代碼定義的Stack_Mem。
具體來說:
AREA STACK, NOINIT, READWRITE, ALIGN=3
這行定義了一個名為STACK的段,該段位于SRAM中,用于定義堆棧空間。
Stack_Mem SPACE Stack_Size
這行在該STACK段內(nèi)分配了Stack_Size字節(jié)的存儲空間,作為MCU的堆棧空間。
__initial_sp 該標號的值為這個Stack_Mem的底部地址,即堆棧指針SP的初值。所以,總結(jié)來說:
1.該段代碼定義的堆棧空間Stack_Mem位于MCU的內(nèi)存空間SRAM中。
2.它為中斷服務程序和函數(shù)調(diào)用預留了一片存儲區(qū)域。
3.這個存儲區(qū)域的底部地址被__initial_sp標號指向,用于初始化SP寄存器的值。
4.然后SP寄存器(堆棧指針)在程序運行過程中會根據(jù)推棧和出棧操作在這個區(qū)域中上移和下移。
堆棧底地址指的是堆棧空間的最低可用地址,它表示堆棧區(qū)域中最先分配的空間,用于存放最早推入堆棧的數(shù)據(jù)。
對于Startup文件來說,它使用__initial_sp標號來標記堆棧底地址,這就是SP寄存器(堆棧指針)的初始值。
具體代碼如下:
AREA STACK, NOINIT, READWRITE, ALIGN=3
Stack_Mem SPACE Stack_Size __initial_sp
這段代碼:
1. 定義了大小為Stack_Size的堆棧空間Stack_Mem。
2.__initial_sp標號的值指向Stack_Mem的最低地址,這就是堆棧底地址。3.啟動文件會將SP寄存器初始化為__initial_sp的值,使其指向堆棧底,以便后續(xù)的推棧操作。
4.SP寄存器的值會隨著推棧和出棧操作在Stack_Mem區(qū)域內(nèi)增大和減小。所以,對這個堆棧空間來說:
1.堆棧底地址由__initial_sp標號的值決定,它指向Stack_Mem的最低可用地址。
2.SP寄存器初值為__initial_sp,會從堆棧底開始向高地址生長,來保存推入堆棧的數(shù)據(jù)。
3.出棧操作會使SP寄存器的值減小,釋放堆棧頂?shù)臄?shù)據(jù),直到恢復到堆棧底。
4.堆棧空間的使用是從底至頂,底部空間存放最早的數(shù)據(jù)。所以,總結(jié)來說,堆棧底地址就是堆棧空間中最先被使用的那部分地址空間,它標記了堆棧區(qū)域的起始,為堆棧的推棧和出棧操作提供基礎。
堆!
這段代碼定義了MCU的堆空間以及相關(guān)標號。
具體分析下
Heap_Size EQU 0x00000200
該行定義了堆大小為512字節(jié)(0x200)
AREA HEAP, NOINIT, READWRITE, ALIGN=3
該行指令定義一個名為HEAP的無初值段,具有讀寫屬性,地址按4字節(jié)對齊。這個段用于定義堆空間。
__heap_base 該標號的值為HEAP段的起始地址,表示堆底地址。
Heap_Mem SPACE Heap_Size
該行在HEAP段中分配了0x200字節(jié)的存儲空間作為MCU的堆空間。
__heap_limit
該標號的值為HEAP段結(jié)束地址,表示堆頂?shù)刂贰?/span>
所以,這段匯編代碼完成了以下工作:
1. 定義了512字節(jié)的堆存儲空間Heap_Mem。
2. 使用__heap_base標號來標記這個堆空間的起始地址,表示堆底。
3. 使用__heap_limit標號來標記這個堆空間的結(jié)束地址,表示堆頂。
4. 該堆空間在程序運行過程中由malloc/free管理動態(tài)內(nèi)存的分配與釋放。5. C語言中可以通過extern聲明__heap_base和__heap_limit,以獲取堆信息。
6. PRESERVE8指令用于強制8字節(jié)對齊。
THUMB指令指定該代碼區(qū)使用Thumb-2指令。
該段代碼定義的堆空間為MCU動態(tài)內(nèi)存管理提供了存儲區(qū)域與邊界標記。通過__heap_base和__heap_limit,C代碼可以方便獲取到這個堆空間的信息,為malloc/free調(diào)用服務。所以,這段匯編代碼對MCU的動態(tài)內(nèi)存分配起到很關(guān)鍵的作業(yè)。
這里說下如何訪問棧空間。
對于啟動文件定義的堆棧空間Stack_Mem來說,如果要在C代碼中訪問和使用這個區(qū)域,有以下幾種方法:
1. 使用全局變量在Startup文件中定義:
Stack_Mem SPACE Stack_Size __initial_sp
然后在C代碼中使用全局變量聲明:
這樣stack_ptr變量就指向了堆棧頂,可以通過stack_ptr訪問和修改堆棧空間中的數(shù)據(jù)。
2. 基址偏移Startup文件中Label Stack_Mem的地址為base_addr,則在C代碼中可以通過:
unsigned int stack_ptr = (unsigned int )(base_addr + Stack_Size -
4);
來得到堆棧頂指針,以訪問數(shù)據(jù)。需要知道Stack_Mem的絕對地址base_addr。
3. 定義指針數(shù)組在C代碼中聲明指針數(shù):
unsigned int Stack[Stack_Size];然后stack_ptr = &Stack[Sta
ck_Size - 1];
這種方法需要在鏈接過程中正確設置該指針數(shù)組與Startup文件中Stack_Mem區(qū)域的關(guān)聯(lián)。
4. 借助寄存器因為Startup文件已經(jīng)將SP寄存器初始化為__initial_sp,也就是Stack_Mem底部,所以我們可以通過SP寄存器讀寫這個區(qū)域:
使用inline匯編直接通過SP寄存器讀寫堆棧空間。
好了,再說堆空間的作用:
堆空間主要用于MCU的動態(tài)內(nèi)存分配,它為malloc()和free()函數(shù)調(diào)用提供內(nèi)存池。具體來說,堆空間在MCU中的作用有:
1. 為動態(tài)內(nèi)存申請?zhí)峁┐鎯臻g:在程序運行時,可以通過malloc()函數(shù)向堆空間申請任意大小的內(nèi)存塊,以保存數(shù)據(jù)或創(chuàng)建對象。
2. 釋放不再使用的內(nèi)存:通過free()函數(shù)可以將堆空間中的內(nèi)存塊釋放,以便下次繼續(xù)使用。
3. 管理內(nèi)存碎片:堆空間可以由malloc()函數(shù)自動管理內(nèi)存碎片,試圖利用所有的內(nèi)存空間。
4. 實現(xiàn)內(nèi)存動態(tài)擴展:如果堆空間初始分配的空間不足,還可以在運行時通過sbrk()函數(shù)向操作系統(tǒng)申請更多內(nèi)存空間。
5. C++程序的new和delete操作也依賴于堆空間,用于創(chuàng)建和銷毀對象。
所以,簡單來說,堆空間為動態(tài)內(nèi)存分配提供內(nèi)存池,它最大的作用在于:
1. 滿足運行時變化的內(nèi)存需求,程序不確定對象到底占用多少內(nèi)存空間,只能在運行時進行內(nèi)存分配。
2. 實現(xiàn)堆內(nèi)存的重復分配與釋放,節(jié)省內(nèi)存空間,避免內(nèi)存浪費。
3. C++程序可以在堆上創(chuàng)建對象,并使用new和delete進行內(nèi)存管理。總之,堆空間為程序運行時的動態(tài)內(nèi)存分配需求提供了支持,它的管理主要依賴malloc、free和new、delete函數(shù)。
堆棧的區(qū)別是什么?
堆空間和棧空間都是MCU內(nèi)存空間的一部分,但二者有以下主要區(qū)別:
1. 生長方向不同:
堆空間:向高地址生長,使用malloc()函數(shù)申請內(nèi)存,按需動態(tài)分配。
棧空間:向低地址生長,大小在編譯時確定,自動分配。
2. 申請方式不同:堆空間:使用malloc()和free()函數(shù)顯式申請和釋放內(nèi)存。棧空間: FUN調(diào)用時自動分配和釋放,不需要顯式控制。
3. 生命周期不同:
堆空間:使用完畢須手動釋放,生命周期不確定,動態(tài)決定。
棧空間:函數(shù)調(diào)用結(jié)束自動釋放,生命周期與函數(shù)調(diào)用一致,編譯時決定。
4. 使用目的不同:
堆空間:用于動態(tài)內(nèi)存分配,對象創(chuàng)建。
棧空間:用于存儲函數(shù)參數(shù)、局部變量、返回地址等,實現(xiàn)函數(shù)調(diào)用機制。
5. 空間大小不同:
堆空間:大小動態(tài)變化,按需分配。
棧空間:大小在編譯時確定,固定分配。
所以,總結(jié)來說:
堆空間為動態(tài)內(nèi)存分配和對象創(chuàng)建提供能力,大小和生命周期不定,需要手工管理。
棧空間為函數(shù)調(diào)用機制提供自動分配和釋放的臨時存儲空間,大小和生命周期在編譯時決定,無需手工控制。
我直接回馬槍,堆空間如何訪問呢?
1. 使用全局變量
在啟動文件中定義:
?
然后在C代碼中使用全局變量聲明:
這樣heap_ptr變量就指向了堆底,通過它可以訪問堆空間中的數(shù)據(jù)。
2. 基址偏移
啟動文件中Label __heap_base和__heap_limit的地址分別為base_addr和limit_addr,則在C代碼中可以通過:
來訪問堆空間數(shù)據(jù),offset為任意偏移量。需要知道__heap_base和__heap_limit的絕對地址。
3. 定義指針數(shù)組
在C代碼中聲明指針數(shù)組:
然后heap_ptr = Heap;
這種方法需要在鏈接過程中正確設置該指針數(shù)組與啟動文件中Heap_Mem區(qū)域的關(guān)聯(lián)。
4. 借助malloc()函數(shù)
因為啟動文件已經(jīng)定義了__heap_base和__heap_limit來標識堆空間的范圍,所以我們可以直接使用malloc()函數(shù)向這個區(qū)域申請內(nèi)存:
malloc()函數(shù)會在Heap_Mem區(qū)域分配size大小的內(nèi)存,并返回其起始地址,通過ptr訪問這片內(nèi)存。
接下來看這一段
這段代碼定義了STM32的矢量表。
矢量表包含了中斷服務程序的入口地址,以及系統(tǒng)使用的其他處理程序的入口地址。它位于地址0x00000000,并在復位后被映射到這個位置。所以,這段代碼完成的主要工作有:
1. 定義了名為RESET的只讀數(shù)據(jù)段,該段位置是0x00000000。
2. EXPORT指令導出了__Vectors、__Vectors_End和__Vectors_Size標號,以方便其他文件使用。
3. __Vectors標號指向矢量表的起始地址。__Vectors_End和__Vectors_Size分別用于標識矢量表的結(jié)束地址和大小。
4. 表起始地址存儲初始堆棧指針SP的值(__initial_sp標號)。
5. 接下來定義了各種異常和中斷處理程序的入口地址:
Reset_Handler:復位中斷服務程序
NMI_Handler:NMI中斷服務程序
HardFault_Handler:硬件故障中斷服務程序
......
SysTick_Handler:SysTick中斷服務程序
6. 一些入口保留為0,用于未來擴展。
所以,這個矢量表完成了以下工作:
1. 定義了各種異常和中斷處理程序的入口地址,這些入口地址指向?qū)袛喾粘绦颉?/span>
2. 矢量表位于地址0x00000000,并在MCU復位后被映射到這個位置。
3. 初始SP寄存器的值由矢量表的第一個字存儲。
4. 中斷發(fā)生時,MCU會根據(jù)中斷類型在矢量表中找到對應的入口地址,然后跳轉(zhuǎn)到此地址執(zhí)行中斷服務程序。矢量表是MCU中斷機制的基礎,它為異常和中斷提供入口和服務程序。所以,這個矢量表定義對MCU的中斷配置和服務起到了基礎作用。
矢量表在MCU的內(nèi)存空間中保存。它以數(shù)組的形式保存,每個數(shù)組元素存儲一個函數(shù)入口地址。
具體來說:
1. 這個矢量表定義在啟動文件中的RESET數(shù)據(jù)段中,該數(shù)據(jù)段位于內(nèi)存地址0x00000000處。
2. __Vectors標號指向這個矢量表的數(shù)組起始地址,__Vectors_End標號指向數(shù)組結(jié)束地址。
3. 數(shù)組每個元素大小為4字節(jié)(32位MCU),用于存儲一個函數(shù)入口地址。
4. 數(shù)組第一個元素存儲了初始SP值,接下來的元素存儲各種異常和中斷處理程序的入口地址。
5. 當某個中斷發(fā)生時,MCU會計算出中斷類型對應的數(shù)組下標,然后跳轉(zhuǎn)到該下標元素所指向的入口地址執(zhí)行中斷服務程序。
6. 這個數(shù)組實際上定義在編譯器產(chǎn)生的啟動文件匯編代碼中。然后通過鏈接器與其他文件連接,最終在MCU的內(nèi)存空間布局中實現(xiàn)布局。
所以,總結(jié)來說:
1. 這個矢量表以數(shù)組的形式在MCU內(nèi)存中實現(xiàn),數(shù)組每個元素存儲一個入口地址。
2. 數(shù)組第一個元素存儲SP初值,其他元素存儲異常和中斷服務程序的入口地址。
3. 該數(shù)組位于地址0x00000000,在MCU上電復位后被映射到該位置。
4. 中斷發(fā)生時,通過中斷類型計算數(shù)組下標,獲取中斷服務程序入口地址。
5. 該數(shù)組最終通過鏈接過程存放在MCU內(nèi)存的正確位置,并在程序運行時被使用。這種數(shù)組的形式便于通過索引快速查找對應的入口地址,實現(xiàn)MCU的異常和中斷機制。所以,矢量表采用這種數(shù)組結(jié)構(gòu)實際上加快了中斷響應速度,提高了系統(tǒng)實時性。
下面還有一段,其實和上面一樣,都是地址而已
接下來看這段
所有的單片機,電腦,都會說復位這個事情。那它為什么如此的重要?因為我們?yōu)榱丝煽兀械某跏蓟覀冎溃\行的規(guī)律知道,所以復位是為了可控而已。
復位中斷服務程序(Reset Handler)之所以存在,主要是為了實現(xiàn)MCU的初始化配置和C語言程序的啟動。
具體來說,Reset Handler的作用有:
1. 完成MCU的初始化配置:設置時鐘,配置GPIO等,為程序正確運行做準備。這一般由啟動文件中定義的SystemInit函數(shù)完成。
2. 跳轉(zhuǎn)到C語言程序的入口函數(shù)__main:C語言程序的執(zhí)行是從__main函數(shù)開始的,所以Reset Handler需要跳轉(zhuǎn)到__main函數(shù)。
3. 設置初始堆棧指針SP的值:在MCU上電復位后,SP寄存器的值是未知的,需要設置為一個正確的值,以便進行后續(xù)的堆棧操作。這個值通常存儲在矢量表的第一個元素中。
4. 作為MCU的最高優(yōu)先級中斷服務程序:當MCU上電或復位時,首先會執(zhí)行Reset Handler來完成系統(tǒng)的初始化和C語言程序的啟動。
5. 如果用戶定義了自己的Reset Handler,它將覆蓋啟動文件中的定義,用戶程序?qū)挠脩舳x的Reset Handler開始執(zhí)行。
所以,簡單來說,Reset Handler之所以存在,主要是為了:
1. 完成MCU的初始化配置,為正確運行提供基礎。
2. 跳轉(zhuǎn)到C語言程序的入口,啟動程序執(zhí)行。
3. 設置SP寄存器的初始值,為使用堆棧做準備。
4. 作為上電復位中斷服務程序,首先被執(zhí)行。
5. 可以被用戶定義的Reset Handler覆蓋。如果沒有Reset Handler,MCU將啟動在未初始化的狀態(tài),C語言程序也無法得到執(zhí)行,SP的值是未知的,這會導致程序運行錯誤或無法運行。所以,Reset Handler對MCU的啟動起到了基礎作用,它為C程序的執(zhí)行提供了啟動條件和基本環(huán)境,是MCU上電必不可少的初始化代碼。
來,這次有了前置知識再看代碼:
代碼定義了復位中斷服務程序Reset_Handler。具體分析如下:
__Vectors_Size EQU __Vectors_End - __Vectors
這行指令計算了矢量表的大小,用于其他文件使用。
AREA |.text|, CODE, READONLY
這行指令定義了一個只讀的代碼段,用于存放異常和中斷服務程序。
Reset_Handler PROC
這行指令定義了Reset_Handler的過程,表示復位中斷服務程序的起始。
EXPORT Reset_Handler [WEAK]
此行導出Reset_Handler過程,以便其他文件使用,并指定該過程為弱定義,意味著如果用戶定義了自己的Reset_Handler過程,那么編譯器會使用用戶定義的過程,而忽略此處的定義。
看這個
IMPORTSystemInit
此行導入SystemInit函數(shù),表示Reset_Handler過程調(diào)用該函數(shù)。
IMPORT __main
此行導入C語言程序的入口函數(shù)__main。
LDR R0, =SystemInit
此指令將SystemInit函數(shù)地址加載到R0寄存器。
BLX R0
此指令調(diào)用SystemInit函數(shù)。
LDR R0, =__main
此指令將__main函數(shù)地址加載到R0寄存器。
BX R0
此指令跳轉(zhuǎn)到__main函數(shù),執(zhí)行C語言程序。
ENDP
此行指令表示Reset_Handler過程的結(jié)束。
所以,這個Reset_Handler過程完成了以下工作:
1. 調(diào)用SystemInit函數(shù)完成系統(tǒng)初始化。
2. 跳轉(zhuǎn)到__main函數(shù),執(zhí)行C語言程序。
3. 作為復位中斷的中斷服務程序,在MCU上電復位后首先被執(zhí)行。
4. 如果用戶定義了自己的Reset_Handler,那么由于該過程被定義為弱定義,用戶定義會覆蓋此處定義,被執(zhí)行。該過程為C語言程序的執(zhí)行提供了啟動入口,完成了MCU的初始化配置.
接下來的代碼很多的一樣,拿兩個看
這段代碼定義了NMI中斷服務程序NMI_Handler和硬件故障中斷服務程序HardFault_Handler。
NMI_Handler PROC
此行定義NMI_Handler過程,表示NMI中斷服務程序的起始。
EXPORT NMI_Handler [WEAK]
此行導出NMI_Handler過程,指定為弱定義,意味著如果用戶定義了自己的NMI_Handler過程,編譯器會使用用戶定義的過程,忽略此處定義。
B . 此指令是一個空操作,不執(zhí)行任何動作。
ENDP
此行表示NMI_Handler過程的結(jié)束。
HardFault_HandlerPROC
此行定義HardFault_Handler過程,表示硬件故障中斷服務程序的起始。EXPORT HardFault_Handler [WEAK]
此行導出HardFault_Handler過程,指定為弱定義,意味著如果用戶定義了自己的HardFault_Handler過程,編譯器會使用用戶定義的過程,忽略此處定義。
B . 此指令是一個空操作,不執(zhí)行任何動作。
ENDP 此行表示HardFault_Handler過程的結(jié)束。
所以,這兩個中斷服務程序定義完成了:
1. 定義了對應的中斷服務程序入口,但程序體為空。
2. 將這兩個過程定義為弱定義,意味著如果用戶定義了自己的服務程序,編譯器會使用用戶定義的過程。
3. 以空操作結(jié)束了這兩個過程。
之所以這兩個過程定義為空,主要是考慮到:
1. 出于兼容性考慮,啟動文件需要定義所有標準的異常與中斷入口,但具體處理由用戶決定是否定義。
2. 對于一些使用不太頻繁或處理比較復雜的中斷,用戶可能按需選擇是否定義自己的服務程序。
3. 定義為空的中斷服務程序不會對程序產(chǎn)生任何影響,保證定義后的兼容性。
所以,這兩個定義主要是為了實現(xiàn)異常與中斷入口的完整定義,但具體處理交由用戶根據(jù)需要選擇是否定義,如果未定義則默認為空操作。
今日最后的代碼,我覺得全網(wǎng)我是最全的啟動代碼解析
這段代碼主要用于定義堆棧的初始化。具體分析如下:
IF __MICROLIB
此行條件指令檢查__MICROLIB宏是否被定義。如果定義,執(zhí)行IF內(nèi)語句,否則執(zhí)行ELSE內(nèi)語句。
EXPORT __initial_sp
此行導出__initial_sp符號,用于初始化SP寄存器。
EXPORT __heap_base
此行導出__heap_base符號,用于標識堆起始地址。
EXPORT __heap_limit
此行導出__heap_limit符號,用于標識堆結(jié)束地址。
IMPORT __use_two_region_memory
此行導入__use_two_region_memory符號,用于檢查是否使用兩片內(nèi)存區(qū)域。
__user_initial_stackheap 此符號用于標識初始化堆棧的過程。
LDR R0, = Heap_Mem 此指令將堆起始地址加載到R0寄存器。
這幾行指令完成堆起始地址,堆結(jié)束地址,棧起始地址以及棧結(jié)束地址的加載與設置。
BX LR此指令用于返回,結(jié)束初始化堆棧的過程。
ALIGN此行用于4字節(jié)對齊。
ENDIF 此行表示IF語句的結(jié)束。
END 此行表示文件的結(jié)束。
所以,這段代碼的主要工作是:
1. 如果定義了__MICROLIB宏,則直接導出__initial_sp等符號,否則執(zhí)行初始化堆棧的過程。
2. __user_initial_stackheap過程用于根據(jù)傳入的Heap_Mem和Stack_Mem參數(shù)設置堆棧的參數(shù)。
3. 該過程判斷是否使用兩片區(qū)域設置堆棧參數(shù),如果使用則設置兩片區(qū)域的起始結(jié)束地址。
4. 這段代碼的目的是在啟動文件中設置初始化堆棧所需要的參數(shù)與地址,為C語言程序的執(zhí)行提供堆棧環(huán)境。這段代碼的定義為用戶編寫的C語言程序提供了基本的堆棧初始化與設置,完成了與編譯器相關(guān)的參數(shù)定義,實現(xiàn)了C語言程序運行所需要的內(nèi)存環(huán)境配置。
我再總結(jié)一下:
1. 定義了RESET只讀數(shù)據(jù)段,用于存放矢量表,該段位于地址0x00000000,在上電復位后被映射到該地址。
2. 定義并導出了__Vectors、__Vectors_End和__Vectors_Size等符號,方便其他文件使用矢量表。
3. 矢量表首地址存儲初始SP值,其他地址存儲各種異常和中斷服務程序入口。
4. 定義了復位中斷服務程序Reset_Handler,它調(diào)用SystemInit完成系統(tǒng)初始化,然后跳轉(zhuǎn)到__main函數(shù)執(zhí)行C語言程序。
5. 定義了部分中斷服務程序如NMI_Handler和HardFault_Handler,但僅定義了入口,程序體為空,這主要考慮程序的兼容性與擴展性。
6. 根據(jù)__MICROLIB的定義情況,選擇是否執(zhí)行__user_initial_stackheap過程來設置堆棧參數(shù),為C語言程序執(zhí)行提供堆棧環(huán)境。
7. 定義并導出了__initial_sp、__heap_base和__heap_limit等符號,用于標識SP、堆和棧的起始和結(jié)束地址。
8. 使用ALIGN指令實現(xiàn)了部分字節(jié)對齊,提高了程序的兼容性和效率。
9. 文件最后通過END指令實現(xiàn)了文件的結(jié)束。
所以,這個啟動文件完成的主要工作是:
1. 完成MCU的復位向量表定義,為各種中斷提供入口與服務。
2. 定義復位中斷服務程序,完成系統(tǒng)初始化和C語言程序啟動。
3. 定義部分中斷服務程序入口,但程序體為空,由用戶決定是否具體定義。
4. 根據(jù)情況設置堆棧參數(shù),為C語言程序執(zhí)行提供環(huán)境。
5. 定義各種導出符號,方便其他文件使用。
6. 使用ALIGN實現(xiàn)部分字節(jié)對齊,提高效率。7. 標識文件的結(jié)束。
頻繁的說這個對齊,我補個對齊的作用:
字節(jié)對齊指的是存儲單元的地址要遵循某種邊界限制,即地址的低幾位要為0。
它的主要作用有:
1. 提高訪問效率:當數(shù)據(jù)的地址是某種邊界的整數(shù)倍時,CPU可以以更大寬度的訪問單元去訪問數(shù)據(jù),這樣可以減少CPU讀取數(shù)據(jù)的次數(shù),提高訪問效率。2. 減少存儲空間浪費:如果不對齊,在訪問更大寬度的數(shù)據(jù)類型時,CPU需要訪問的數(shù)據(jù)可能超出實際需要,這會占用額外的存儲空間并影響總線帶寬,對齊可以避免這種浪費。
3. 方便數(shù)據(jù)的轉(zhuǎn)換:在某些特定的地址處訪問的數(shù)據(jù)可以直接轉(zhuǎn)換為其他數(shù)據(jù)類型,這可以提高處理效率,如果地址未對齊,這種直接轉(zhuǎn)換就無法實現(xiàn)。
總的來說,字節(jié)對齊主要帶來三個方面的好處:
1. 提高訪問和處理數(shù)據(jù)的效率。
2. 避免存儲空間的浪費。
3. 方便數(shù)據(jù)之間的直接轉(zhuǎn)換。
而對齊的基本實現(xiàn)方法是:
1. 指定對齊類型:如2字節(jié)對齊、4字節(jié)對齊等,這通常由編譯器來指定,可以通過定義宏的方式實現(xiàn)。
2. 數(shù)據(jù)聲明時通過對齊類型進行限定:如int __align(4) num;表示num為4字節(jié)對齊。
3. 在某些關(guān)鍵數(shù)據(jù)聲明前通過專用指令進行手動對齊:如ALIGN 4對下一數(shù)據(jù)進行4字節(jié)對齊。
4. 編譯器會自動選擇合適的對齊方式來對數(shù)據(jù)進行對齊,通常為最大數(shù)據(jù)類型的長度,這樣可以發(fā)揮對齊帶來的所有好處。
這幾個文件詳細的作用我之后的文章來說
core_cm3.c是CMSIS標準定義的Cortex-M3內(nèi)核芯片支持包,它提供了以下內(nèi)容:
1. MCU寄存器結(jié)構(gòu)體定義:SCB, SysTick, NVIC等內(nèi)核寄存器的結(jié)構(gòu)體定義。
2. 系統(tǒng)時鐘配置函數(shù):CMU_ClkInit()、CMU_ClockSelectConfig()等。
3. 系統(tǒng)滴答配置函數(shù):SysTick_Config()用于配置SysTick定時器的溢出中斷周期。
4. 中斷配置函數(shù):NVIC_EnableIRQ()、NVIC_DisableIRQ()、NVIC_SetPriority()等,用于配置中斷優(yōu)先級與使能狀態(tài)。
5. 系統(tǒng)SoftReset和HardReset函數(shù):用于軟復位和硬復位MCU。
6. 內(nèi)部Flash配置函數(shù):用于配置Flash預取指緩存、等待信號以及訪問權(quán)限等。
7. 函數(shù)調(diào)用棧配置函數(shù):用于配置PSP堆棧指針和MSP主堆棧指針。
8. 系統(tǒng)滴答定時器SysTick的中斷服務函數(shù)SysTick_Handler()。
9. 設備向量表__Vectors定義,里面包含SysTick和外設中斷服務程序入口,以及各類異常入口。
10. 用戶可根據(jù)需要實現(xiàn)的空操作清零函數(shù):Default_Handler()。
11. MPU配置函數(shù):用于配置內(nèi)存保護單元,設定不同存儲區(qū)域的訪問權(quán)限。 所以,core_cm3.c文件為基于Cortex-M3內(nèi)核的MCU提供了系統(tǒng)級配置與接口,包括時鐘、中斷、內(nèi)存管理等方方面面。用戶可以直接調(diào)用這些函數(shù)接口來配置MCU,也可以根據(jù)需要修改或擴充。這個文件遵循CMSIS標準,具有很高的通用性,主要目的是降低不同MCU的移植difficulty,加速嵌入式工程師的開發(fā)工作。所以,它是開發(fā)基于ARM Cortex-M3內(nèi)核MCU的重要基石。
stm32f10x.h是ST公司為STM32F10x系列MCU提供的頂層頭文件,它包含以下內(nèi)容:
1. 對標準庫的引用,如stdint.h、stdbool.h等。
2. 對CMSIS標準的引用,引入CMSIS定義的一些數(shù)據(jù)類型、寄存器定義和函數(shù)原型。
3. MCU型號選擇,根據(jù)具體的芯片型號選擇正確的定義。
4. 外設時鐘使能宏定義,方便開啟或關(guān)閉外設時鐘。
5. 寄存器定義,定義MCU所有的內(nèi)核寄存器和外設寄存器結(jié)構(gòu)體。
6. 中斷編號定義,定義所有的中斷源所對應的值。
7. 位帶操作宏定義,提供對寄存器位的設置、清除以及翻轉(zhuǎn)等操作。
8. FLASH和OTP操作函數(shù),提供FLASH讀、寫、擦除以及OTP讀寫的函數(shù)原型。
9. 復位和時鐘控制寄存器地址的定義以及函數(shù)原型。
10. GPIO通用IO口操作函數(shù)原型定義。
11. 中斷和事件管理函數(shù)原型定義,NVIC相關(guān)操作函數(shù)。
12. 其他外設(串口、SPI、I2C、ADC等)配置函數(shù)和外設特有的一些定義。所以,stm32f10x.h作為STM32F10x系列MCU的頂層頭文件,它包含了影響MCU所有外設和內(nèi)核的內(nèi)容,包括但不限于:寄存器定義、中斷定義、位帶操作、復位和時鐘配置、GPIO配置、串口配置等等。開發(fā)人員可以直接include這個頭文件,就可以使用里面定義的東西,非常方便。
system_stm32f10x.c文件主要包含STM32F10x系列MCU的系統(tǒng)時鐘配置函數(shù)。系統(tǒng)時鐘配置主要完成以下工作:
1. 根據(jù)外部晶振的頻率,配置PLL時鐘進行倍頻,得到MCU內(nèi)核時鐘和AHB/APB總線時鐘。
2. 配置HSE(高速外部時鐘)作為系統(tǒng)時鐘,或者作為PLL的時鐘源。
3. 配置LSE(低速外部時鐘)作為RTC的時鐘源。
4. 選擇不同的預分頻因子,以滿足系統(tǒng)時鐘等于內(nèi)核時鐘的要求。
5. 配置各總線時鐘AHB, APB1和APB2的預分頻因子。
6. 根據(jù)配置的總線時鐘頻率,配置外設的時鐘。
7. 根據(jù)CPU工作頻率,設置Flash訪問時間 FlashLatency。
所以,system_stm32f10x.c文件主要通過調(diào)用stm32f10x_clk.c和stm32f10x_rcc.c等文件里的函數(shù),來配置MCU的整個時鐘系統(tǒng),包括選擇PLL時鐘源、PLL倍頻系數(shù)、AHB/APB總線分頻系數(shù)以及外設時鐘使能等。這個文件實現(xiàn)了ClockConfiguration()函數(shù),該函數(shù)會在MCU啟動時被startup_stm32f10x_xx.s的SystemInit()調(diào)用,從而完成MCU系統(tǒng)時鐘的初始化配置。
https://developer.arm.com/Processors/Cortex-M3
https://github.com/ARM-software/CMSIS-Drive
-
ARM
+關(guān)注
關(guān)注
134文章
9286瀏覽量
374406 -
Cortex
+關(guān)注
關(guān)注
2文章
203瀏覽量
47085 -
源碼
+關(guān)注
關(guān)注
8文章
667瀏覽量
30112
原文標題:Cortex-M3精通之路-1(匯編啟動文件)
文章出處:【微信號:TT1827652464,微信公眾號:云深之無跡】歡迎添加關(guān)注!文章轉(zhuǎn)載請注明出處。
發(fā)布評論請先 登錄
ADuCM362集成雙通道Σ-Δ型ADC和ARM Cortex-M3的低功耗精密模擬微控制器技術(shù)手冊

MAX32510基于DeepCover安全Arm Cortex-M3的閃存微控制器技術(shù)手冊

MAX32558 DeepCover安全ARM Cortex-M3閃存微控制器技術(shù)手冊

MAX32555 Cortex-M3閃存微控制器英文數(shù)據(jù)手冊
為什么無法在iMX8ULP上使用imx-mkimage啟動Cortex M33演示映像?
瑞芯微RK3506(3核ARM+Cortex-A7 + ARM Cortex-M0)工業(yè)核心板選型資料

STM32的上電啟動過程分享
具有大型嵌入式SRAM,用于一般MCU應用程序的指紋芯片-P1032BF1
RK3562J 處理器 M 核啟動實操
Cortex-M3/M4F指令集技術(shù)用戶手冊

如何使用Ozone分析Cortex-M異常

適用于低功耗和無線通信距離要求較高應用的智能通信模組-RF-SM-1077B1

基于ARM Cortex-M3單片機研發(fā)的國產(chǎn)指紋芯片 - P1032BF1

評論