MCU啟動(dòng)流程、文件與keil配置
文件
map文件
Section Cross References
這部分內(nèi)容描述了各個(gè)文件之間函數(shù)的調(diào)用關(guān)系,如下圖main.o(i.main) refers to sys.o(i.sys_stm32_clock_init) for sys_stm32_clock_init,表示main.c文件中的main函數(shù)調(diào)用了sys.c中的sys_stm32_clock_init函數(shù)。i.main表示main函數(shù)的入口地址,i.sys_stm32_clock_init表示sys_stm32_clock_init的入口地址。

Removing Unused input sections from the image
這部分內(nèi)容描述了工程中由于未被調(diào)用而被刪除的冗余程序段(函數(shù)/數(shù)據(jù))。

Image Symbol Table
映像符號(hào)表(Image Symbol Table)描述了被引用的各個(gè)符號(hào)(程序段/數(shù)據(jù))在存儲(chǔ)器中的存儲(chǔ)地址、類型、 大小等信息。分為本地符號(hào)(Local Symbols)和全局符號(hào)(Global Symbols)。
Local Symbols
本地符號(hào)(Local Symbols)記錄了用static聲明的全局變量地址和大小, c文件中函數(shù)的地址和用static聲明的函數(shù)代碼大小,匯編文件中的標(biāo)號(hào)地址(作用域:本文件)。
.text 段
內(nèi)容:代碼(程序指令)、常量(const)、中斷向量表。
位置:通常放在Flash(只讀存儲(chǔ)器)。
特點(diǎn):程序不會(huì)在運(yùn)行時(shí)修改.text段內(nèi)容。
map文件里顯示從某個(gè)地址(如 0x08000000)開(kāi)始,大小等于編譯出的代碼量。
.data 段
內(nèi)容:已初始化的全局變量/靜態(tài)變量。
位置:運(yùn)行時(shí)在 RAM,但初始化值存放在Flash。
啟動(dòng)時(shí)操作:Reset_Handler會(huì)把Flash中的初值拷貝到 RAM。
map文件里會(huì)看到 .data 在RAM中的地址范圍和大小,也會(huì)有對(duì)應(yīng)的Load Region指向Flash地址。
.bss 段
內(nèi)容:未初始化的全局變量/靜態(tài)變量,編譯器會(huì)自動(dòng)填0。
位置:RAM中。
啟動(dòng)時(shí)操作:Reset_Handler會(huì)把.bss段區(qū)域清零。
map文件里會(huì)顯示起始地址和大小,初始值全0。
Global Symbols
全局符號(hào)(Global Symbols)記錄了全局變量的地址和大小, C文件中函數(shù)的地址及其代碼大小,匯編文件中的標(biāo)號(hào)地址(作用域:全工程)。
Memory Map of the image
映像文件分為加載域(Load Region)和運(yùn)行域(Execution Region),一個(gè)程序可以有多個(gè)加載域,一個(gè)加載域有至少一個(gè)運(yùn)行域。加載域?yàn)橛诚癯绦虻膶?shí)際存儲(chǔ)區(qū)域,運(yùn)行域是MCU上電后的運(yùn)行狀態(tài)。加載域和運(yùn)行域的簡(jiǎn)化關(guān)系如圖所示,在執(zhí)行main函數(shù)之前, RW(有初值且不為0的變量)數(shù)據(jù)會(huì)被拷貝到RAM區(qū),同時(shí)還會(huì)在RAM里面創(chuàng)建ZI區(qū)(初始化為0的變量)。

映像的入口地址,也就是整個(gè)程序運(yùn)行的起始地址,為0x08000131。
LR_IROM1加載域,其內(nèi)部包含兩個(gè)運(yùn)行域:ER_IROM1和RW_IRAM1。


Image component sizes
映像組件大小(Image component sizes)給出了整個(gè)映像所有代碼(.o)占用空間的匯總信息。

表示.c/.s 文件生成對(duì)象所占空間大小(單位:字節(jié),下同),即.c/.s 文件編
譯后所占代碼空間的大小。

表示被提取的庫(kù)成員( .lib)添加到映像中的部分所占空間大小。

表示本工程全部程序匯總后的占用情況。
.S文件
棧空間配置

EQU是宏定義的偽指令, 類似C中的define。定義棧大小為0x00000400字節(jié),1024B(1KB)。
AREA命令匯編一個(gè)新的代碼段或者數(shù)據(jù)段。段名為STACK;NOINIT表示不初始化;READWRITE表示可讀寫(xiě);ALIGN=3,表示按照2^3即8字節(jié)對(duì)齊。
SPACE 分配內(nèi)存指令, 分配大小為 Stack_Size 字節(jié)連續(xù)的存儲(chǔ)單元給棧空間。
__initial_sp挨著SPACE放置,表示棧的結(jié)束地址,棧是從高往低生長(zhǎng),所以結(jié)束地址就是棧頂?shù)刂贰S糜诖娣啪植孔兞浚瘮?shù)形參等,屬于編譯器自動(dòng)分配和釋放的內(nèi)存,大小不能超過(guò)內(nèi)部SRAM的大小。

