計算機實驗室之樹莓派:課程 11 輸入02
課程輸入 02 是以課程輸入 01 為基礎講解的,透過一個簡單的命令列實現使用者的命令輸入和計算機的處理和顯示。本文假設你已經具備 課程11:輸入01 的作業系統程式碼基礎。
1、終端
幾乎所有的作業系統都是以字元終端顯示啟動的。經典的黑底白字,透過鍵盤輸入計算機要執行的命令,然後會提示你拼寫錯誤,或者恰好得到你想要的執行結果。這種方法有兩個主要優點:鍵盤和顯示器可以提供簡易、健壯的計算機互動機制,幾乎所有的計算機系統都採用這個機制,這個也廣泛被系統管理員應用。
早期的計算一般是在一棟樓裡的一個巨型計算機系統,它有很多可以輸命令的'終端'。計算機依次執行不同來源的命令。
讓我們分析下真正想要哪些資訊:
- 計算機開啟後,顯示歡迎資訊
- 計算機啟動後可以接受輸入標誌
- 使用者從鍵盤輸入帶引數的命令
- 使用者輸入Enter鍵或提交按鈕
- 計算機解析命令後執行可用的命令
- 計算機顯示命令的執行結果,過程資訊
- 迴圈跳轉到步驟 2
這樣的終端被定義為標準的輸入輸出裝置。用於(顯示)輸入的螢幕和列印輸出內容的螢幕是同一個(LCTT 譯註:最早期的輸出列印真是“列印”到印表機/電傳機的,而用於輸入的終端只是鍵盤,除非做了回顯,否則輸出終端是不會顯示輸入的字元的)。也就是說終端是對字元顯示的一個抽象。字元顯示中,單個字元是最小的單元,而不是畫素。螢幕被劃分成固定數量不同顏色的字元。我們可以在現有的螢幕程式碼基礎上,先儲存字元和對應的顏色,然後再用方法 DrawCharacter
把其推送到螢幕上。一旦我們需要字元顯示,就只需要在螢幕上畫出一行字串。
新建檔名為 terminal.s
,如下:
.section .data
.align 4
terminalStart:
.int terminalBuffer
terminalStop:
.int terminalBuffer
terminalView:
.int terminalBuffer
terminalColour:
.byte 0xf
.align 8
terminalBuffer:
.rept 128*128
.byte 0x7f
.byte 0x0
.endr
terminalScreen:
.rept 1024/8 core.md Dict.md lctt2014.md lctt2016.md lctt2018.md LICENSE published README.md scripts sources translated 768/16
.byte 0x7f
.byte 0x0
.endr
這是檔案終端的配置資料檔案。我們有兩個主要的儲存變數:terminalBuffer
和 terminalScreen
。terminalBuffer
儲存所有顯示過的字元。它儲存 128 行字元文字(1 行包含 128 個字元)。每個字元有一個 ASCII 字元和顏色單元組成,初始值為 0x7f(ASCII 的刪除字元)和 0(前景色和背景色為黑)。terminalScreen
儲存當前螢幕顯示的字元。它儲存 128x48 個字元,與 terminalBuffer
初始化值一樣。你可能會覺得我僅需要 terminalScreen
就夠了,為什麼還要terminalBuffer
,其實有兩個好處:
- 我們可以很容易看到字串的變化,只需畫出有變化的字元。
- 我們可以回滾終端顯示的歷史字元,也就是緩衝的字元(有限制)
這種獨特的技巧在低功耗系統裡很常見。畫屏是很耗時的操作,因此我們僅在不得已的時候才去執行這個操作。在這個系統裡,我們可以任意改變 terminalBuffer
,然後呼叫一個僅複製螢幕上位元組變化的方法。也就是說我們不需要持續畫出每個字元,這樣可以節省一大段跨行文字的操作時間。
你總是需要嘗試去設計一個高效的系統,如果在很少變化的情況下這個系統會執行的更快。
其他在 .data
段的值得含義如下:
terminalStart
寫入到terminalBuffer
的第一個字元terminalStop
寫入到terminalBuffer
的最後一個字元terminalView
表示當前螢幕的第一個字元,這樣我們可以控制滾動螢幕temrinalColour
即將被描畫的字元顏色
terminalStart
需要儲存起來的原因是 termainlBuffer
是一個環狀緩衝區。意思是當緩衝區變滿時,末尾地方會回滾覆蓋開始位置,這樣最後一個字元變成了第一個字元。因此我們需要將 terminalStart
往前推進,這樣我們知道我們已經佔滿它了。如何實現緩衝區檢測:如果索引越界到緩衝區的末尾,就將索引指向緩衝區的開始位置。環狀緩衝區是一個比較常見的儲存大量資料的高明方法,往往這些資料的最近部分比較重要。它允許無限制的寫入,只保證最近一些特定資料有效。這個常常用於訊號處理和資料壓縮演算法。這樣的情況,可以允許我們儲存 128 行終端記錄,超過128行也不會有問題。如果不是這樣,當超過第 128 行時,我們需要把 127 行分別向前複製一次,這樣很浪費時間。
環狀緩衝區是資料結構一個例子。這是一個組織資料的思路,有時我們透過軟體實現這種思路。
之前已經提到過 terminalColour
幾次了。你可以根據你的想法實現終端顏色,但這個文字終端有 16 個前景色和 16 個背景色(這裡相當於有 162 = 256 種組合)。CGA終端的顏色定義如下:
表格 1.1 - CGA 顏色編碼
序號 | 顏色 (R, G, B) |
---|---|
0 | 黑 (0, 0, 0) |
1 | 藍 (0, 0, ⅔) |
2 | 綠 (0, ⅔, 0) |
3 | 青色 (0, ⅔, ⅔) |
4 | 紅色 (⅔, 0, 0) |
5 | 品紅 (⅔, 0, ⅔) |
6 | 棕色 (⅔, ⅓, 0) |
7 | 淺灰色 (⅔, ⅔, ⅔) |
8 | 灰色 (⅓, ⅓, ⅓) |
9 | 淡藍色 (⅓, ⅓, 1) |
10 | 淡綠色 (⅓, 1, ⅓) |
11 | 淡青色 (⅓, 1, 1) |
12 | 淡紅色 (1, ⅓, ⅓) |
13 | 淺品紅 (1, ⅓, 1) |
14 | 黃色 (1, 1, ⅓) |
15 | 白色 (1, 1, 1) |
我們將前景色儲存到顏色的低位元組,背景色儲存到顏色高位元組。除了棕色,其他這些顏色遵循一種模式如二進位制的高位位元代表增加 ⅓ 到每個元件,其他位元代表增加 ⅔ 到各自元件。這樣很容易進行 RGB 顏色轉換。
棕色作為替代色(黑黃色)既不吸引人也沒有什麼用處。
我們需要一個方法從 TerminalColour
讀取顏色編碼的四個位元,然後用 16 位元等效引數呼叫 SetForeColour
。嘗試你自己實現。如果你感覺麻煩或者還沒有完成螢幕系列課程,我們的實現如下:
.section .text
TerminalColour:
teq r0,#6
ldreq r0,=0x02B5
beq SetForeColour
tst r0,#0b1000
ldrne r1,=0x52AA
moveq r1,#0
tst r0,#0b0100
addne r1,#0x15
tst r0,#0b0010
addne r1,#0x540
tst r0,#0b0001
addne r1,#0xA800
mov r0,r1
b SetForeColour
2、文字顯示
我們的終端第一個真正需要的方法是 TerminalDisplay
,它用來把當前的資料從 terminalBuffer
複製到 terminalScreen
和實際的螢幕。如上所述,這個方法必須是最小開銷的操作,因為我們需要頻繁呼叫它。它主要比較 terminalBuffer
與 terminalDisplay
的文字,然後只複製有差異的位元組。請記住 terminalBuffer
是以環狀緩衝區執行的,這種情況,就是從 terminalView
到 terminalStop
,或者 128*48 個字元,要看哪個來的最快。如果我們遇到 terminalStop
,我們將會假定在這之後的所有字元是 7f16 (ASCII 刪除字元),顏色為 0(黑色前景色和背景色)。
讓我們看看必須要做的事情:
- 載入
terminalView
、terminalStop
和terminalDisplay
的地址。 - 對於每一行:
- 對於每一列:
- 如果
terminalView
不等於terminalStop
,根據terminalView
載入當前字元和顏色 - 否則載入 0x7f 和顏色 0
- 從
terminalDisplay
載入當前的字元 - 如果字元和顏色相同,直接跳轉到第 10 步
- 儲存字元和顏色到
terminalDisplay
- 用
r0
作為背景色引數呼叫TerminalColour
- 用
r0 = 0x7f
(ASCII 刪除字元,一個塊)、r1 = x
、r2 = y
呼叫DrawCharacter
- 用
r0
作為前景色引數呼叫TerminalColour
- 用
r0 = 字元
、r1 = x
、r2 = y
呼叫DrawCharacter
- 對位置引數
terminalDisplay
累加 2 - 如果
terminalView
不等於terminalStop
,terminalView
位置引數累加 2 - 如果
terminalView
位置已經是檔案緩衝器的末尾,將它設定為緩衝區的開始位置 - x 座標增加 8
- 如果
- y 座標增加 16
- 對於每一列:
嘗試去自己實現吧。如果你遇到問題,我們的方案下面給出來了:
1、我這裡的變數有點亂。為了方便起見,我用 taddr
儲存 textBuffer
的末尾位置。
.globl TerminalDisplay
TerminalDisplay:
push {r4,r5,r6,r7,r8,r9,r10,r11,lr}
x .req r4
y .req r5
char .req r6
col .req r7
screen .req r8
taddr .req r9
view .req r10
stop .req r11
ldr taddr,=terminalStart
ldr view,[taddr,#terminalView - terminalStart]
ldr stop,[taddr,#terminalStop - terminalStart]
add taddr,#terminalBuffer - terminalStart
add taddr,#128*128*2
mov screen,taddr
2、從 yLoop
開始執行。
mov y,#0
yLoop$:
2.1、
mov x,#0
xLoop$:
從 xLoop
開始執行。
2.1.1、為了方便起見,我把字元和顏色同時載入到 char
變數了
teq view,stop
ldrneh char,[view]
2.1.2、這行是對上面一行的補充說明:讀取黑色的刪除字元
moveq char,#0x7f
2.1.3、為了簡便我把字元和顏色同時載入到 col
裡。
ldrh col,[screen]
2.1.4、 現在我用 teq
指令檢查是否有資料變化
teq col,char
beq xLoopContinue$
2.1.5、我可以容易的儲存當前值
strh char,[screen]
2.1.6、我用位元偏移指令 lsr
和 and
指令從切分 char
變數,將顏色放到 col
變數,字元放到 char
變數,然後再用位元偏移指令 lsr
獲取背景色後呼叫 TerminalColour
。
lsr col,char,#8
and char,#0x7f
lsr r0,col,#4
bl TerminalColour
2.1.7、寫入一個彩色的刪除字元
mov r0,#0x7f
mov r1,x
mov r2,y
bl DrawCharacter
2.1.8、用 and
指令獲取 col
變數的低半位元組,然後呼叫 TerminalColour
and r0,col,#0xf
bl TerminalColour
2.1.9、寫入我們需要的字元
mov r0,char
mov r1,x
mov r2,y
bl DrawCharacter
2.1.10、自增螢幕指標
xLoopContinue$:
add screen,#2
2.1.11、如果可能自增 view
指標
teq view,stop
addne view,#2
2.1.12、很容易檢測 view
指標是否越界到緩衝區的末尾,因為緩衝區的地址儲存在 taddr
變數裡
teq view,taddr
subeq view,#128*128*2
2.1.13、 如果還有字元需要顯示,我們就需要自增 x
變數然後到 xLoop
迴圈執行
add x,#8
teq x,#1024
bne xLoop$
2.2、 如果還有更多的字元顯示我們就需要自增 y
變數,然後到 yLoop
迴圈執行
add y,#16
teq y,#768
bne yLoop$
3、不要忘記最後清除變數
pop {r4,r5,r6,r7,r8,r9,r10,r11,pc}
.unreq x
.unreq y
.unreq char
.unreq col
.unreq screen
.unreq taddr
.unreq view
.unreq stop
3、行列印
現在我有了自己 TerminalDisplay
方法,它可以自動顯示 terminalBuffer
內容到 terminalScreen
,因此理論上我們可以畫出文字。但是實際上我們沒有任何基於字元顯示的例程。 首先快速容易上手的方法便是 TerminalClear
, 它可以徹底清除終端。這個方法不用迴圈也很容易實現。可以嘗試分析下面的方法應該不難:
.globl TerminalClear
TerminalClear:
ldr r0,=terminalStart
add r1,r0,#terminalBuffer-terminalStart
str r1,[r0]
str r1,[r0,#terminalStop-terminalStart]
str r1,[r0,#terminalView-terminalStart]
mov pc,lr
現在我們需要構造一個字元顯示的基礎方法:Print
函式。它將儲存在 r0
的字串和儲存在 r1
的字串長度簡單的寫到螢幕上。有一些特定字元需要特別的注意,這些特定的操作是確保 terminalView
是最新的。我們來分析一下需要做什麼:
- 檢查字串的長度是否為 0,如果是就直接返回
- 載入
terminalStop
和terminalView
- 計算出
terminalStop
的 x 座標 - 對每一個字元的操作:
- 檢查字元是否為新起一行
- 如果是的話,自增
bufferStop
到行末,同時寫入黑色刪除字元 - 否則複製當前
terminalColour
的字元 - 檢查是否在行末
- 如果是,檢查從
terminalView
到terminalStop
之間的字元數是否大於一屏 - 如果是,
terminalView
自增一行 - 檢查
terminalView
是否為緩衝區的末尾,如果是的話將其替換為緩衝區的起始位置 - 檢查
terminalStop
是否為緩衝區的末尾,如果是的話將其替換為緩衝區的起始位置 - 檢查
terminalStop
是否等於terminalStart
, 如果是的話terminalStart
自增一行。 - 檢查
terminalStart
是否為緩衝區的末尾,如果是的話將其替換為緩衝區的起始位置
- 存取
terminalStop
和terminalView
試一下自己去實現。我們的方案提供如下:
1、這個是 Print
函式開始快速檢查字串為0的程式碼
.globl Print
Print:
teq r1,#0
moveq pc,lr
2、這裡我做了很多配置。 bufferStart
代表 terminalStart
, bufferStop
代表terminalStop
, view
代表 terminalView
,taddr
代表 terminalBuffer
的末尾地址。
push {r4,r5,r6,r7,r8,r9,r10,r11,lr}
bufferStart .req r4
taddr .req r5
x .req r6
string .req r7
length .req r8
char .req r9
bufferStop .req r10
view .req r11
mov string,r0
mov length,r1
ldr taddr,=terminalStart
ldr bufferStop,[taddr,#terminalStop-terminalStart]
ldr view,[taddr,#terminalView-terminalStart]
ldr bufferStart,[taddr]
add taddr,#terminalBuffer-terminalStart
add taddr,#128*128*2
3、和通常一樣,巧妙的對齊技巧讓許多事情更容易。由於需要對齊 terminalBuffer
,每個字元的 x 座標需要 8 位要除以 2。
and x,bufferStop,#0xfe
lsr x,#1
4.1、我們需要檢查新行
charLoop$:
ldrb char,[string]
and char,#0x7f
teq char,#'\n'
bne charNormal$
4.2、迴圈執行值到行末寫入 0x7f;黑色刪除字元
mov r0,#0x7f
clearLine$:
strh r0,[bufferStop]
add bufferStop,#2
add x,#1
teq x,#128 blt clearLine$
b charLoopContinue$
4.3、儲存字串的當前字元和 terminalBuffer
末尾的 terminalColour
然後將它和 x 變數自增
charNormal$:
strb char,[bufferStop]
ldr r0,=terminalColour
ldrb r0,[r0]
strb r0,[bufferStop,#1]
add bufferStop,#2
add x,#1
4.4、檢查 x 是否為行末;128
charLoopContinue$:
cmp x,#128
blt noScroll$
4.5、設定 x 為 0 然後檢查我們是否已經顯示超過 1 屏。請記住,我們是用的迴圈緩衝區,因此如果 bufferStop
和 view
之前的差是負值,我們實際上是環繞了緩衝區。
mov x,#0
subs r0,bufferStop,view
addlt r0,#128*128*2
cmp r0,#128*(768/16)*2
4.6、增加一行位元組到 view
的地址
addge view,#128*2
4.7、 如果 view
地址是緩衝區的末尾,我們就從它上面減去緩衝區的長度,讓其指向開始位置。我會在開始的時候設定 taddr
為緩衝區的末尾地址。
teq view,taddr
subeq view,taddr,#128*128*2
4.8、如果 stop
的地址在緩衝區末尾,我們就從它上面減去緩衝區的長度,讓其指向開始位置。我會在開始的時候設定 taddr
為緩衝區的末尾地址。
noScroll$:
teq bufferStop,taddr
subeq bufferStop,taddr,#128*128*2
4.9、檢查 bufferStop
是否等於 bufferStart
。 如果等於增加一行到 bufferStart
。
teq bufferStop,bufferStart
addeq bufferStart,#128*2
4.10、如果 start
的地址在緩衝區的末尾,我們就從它上面減去緩衝區的長度,讓其指向開始位置。我會在開始的時候設定 taddr
為緩衝區的末尾地址。
teq bufferStart,taddr
subeq bufferStart,taddr,#128*128*2
迴圈執行知道字串結束
subs length,#1
add string,#1
bgt charLoop$
5、儲存變數然後返回
charLoopBreak$:
sub taddr,#128*128*2
sub taddr,#terminalBuffer-terminalStart
str bufferStop,[taddr,#terminalStop-terminalStart]
str view,[taddr,#terminalView-terminalStart]
str bufferStart,[taddr]
pop {r4,r5,r6,r7,r8,r9,r10,r11,pc}
.unreq bufferStart
.unreq taddr
.unreq x
.unreq string
.unreq length
.unreq char
.unreq bufferStop
.unreq view
這個方法允許我們列印任意字元到螢幕。然而我們用了顏色變數,但實際上沒有設定它。一般終端用特性的組合字元去行修改顏色。如 ASCII 轉義(1b16)後面跟著一個 0 - f 的 16 進位制的數,就可以設定前景色為 CGA 顏色號。如果你自己想嘗試實現;在下載頁面有一個我的詳細的例子。
4、標誌輸入
現在我們有一個可以列印和顯示文字的輸出終端。這僅僅是說對了一半,我們需要輸入。我們想實現一個方法:ReadLine
,可以儲存檔案的一行文字,文字位置由 r0
給出,最大的長度由 r1
給出,返回 r0
裡面的字串長度。棘手的是使用者輸出字元的時候要回顯功能,同時想要退格鍵的刪除功能和命令回車執行功能。它們還需要一個閃爍的下劃線代表計算機需要輸入。這些完全合理的要求讓構造這個方法更具有挑戰性。有一個方法完成這些需求就是儲存使用者輸入的文字和檔案大小到記憶體的某個地方。然後當呼叫 ReadLine
的時候,移動 terminalStop
的地址到它開始的地方然後呼叫 Print
。也就是說我們只需要確保在記憶體維護一個字串,然後構造一個我們自己的列印函式。
按照慣例,許多程式語言中,任意程式可以訪問 stdin 和 stdin,它們可以連線到終端的輸入和輸出流。在圖形程式其實也可以進行同樣操作,但實際幾乎不用。
讓我們看看 ReadLine
做了哪些事情:
- 如果字串可儲存的最大長度為 0,直接返回
- 檢索
terminalStop
和terminalStop
的當前值 - 如果字串的最大長度大約緩衝區的一半,就設定大小為緩衝區的一半
- 從最大長度裡面減去 1 來確保輸入的閃爍字元或結束符
- 向字串寫入一個下劃線
- 寫入一個
terminalView
和terminalStop
的地址到記憶體 - 呼叫
Print
列印當前字串 - 呼叫
TerminalDisplay
- 呼叫
KeyboardUpdate
- 呼叫
KeyboardGetChar
- 如果是一個新行直接跳轉到第 16 步
- 如果是一個退格鍵,將字串長度減 1(如果其大於 0)
- 如果是一個普通字元,將它寫入字串(字串大小確保小於最大值)
- 如果字串是以下劃線結束,寫入一個空格,否則寫入下劃線
- 跳轉到第 6 步
- 字串的末尾寫入一個新行字元
- 呼叫
Print
和TerminalDisplay
- 用結束符替換新行
- 返回字串的長度
為了方便讀者理解,然後然後自己去實現,我們的實現提供如下:
- 快速處理長度為 0 的情況
.globl ReadLine
ReadLine:
teq r1,#0
moveq r0,#0
moveq pc,lr
2、考慮到常見的場景,我們初期做了很多初始化動作。input
代表 terminalStop
的值,view
代表 terminalView
。Length
預設為 0
。
string .req r4
maxLength .req r5
input .req r6
taddr .req r7
length .req r8
view .req r9
push {r4,r5,r6,r7,r8,r9,lr}
mov string,r0
mov maxLength,r1
ldr taddr,=terminalStart
ldr input,[taddr,#terminalStop-terminalStart]
ldr view,[taddr,#terminalView-terminalStart]
mov length,#0
3、我們必須檢查異常大的讀操作,我們不能處理超過 terminalBuffer
大小的輸入(理論上可行,但是 terminalStart
移動越過儲存的 terminalStop`,會有很多問題)。
cmp maxLength,#128*64
movhi maxLength,#128*64
4、由於使用者需要一個閃爍的游標,我們需要一個備用字元在理想狀況在這個字串後面放一個結束符。
sub maxLength,#1
5、寫入一個下劃線讓使用者知道我們可以輸入了。
mov r0,#'_'
strb r0,[string,length]
6、儲存 terminalStop
和 terminalView
。這個對重置一個終端很重要,它會修改這些變數。嚴格講也可以修改 terminalStart
,但是不可逆。
readLoop$:
str input,[taddr,#terminalStop-terminalStart]
str view,[taddr,#terminalView-terminalStart]
7、寫入當前的輸入。由於下劃線因此字串長度加 1
mov r0,string
mov r1,length
add r1,#1
bl Print
8、複製下一個文字到螢幕
bl TerminalDisplay
9、獲取最近一次鍵盤輸入
bl KeyboardUpdate
10、檢索鍵盤輸入鍵值
bl KeyboardGetChar
11、如果我們有一個Enter鍵,迴圈中斷。如果有結束符和一個退格鍵也會同樣跳出迴圈。
teq r0,#'\n'
beq readLoopBreak$
teq r0,#0
beq cursor$
teq r0,#'\b'
bne standard$
12、從 length
裡面刪除一個字元
delete$:
cmp length,#0
subgt length,#1
b cursor$
13、寫回一個普通字元
standard$:
cmp length,maxLength
bge cursor$
strb r0,[string,length]
add length,#1
14、載入最近的一個字元,如果不是下劃線則修改為下換線,如果是則修改為空格
cursor$:
ldrb r0,[string,length]
teq r0,#'_'
moveq r0,#' '
movne r0,#'_'
strb r0,[string,length]
15、迴圈執行值到使用者輸入按下
b readLoop$
readLoopBreak$:
16、在字串的結尾處存入一個新行字元
mov r0,#'\n'
strb r0,[string,length]
17、重置 terminalView
和 terminalStop
然後呼叫 Print
和 TerminalDisplay
顯示最終的輸入
str input,[taddr,#terminalStop-terminalStart]
str view,[taddr,#terminalView-terminalStart]
mov r0,string
mov r1,length
add r1,#1
bl Print
bl TerminalDisplay
18、寫入一個結束符
mov r0,#0
strb r0,[string,length]
19、返回長度
mov r0,length
pop {r4,r5,r6,r7,r8,r9,pc}
.unreq string
.unreq maxLength
.unreq input
.unreq taddr
.unreq length
.unreq view
5、終端:機器進化
現在我們理論用終端和使用者可以互動了。最顯而易見的事情就是拿去測試了!刪除 main.s
裡 bl UsbInitialise
後面的程式碼後如下:
reset$:
mov sp,#0x8000
bl TerminalClear
ldr r0,=welcome
mov r1,#welcomeEnd-welcome
bl Print
loop$:
ldr r0,=prompt
mov r1,#promptEnd-prompt
bl Print
ldr r0,=command
mov r1,#commandEnd-command
bl ReadLine
teq r0,#0
beq loopContinue$
mov r4,r0
ldr r5,=command
ldr r6,=commandTable
ldr r7,[r6,#0]
ldr r9,[r6,#4]
commandLoop$:
ldr r8,[r6,#8]
sub r1,r8,r7
cmp r1,r4
bgt commandLoopContinue$
mov r0,#0
commandName$:
ldrb r2,[r5,r0]
ldrb r3,[r7,r0]
teq r2,r3
bne commandLoopContinue$
add r0,#1
teq r0,r1
bne commandName$
ldrb r2,[r5,r0]
teq r2,#0
teqne r2,#' '
bne commandLoopContinue$
mov r0,r5
mov r1,r4
mov lr,pc
mov pc,r9
b loopContinue$
commandLoopContinue$:
add r6,#8
mov r7,r8
ldr r9,[r6,#4]
teq r9,#0
bne commandLoop$
ldr r0,=commandUnknown
mov r1,#commandUnknownEnd-commandUnknown
ldr r2,=formatBuffer
ldr r3,=command
bl FormatString
mov r1,r0
ldr r0,=formatBuffer
bl Print
loopContinue$:
bl TerminalDisplay
b loop$
echo:
cmp r1,#5
movle pc,lr
add r0,#5
sub r1,#5
b Print
ok:
teq r1,#5
beq okOn$
teq r1,#6
beq okOff$
mov pc,lr
okOn$:
ldrb r2,[r0,#3]
teq r2,#'o'
ldreqb r2,[r0,#4]
teqeq r2,#'n'
movne pc,lr
mov r1,#0
b okAct$
okOff$:
ldrb r2,[r0,#3]
teq r2,#'o'
ldreqb r2,[r0,#4]
teqeq r2,#'f'
ldreqb r2,[r0,#5]
teqeq r2,#'f'
movne pc,lr
mov r1,#1
okAct$:
mov r0,#16
b SetGpio
.section .data
.align 2
welcome: .ascii "Welcome to Alex's OS - Everyone's favourite OS"
welcomeEnd:
.align 2
prompt: .ascii "\n> "
promptEnd:
.align 2
command:
.rept 128
.byte 0
.endr
commandEnd:
.byte 0
.align 2
commandUnknown: .ascii "Command `%s' was not recognised.\n"
commandUnknownEnd:
.align 2
formatBuffer:
.rept 256
.byte 0
.endr
formatEnd:
.align 2
commandStringEcho: .ascii "echo"
commandStringReset: .ascii "reset"
commandStringOk: .ascii "ok"
commandStringCls: .ascii "cls"
commandStringEnd:
.align 2
commandTable:
.int commandStringEcho, echo
.int commandStringReset, reset$
.int commandStringOk, ok
.int commandStringCls, TerminalClear
.int commandStringEnd, 0
這塊程式碼整合了一個簡易的命令列作業系統。支援命令:echo
、reset
、ok
和 cls
。echo
複製任意文字到終端,reset
命令會在系統出現問題的是復位作業系統,ok
有兩個功能:設定 OK 燈亮滅,最後 cls
呼叫 TerminalClear 清空終端。
試試樹莓派的程式碼吧。如果遇到問題,請參照問題集錦頁面吧。
如果執行正常,祝賀你完成了一個作業系統基本終端和輸入系列的課程。很遺憾這個教程先講到這裡,但是我希望將來能製作更多教程。有問題請反饋至 awc32@cam.ac.uk。
你已經在建立了一個簡易的終端作業系統。我們的程式碼在 commandTable 構造了一個可用的命令表格。每個表格的入口是一個整型數字,用來表示字串的地址,和一個整型數字表格程式碼的執行入口。 最後一個入口是 為 0 的 commandStringEnd
。嘗試實現你自己的命令,可以參照已有的函式,建立一個新的。函式的引數 r0
是使用者輸入的命令地址,r1
是其長度。你可以用這個傳遞你輸入值到你的命令。也許你有一個計算器程式,或許是一個繪圖程式或國際象棋。不管你的什麼點子,讓它跑起來!
via: https://www.cl.cam.ac.uk/projects/raspberrypi/tutorials/os/input02.html
作者:Alex Chadwick 選題:lujun9972 譯者:guevaraya 校對:wxy
相關文章
- 計算機實驗室之樹莓派:課程 2 OK02計算機樹莓派
- 計算機實驗室之樹莓派:課程 7 螢幕02計算機樹莓派
- 計算機實驗室之樹莓派:課程 8 螢幕03計算機樹莓派
- 計算機實驗室之樹莓派:課程 9 螢幕04計算機樹莓派
- 計算機實驗室之樹莓派:課程 1 OK01計算機樹莓派
- 計算機實驗室之樹莓派:課程 3 OK03計算機樹莓派
- 計算機實驗室之樹莓派:課程 4 OK04計算機樹莓派
- 計算機實驗室之樹莓派:課程 5 OK05計算機樹莓派
- 計算機實驗室之樹莓派計算機樹莓派
- 樹莓派使用入門:如何更新樹莓派樹莓派
- 《計算機網路實驗課程》——Wireshark實驗計算機網路
- 樹莓派使用入門:用樹莓派學 Linux樹莓派Linux
- 第七天 樹莓派+計算棒配置過程樹莓派
- 樹莓派桌面體驗樹莓派
- 樹莓派使用入門:慶祝樹莓派的 14 天樹莓派
- 樹莓派使用入門:如何用樹莓派來娛樂樹莓派
- 樹莓派使用入門:如何購買一個樹莓派樹莓派
- 樹莓派 -入門篇樹莓派
- 樹莓派使用入門:如何為樹莓派社群做出貢獻樹莓派
- 樹莓派 - 實戰篇 [基於 websocket 實現手機遠端控制樹莓派小車]樹莓派Web
- 樹莓派使用入門:如何啟動一個新的樹莓派樹莓派
- 樹莓派語音互動--語音輸入識別樹莓派
- 樹莓派使用入門:教孩子們用樹莓派學程式設計的 5 種方法樹莓派程式設計
- 樹莓派使用入門:進入物理世界 —— 如何使用樹莓派的 GPIO 針腳樹莓派
- 樹莓派是什麼 樹莓派能做什麼 樹莓派的功能用途樹莓派
- 樹莓派搭建下載機樹莓派
- 樹莓派自建 NAS 雲盤之——樹莓派搭建網路儲存盤樹莓派
- 樹莓派GUI程式設計樹莓派GUI程式設計
- 計算機課程程式設計經驗分享計算機程式設計
- 樹莓派使用入門:在樹莓派上使用 Mathematica 進行高階數學運算樹莓派
- 樹莓派 4 代-入門篇樹莓派
- C#控制樹莓派入門C#樹莓派
- 如何在電腦和樹莓派之間傳輸檔案樹莓派
- 樹莓派 Zero WH 初使用體驗樹莓派
- 樹莓派使用樹莓派
- 【.NET 與樹莓派】控制舵機樹莓派
- 樹莓派使用入門:透過樹莓派和 kali Linux 學習電腦保安樹莓派Linux
- 樹莓派的GPIO程式設計樹莓派程式設計