Linux中國

計算機實驗室之樹莓派:課程 10 輸入01

歡迎進入輸入課程系列。在本系列,你將會學會如何使用鍵盤接收輸入給樹莓派。我們將會從揭示輸入開始本課,然後轉向更傳統的文本提示符。

這是第一堂輸入課,會教授一些關於驅動和鏈接的理論,同樣也包含鍵盤的知識,最後以在屏幕上顯示文本結束。

1、開始

希望你已經完成了 OK 系列課程,這會對你完成屏幕系列課程很有幫助。很多 OK 課程上的文件會被使用而不會做解釋。如果你沒有這些文件,或者希望使用一個正確的實現,可以從該堂課的下載頁下載模板。如果你使用你自己的實現,請刪除調用了 SetGraphicsAddress 之後全部的代碼。

2、USB

如你所知,樹莓派 B 型有兩個 USB 介面,通常用來連接一個滑鼠和一個鍵盤。這是一個非常好的設計決策,USB 是一個非常通用的介面,很多種設備都可以使用它。這就很容易為它設計新外設,很容易為它編寫設備驅動,而且通過 USB 集線器可以非常容易擴展。還能更好嗎?當然是不能,實際上對一個操作系統開發者來說,這就是我們的噩夢。USB 標準太大了。我是說真的,在你思考如何連接設備之前,它的文檔將近 700 頁。

USB 標準的設計目的是通過複雜的軟體來簡化硬體交互。

我和很多愛好操作系統的開發者談過這些,而他們全部都說幾句話:不要抱怨。「實現這個需要花費很久時間」,「你不可能寫出關於 USB 的教程」,「收益太小了」。在很多方面,他們是對的,我不可能寫出一個關於 USB 標準的教程,那得花費幾周時間。我同樣不能教授如何為全部所有的設備編寫外設驅動,所以使用自己寫的驅動是沒什麼用的。然而,即便不能做到最好,我仍然可以獲取一個正常工作的 USB 驅動,拿一個鍵盤驅動,然後教授如何在操作系統中使用它們。我開始尋找可以運行在一個甚至不知道文件是什麼的操作系統的自由驅動,但是我一個都找不到,它們都太高層了,所以我嘗試寫一個。大家說的都對,這耗費了我幾周時間。然而我可以高興的說我做的這些工作沒有獲取操作系統以外的幫助,並且可以和滑鼠和鍵盤通信。這絕不是完整的、高效的,或者正確的,但是它能工作。驅動是以 C 編寫的,而且有興趣的可以在下載頁找到全部源代碼。

所以,這一個教程不會是 USB 標準的課程(一點也沒有)。實際上我們將會看到如何使用其他人的代碼。

3、鏈接

既然我們要引進外部代碼到操作系統,我們就需要談一談 鏈接 linking 。鏈接是一種過程,可以在程序或者操作系統中鏈接函數。這意味著當一個程序生成之後,我們不必要編寫每一個函數(幾乎可以肯定,實際上並非如此)。鏈接就是我們做的用來把我們程序和別人代碼中的函數連結在一起。這個實際上已經在我們的操作系統進行了,因為鏈接器把所有不同的文件鏈接在一起,每個都是分開編譯的。

鏈接允許我們製作可重用的代碼庫,所有人都可以在他們的程序中使用。

有兩種鏈接方式:靜態和動態。靜態鏈接就像我們在製作自己的操作系統時進行的。鏈接器找到全部函數的地址,然後在鏈接結束前,將這些地址都寫入代碼中。動態鏈接是在程序「完成」之後。當程序載入後,動態鏈接器檢查程序,然後在操作系統的庫找到所有不在程序里的函數。這就是我們的操作系統最終應該能夠完成的一項工作,但是現在所有東西都將是靜態鏈接的。

程序經常調用調用庫,這些庫會調用其它的庫,直到最終調用了我們寫的操作系統的庫。

我編寫的 USB 驅動程序適合靜態編譯。這意味著我給你的是每個文件的編譯後的代碼,然後鏈接器找到你的代碼中的那些沒有實現的函數,就將這些函數鏈接到我的代碼。在本課的 下載頁 是一個 makefile 和我的 USB 驅動,這是接下來需要的。下載並使用這個 makefile 替換你的代碼中的 makefile, 同事將驅動放在和這個 makefile 相同的文件夾。

