Kalan's Blog

目前主題 亮色

鍵盤入坑指南 - 韌體篇 | 興趣使然的研究之旅

雖然標題是寫入坑指南,不過這篇會更專注在鍵盤的運作原理跟電路。

偵測按下的鍵位

上一篇提到,鍵盤其實是個多個開關組成的電路,我們可以用微控制器 GPIO 接腳來偵測開關是否被按下。具體的做法是先將開關的一端接到電源,另一端接地。當按下按鍵時電路導通,微控制器就可以感知到這個變化,進而送出對應的信號。實際上是怎樣的信號,我們會在後面的章節提到。

不過這樣做有個缺點,就是每個按鍵都需要一個接腳,以一把有 104 個鍵位的鍵盤來說,就需要微控制器有 104 個接腳。通常微控制器並不會有那麼多接腳可以用,又或者有些接腳需要用來當作其他用途。

掃描與鍵盤矩陣

在實務上我們會用矩陣的連接方式來連接鍵盤的電路。每個矩陣都會接到微控制器的接腳,這樣一來假設按鍵要 60 個,6x10 的矩陣只要 16 個接腳即可。

test1

我們可以分別將 R1、R2、R3 分別給予高電位,再分別讀取 C1、C2、C3 的電位,就可以知道哪個按鍵被按下了。舉例當下圖的按鍵被按下時,C1 可以讀取到現在的電位是高電位,就能偵測到是哪個按鍵被按下。

test3

這邊的順序是:

  • 將 R1 設定為高電位
    • 讀取 C1
    • 讀取 C2
    • 讀取 C3
  • 將 R2 設定為高電位
    • 讀取 C1
    • 讀取 C2
    • 讀取 C3
  • 將 R3 設定為高電位
    • 讀取 C1
    • 讀取 C2
    • 讀取 C3

理想上當然是讓矩陣 Row 與 Col 的數量一樣,不過在實務上不一定會這樣做。一方面是因為微控制器的接腳通常是足夠的,另外一方面是如果強制讓兩個數量一樣,有時候 layout 會很難拉線。例如鍵盤就是一個橫列比較多,但是直列比較少。

這邊會有一個疑問是,那如果 R1 跟 R2 裡的按鍵同時按下的話會不會偵測不到?因為不是同時偵測而是輪流偵測。這點不需要擔心,一般的微控制器都至少有 16MHz 以上,偵測的速度絕對比人體極限還要高。微控制器會先記住按下的狀態,當再次讀取時發現與先前的狀態不同時,就會再送出放開的訊號。

Ghosting

test2

剛剛的電路有個問題,如果 R3 也有開關按下,那麼一部分的電流會從這裡跑過去導致誤判。解決方式是加入二極體。二極體具有單向導通的功能,電流只能夠從一個方向流通,進而避免 Ghosting。

HID(Human Interface Device)

接下來就來到將訊號傳給電腦的邏輯了。早期的鍵盤是使用 PS/2 連接器,不過現在最常見的則是 USB 與藍芽。

HID 透過規範各種常見的輸入裝置協定,像是鍵盤、滑鼠、控制器等等,讓以往需要額外撰寫驅動程式的成本簡化成只要符合 HID 符合的規格,不管什麼裝置都不需要驅動程式就可以執行。

HID 包含了輸入報告、描述子跟輸出報告。通常描述子可以用來描述這個裝置是什麼,像是用途、名稱、廠商、 ID 等等。電腦讀取到這些報告之後就知道該用什麼方式處理它。

USB

一般來說微控制器裡頭都會有 USB 的功能,可以幫你用 USB 的格式來傳輸資料。

HID 也一樣,一般來說可以透過 USB 或是藍芽來傳輸。不過這邊算是工程師會比較在意的技術細節,USB 分成兩個,host 跟 device。

由於像滑鼠、鍵盤這類型的輸入裝置,跟 CPU 比起來通常速度慢很多,所以如果讓 CPU 去等這些裝置的輸入,會非常浪費效能。實際上的做法是,電腦的 USB 控制器(host)會持續地像 device 端 pulling 資料,buffer 有資料時再去呼叫 CPU 中斷。因此在鍵盤端這邊,只會負責一直丟資料,剩下的都是由 host 端完成。

