キーボードの作成 その2
前回の続きです。
64キーを次のような感じに割り付けました。
64キーだと足りないため、Fnキーを押すことで、ファンクションキー等の水色キーが
入力できます。

押したボタン入力は、PS/2キーボード I/Fの信号に変換します。
とりあえず、IchigoJamにて利用出来ることを確認しました。
PS/2 キーボードのエミュレーションは ps2devライブラリを利用しました。
・PS2 mouse interface for Arduino
http://playground.arduino.cc/ComponentLib/Ps2mouse
スケッチは次のような感じです。
Fnキーの処理とキーの長押し時のリピート処理がちょっと面倒でした。
(2017/9/6 スケッチを修正しました)
#include <Arduino.h> #include <ps2dev.h> #define KB_CLK 9 // PS/2 CLK IchigoJamのKBD1に接続 #define KB_DATA 10 // PS/2 DATA IchigoJamのKBD2に接続 #define SOUT_PIN 2 // シリアルアウト #define CLK_PIN 3 // クロック #define LATCH_PIN 4 // ラッチ #define SCN1_PIN 5 // ライン1 #define SCN2_PIN 6 // ライン2 #define SCN3_PIN 7 // ライン3 #define SCN4_PIN 8 // ライン4 #define KEY_NUM 64 // キー数 // キーリピートの定義 #define REPEATTIME 5 // キーを押し続けて、REP_INTERVALxREPEATTIMEmsec後にリピート開始 #define EMPTY 0 // リピート管理テーブルが空状態 #define MAXKEYENTRY 64 // リピート管理テーブルサイズ #define REP_INTERVAL 10 // リピート間隔 100msec PS2dev keyboard(KB_CLK, KB_DATA); // PS/2デバイス // スキャンコードテーブル const uint8_t scan_table[][4] = { { 0x76,0x00 , 0x05,0x00 , }, // 0 ESC { 0x16,0x00 , 0x06,0x00 , }, // 1 1 { 0x1E,0x00 , 0x04,0x00 , }, // 2 2 { 0x26,0x00 , 0x0C,0x00 , }, // 3 3 { 0x25,0x00 , 0x03,0x00 , }, // 4 4 { 0x2E,0x00 , 0x0B,0x00 , }, // 5 5 { 0x36,0x00 , 0x83,0x00 , }, // 6 6 { 0x3D,0x00 , 0x0A,0x00 , }, // 7 7 { 0x3E,0x00 , 0x01,0x00 , }, // 8 8 { 0x46,0x00 , 0x09,0x00 , }, // 9 9 { 0x45,0x00 , 0x78,0x00 , }, // 10 0 { 0x4E,0x00 , 0x07,0x00 , }, // 11 - { 0x55,0x00 , 0x00,0x00 , }, // 12 ^ { 0x6A,0x00 , 0x00,0x00 , }, // 13 ¥ { 0xE0,0x6C , 0xE0,0x70 , }, // 14 Home { 0x66,0x00 , 0xE0,0x71 , }, // 15 BS { 0x29,0x00 , 0x0D,0x00 , }, // 16 Space { 0x15,0x00 , 0x6C,0x00 , }, // 17 q { 0x1D,0x00 , 0x75,0x00 , }, // 18 w { 0x24,0x00 , 0x7D,0x00 , }, // 19 e { 0x2D,0x00 , 0x7B,0x00 , }, // 20 r { 0x2C,0x00 , 0xE0,0x4A , }, // 21 t { 0x35,0x00 , 0x00,0x00 , }, // 22 y { 0x3C,0x00 , 0x00,0x00 , }, // 23 u { 0x43,0x00 , 0x00,0x00 , }, // 24 i { 0x44,0x00 , 0x00,0x00 , }, // 25 o { 0x4D,0x00 , 0x00,0x00 , }, // 26 p { 0x54,0x00 , 0x00,0x00 , }, // 27 @ { 0x5B,0x00 , 0x00,0x00 , }, // 28 [ { 0x00,0x00 , 0x00,0x00 , }, // 29 Fn { 0xE0,0x69 , 0x00,0x00 , }, // 30 End { 0x59,0x00 , 0x00,0x00 , }, // 31 RghtShift { 0x12,0x00 , 0x58,0x00 , }, // 32 LeftShift { 0x1C,0x00 , 0x6B,0x00 , }, // 33 a { 0x1B,0x00 , 0x73,0x00 , }, // 34 s { 0x23,0x00 , 0x74,0x00 , }, // 35 d { 0x2B,0x00 , 0x79,0x00 , }, // 36 f { 0x34,0x00 , 0x7C,0x00 , }, // 37 g { 0x33,0x00 , 0x00,0x00 , }, // 38 h { 0x3B,0x00 , 0x00,0x00 , }, // 39 j { 0x42,0x00 , 0x00,0x00 , }, // 40 k { 0x4B,0x00 , 0x00,0x00 , }, // 41 l { 0x4C,0x00 , 0x00,0x00 , }, // 42 ; { 0x52,0x00 , 0x00,0x00 , }, // 43 : { 0x5D,0x00 , 0x00,0x00 , }, // 44 ] { 0xE0,0x71 , 0x00,0x00 , }, // 45 PageUp { 0xE0,0x75 , 0x00,0x00 , }, // 46 ↑ { 0xE0,0x7A , 0x00,0x00 , }, // 47 PageDown { 0x14,0x00 , 0x13,0x00 , }, // 48 LeftCtrl { 0x1A,0x00 , 0x69,0x00 , }, // 49 z { 0x22,0x00 , 0x72,0x00 , }, // 50 x { 0x21,0x00 , 0x7A,0x00 , }, // 51 c { 0x2A,0x00 , 0x70,0x00 , }, // 52 v { 0x32,0x00 , 0x71,0x00 , }, // 53 b { 0x31,0x00 , 0x00,0x00 , }, // 54 n { 0x3A,0x00 , 0x00,0x00 , }, // 55 m { 0x41,0x00 , 0x00,0x00 , }, // 56 , { 0x49,0x00 , 0x00,0x00 , }, // 57 . { 0x4A,0x00 , 0x00,0x00 , }, // 58 / { 0x51,0x00 , 0x00,0x00 , }, // 59 ¥ { 0x5A,0x00 , 0x00,0x00 , }, // 60 Enter { 0xE0,0x6B , 0xE0,0x2F , }, // 61 ← { 0xE0,0x72 , 0xE0,0x1F , }, // 62 ↓ { 0xE0,0x74 , 0x11,0x00 , }, // 63 → }; uint8_t lines[] = { SCN1_PIN, SCN2_PIN, SCN3_PIN, SCN4_PIN }; uint16_t scn[4]; // キーボード状態 uint16_t prev_scn[4]; // 前回のキーボード状態 uint8_t enabled =0; // PS/2 ホスト送信可能状態 uint8_t keyentry[MAXKEYENTRY]; // リピート管理テーブル uint8_t repeatWait[MAXKEYENTRY]; // リピート開始待ち管理テーブル // PS/2 ホストにack送信 void ack() { while(keyboard.write(0xFA)); } // PS/2 ホストから送信されるコマンドの処理 int keyboardcommand(int command) { unsigned char val; uint32_t tm; switch (command) { case 0xFF: ack();// Reset: キーボードリセットコマンド。正しく受け取った場合ACKを返す。 //keyboard.write(0xAA); break; case 0xFE: // 再送要求 ack(); break; case 0xF6: // 起動時の状態へ戻す //enter stream mode ack(); break; case 0xF5: //起動時の状態へ戻し、キースキャンを停止する //FM enabled = 0; ack(); break; case 0xF4: //キースキャンを開始する //FM enabled = 1; ack(); break; case 0xF3: //set typematic rate/delay : ack(); keyboard.read(&val); //do nothing with the rate ack(); break; case 0xF2: //get device id : ack(); keyboard.write(0xAB); keyboard.write(0x83); break; case 0xF0: //set scan code set ack(); keyboard.read(&val); //do nothing with the rate ack(); break; case 0xEE: //echo :キーボードが接続されている場合、キーボードはパソコンへ応答(ECHO Responce)を返す。 //ack(); keyboard.write(0xEE); break; case 0xED: //set/reset LEDs :キーボードのLEDの点灯/消灯要求。これに続くオプションバイトでLEDを指定する。 ack(); keyboard.read(&val); //do nothing with the rate ack(); break; } } // // PS/2 makeコード送信 // 引数 index : キーライン 0 ~ 3 // no : ライン内番号 0 ~ 15 // flgFnKey : Fnキーの状態 0 or 1 // void sendKeyMake(uint8_t index, uint8_t no, uint8_t flgFnkey) { uint8_t code1,code2; // キーコードの取得 if (flgFnkey) { code1 = scan_table[index*16+no][2]; code2 = scan_table[index*16+no][3]; } else { code1 = scan_table[index*16+no][0]; code2 = scan_table[index*16+no][1]; } // PS/2キーの発行 if (code2 == 0) { keyboard.write(code1); } else { keyboard.write(code1); keyboard.write(code2); } } // // PS/2 breakコード送信 // 引数 index : キーライン 0 ~ 3 // no : ライン内番号 0 ~ 15 // flgFnKey : Fnキーの状態 0 or 1 // void sendKeyBreak(uint8_t index, uint8_t no, uint8_t flgFnkey) { uint8_t code1,code2; // キーコードの取得 if (flgFnkey) { code1 = scan_table[index*16+no][2]; code2 = scan_table[index*16+no][3]; } else { code1 = scan_table[index*16+no][0]; code2 = scan_table[index*16+no][1]; } // PS/2キーの発行 if (code2 == 0) { keyboard.write(0xF0); keyboard.write(code1); } else { keyboard.write(code1); keyboard.write(0xF0); keyboard.write(code2); } } // リピート処理(タイマー割り込み処理から呼ばれる) void sendRepeat() { uint8_t index,no,flgFnKey; uint8_t key; for (uint8_t i=0; i < MAXKEYENTRY; i++) { if (keyentry[i] != EMPTY) { if (repeatWait[i] == 0) { key = keyentry[i]; flgFnKey = key & 0x80 ? 1:0; no = key % 16; index = (key & 0x7F)/16; sendKeyMake(index,no,flgFnKey); } else { repeatWait[i]--; } } } } // キーボード状態の取得 void getKeyCode() { uint16_t clm = 0x0001; memset(scn, 0, 8); for (uint8_t i=0; i < 16; i++) { digitalWrite(LATCH_PIN, LOW); shiftOut(SOUT_PIN, CLK_PIN, LSBFIRST, ~clm & 0xff ); shiftOut(SOUT_PIN, CLK_PIN, LSBFIRST, ~(clm>>8) & 0xff ); digitalWrite(LATCH_PIN, HIGH); for (uint8_t j=0; j <4; j++) { scn[j]<<=1; scn[j] |= digitalRead(lines[j]); } clm<<=1; } } // スキャンコードの送信 void sendScanCode() { uint8_t scn_bit; // 現在の状態 uint8_t prev_scn_bit; // 前回の状態 uint8_t flgFnKey; // Fnキーの状態 uint8_t prevflgFnKey; // Fnキーの状態 // Fnキーのチェック flgFnKey = scn[1]&(0x8000>>13) ? 0:1; prevflgFnKey = prev_scn[1]&(0x8000>>13) ? 0:1; for (uint8_t i=0; i < 4; i++) { for (uint8_t j=0; j < 16; j++) { scn_bit = scn[i]&(0x8000>>j) ? 0:1; prev_scn_bit = prev_scn[i]&(0x8000>>j) ? 0:1; // Fnキーの場合 if (i==1 && j==13) { continue; } // 前回から状態が変化しているキーの処理 if (scn_bit != prev_scn_bit) { if (scn_bit) { // キーが押された sendKeyMake(i, j, flgFnKey); if ( !(i == 2 && j == 0) && !(i == 1 && j == 15)) { addKey(i, j, flgFnKey); } } else { // キーが離された sendKeyBreak(i, j, flgFnKey); if ( !(i == 2 && j == 0) && !(i == 1 && j == 15)) { delKey(i, j, flgFnKey); } } } else { if (scn_bit && flgFnKey != prevflgFnKey){ // キーを押している状態でFnキーが切り替わった sendKeyBreak(i, j, prevflgFnKey); if ( !(i == 2 && j == 0) && !(i == 1 && j == 15)) { delKey(i, j, prevflgFnKey); sendKeyMake(i, j, flgFnKey); addKey(i, j, flgFnKey); } } } } } } // リピート管理テーブルのクリア void claerKeyEntry() { for (uint8_t i=0; i <MAXKEYENTRY; i++) keyentry[i] = EMPTY; } // リピート管理テーブルにキーを追加 void addKey(uint8_t index, uint8_t no, uint8_t flgFnkey) { uint8_t key = index*16+no | (flgFnkey ? 0x80:0); for (uint8_t i=0; i <MAXKEYENTRY; i++) { if (keyentry[i] == EMPTY) { keyentry[i] = key; repeatWait[i] = REPEATTIME; break; } } } // リピート管理テーブルからキーを削除 void delKey(uint8_t index, uint8_t no, uint8_t flgFnkey) { uint8_t key = index*16+no | (flgFnkey ? 0x80:0); for (uint8_t i=0; i <MAXKEYENTRY; i++) { if (keyentry[i] == key) { keyentry[i] = EMPTY; //break; } } } void setup() { Serial.begin(115200); pinMode(SOUT_PIN, OUTPUT); pinMode(CLK_PIN, OUTPUT); pinMode(LATCH_PIN, OUTPUT); pinMode(SCN1_PIN, INPUT_PULLUP); pinMode(SCN2_PIN, INPUT_PULLUP); pinMode(SCN3_PIN, INPUT_PULLUP); pinMode(SCN4_PIN, INPUT_PULLUP); while(keyboard.write(0xAA)!=0); while(keyboard.write(0x00)!=0); claerKeyEntry(); memset(scn, 0xff, 8); Serial.println(F("Start.")); } void loop() { static uint32_t tm = 0; unsigned char c; // ホストからの送信データ if( (digitalRead(KB_CLK)==LOW) || (digitalRead(KB_DATA) == LOW)) { while(keyboard.read(&c)) ; keyboardcommand(c); } for (uint8_t i=0; i <4; i++) prev_scn[i] = scn[i]; getKeyCode(); sendScanCode(); //キーリピート処理 if (millis() >= tm+100) { sendRepeat(); tm = millis(); } }
一点、問題が発覚。
なぜか、同じライン(横一列)の同時入力が出来ません。
シフトキーとの同時押しが出来ないのはちょっと問題です。
ハードウェア的には可能なはずなのですが.. ソフト的な問題?
原因調査中です。
2017/09/05 追記
スイッチを2つにて単純化して検証。
結線して確認しました(左側の回路図)。
K1のボタンを押すとLEDが点灯しますが、K2を同時に押すとVCC→K2→K1→GNDの経路で、
電流が流れてしまいショートしてしまいます。
これはちょっとまずいですね。
右の回路のようにダイオードを入れて対応するしかなさそうです。
このタクトスイッチモジュール、そのまま使うには問題ありですね。
安いですが、あまりお手軽に利用出来ないようです。
配線もちょっとめんどくさくなりますね。
全体をダイオードを追加する回路図に修正しましました。
端子を基板に乗せられるものに変更し、実装し直しました。
再度、IchigoJamに接続して動作確認

