從 0 開始,手打一把專屬的藍牙雙模人體工學鍵盤 (3) Debounce

李松錡
7 min readOct 23, 2017

--

此為系列文,目前已完成的文章列表:

  1. 從 0 開始,手打一把專屬的藍牙雙模人體工學鍵盤 (0)
  2. 從 0 開始,手打一把專屬的藍牙雙模人體工學鍵盤 (1) 製作左手
  3. 從 0 開始,手打一把專屬的藍牙雙模人體工學鍵盤 (2) 設定 BLE
  4. 從 0 開始,手打一把專屬的藍牙雙模人體工學鍵盤 (3) Debounce
  5. 從 0 開始,手打一把專屬的藍牙雙模人體工學鍵盤 (4) 定義 keyboardMgr class
  6. 從 0 開始,手打一把專屬的藍牙雙模人體工學鍵盤 (5) 製作右手
  7. 從 0 開始,手打一把專屬的藍牙雙模人體工學鍵盤 (6) 基礎鍵盤
  8. 從 0 開始,手打一把專屬的藍牙雙模人體工學鍵盤 (7) 軌跡球

僅在狀態改變時才觸發

經過上一回連接兩個藍牙裝置,我們已經可以把左手哪顆鍵按下去的事件傳過去給右手的 Arduino Pro Micro 了,但我們在 (1) 的 overview 的時候不是說,有按鍵被按下去或放開,我們才送一次訊號給右手嗎?

要達成這件事其實蠻簡單的,我們可以在這裡創造一個叫做 KeyBtn 的 class,讓每一個按鍵都是一個 KeyBtn 的 object,而每個 object 裡面都記錄了目前這個按鍵的狀態。每一次我們在掃描 keyborad matrix 的時候,我們都去更新一次這個 object,而只要發現更新的狀態與原本狀態不同,就知道這個按鍵有改變了。有這樣的概念以後,我們就可以來定義下面的 class:

class KeyBtn {
public:
KeyBtn() {
last_state = HIGH; // 假設一開始按鍵處於沒有按下的狀態
};
bool state_changed(bool read_state){
if (read_state != last_state) { // 如果讀到的狀態跟之前的狀態不同
last_state = read_state; // 更新紀錄的狀態
return true; // true => state changed
} else {
return false; // false => state not changed
}
};
private:
bool last_state;
};

透過上面那個例子,我們就可以每次掃瞄 keyboard matrix 的時候去呼叫 state_changed 這個 member function,如果回傳 true,則我們才發送改變事件,回傳 false,就表示該按鍵沒有任何變更。

Debounce

這樣是不是就沒有什麼問題了呢? 其實還是有的,很偶爾的時候,你會看到明明按鍵按下去一次,卻會連續觸發兩次到三次。這是為什麼? 我不是連鬼鍵的狀況都處理了嗎? 怎麼還會有這種事情發生?! 這個狀況並不是鬼鍵盤,事情是這樣的,因為我們用的開關 (按鍵) 物理結構非常簡單,而在按下去的那時間附近的電壓變化是這個樣子的:

因為這類物理壓下的開關,在狀態切換的瞬間電壓都不穩,所以只要期間 Arduino 就有可能讀到無數次的高低切換,也因此才會有連續按下的現象發生。我們稱這種現象為 bounce。

那要如何處理 bounce 呢? 處理 bounce 的動作我們稱之為 debounce,一般比較直覺的想法就是,當我發現到開始跳動的瞬間時,我覺得他可能只是受到環境的干擾,所以我會等一下下,然後再次測一次電壓

透過這樣的方式,我們就可以免除掉 bounce 的狀況。那一般在 keyboard matrix 的 bounce 大概要等多久才會穩定呢? 一般 1~ 2 ms 就會穩定。因此,如果你在 KeyBtn 的 state_changed 函數裡面做修改:

bool state_changed (bool read_state) {
if (read_state != last_state) {
delay(2); // 暫停 2 ms
if (digitalRead(pin) == read_state) {
last_state = read_state;
return true;
}
}
return false;
}

這樣就實作了 debounce 了。但其實這有一個非常嚴重的問題,大家還記得我們有幾顆按鈕嗎? 單邊的按鈕是 42 顆 (row * column = 6 * 7),也就是說我真正掃過一遍 keyboard matrix 光是等待的時間就需要的 42 * 2 = 84 ms,這還僅僅只是等待的時間,若加上其他運算,時間還會更長,因此你的鍵盤掃描會變得非常的緩慢,有可能會沒偵測到某個按下或放開的動作。那我們要怎麼改進呢? 我們可以換一個角度想,因為現在 keyboard matrix 其實蠻穩定的,所以電壓有任何變動一定是按鍵事件發生的

那我們就把發生變動的時間點記錄下來,然後 Arduino 繼續執行掃描其他按鍵的動作。當我再次掃秒這顆鍵的時候,我會先看是不是已經過了 bounce 的時間,如果還沒超過 bounce 的時間,我們就完全不理會讀到的電壓如何。如此就達成了能夠高速掃秒 keyboard matrix 且帶有 debounce 的功能。大家可以打開 3_debounce/basic_debounce,裡面的 KeyBtn.h 跟 KeyBtn.cpp 已經實作了這個功能。

定義協定

完成這個部分以後,左手的程式其實也近乎完成了,但我們到底要怎麼告訴右手的 Arduino 是 “哪一顆按鍵” 跟 “被按下或是放開” 呢? 所以在這邊我們要定義按鍵事件的協定。首先我們把左右手的每個按鍵編號是照這個順序編排的:

剩下的按鍵我就不編了,應該看得出來就是照著 keyboard matrix 左右交錯編下去

所以我們可以看到左手的按鍵最多 42 個,最大的編號是 76,而 76的 binary 表示則是 1001100,總共用了 7 個 bit。而我們跟藍牙模組通訊用的 UART 協定是一次傳 8 個 data bit,在 Arduino 的 byte 是 8 個 bit,所以我們可以把最前面那個 bit 作為表示按鍵按下或放開,如果是 1 的話則表示放開按鍵,是 0 的話則表示按下按鍵

如此我們就將按鍵事件用很簡潔的方式編碼並傳送過去了。此部分完整的程式碼可查看 3_debounce/left_send_matrix_key_event。那我們在右手 Pro Micro 也可以遵循這個方式進行解碼。

既然我們都已經可以做到解碼,可以知道左邊的鍵盤上哪顆按鍵是被按下或放開,我們其實可以把 usb 按鍵事件送出去給電腦了,因此可以把 3_debounce/right_receive_key_event 的程式燒到 Arduino Pro Micro 上面,然後左手按下一些按鍵,右手的 Pro Micro 收到以後就會像一般鍵盤一樣把按鍵事件傳給電腦。有沒有覺得這一講特別的輕鬆呢XD 既不用焊電路,然後又可以讓鍵盤動了,超有成就感的!

--

--