4、鍵盤

為了將輸入傳給我們的操作系統,我們需要在某種程度上理解鍵盤是如何實際工作的。鍵盤有兩種按鍵:普通鍵和修飾鍵。普通按鍵是字母、數字、功能鍵,等等。它們構成了鍵盤上幾乎全部按鍵。修飾鍵是多達 8 個的特殊鍵。它們是左 shift、右 shift、左 ctrl、右 ctrl、左 alt、右 alt、左 GUI 和右 GUI。鍵盤可以檢測出所有的組合中那個修飾鍵被按下了,以及最多 6 個普通鍵。每次一個按鈕變化了(例如,是按下了還是釋放了),鍵盤就會報告給電腦。通常,鍵盤也會有 3 個 LED 燈,分別指示大寫鎖定,數字鍵鎖定,和滾動鎖定,這些都是由電腦控制的,而不是鍵盤自己。鍵盤也可能有更多的燈,比如電源、靜音,等等。

對於標準 USB 鍵盤,有一個按鍵值的表,每個鍵盤按鍵都一個唯一的數字,每個可能的 LED 也類似。下面的表格列出了前 126 個值。

表 4.1 USB 鍵盤值

序號 描述 序號 描述 序號 描述 序號 描述
4 aA 5 bB 6 cC 7 dD
8 eE 9 fF 10 gG 11 hH
12 iI 13 jJ 14 kK 15 lL
16 mM 17 nN 18 oO 19 pP
20 qQ 21 rR 22 sS 23 tT
24 uU 25 vV 26 wW 27 xX
28 yY 29 zZ 30 1! 31 2@
32 3# 33 4$ 34 5% 35 6^
36 7& 37 8* 38 9( 39 0)
40 ReturnEnter 41 Escape 42 DeleteBackspace 43 Tab
44 Spacebar 45 -_ 46 =+ 47 [{
48 ]} 49 | | 50 | #~ | 51 | ;:
52 &apos;" 53 `~ | 54 | ,< | 55 | .>
56 /? 57 Caps Lock 58 F1 59 F2
60 F3 61 F4 62 F5 63 F6
64 F7 65 F8 66 F9 67 F10
68 F11 69 F12 70 Print Screen 71 Scroll Lock
72 Pause 73 Insert 74 Home 75 Page Up
76 Delete forward 77 End 78 Page Down 79 Right Arrow
80 Left Arrow 81 Down Arrow 82 Up Arrow 83 Num Lock
84 小鍵盤 / 85 小鍵盤 * 86 小鍵盤 - 87 小鍵盤 +
88 小鍵盤 Enter 89 小鍵盤 1End 90 小鍵盤 2Down Arrow 91 小鍵盤 3Page Down
92 小鍵盤 4Left Arrow 93 小鍵盤 5 94 小鍵盤 6Right Arrow 95 小鍵盤 7Home
96 小鍵盤 8Up Arrow 97 小鍵盤 9Page Up 98 小鍵盤 0Insert 99 小鍵盤 .Delete
100 | | 101 | Application | 102 | Power | 103 | 小鍵盤 =
104 F13 105 F14 106 F15 107 F16
108 F17 109 F18 110 F19 111 F20
112 F21 113 F22 114 F23 115 F24
116 Execute 117 Help 118 Menu 119 Select
120 Stop 121 Again 122 Undo 123 Cut
124 Copy 125 Paste 126 Find 127 Mute
128 Volume Up 129 Volume Down

完全列表可以在HID 頁表 1.12的 53 頁,第 10 節找到。

5、車輪後的螺母

通常,當你使用其他人的代碼,他們會提供一份自己代碼的總結,描述代碼都做了什麼,粗略介紹了是如何工作的,以及什麼情況下會出錯。下面是一個使用我的 USB 驅動的相關步驟要求。

這些總結和代碼的描述組成了一個 API - 應用程序產品介面。

表 5.1 CSUD 中和鍵盤相關的函數