同時押しも出来るようになりました。
このサイズのキーボード、意外と使い心地良さそうです。
現時点ではArduinoを使っていますが、Atmega328マイコンに置き換えて、
基板実装すれば、小型化は出来ると思います。
キーの刻印を何としたいですね。方法をちょっと考えてみます。
2017/09/09追記
結局、タクトスイッチを基板に並べ5行、12列の60キーで実装することにしました。
キーの刻印は、基板にシールを貼る形にします。
手持ちのタクトスイッチでは足りないため、手ごろな製品をAliexpressにて見つけて発注しました。
100個 $0.79の激安タクトスイッチ 試作に使うにはこれで良いでしょう

2ピンタイプのタクトスイッチ 100個 $1.57 はんだ付け楽になるが強度不足になるかも
2ピンタイプで通常の1/2幅 100個 $1.50 空いたスーペースにキーの刻印表記出来そう
ボタン部分の素材がシリコン 100個 $4.90 (多く買うと割引) 押し心地がよさそう
キーが5行×12列の場合、キーの状態をスキャンする端子は17本となります。
これとは別に、PS/2 IF に2本、シリアル通信 2本が必要で合計で21本が必要。
ATmega328をArduinoとして利用する場合、最大22本をデジタル入出力で利用出来ます。
そうすると、シフトレジスタ無しで直接制御も出来そうですね。
結線もさらに簡略化出来ると思います。
1本余るので状況に応じて、1列または1行分キーの追加も出来ます。
« キーボードの作成 その1 | トップページ | ベットに寝ているタマちゃん »
「arduino」カテゴリの記事
- Freenove Mecanum ホイール カー キットを購入しました(2025.05.28)
- Arduino IDE+Arduino STM32環境で指定と異なるgccが使われてしまう(2025.01.23)
- Zorin OSでArduino Uno互換機(CH340)が認識しない(2025.01.19)
- Arduino IDE 2.3.4でArduino STM32を利用する(2025.01.12)
- Arduino用 SKK日本語変換ライブラリの開発 その1(2024.12.28)


コメント