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

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

基于ARM Cortex-M3單片機研發的國產指紋芯片 - P1032BF1

評論