四章 分析第一個程式
今天我們會討論一下第二章的那段程式,不過之前我們要再多學點指令。
我們要學的第一條指令是STR的另一種形式。我們能以32位方式工作,其實也能以16位,8位方式工作,不過你要確定你使用的地址是不是按照32位,16位對齊了。這裡幾個例子示範了如果讀取和寫入16位和8位的值:(我這裡只用了寄存器定址方式,其實都可以用)
ldrh r0, [r1] LDR後面的H意思是halfword(半字,word=32,half=16,byte=8 bits),這條指令讀取r1所指地址處的16bits到r0。高16位清零。
ldrsh r0, [r1] 讀取有符號的半字。和上面這條差不多,只是這個16位元會符號擴充到r0的高16位。如果你讀取的數是0xFE3A,那麼r0就變成0xFFFFFE3A。如果有人不知道符號擴充的話,就是把最高位填充到高位。如果你讀取的數是0x0342, r0就是0x00000342。
ldrb r0, [r1] 讀取8bits資料,高24位清零。
ldrsb r0, [r1] 讀取有符號的8bits數,並且符號擴充。(bit 7填充到高位去)
指令STR也有類似的變種,不過沒有符號擴充的那種。
分支指令
分支指令可以讓我們跳轉到另一段代碼,也可以是條件跳轉。一般來說分支指令是這樣的:
b label_name 這條指令將會跳轉到標籤名之後的代碼。希望你知道什麼是標籤,如果你不知道話,標籤是這樣:
label: 標籤是一個詞(規則基本和C一樣),後面有一個冒號。
條件分支基本一樣,只是b後面有一個條件代碼。條件有:
EQ Equal,等於
NE Not Equal,不等
CS Carry Set,進位置位
CC Carry Clear,進位為零
HS Unsigned Higher or Same,無符號數比較,大於等於
LO Unsigned Lower,無符號數比較,小於
MI Minus/Negative,負數
PL Plus/Positive or Zero,正數和零
VS Overflow,溢出
VC No Overflow,沒溢出
HI Unsigned Higher,無符號數比較,大於
LS Unsigned Lower or Same,無符號數比較,小於等於
GE Signed Greater than or Equal,有符號數比較,大於等於
LT Signed Less than,有符號數比較,小於
GT Signed Greater than,有符號數比較,大於
LE Signed Less than or Equal,有符號數比較,小於等於
AL Always,總是跳轉,預設,不需要寫明
NV Never,總不跳轉,無用
下面我們來分析一下第二章的程式。
.arm - 告訴彙編器使用ARM指令集
.text - 告訴彙編器將下面內容放置於TEXT區段(代碼區,一般是GBA卡的ROM)
.global main - 告訴彙編器允許其他檔案訪問main,這是必須的,這樣連接器才能找到main函數
main: - 指明main函數的標籤
mov r0, #0x4000000 - r0賦值為0x4000000(這是REG_DISPCNT的地址, LCD螢幕的控制寄存器)。
mov r1, #0x400 - r1賦值為x400 (不能直接mov 0x403,因為這個數不能由一個8bits數加位移獲得)
add r1, r1, #3 - r1加3 (r1 = r1 + 3), r1現在是0x403。(含義是mode 3和BG 2)
str r1, [r0] - 寫入到REG_DISPCNT
@blank - asm中的注釋是以@開始的,不過我們把檔案儲存為.S檔案,再用GCC編譯的話,也能用C/C++風格的注釋符號
mov r0, #0x6000000 @ VRM地址
mov r1, #0xFF @ 紅色
mov r2, #0x9600 @ 要填滿整個螢幕需要寫入的字數(2byte)
loop1: @ 一個標籤
strh r1, [r0], #2 @ 把r1中的16bits數賦值到r0所指的地址處
@ 然後r0加2,因為16bits是2bytes。
subs r2, r2, #1 @ r2減1(r2 = r2 - 1). SUB後面的S表明需要根據結果影響CPSR
@ (當前處理器狀態寄存器)中的標誌位
bne loop1 @ 如果狀態寄存器表明結果不為0,則跳轉到loop1
infin: @ 一個無限迴圈
b infin
請注意SUB後面的那個S,如果不寫這個S,那麼狀態寄存器就永遠不會顯示出結果為0,於是這個迴圈變成了死迴圈。算術指令(還有些其他指令)只有加上S才會影響到CPSR,這很重要。
顯示圖片
我覺得你可能已經打算要顯示圖片了,那麼來做做看吧。如果你沒有Bimbo這個軟體,那麼到GbaDev下一個,然後找一張你喜歡的照片,把它導成二進位檔案,命名為“pic.bin”,模式設定為3。下面我會列出完整的程式和注釋來解釋如何顯示:
.arm
.text
.global main
main:
mov r0, #0x4000000 @ 通常的初始化設定
mov r1, #0x400 @ 0x403是BG 2 enable,mode 3.
add r1, r1, #3
strh r1, [r0] @ 16位的寫操作
mov r0, #0x6000000 @ VRAM
ldr r1, =pic @ 用這種參數是標籤的LDR形式,會把該標籤的地址寫入到r1
mov r2, #0x960 @ 需要填滿整個螢幕要寫入的次數,這次是32位元組一寫,方式和上段代碼不同。
loop1:
ldmia r1!, { r3,r4,r5,r6,r7,r8,r9,r10 } @ 將r1所指的地址處的資料32bits一組讀取到列表中的各個寄存器中
@ 每讀取一次地址+4,最後讀取完這個地址寫回到r1,因為r1後面有個!
@ 要注意這個指令不使用方括弧,但是r1還是用作指標。
stmia r0!, { r3,r4,r5,r6,r7,r8,r9,r10 } @ 將列表中的各個寄存器的值依次寫入到r0所指的地址中,每次寫入32bits
@ 並且地址+4,最後地址寫回到r0。
@ 這些指令算是一個比較快的記憶體複製的方法,不過只能用在你有很多寄存器閒置情況
@ 這裡使用的8個寄存器是r3-r10,r2用作計數器了,r0和r1是地址寄存器,不要把這些寫到列表中
subs r2, r2, #1 @ 更新標誌位的減法
bne loop1 @ r2不是0的話會不停迴圈
infin:
b infin @ 無限迴圈
.ltorg @ 給彙編器一個位置來放置立即數“池”,為了“ldr REG,= (s)”這種指令。
pic: @ 一個指向include進來的位元據的標籤
.incbin "pic.bin" @ include一個二進位檔案
挑戰:到gbadev去下GBATEK,然後看看你能不能夠在mode4下面顯示這個圖片。你需要減少一些顏色,因為mode 4是8位色調色盤模式的。
提示:如果你還是使用二進位方式匯出,那麼先匯出調色盤,使用一個迴圈進行複製(使用寫回),然後在用一個類似的迴圈複製圖片。調色盤大小是0x200 bytes,圖片大小時0x12c00 bytes。
我會在第五章末尾給出答案。
在第五章,我們會較細緻地研究一下GBA的圖片系統。