查看map文件可以看出,棧大小與.s文件配置相同。
這里只定義了棧大小,棧的起始位置在keil的“option for target”的target里配置。
堆空間配置

如果不使用C庫(kù)的malloc和free等函數(shù),就用不到堆空間,可以設(shè)置Heap_Size的大小為0。
中斷向量表
AREA RESET, DATA, READONLY
定義一個(gè)只讀的段名字為RESET,放在數(shù)據(jù)段里。
.s文件里的中斷向量表(.vectors或者叫中斷向量表)就是一張函數(shù)指針表,本質(zhì)上是一段連續(xù)的存儲(chǔ)單元,里面存放的是地址(函數(shù)入口地址或者初始棧指針地址),MCU硬件在復(fù)位或發(fā)生中斷時(shí)會(huì)根據(jù)這張表找到要執(zhí)行的函數(shù)。
__Vectors DCD __initial_sp ; Top of Stack
DCD Reset_Handler ; Reset Handler
DCD NMI_Handler ; NMI Handler
DCD HardFault_Handler ; Hard Fault Handler
...
__Vectors_End
DCD: 分配一個(gè)或者多個(gè)以字為單位的內(nèi)存,以四字節(jié)對(duì)齊,并要求初始化這些內(nèi)存。
__initial_sp:初始棧頂指針值(MCU 上電后,SP=這個(gè)值)。
Reset_Handler的地址:復(fù)位時(shí) CPU 會(huì)跳轉(zhuǎn)執(zhí)行這個(gè)函數(shù)。
后面依次是NMI、中斷服務(wù)函數(shù)的入口地址。
中斷發(fā)生時(shí),NVIC硬件會(huì)去查這張表,取出對(duì)應(yīng)的函數(shù)入口地址,CPU 跳轉(zhuǎn)到這個(gè)函數(shù)執(zhí)行。這些函數(shù)就是在startup_xxx.s里定義的xxx_Handler。
中斷向量表被放置在代碼段的最前面。程序在FLASH運(yùn)行時(shí),向量表的起始地址是0x0800 0000。地址0x0800 0000存放的是棧頂?shù)刂贰CD是以四字節(jié)對(duì)齊分配內(nèi)存,也就是下個(gè)地址是0x0800 0004,存放的是Reset_Handler中斷函數(shù)入口地址。向量表中存放的都是中斷服務(wù)函數(shù)的函數(shù)名, 所以C語(yǔ)言中的函數(shù)名對(duì)芯片來(lái)說(shuō)實(shí)際上就是一個(gè)地址。
復(fù)位程序
AREA |.text|, CODE, READONLY
定義一個(gè)名為 .text 的只讀代碼段,存放的是指令(代碼)。|...| 表示名字是字符串,不需要符合符號(hào)命名規(guī)則。

利用PROC、ENDP這一對(duì)偽指令把程序段分為若干個(gè)過(guò)程,使程序的結(jié)構(gòu)加清晰。
PROC表示子程序開(kāi)始。
EXPORT Reset_Handler [WEAK]
聲明復(fù)位中斷向量Reset_Handler為全局屬性,這樣外部文件就可以調(diào)用此復(fù)位中斷服務(wù)。WEAK表示弱定義,如果外部文件優(yōu)先定義了該標(biāo)號(hào)則首先引用外部定義的標(biāo)號(hào),如果外部文件沒(méi)有聲明也不會(huì)出錯(cuò)。這里表示復(fù)位子程序可以由用戶在其他文件重新實(shí)現(xiàn)。
IMPORT __main
IMPORT表示該標(biāo)號(hào)來(lái)自外部文件。這里表示__main這個(gè)函數(shù)來(lái)自外部的文件。
LDR R0, =__main
把符號(hào)__main的地址加載到寄存器R0。這里的__main不是程序員寫(xiě)的main(),而是C運(yùn)行時(shí)庫(kù)的入口函數(shù)(C runtime,CRT)。作用是初始化C環(huán)境,比如:
初始化.data段(把Flash中的初始值拷貝到RAM)。
清零.bss段。
調(diào)用SystemInit()配置時(shí)鐘、外設(shè)。
最后才調(diào)用真正的main()。_main和main是兩個(gè)完全不同的函數(shù)。_main代碼是編譯器自動(dòng)創(chuàng)建的。
BX R0
跳轉(zhuǎn)到R0里保存的地址,也就是執(zhí)行__main函數(shù)。
ENDP 表示子程序結(jié)束。
中斷服務(wù)程序


中斷函數(shù)分為系統(tǒng)異常中斷和外部中斷,外部中斷根據(jù)不同芯片有所變化。B指令是跳轉(zhuǎn)到一個(gè)‘.’,表示無(wú)限循環(huán)。
這些中斷服務(wù)函數(shù)都被[WEAK]聲明為弱定義函數(shù),如果外部文件聲明了一個(gè)標(biāo)號(hào),則優(yōu)先使用外部文件定義的標(biāo)號(hào),如果外部文件沒(méi)有定義也不會(huì)出錯(cuò)。
用戶堆棧配置