函數 參數 返回值 描述
UsbInitialise r0 是結果碼 這個方法是一個集多種功能於一身的方法,它載入 USB 驅動程序,枚舉所有設備並嘗試與它們通信。這種方法通常需要大約一秒鐘的時間來執行,但是如果插入幾個 USB 集線器,執行時間會明顯更長。在此方法完成之後,鍵盤驅動程序中的方法就可用了,不管是否確實插入了鍵盤。返回代碼如下解釋。
UsbCheckForChange 本質上提供與 UsbInitialise 相同的效果,但不提供相同的一次初始化。該方法遞歸地檢查每個連接的集線器上的每個埠,如果已經添加了新設備,則添加它們。如果沒有更改,這應該是非常快的,但是如果連接了多個設備的集線器,則可能需要幾秒鐘的時間。
KeyboardCount r0 是計數 返回當前連接並檢測到的鍵盤數量。UsbCheckForChange 可能會對此進行更新。默認情況下最多支持 4 個鍵盤。可以通過這個驅動程序訪問多達這麼多的鍵盤。
KeyboardGetAddress r0 是索引 r0 是地址 檢索給定鍵盤的地址。所有其他函數都需要一個鍵盤地址,以便知道要訪問哪個鍵盤。因此,要與鍵盤通信,首先要檢查計數,然後檢索地址,然後使用其他方法。注意,在調用 UsbCheckForChange 之後,此方法返回的鍵盤順序可能會改變。
KeyboardPoll r0 是地址 r0 是結果碼 從鍵盤讀取當前鍵狀態。這是通過直接輪詢設備來操作的,與最佳實踐相反。這意味著,如果沒有頻繁地調用此方法,可能會錯過一個按鍵。所有讀取方法只返回上次輪詢時的值。
KeyboardGetModifiers r0 是地址 r0 是修飾鍵狀態 檢索上次輪詢時修飾鍵的狀態。這是兩邊的 shift 鍵、alt 鍵和 GUI 鍵。這回作為一個位欄位返回,這樣,位 0 中的 1 表示左控制項被保留,位 1 表示左 shift,位 2 表示左 alt ,位 3 表示左 GUI,位 4 到 7 表示前面幾個鍵的右版本。如果有問題,r0 包含 0。
KeyboardGetKeyDownCount r0 是地址 r0 是計數 檢索當前按下鍵盤的鍵數。這排除了修飾鍵。這通常不能超過 6。如果有錯誤,這個方法返回 0。
KeyboardGetKeyDown r0 是地址,r1 鍵號 r0 是掃描碼 檢索特定按下鍵的掃描碼(見表 4.1)。通常,要計算出哪些鍵是按下的,可以調用 KeyboardGetKeyDownCount,然後多次調用 KeyboardGetKeyDown ,將 r1 的值遞增,以確定哪些鍵是按下的。如果有問題,返回 0。可以(但不建議這樣做)在不調用 KeyboardGetKeyDownCount 的情況下調用此方法將 0 解釋為沒有按下的鍵。注意,順序或掃描代碼可以隨機更改(有些鍵盤按數字排序,有些鍵盤按時間排序,沒有任何保證)。
KeyboardGetKeyIsDown r0 是地址,r1 掃描碼 r0 是狀態 除了 KeyboardGetKeyDown 之外,還可以檢查按下的鍵中是否有特定的掃描碼。如果沒有,返回 0;如果有,返回一個非零值。當檢測特定的掃描碼(例如尋找 ctrl+c)時更快。出錯時,返回 0。
KeyboardGetLedSupport r0 是地址 r0 是 LED 檢查特定鍵盤支持哪些 LED。第 0 位代表數字鎖定,第 1 位代表大寫鎖定,第 2 位代表滾動鎖定,第 3 位代表合成,第 4 位代表假名,第 5 位代表電源,第 6 位代表 Shift ,第 7 位代表靜音。根據 USB 標準,這些 LED 都不是自動更新的(例如,當檢測到大寫鎖定掃描代碼時,必須手動設置大寫鎖定 LED)。
KeyboardSetLeds r0 是地址, r1 是 LED r0 是結果碼 試圖打開/關閉鍵盤上指定的 LED 燈。查看下面的結果代碼值。參見 KeyboardGetLedSupport 獲取 LED 的值。

有幾種方法返回「返回值」。這些都是 C 代碼的老生常談了,就是用數字代表函數調用發生了什麼。通常情況, 0 總是代表操作成功。下面的是驅動用到的返回值。

返回值是一種處理錯誤的簡單方法,但是通常更優雅的解決途徑會出現於更高層次的代碼。

表 5.2 - CSUD 返回值

