從匯編編程的角度上看,STM32從復位到主程序的過程要比MCS稍顯復雜,一個比較主要的原因是CortexM3用很多偽指令來描述啟動過程,會讓我這樣的菜鳥感到云遮霧罩。
說到底,CortexM3也好,MCS51也罷,啟動過程都是大同小異的,無非是PC指針初始化,執行完用戶的復位程序之后取出主程序地址,跳到主程序上。
對于MCS51來說,這個過程非常好理解:
ORG 0000H
LJMP Reset
ORG 0003H
;配置各個中斷服務程序的入口
………………
ORG 0100H
Reset:
………………
;初始化及主程序段
51單片機復位,PC=0000H指向首地址,取指令和操作數,轉0100H執行初始化及主程序,先定義堆棧指針SP,然后再干別的。
這個過程說明MCS51程序存儲器是這樣分區的:
復位入口 0000H-0002H。放置越過中斷向量程序段,直達主程序段的跳轉指令。
中斷向量 0003H-00BBH。放置中斷服務程序向量。有些增強型51單片機的中斷向量已經用到了00BBH。
復位程序 從最后一個中斷向量操作數之后的地址開始。通常是先配置堆棧指針SP。
主程序段 主程序代碼區。
但對于CortexM3來說,它的復位過程有點兒特殊:如果BOOT0配置成從FLASH區啟動,那首先是PC=0x08000000,先把這個地址開始的4個字節數值賦值給主棧指針MSP,然后再把0x08000004開始的4個字節賦值給PC,轉到復位程序。
實際上,復位時PC=0x00000000,但BOOT0=0時,0x00000000會被映射到0x08000000上,所以說PC是從0x08000000開始也是可以的。
CortexM3程序存儲器分區是如下:
MSP賦值 0x08000000—0x08000003。當PC指向這個數據塊時,4字節數自動賦值給MSP。
復位入口 0x08000004—0x08000007。4個字節,放置復位程序的入口地址。
中斷向量 在0x08000007之后,每個向量的入口地址可以定義。每個中斷向量地址可以浮動。
復位程序 從最后一個中斷向量操作數之后的地址開始。
主程序段 主程序代碼區。
歸結起來,兩者跳轉的方式有所不同,MCS51是通過入口放置指令,而CortexM3則是通過入口處放置地址來實現跳轉。
為了深入探究一下CortexM3的復位機制,我編一個簡單的程序,力圖通過HEX和LST文件理清脈絡。
環境如下:
IDE:Keil 4.12
硬件:STM32F103VCT6,BOOT0=0
程序:匯編語言
LST程序如下:
(1) 初始化程序Initialization.LST
00000000 20005000 MSP_Top EQU 0x20005000
00000000 THUMB
00000000 AREA Reset, Data, ReadOnly
00000000 EXPORT __Vectors
00000000 EXPORT __Vectors_End
00000000 EXPORT __Vectors_Size
00000000 __Vectors
00000000 20005000 DCD MSP_Top
00000004 00000000 DCD Reset_Handler
00000008 00 00 00
00 00 00
00 00 SPACE 0x8
00000010 __Vectors_End
00000010 00000010 __Vectors_Size EQU __Vectors_End - __Vectors
00000010 AREA |.text|, Code, Readonly
00000000 Reset_Handler PROC
00000000 EXPORT Reset_Handler [WEAK]
00000000 IMPORT Main
00000000 4800 LDR R0,=Main
00000002 4700 BX R0
00000004 ENDP
00000004 ALIGN
00000004 END
主棧配置到SRAM區0x20005000,暫時未定義主棧大小。暫時未定義其它中斷向量。
(2) 主程序Main.LST
INCLUDE Stm32F103REG.h
00000000 Main PROC
00000000 EXPORT Main
00000000 4801 LDR R0,=RCC_CR
00000002 6801 LDR R1,[R0]
00000004 E7FE B Main
00000006 ENDP
00000006 END
Stm32F103REG.h是自行編制的寄存器定義程序。
上述程序編譯之后打開工程HEX文件和兩個程序段的LST文件并整理一下,刪除一些不重要的數據,結果如下:
(3)工程HEX文件
:020000040800F2
:100000000050002011000008000000000000000067
:10001000004800471900000801480168FCE700009B
:04002000001002408A
:0400000508000019D6
:00000001FF
看起來有些亂七八糟,整理一下。
數據長度 地址偏移 數據類型 數據塊 校驗和
1byte 2bytes 1byte n bytes 1byte
02 00 00 04 08 00 F2
10 00 00 00 00 50 00 20 11 00 00 08 00 00 00 00 00 00 00 00 67
10 00 10 00 00 48 00 47 19 00 00 08 01 48 01 68 FC E7 00 00 9B
04 00 20 00 00 10 02 40 8A
04 00 00 05 08 00 00 19 D6
00 00 00 01 FF
數據長度好理解,是說本行中數據塊有幾個字節。
地址偏移是指當前行第一個數據相對于基址的位置。
查了一下資料,數據類型可分為5種類型:
00=這一行的數據塊里全是數據;
01=HEX文件結束符,所在行數據塊為空;
02=數據塊里的數據是擴展段地址;
03=數據塊里的數據是段地址;
04=數據塊里的數據是線性地址高位;
05=數據塊里的數據是線性地址。
看起來,數據類型可分為三大類:數據類(00)、地址類(02~05)和編譯標識類(01)
第一行數據類型為04,說明后面的0800和地址偏移0000構成了一個基址0x08000000,這是STM32F103 FLASH區的首址,后序行的數據存儲地址都可以根據這個基址加偏移量計算出來。
第二行數據類型為00,偏移量為0000,也就是說,數據塊的第一個00被存放在地址0x08000000中,這就是單片機復位時從FLASH區啟動的首址。
按照前面的說明,首址之后的4個字節應當是準備賦給MSP的值,也就是程序指定的0x20005000,可現在卻是00 50 00 20,看了半天恍然大悟,突然想起CortexM3數據格式是所謂的小端模式Little-endian,所以這4個字節的32位數據得按字節倒過來讀成20 00 50 00。但當數據類型為地址類時,數據塊數據就可以正著讀了,哈哈。
00 50 00 20之后又是一個地址,倒過來是08 00 00 11,這就是復位程序入口地址。其后的數據是8個字節的00,這就是SPACE 0x8的功勞了。 按照流程,復位地址賦值給PC之后會從地址0x08000011開始運行,0x08000011單元的數據是48,對照一下Initialization.LST,對應的是它的第一條可執行語句'LDR R0,=Main',只不過這條指令的代碼48 00也存儲成了小端模式。
以此類推,逐一閱讀后面的數據,過程就出來了,Main的段地址是第三行的那個大紅圈里的數據0x08000019,從它以后就是Main的指令代碼了。但這里有一個問題,Main.LST表明‘B Main’指令的代碼是E7 FE,可HEX里卻是E7 FC,這又是怎么回事兒呢?不知道哪位前輩能告訴我。
還有一個問題:從0x08000020開始的數據明顯是RCC_CR的地址,它怎么會出現在這兒呢?
用匯編寫STM32程序是苦中有樂,有人說我這是自找麻煩,但我不這么認為。與使用固件庫編程相比,無論是面向寄存器編程還是直接用匯編,編程難度都要大很多,但是,程序透明度也比前者大得多,絕對不會出現神龍見首不見尾的感覺,尤其是對于我這種不太會C語言的硬件工程師就更是如此。
|