第一行判斷是否定義了__MICROLIB。__MICROLIB這個(gè)宏定義在KEIL的“option for target”的target里配置。如果定義__MICROLIB,則__initial_sp、__heap_base和__heap_limit這三個(gè)標(biāo)號(hào)具有全局屬性,可被外部的文件使用。__initial_sp表示棧頂?shù)刂罚琠_heap_base表示堆起始地址,__heap_limit表示堆結(jié)束地址。
如果沒(méi)有定義__MICROLIB,則使用默認(rèn)的 C 庫(kù)運(yùn)行。堆棧的初始化由C庫(kù)函數(shù)__main完成。
IMPORT __use_two_region_memory
告訴匯編器本文件里要用到外部符號(hào)__use_two_region_memory,符號(hào)的作用是選擇內(nèi)存分配模型:
two-region model(常見(jiàn)):堆(heap)從RAM的低地址向上長(zhǎng),棧(stack)從RAM的高地址向下長(zhǎng),二者之間逐漸逼近。
one-region model:堆和棧放在一起,由用戶自己管理。
大多數(shù) Cortex-M 項(xiàng)目默認(rèn)就是 two-region 模型。
EXPORT __user_initial_stackheap
對(duì)外導(dǎo)出一個(gè)函數(shù)符號(hào),ARM C庫(kù)在啟動(dòng)時(shí)會(huì)調(diào)用它,用來(lái)得到堆和棧的初始位置。
LDR R0, = Heap_Mem
保存堆起始地址。
LDR R1, =(Stack_Mem + Stack_Size)
保存棧大小。
LDR R2, = (Heap_Mem + Heap_Size)
保存堆大小。
LDR R3, = Stack_Mem
保存棧頂指針。
BX LR
跳轉(zhuǎn)到 LR 標(biāo)號(hào)給出的地址,不用返回。
.sct文件
是Keil/ARM編譯器(ARMCC)或Arm Compiler 6 (armclang)用來(lái)描述鏈接過(guò)程和內(nèi)存布局的文件,叫做scatter file(分散加載文件)。新建一個(gè)工程時(shí)Keil會(huì)根據(jù)芯片的Flash和RAM大小,生成一個(gè)默認(rèn)的.sct文件。如果需要更復(fù)雜的內(nèi)存分布(多段RAM、外部SDRAM、雙區(qū)Flash),開(kāi)發(fā)者需要手動(dòng)寫(xiě).sct。
MCU廠商(如 ST、NXP、瑞薩)提供的Keil示例工程里通常已經(jīng)寫(xiě)好了.sct文件,配合啟動(dòng)文件(startup_xxx.s)使用。
LR_IROM1 0x08000000 0x00040000 { ; load region size_region
ER_IROM1 0x08000000 0x00040000 { ; load address = execution address
*.o (RESET, +First)
*(InRoot$$Sections)
.ANY (+RO)
.ANY (+XO)
}
RW_IRAM1 0x20000000 0x0000C000 { ; RW data
.ANY (+RW +ZI)
}
}
LR_IROM1表示整個(gè)工程的 Load Region(裝載位置)——對(duì)應(yīng) Flash。
ER_IROM1是Execution Region(執(zhí)行位置)——程序?qū)嶋H運(yùn)行時(shí)的代碼位置。
.ANY (+RO) 把所有只讀段(.text、.rodata)放到 Flash。
.ANY (+RW +ZI)把.data(RW,帶初值的全局變量)和.bss(ZI,Zero Init,未初始化變量)放到RAM。
系統(tǒng)啟動(dòng)流程
MCU上下電會(huì)被復(fù)位,上電后執(zhí)行的程序?qū)嶋H上就是復(fù)位時(shí)執(zhí)行的程序。.S類的啟動(dòng)文件由芯片廠商提供。啟動(dòng)文件用匯編編寫(xiě),是系統(tǒng)上電復(fù)位后第一個(gè)執(zhí)行的程序。啟動(dòng)文件主要做了以下工作:
1、初始化堆棧指針SP = _initial_sp
2、初始化程序計(jì)數(shù)器指針PC = Reset_Handler(這兩步是硬件完成,無(wú)需軟件參與)
3、配置堆棧起始地址、大小
4、初始化.data數(shù)據(jù)段,從flash中復(fù)制到SRAM(這步開(kāi)始是_main()函數(shù))
5、清零.bss全局未初始化變量
6、調(diào)用系統(tǒng)初始化函數(shù)systemInit(),配置時(shí)鐘系統(tǒng)
7、最終調(diào)用main函數(shù)
示例
打開(kāi)一個(gè)keil工程,打開(kāi)debug并點(diǎn)擊reset,查看0x0800 0000處的值,可以看到和SP、PC寄存器的值相同(差一是ARM和thumb指令的規(guī)則)。同時(shí)查看map文件可以看到,和__initial_sp以及startup_stm32f103xe.o(.text)的值相同。

圖中左邊還能看到,Cortex‐M3處理器擁有R0‐R15的寄存器組。其中R13作為堆棧指針SP。SP有兩個(gè),但在同一時(shí)刻只能有一個(gè)可以看到。

浙公網(wǎng)安備 33010602011771號(hào)