代碼 描述
0 方法成功完成。
-2 參數:函數調用了無效參數。
-4 設備:設備沒有正確響應請求。
-5 不匹配:驅動不適用於這個請求或者設備。
-6 編譯器:驅動沒有正確編譯,或者被破壞了。
-7 內存:驅動用盡了內存。
-8 超時:設備沒有在預期的時間內響應請求。
-9 斷開連接:被請求的設備斷開連接,或者不能使用。

驅動的通常用法如下:

  1. 調用 UsbInitialise
  2. 調用 UsbCheckForChange
  3. 調用 KeyboardCount
  4. 如果返回 0,重複步驟 2。
  5. 針對你支持的每個鍵盤:
    1. 調用 KeyboardGetAddress
    2. 調用 KeybordGetKeyDownCount
    3. 針對每個按下的按鍵:
      1. 檢查它是否已經被按下了
      2. 保存按下的按鍵
    4. 針對每個保存的按鍵:
      1. 檢查按鍵是否被釋放了
      2. 如果釋放了就刪除
  6. 根據按下/釋放的案件執行操作
  7. 重複步驟 2

最後,你可以對鍵盤做所有你想做的任何事了,而這些方法應該允許你訪問鍵盤的全部功能。在接下來的兩節課,我們將會著眼於完成文本終端的輸入部分,類似於大部分的命令行電腦,以及命令的解釋。為了做這些,我們將需要在更有用的形式下得到一個鍵盤輸入。你可能注意到我的驅動是(故意的)沒有太大幫助,因為它並沒有方法來判斷是否一個按鍵剛剛按下或釋放了,它只有方法來判斷當前那個按鍵是按下的。這就意味著我們需要自己編寫這些方法。

6、可用更新

首先,讓我們實現一個 KeyboardUpdate 方法,檢查第一個鍵盤,並使用輪詢方法來獲取當前的輸入,以及保存最後一個輸入來對比。然後我們可以使用這個數據和其它方法來將掃描碼轉換成按鍵。這個方法應該按照下面的說明準確操作:

重複檢查更新被稱為「輪詢」。這是針對驅動 IO 中斷而言的,這種情況下設備在準備好後會發一個信號。

  1. 提取一個保存好的鍵盤地址(初始值為 0)。
  2. 如果不是 0 ,進入步驟 9.
  3. 調用 UsbCheckForChange 檢測新鍵盤。
  4. 調用 KeyboardCount 檢測有幾個鍵盤在線。
  5. 如果返回 0,意味著沒有鍵盤可以讓我們操作,只能退出了。
  6. 調用 KeyboardGetAddress 參數是 0,獲取第一個鍵盤的地址。
  7. 保存這個地址。
  8. 如果這個值是 0,那麼退出,這裡應該有些問題。
  9. 調用 KeyboardGetKeyDown 6 次,獲取每次按鍵按下的值並保存。
  10. 調用 KeyboardPoll
  11. 如果返回值非 0,進入步驟 3。這裡應該有些問題(比如鍵盤斷開連接)。

要保存上面提到的值,我們將需要下面 .data 段的值。

.section .data
.align 2
KeyboardAddress:
.int 0
KeyboardOldDown:
.rept 6
.hword 0
.endr
.hword num 直接將半字的常數插入文件。
.rept num [commands] .endr 複製 `commands` 命令到輸出 num 次。

試著自己實現這個方法。對此,我的實現如下:

1、我們載入鍵盤的地址。

.section .text
.globl KeyboardUpdate
KeyboardUpdate:
push {r4,r5,lr}

kbd .req r4
ldr r0,=KeyboardAddress
ldr kbd,[r0]

2、如果地址非 0,就說明我們有一個鍵盤。調用 UsbCheckForChanges 慢,所以如果一切正常,我們要避免調用這個函數。

teq kbd,#0
bne haveKeyboard$

3、如果我們一個鍵盤都沒有,我們就必須檢查新設備。

getKeyboard$:
bl UsbCheckForChange

4、如果有新鍵盤添加,我們就會看到這個。

bl KeyboardCount

5、如果沒有鍵盤,我們就沒有鍵盤地址。

teq r0,#0
ldreq r1,=KeyboardAddress
streq r0,[r1]
beq return$

6、讓我們獲取第一個鍵盤的地址。你可能想要支持更多鍵盤。

mov r0,#0
bl KeyboardGetAddress

7、保存鍵盤地址。