通常 HID 的格式要怎麼寫,在實際操作當中都是由函式庫幫你處理好了,所以只要專注在將對應的按鍵 mapping 到對應的鍵即可。

這邊放一個 Raspberry Pi pico 的例子:

static void send_hid_report(uint8_t report_id, uint32_t btn)
{
  // skip if hid is not ready yet
  if ( !tud_hid_ready() ) return;

  switch(report_id)
  {
    case REPORT_ID_KEYBOARD:
    {
      // use to avoid send multiple consecutive zero report for keyboard
      static bool has_keyboard_key = false;

      if ( btn )
      {
        uint8_t keycode[6] = { 0 };
        keycode[0] = HID_KEY_A;

        tud_hid_keyboard_report(REPORT_ID_KEYBOARD, 0, keycode);
        has_keyboard_key = true;
      }
      ...
}

這邊的例子只有一個按鈕,並且永遠都是 A 鍵。不過從程式碼我們可以大概得知要怎麼寫。程式碼首先先宣告了一個陣列 keycode。長度 6 代表同時可以傳送 6 個按鍵,而這邊只用了 keycode[0],然後呼叫 tud_hid_keyboard_report 將 HID report 送出。

電路板設計與佈線

我對電路板設計的涉略不深,這邊主要是想要說明只要有基本知識,任何人都可以做出電路板來。在電路板設計中,有個非常著名的開源軟體 Kicad,完全免費。

透過這個軟體,你可以自己設計想要的電路,並且它也支援 PCB layout 佈線。你可以將你設計的電路放到電路板拉線。

全部都弄好了之後,可以將電路圖(有專門的檔案格式)輸出後上傳到 PCB 印刷廠的網站,大概過幾個禮拜就能收到成品。比較常見的便宜廠商有 JLCPCBPCBWay

QMK

在客製化鍵盤的圈子裡,有些玩家對高度的客製化相當執著,例如 LED 要怎麼亮、配列要怎麼改、鍵盤上要有顆旋鈕,甚至是鍵盤上也要放個 LCD 螢幕。大家都有自己一套標準,所以除了硬體方面是各種工藝展現外,連韌體也講求高度客製化。

(圖取自於 Zoom75 官網)

Feature Case

理論上只要有微控制器和電路就可以寫出鍵盤韌體,然而如果每次改鍵盤配置都要修改程式碼、再加上如果微控制器更換就要重寫的話也很麻煩。因此有些玩家就自己寫了一個可以高度客製化配置的韌體,只要定義好鍵盤相關設定,就可以自動生出對應的韌體,一行程式碼都不用寫。

目前在鍵盤圈最有名的就是 QMK,它是由 tmk 衍伸出來的開源韌體。在鍵盤圈相當常見,甚至像 Keychron 也支援 QMK 設定。

剛剛提到的功能,QMK 幾乎都有。如何定義鍵位、巨集、LCD、LED 控制、藍芽、Joystick,鍵盤甚至是 MIDI,任何你想得到的功能 QMK 裡頭幾乎都有。而且支援的客製化鍵盤很多,例如我現在用的 Zoom65 就可以在這裡找到它的定義檔案。

除此之外,蠻令我驚訝的是現在還有支援 Web USB,也就是直接在網路上去修改鍵位,修改完之後直接調整韌體的設定。

詳細原理我還沒研究過,不過我猜應該是有對應的程式碼實作,透過 Web USB 直接將資料傳給鍵盤的韌體,有興趣的可以參考 VIA

結語

現在很多電路板、外殼都有開源的模型跟電路設計可以參考,想要自己「從頭」做一把雖然還是有難度,但跟以前比起來算是容易很多。不過光是了解背後的過程,就會覺得做一把鍵盤也不是那麼容易。

如果覺得這篇文章對你有幫助的話,可以考慮到下面的連結請我喝一杯 ☕️ 可以讓我平凡的一天變得閃閃發光 ✨

Buy me a coffee