USB HID Test 01 : Keyboard-2 4x4
啟稿: 2018/04/21
完稿: 2018/04/
測試需求
上次在測試單鍵的時候有提到,對於多個按鍵的情況,按鍵的配置與控制的方法就不一樣,所以在此要利用Arduino ProMicro來測試一個4x4矩陣的鍵盤來模擬一個小型的USB鍵盤。
材料與工具
需要如下所列的材料:
- Arduino ProMicro x1
- 4x4矩陣式16鍵鍵盤模組 x1
基本知識
在上篇已經介紹的一些的基本知識,可以參考以下連結:
USB HID Test 01 : Keyboard-1 Hello World
在本篇測試USB HID Keyboard可獲得基本的知識如下:
- 有Arduino ProMicro USB HID寫作的知識
- 4x4矩陣式16鍵Keyboard運作的知識
1.4x4矩陣式16鍵Keyboard運作
矩陣式鍵盤操作運作原理圖如下,就是利用交互感測電極包括row / X電極及column / Y電極,當使用者按鍵時,就會導通兩個電極;所以按鍵就需要一個X-line (驅動線)及一個Y-line (感測線)。
4x4矩陣式16鍵鍵盤按鍵配置組成在不同的4x4 X-Y軸的觸控矩陣上,所以總共需要8個輸入腳位來判斷按鍵。
一般來說有很多的方法來掃描矩陣式鍵盤,常見的有column-interrupt加上polling的方法,在此說明此種方法說明如下:
- Step1 column-interrupt:column-interrupt此方法會將所有的列(row)同時設為主動驅動,而列(column)的腳色設定為當有單一按鍵按下時中斷處理器。此種方法多用在低功耗的模式,因為任何鍵可以喚醒微控制器;但同時也很重要的標示了此方法按鍵只能應用在此種目的,因為它沒法提供知道有其他額外的鍵按下的功能。
下圖就顯示column-interrupt此方法當按下Enter鍵時矩陣式鍵盤的行為,行列中紅色部分都是主動驅動的所有鍵,也就是說在此行所有的鍵按下都會有同樣的觸發。
reference TI MSP430 Application Note
- Step2 polling:當系統被column-interrupt法喚醒後,接下來就可以polling法來確定是哪一個鍵按下;polling方法就是一個個列(row)依序分開進行掃描,就可以知道是Enter鍵按下。
在此我們不用此種column-interrupt加上polling的方法,而簡化了第一步驟不用觸發而採用簡單的主動掃描法,先將每個column拉到高電位及每個row拉到地,利用for迴圈掃描column並讀取每一row讀取值就可知道是哪一個按鍵被按下。
2.Arduino HID Library
基本上所需要的USB鍵盤函式庫功能函數主要仍是我們在第一次測試所用的Keyboard.write()功能函數,這對於簡單一次只有一個按鍵的需求來說是足夠了,但對於可能同時多個按鍵及長按某個按鍵的判斷,利用此功能函數就會有問題,所以在此我們還需要知道另外的功能函數如下:
Keyboard.press(char) – 這個功能傳送鍵盤按下char該鍵,但尚未釋放開該鍵。
Keyboard.release(char) – 這個功能傳送鍵盤釋放char該鍵。
Keyboard.releaseAll() – 這個功能傳送所有鍵盤同時釋放。
所以若要模擬使用者同時按下Ctrl+c兩個鍵,做法就是呼叫Keyboard.press(KEY_LEFT_CTRL)及Keyboard.press('c')後,再呼叫Keyboard.releaseAll(),就可達到按下Ctrl+c效果。
測試過程
在此4x4矩陣式16鍵定義如下:
Pin |
2 |
3 |
4 |
5 |
6 |
Esc |
↑ |
Ent |
Z |
7 |
← |
↓ |
→ |
C |
8 |
A |
X |
D |
S |
9 |
1 |
1+5 |
5 |
6 |
所以Pro Micro與16鍵鍵盤配對與連接線就是直接將Pro Micro的pin2~9接到4x4矩陣式16鍵pin0~7如下圖:
所以在此範例碼如下:
#include "Keyboard.h"
#define KEY_UP_ARROW 0xDA
#define KEY_DOWN_ARROW 0xD9
#define KEY_LEFT_ARROW 0xD8
#define KEY_RIGHT_ARROW 0xD7
#define KEY_RETURN 0xB0
#define KEY_ESC 0xB1
#define KeyLoop_ 500
#define KeyDelay_ 200
#define KeyPress_ 50
// Pins 1-8 of the keypad connected to the Arduino respectively:
int keypadPins[8] = {5, 4, 3, 2, 6, 7, 8, 9};
int keypadStatus; // Used to monitor which buttons are pressed.
int timeout; // timeout variable used in loop
void setup() {
// put your setup code here, to run once:
for (int i=0; i<8; i++)
{
pinMode(keypadPins[i], INPUT); // Set all keypad pins as inputs
digitalWrite(keypadPins[i], HIGH); // pull all keypad pins high
}
}
void loop() {
// put your main code here, to run repeatedly:
keypadStatus = getKeypadStatus(); // read which buttons are pressed
if (keypadStatus != 0) // If a button is pressed go into here
{
sendKeyPress(keypadStatus); // send the button over USB
timeout = KeyLoop_; // top of the repeat delay
while ((getKeypadStatus() == keypadStatus) && (--timeout)) // Decrement timeout and check if key is being held down
delayMicroseconds(1);
while (getKeypadStatus() == keypadStatus) // while the same button is held down
{
sendKeyPress(keypadStatus); // continue to send the button over USB
delay(50); // 50ms repeat rate
}
}
}
/* sendKeyPress(int key): This function sends a single key over USB
It requires an int, of which the 12 LSbs are used. Each bit in
key represents a single button on the keypad.
This function will only send a key press if a single button
is being pressed */
void sendKeyPress(int key)
{
switch(key)
{
case 1:
//Keyboard.write('z');
Keyboard.press('z');
delay(KeyPress_);
Keyboard.releaseAll();
delay(KeyDelay_);
break;
case 2:
//Keyboard.write('c');
Keyboard.press('c');
delay(KeyPress_);
Keyboard.releaseAll();
delay(KeyDelay_);
break;
case 3:
//Keyboard.write('s');
Keyboard.press('s');
delay(KeyPress_);
Keyboard.releaseAll();
delay(KeyDelay_);
break;
case 4:
//Keyboard.write('6');
Keyboard.press('6');
delay(KeyPress_);
Keyboard.releaseAll();
delay(KeyDelay_);
break;
case 5:
//Keyboard.write(KEY_RETURN);
Keyboard.press(KEY_RETURN);
delay(KeyPress_);
Keyboard.releaseAll();
delay(KeyDelay_);
break;
case 6:
//Keyboard.write(KEY_RIGHT_ARROW);
Keyboard.press(KEY_RIGHT_ARROW);
delay(KeyPress_);
Keyboard.releaseAll();
delay(KeyDelay_);
break;
case 7:
//Keyboard.write('d');
Keyboard.press('d');
delay(KeyPress_);
Keyboard.releaseAll();
delay(KeyDelay_);
break;
case 8:
//Keyboard.write('5');
Keyboard.press('5');
delay(KeyPress_);
Keyboard.releaseAll();
delay(KeyDelay_);
break;
case 9:
//Keyboard.write(KEY_UP_ARROW);
Keyboard.press(KEY_UP_ARROW);
delay(KeyPress_);
Keyboard.releaseAll();
delay(KeyDelay_);
break;
case 10:
//Keyboard.write(KEY_DOWN_ARROW);
Keyboard.press(KEY_DOWN_ARROW);
delay(KeyPress_);
Keyboard.releaseAll();
delay(KeyDelay_);
break;
case 11:
//Keyboard.write('x');
Keyboard.press('x');
delay(KeyPress_);
Keyboard.releaseAll();
delay(KeyDelay_);
break;
case 12:
Keyboard.press('1');
Keyboard.press('5');
delay(KeyPress_);
Keyboard.releaseAll();
delay(KeyDelay_);
break;
case 13:
//Keyboard.write(KEY_ESC);
Keyboard.press(KEY_ESC);
delay(KeyPress_);
Keyboard.releaseAll();
delay(KeyDelay_);
break;
case 14:
//Keyboard.write(KEY_LEFT_ARROW);
Keyboard.press(KEY_LEFT_ARROW);
delay(KeyPress_);
Keyboard.releaseAll();
delay(KeyDelay_);
break;
case 15:
Keyboard.press('a');
delay(KeyPress_);
Keyboard.releaseAll();
delay(KeyDelay_);
break;
case 16:
Keyboard.press('1');
delay(KeyPress_);
Keyboard.releaseAll();
delay(KeyDelay_);
break;
}
}
/* getKeypadStatus(): This function returns an int that represents
the status of the 12-button keypad. Only the 12 LSb's of the return
value hold any significange. Each bit represents the status of a single
key on the button pad. '1' is bit 0, '2' is bit 1, '3' is bit 2, ...,
'#' is bit 11.
This function doesn't work for multitouch.
*/
int getKeypadStatus()
{
int rowPins[4] = {keypadPins[0], keypadPins[1], keypadPins[2], keypadPins[3]}; // row pins are 1, 2, 3, and 4 of the keypad
int columnPins[4] = {keypadPins[4], keypadPins[5], keypadPins[6], keypadPins[7]}; // column pins are pins 5, 6, 7, and 8 of the keypad
int keypadStatus = 0; // this will be what's returned
/* initialize all pins, inputs w/ pull-ups */
for (int i=0; i<8; i++)
{
pinMode(keypadPins[i], INPUT);
digitalWrite(keypadPins[i], HIGH);
}
for (int row=0; row<4; row++)
{ // initial for loop to check all 4 rows
pinMode(rowPins[row], OUTPUT); // set the row pin as an output
digitalWrite(rowPins[row], LOW); // pull the row pins low
for (int col=0; col<4; col++)
{ // embedded for loop to check all 3 columns of each row
if (!digitalRead(columnPins[col]))
{
keypadStatus = (row*4 + (col+1)); // set the status bit of the keypad return value
}
}
pinMode(rowPins[row], INPUT); // reset the row pin as an input
digitalWrite(rowPins[row], HIGH); // pull the row pin high
}
return keypadStatus;
}
判斷按鍵就在函數getKeypadStatus(),做法就是先將每個column拉到高電位及每個row拉到地,利用for迴圈掃描column,若有按鍵按下,利用digitalRead(columnPins[col])讀到false 低電位就就可知道是哪一個按鍵被按下。
後記
採用Keyboard.press(char)似乎可以解決長按的問題,但實際測試後發現目前程式對於長按某個按鍵的反應是會產生多個按鍵事件,所以用於文書處理可以接受,但用於遊戲鍵盤就覺反應不像一般的USB鍵盤靈敏;另外對於同時多個按鍵的判斷仍然無效;所以在邏輯判斷結構上還有待改進,我將再另外文章進行探討。
本文為個人學習的經驗,後續有所改進將再發文分享;本人因工作因素發文後並不會經常檢視讀者問題,對於沒法及時回覆問題敬請見諒!
若覺本文對讀者有所幫助,可回覆感想及你的分享!謝謝!
留言列表