ldr r1,=KeyboardAddress
str r0,[r1]

8、如果我們沒有鍵盤地址,這裡就沒有其它活要做了。

teq r0,#0
beq return$
mov kbd,r0

9、循環查詢全部按鍵,在 KeyboardOldDown 保存下來。如果我們詢問的太多了,返回 0 也是正確的。

saveKeys$:
  mov r0,kbd
  mov r1,r5
  bl KeyboardGetKeyDown

  ldr r1,=KeyboardOldDown
  add r1,r5,lsl #1
  strh r0,[r1]
  add r5,#1
  cmp r5,#6
  blt saveKeys$

10、現在我們得到了新的按鍵。

mov r0,kbd
bl KeyboardPoll

11、最後我們要檢查 KeyboardOldDown 是否工作了。如果沒工作,那麼我們可能是斷開連接了。

teq r0,#0
bne getKeyboard$

return$:
pop {r4,r5,pc}
.unreq kbd

有了我們新的 KeyboardUpdate 方法,檢查輸入變得簡單,固定周期調用這個方法就行,而它甚至可以檢查鍵盤是否斷開連接,等等。這是一個有用的方法,因為我們實際的按鍵處理會根據條件不同而有所差別,所以能夠用一個函數調以它的原始方式獲取當前的輸入是可行的。下一個方法我們希望它是 KeyboardGetChar,簡單的返回下一個按下的按鈕的 ASCII 字元,或者如果沒有按鍵按下就返回 0。這可以擴展到支持如果它按下一個特定時間當做多次按下按鍵,也支持鎖定鍵和修飾鍵。

如果我們有一個 KeyWasDown 方法可以使這個方法有用起來,如果給定的掃描代碼不在 KeyboardOldDown 值中,它只返回 0,否則返回一個非零值。你可以自己嘗試一下。與往常一樣,可以在下載頁面找到解決方案。

7、查找表

KeyboardGetChar 方法如果寫得不好,可能會非常複雜。有 100 多種掃描碼,每種代碼都有不同的效果,這取決於 shift 鍵或其他修飾符的存在與否。並不是所有的鍵都可以轉換成一個字元。對於一些字元,多個鍵可以生成相同的字元。在有如此多可能性的情況下,一個有用的技巧是查找表。查找表與物理意義上的查找表非常相似,它是一個值及其結果的表。對於一些有限的函數,推導出答案的最簡單方法就是預先計算每個答案,然後通過檢索返回正確的答案。在這種情況下,我們可以在內存中建立一個序列的值,序列中第 n 個值就是掃描代碼 n 的 ASCII 字元代碼。這意味著如果一個鍵被按下,我們的方法只需要檢測到,然後從表中檢索它的值。此外,我們可以為當按住 shift 鍵時的值單獨創建一個表,這樣按下 shift 鍵就可以簡單地換個我們用的表。

在編程的許多領域,程序越大,速度越快。查找表很大,但是速度很快。有些問題可以通過查找表和普通函數的組合來解決。

.section .data 命令之後,複製下面的表:

.align 3
KeysNormal:
    .byte 0x0, 0x0, 0x0, 0x0, &apos;a&apos;, &apos;b&apos;, &apos;c&apos;, &apos;d&apos;
    .byte &apos;e&apos;, &apos;f&apos;, &apos;g&apos;, &apos;h&apos;, &apos;i&apos;, &apos;j&apos;, &apos;k&apos;, &apos;l&apos;
    .byte &apos;m&apos;, &apos;n&apos;, &apos;o&apos;, &apos;p&apos;, &apos;q&apos;, &apos;r&apos;, &apos;s&apos;, &apos;t&apos;
    .byte &apos;u&apos;, &apos;v&apos;, &apos;w&apos;, &apos;x&apos;, &apos;y&apos;, &apos;z&apos;, &apos;1&apos;, &apos;2&apos;
    .byte &apos;3&apos;, &apos;4&apos;, &apos;5&apos;, &apos;6&apos;, &apos;7&apos;, &apos;8&apos;, &apos;9&apos;, &apos;0&apos;
    .byte &apos;n&apos;, 0x0, &apos;b&apos;, &apos;t&apos;, &apos; &apos;, &apos;-&apos;, &apos;=&apos;, &apos;[&apos;
    .byte &apos;]&apos;, &apos;\&apos;, &apos;#&apos;, &apos;;&apos;, &apos;&apos;&apos;, &apos;`&apos;, &apos;,&apos;, &apos;.&apos;
    .byte &apos;/&apos;, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0
    .byte 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0
    .byte 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0
    .byte 0x0, 0x0, 0x0, 0x0, &apos;/&apos;, &apos;*&apos;, &apos;-&apos;, &apos;+&apos;
    .byte &apos;n&apos;, &apos;1&apos;, &apos;2&apos;, &apos;3&apos;, &apos;4&apos;, &apos;5&apos;, &apos;6&apos;, &apos;7&apos;
    .byte &apos;8&apos;, &apos;9&apos;, &apos;0&apos;, &apos;.&apos;, &apos;\&apos;, 0x0, 0x0, &apos;=&apos;

