下圖左是一款常見的薄膜按鍵模組(hex keypad),有4×4或3×4(少了最右邊一行的A, B, C, D鍵),它的內部如同下圖右邊的電路所示,是由16個按鍵(開關)交織而成。有些按鍵模組直接使用按鍵(微觸)開關組裝,連接電路與程式都和本文相同。
4×4按鍵模組有8個接腳,分成列、行兩組,可以接在Arduino的任意8個接腳,筆者將它接在數位6~13腳:
為了簡化接線,可以使用「長腳型」排針,將其中兩個腳用尖嘴鉗稍微折彎,即可插入Arduino的排母:
排針上頭的黑色塑膠,可以用蠻力讓它移到中間的位置,以便銜接兩邊的排母:
4×4薄膜鍵盤的Arduino按鍵偵測程式
撰寫Arduino按鍵偵測程式,最簡單也是最好的方式,就是採用既有的程式庫。本單元將示範使用Keypad程式庫,把偵測到的按鍵字元顯示在序列埠監控視窗。請按此下載Keypad程式庫(原始出處在這篇keypad介紹文),解壓縮之後,置入Arduino的libraries路徑:
底下的程式,修改自Keypad程式庫的“HelloKeypad”範例,使用此程式庫,我們的程式碼需要定義按鍵模組的行(col)、列(row)數、連接Arduino的腳位以及按鍵所代表的字元。
#include <Keypad.h> // 引用Keypad程式庫 #define KEY_ROWS 4 // 按鍵模組的列數 #define KEY_COLS 4 // 按鍵模組的行數 // 依照行、列排列的按鍵字元(二維陣列) char keymap[KEY_ROWS][KEY_COLS] = { {'1', '2', '3', 'A'}, {'4', '5', '6', 'B'}, {'7', '8', '9', 'C'}, {'*', '0', '#', 'D'} }; byte colPins[KEY_COLS] = {9, 8, 7, 6}; // 按鍵模組,行1~4接腳。 byte rowPins[KEY_ROWS] = {13, 12, 11, 10}; // 按鍵模組,列1~4接腳。 // 初始化Keypad物件 // 語法:Keypad(makeKeymap(按鍵字元的二維陣列), 模組列接腳, 模組行接腳, 模組列數, 模組行數) Keypad myKeypad = Keypad(makeKeymap(keymap), rowPins, colPins, KEY_ROWS, KEY_COLS); void setup(){ Serial.begin(9600); } void loop(){ // 透過Keypad物件的getKey()方法讀取按鍵的字元 char key = myKeypad.getKey(); if (key){ // 若有按鍵被按下… Serial.println(key); // 顯示按鍵的字元 } }
編譯並上傳程式碼,再開啟「序列監控視窗」,按下薄膜鍵盤的任何按鍵,該字元將顯示在序列埠監控視窗。
按鍵偵測與掃描原理
為了方便解說,筆者把4×4按鍵簡化成3×1,像下圖這樣串連三個開關,連接到同一個微控制器的輸入腳。此外,因為要簡化開關電路,所以要啟用微控器內部的上拉電阻(請參閱《超圖解Arduino互動設計入門》第四章「啟用微控器內部的上拉電阻」一節,4-13頁):
假設開關的「行1」~「行3」輸入端全都輸入高電位,無論開關是否被按下,Arduino將接收到高電位(1)。為了檢測到其中按鍵被按下,程式必須依序將「行1」~「行3」腳位設定成低電位。
輪到「行2」腳輸入低電位,此時,微控器的輸入腳也將接收到低電位(0),由此可知連接「行2」的「開關B」被按下了。
輪到「行3」腳輸入低電位,由於「開關C」未被按下,因此微控器的輸入腳接收到高電位(1)。
到此,偵測按鍵的程式必須再次回到「行1」,輸入低電位…如此反覆循環掃描,才能持續偵測到某個按鍵是否被按下。實際的程式需要運用雙重迴圈,才能分批掃描每一列:
雙重迴圈的練習,請參閱《超圖解Arduino互動設計入門》第8章「LED矩陣動畫與多維陣列程式設計」一節,8-28頁。
自行撰寫掃描按鍵的程式
根據以上說明,自行撰寫掃描按鍵的程式碼:
const byte colPins[4] = {9, 8, 7, 6}; // 設定「行」腳位 const byte rowPins[4] = {13, 12, 11, 10}; // 設定「列」腳位 const char keymap[4][4] = { // 設定按鍵的「行、列」代表值 {'1','2','3','A'}, {'4','5','6','B'}, {'7','8','9','C'}, {'*','0','#','D'} }; byte i, j; // 暫存迴圈的索引數字 byte scanVal; // 暫存掃描到的按鍵值 void setup(){ Serial.begin(9600); for (i = 0; i < = 3; i++) { pinMode(rowPins[i], INPUT); pinMode(colPins[i], OUTPUT); digitalWrite(colPins[i], HIGH); digitalWrite(rowPins[i], HIGH); } } void loop() { for (i = 0; i <= 3; i++) { for (j = 0; j <= 3; j++) { digitalWrite(colPins[j], LOW); scanVal = digitalRead(rowPins[i]); if (scanVal == LOW) { // 如果輸入值是「低電位」… Serial.println(keymap[i][j]); // 輸出按鍵代表的字元 delay(200); // 掃描按鍵的間隔時間 digitalWrite(colPins[j], HIGH); break; // 跳出迴圈 } digitalWrite(colPins[j], HIGH); } } }
Keypad程式庫的運作方式大致上面的程式相同,主要是多了消除彈跳(debounce)的程式(相關說明請參閱第四章「解決開關訊號的彈跳問題」一節,4-15頁),而且按鍵掃描的間隔時間是透過比對時間差,而非使用delay。上面的程式用於幫助理解掃描按鍵的原理,在實際專案製作上,請使用Keypad程式庫。
未完,待續…
請問一下
1.
Keypad() 這函式是只能接受 Keypad( char , byte , byte , int , int ) 這樣的型態嗎?
byte colPins[KEY_COLS] = {9, 8, 7, 6};
byte rowPins[KEY_ROWS] = {13, 12, 11, 10};
這兩行腳位宣告,我把型態變成int後就在Keypad()出現 no matching function
2.
#define KEY_ROWS 4 // 按鍵模組的列數
#define KEY_COLS 4 // 按鍵模組的行數
如果把這兩行定義變成
int KEY_ROWS = 4;
int KEY_COLS = 4;
後面只要跟這兩變數有關的都會錯誤,說沒有宣告…
這是為什麼呢?
hi vivian:
因為Arduino的C程式,不允許使用變數定義陣列的範圍,請改用常數,例如:
const byte KEY_ROWS = 4; // 按鍵模組的列數
const byte KEY_COLS = 4; // 按鍵模組的行數
thanks,
jeffrey
這是在書的第幾章啊?
有收錄在《超圖解Arduino互動設計入門》第三版,第四版因為新增其他內容所以沒有收錄。
thanks,
jeffrey
希望出第五版時,可以全部收錄(我一定買
呃…第5版應該也不會收錄,《超圖解ESP32深度實作》新書的低功耗藍牙(BLE)人機介面單元,也有用到這個鍵盤模組,但部份內文同樣是參照到這系列貼文,沒有全部收錄。
thanks,
jeffrey
借問這有可能配合 模擬usb 見盤使用嗎
可以,要搭配具備USB介面或者藍牙介面的微控器。我用過ESP32製作藍牙鍵盤滑鼠。