要移植操作系統,匯編是道不得不跨過去的坎。所以承接上篇的思路,我准備用匯編寫一個簡單的閃爍LED燈的程式。以此練習匯編,為操作系統做准備。
第一步,還是和上篇一樣,建立一個空的文件夾。
第二步,因為是要用匯編來寫程式,所以不需要啟動代碼,這里選擇否。
第三步,建立一個.s文件,並把文件添加到工程中。
第四步,在LED.s文件中添加如下代碼。
LED0 EQU 0x422101a0 RCC_APB2ENR EQU 0x40021018 GPIOA_CRH EQU 0x40010804 Stack_Size EQU 0x00000400 AREA STACK, NOINIT, READWRITE, ALIGN=3 Stack_Mem SPACE Stack_Size __initial_sp AREA RESET, DATA, READONLY __Vectors DCD __initial_sp ; Top of Stack DCD Reset_Handler ; Reset Handler AREA |.text|, CODE, READONLY THUMB REQUIRE8 PRESERVE8 ENTRY Reset_Handler BL LED_Init MainLoop BL LED_ON BL Delay BL LED_OFF BL Delay B MainLoop LED_Init PUSH {R0,R1, LR} LDR R0,=RCC_APB2ENR ORR R0,R0,#0x04 LDR R1,=RCC_APB2ENR STR R0,[R1] LDR R0,=GPIOA_CRH BIC R0,R0,#0x0F LDR R1,=GPIOA_CRH STR R0,[R1] LDR R0,=GPIOA_CRH ORR R0,R0,#0x03 LDR R1,=GPIOA_CRH STR R0,[R1] MOV R0,#1 LDR R1,=LED0 STR R0,[R1] POP {R0,R1,PC} LED_ON PUSH {R0,R1, LR} MOV R0,#0 LDR R1,=LED0 STR R0,[R1] POP {R0,R1,PC} LED_OFF PUSH {R0,R1, LR} MOV R0,#1 LDR R1,=LED0 STR R0,[R1] POP {R0,R1,PC} Delay PUSH {R0,R1, LR} MOVS R0,#0 MOVS R1,#0 MOVS R2,#0 DelayLoop0 ADDS R0,R0,#1 CMP R0,#330 BCC DelayLoop0 MOVS R0,#0 ADDS R1,R1,#1 CMP R1,#330 BCC DelayLoop0 MOVS R0,#0 MOVS R1,#0 ADDS R2,R2,#1 CMP R2,#15 BCC DelayLoop0 POP {R0,R1,PC} ; NOP END
///////////////////////////////////////////////////////
代碼的簡單講解
1,預定義
LED0 EQU 0x422101a0 ;PA8的Bit-Bond地址。
RCC_APB2ENR EQU 0x40021018
GPIOA_CRH EQU 0x40010804
為方便操作,給每個需要用到的寄存器地址定義一個名字,類似於C語言的#define。PA8的Bit-Bond地址的計算方法可按上篇文章中C語言的算法算出。后面的兩個地址時固定的,可從STM32的手冊查詢,或者根據ST官方的庫文件查找計算。
2,分配棧空間
Stack_Size EQU 0x00000400
AREA STACK, NOINIT, READWRITE, ALIGN=3
Stack_Mem SPACE Stack_Size
__initial_sp
這一段摘自啟動文件。要讀懂這段代碼,首先要了解兩個命令。
AREA命令:AREA 命令指示匯編器匯編一個新的代碼段或數據段。段是獨立的、指定的、不可見的代碼或數據塊,它們由鏈接器處理。格式如下:
AREA 段名,段屬性1,段屬性2,段屬性3。。。
AREA STACK, NOINIT, READWRITE, ALIGN=3
NOINIT: = NO Init,不初始化。
READWRITE : 可讀,可寫。
ALIGN =3 : 2^3 對齊,即8字節對齊。
SPACE命令:SPACE 命令保留一個用零填充的存儲器塊。
所以整段的意思為:分配一個STACK段,該段不初始化,可讀寫,按8字節對齊。分配一個大小為Stack_Size的存儲空間,並使棧頂的地址為__initial_sp。
3,分配向量表
AREA RESET, DATA, READONLY
__Vectors DCD __initial_sp ; Top of Stack
DCD Reset_Handler ; Reset Handler
這里的向量可參考我之前寫的《STM32向量表詳細分析》。
4,開始代碼段
AREA |.text|, CODE, READONLY
通知匯編器,開始代碼段。
THUMB
REQUIRE8
PRESERVE8
這段的意思是,匯編器支持THUMB指令,代碼段按8字節對齊。
ENTRY命令:聲明整個程式的入口點,入口點有且僅有一個。不管哪種語言,編譯器都得有個入口點,這沒什么好說的。
5,程序正式開始。
后面的代碼,皆用標准的THUMB2匯編指令。首先了解下代碼中用到的指令。
BL:帶鏈接的跳轉指令。當使用該指令跳轉時,當前地址(PC)會自動送入LR寄存器。
B:無條件跳轉。
PUSH和POP:可以看到,所有的子程序都是由PUSH和POP包起來的。借用一張圖解釋下這兩個指令。
據上可知,PUSH {R0,R1, LR}的意思即把R0,R1,LR中的值放入堆棧中。由於主程式中使用BL跳轉指令,所以LR中的值實際上就是當前PC的值。而POP {R0,R1,PC}的意思即是將棧中之前存的R0,R1,LR的值返還給R0,R1,PC。這樣就完成了子程序的返回。
LDR和STR:寄存器的裝載和存儲指令。
LDR是把地址裝載到寄存器中(比如R0)。
STR是把值存儲到寄存器所指的地址中。
舉個例子:
MOV R0,#1 ;將立即數1送入R0.
LDR R1,=LED0;將PA8 bit-bond的地址送入R1.
STR R0,[R1];將R0的值,也就是1,送給R1中的值所指向的地址中,也就是PA8的bit-bond地址。
上面三句話的意思即是將PA8置1。
ORR和BIC:
ORR 按位或操作。ORR R0,R0,#0x04意思即將R0中的數或上0x04,再將結果送往R0中。實際意思就是將R0的第二位置1,其他位不變。
BIC 先把立即數取反,再按位與。
CMP和BCC:CMP是比較兩個數,相等或大於則將標志位C置位,否則將C清零。BCC是個組合指令,實際為B+CC,意思是如果C=0則跳轉。
CMP R2,#15; 計算R2-15的值,若是R2<15,則C=0;若是R2>=15,則C=1。
BCC DelayLoop0;若是C=0,則跳到DelayLoop0,若是c=1,則不跳轉。
以上就是代碼段相關指令的介紹,相信了解了這些指令的含義,要理解代碼並不困難。
整個代碼的結構和上篇用C語言寫的基本是一樣的。可參照理解
////////////////////////////////////////////////////
第五步,編譯,下載。
編譯后,會有一個警告 No section matches pattern……可不用管。下載后,LED燈正常閃爍。