.align 3
KeysShift:
    .byte 0x0, 0x0, 0x0, 0x0, &apos;A&apos;, &apos;B&apos;, &apos;C&apos;, &apos;D&apos;
    .byte &apos;E&apos;, &apos;F&apos;, &apos;G&apos;, &apos;H&apos;, &apos;I&apos;, &apos;J&apos;, &apos;K&apos;, &apos;L&apos;
    .byte &apos;M&apos;, &apos;N&apos;, &apos;O&apos;, &apos;P&apos;, &apos;Q&apos;, &apos;R&apos;, &apos;S&apos;, &apos;T&apos;
    .byte &apos;U&apos;, &apos;V&apos;, &apos;W&apos;, &apos;X&apos;, &apos;Y&apos;, &apos;Z&apos;, &apos;!&apos;, &apos;"&apos;
    .byte &apos;£&apos;, &apos;$&apos;, &apos;%&apos;, &apos;^&apos;, &apos;&&apos;, &apos;*&apos;, &apos;(&apos;, &apos;)&apos;
    .byte &apos;n&apos;, 0x0, &apos;b&apos;, &apos;t&apos;, &apos; &apos;, &apos;_&apos;, &apos;+&apos;, &apos;{&apos;
    .byte &apos;}&apos;, &apos;|&apos;, &apos;~&apos;, &apos;:&apos;, &apos;@&apos;, &apos;¬&apos;, &apos;<&apos;, &apos;>&apos;
    .byte &apos;?&apos;, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0
    .byte 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0
    .byte 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0
    .byte 0x0, 0x0, 0x0, 0x0, &apos;/&apos;, &apos;*&apos;, &apos;-&apos;, &apos;+&apos;
    .byte &apos;n&apos;, &apos;1&apos;, &apos;2&apos;, &apos;3&apos;, &apos;4&apos;, &apos;5&apos;, &apos;6&apos;, &apos;7&apos;
    .byte &apos;8&apos;, &apos;9&apos;, &apos;0&apos;, &apos;.&apos;, &apos;|&apos;, 0x0, 0x0, &apos;=&apos;

這些表直接將前 104 個掃描碼映射到 ASCII 字元作為一個位元組表。我們還有一個單獨的表來描述 shift 鍵對這些掃描碼的影響。我使用 ASCII null 字元(0)表示所有沒有直接映射的 ASCII 鍵(例如功能鍵)。退格映射到 ASCII 退格字元(8 表示 b),enter 映射到 ASCII 新行字元(10 表示 n), tab 映射到 ASCII 水平製表符(9 表示 t)。

.byte num 直接插入位元組常量 num 到文件。

.

大部分的彙編器和編譯器識別轉義序列;如 t 這樣的字元序列會插入該特殊字元。

KeyboardGetChar 方法需要做以下工作:

  1. 檢查 KeyboardAddress 是否返回 0。如果是,則返回 0。
  2. 調用 KeyboardGetKeyDown 最多 6 次。每次:
    1. 如果按鍵是 0,跳出循環。
    2. 調用 KeyWasDown。 如果返回是,處理下一個按鍵。
    3. 如果掃描碼超過 103,進入下一個按鍵。
    4. 調用 KeyboardGetModifiers
    5. 如果 shift 是被按著的,就載入 KeysShift 的地址,否則載入 KeysNormal 的地址。
    6. 從表中讀出 ASCII 碼值。
    7. 如果是 0,進行下一個按鍵,否則返回 ASCII 碼值並退出。
  3. 返回 0。

試著自己實現。我的實現展示在下面:

1、簡單的檢查我們是否有鍵盤。

.globl KeyboardGetChar
KeyboardGetChar:
ldr r0,=KeyboardAddress
ldr r1,[r0]
teq r1,#0
moveq r0,#0
moveq pc,lr

2、r5 將會保存按鍵的索引,r4 保存鍵盤的地址。

push {r4,r5,r6,lr}
kbd .req r4
key .req r6
mov r4,r1
mov r5,#0
keyLoop$:
  mov r0,kbd
  mov r1,r5
  bl KeyboardGetKeyDown

2.1、 如果掃描碼是 0,它要麼意味著有錯,要麼說明沒有更多按鍵了。

teq r0,#0
beq keyLoopBreak$

2.2、如果按鍵已經按下了,那麼他就沒意義了,我們只想知道按下的按鍵。

mov key,r0
bl KeyWasDown
teq r0,#0
bne keyLoopContinue$

2.3、如果一個按鍵有個超過 104 的掃描碼,它將會超出我們的表,所以它是無關的按鍵。

cmp key,#104
bge keyLoopContinue$

2.4、我們需要知道修飾鍵來推斷字元。

mov r0,kbd
bl KeyboardGetModifiers

2.5、當將字元更改為其 shift 變體時,我們要同時檢測左 shift 鍵和右 shift 鍵。記住,tst 指令計算的是邏輯和,然後將其與 0 進行比較,所以當且僅當移位位都為 0 時,它才等於 0。

tst r0,#0b00100010
ldreq r0,=KeysNormal
ldrne r0,=KeysShift

2.6、現在我們可以從查找表載入按鍵了。

ldrb r0,[r0,key]

2.7、如果查找碼包含一個 0,我們必須繼續。為了繼續,我們要增加索引,並檢查是否到 6 次了。

teq r0,#0
bne keyboardGetCharReturn$
keyLoopContinue$:
add r5,#1
cmp r5,#6
blt keyLoop$

3、在這裡我們返回我們的按鍵,如果我們到達 keyLoopBreak$ ,然後我們就知道這裡沒有按鍵被握住,所以返回 0。

keyLoopBreak$:
mov r0,#0
keyboardGetCharReturn$:
pop {r4,r5,r6,pc}
.unreq kbd
.unreq key

8、記事本操作系統

現在我們有了 KeyboardGetChar 方法,可以創建一個操作系統,只列印出用戶對著屏幕所寫的內容。為了簡單起見,我們將忽略所有非常規的鍵。在 main.s,刪除 bl SetGraphicsAddress 之後的所有代碼。調用 UsbInitialise,將 r4r5 設置為 0,然後循環執行以下命令:

  1. 調用 KeyboardUpdate
  2. 調用 KeyboardGetChar
  3. 如果返回 0,跳轉到步驟 1
  4. 複製 r4r5r1r2 ,然後調用 DrawCharacter
  5. r0 加到 r4
  6. 如果 r4 是 1024,將 r1 加到 r5,然後設置 r4 為 0。
  7. 如果 r5 是 768,設置 r5 為0
  8. 跳轉到步驟 1

現在編譯,然後在樹莓派上測試。你幾乎可以立即開始在屏幕上輸入文本。如果沒有工作,請參閱我們的故障排除頁面。

當它工作時,祝賀你,你已經實現了與計算機的介面。現在你應該開始意識到,你幾乎已經擁有了一個原始的操作系統。現在,你可以與計算機交互、發出命令,並在屏幕上接收反饋。在下一篇教程輸入02中,我們將研究如何生成一個全文本終端,用戶在其中輸入命令,然後計算機執行這些命令。

via: https://www.cl.cam.ac.uk/projects/raspberrypi/tutorials/os/input01.html

作者:Alex Chadwick 選題:lujun9972 譯者:ezio 校對:wxy

本文由 LCTT 原創編譯,Linux中國 榮譽推出


本文轉載來自 Linux 中國: https://github.com/Linux-CN/archive

對這篇文章感覺如何?

太棒了
0
不錯
0
愛死了
0
不太好
0
感覺很糟
0
雨落清風。心向陽

    You may also like

    Leave a reply

    您的郵箱地址不會被公開。 必填項已用 * 標註

    此站點使用Akismet來減少垃圾評論。了解我們如何處理您的評論數據

    More in:Linux中國