前回の続きです。
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行分キーの追加も出来ます。
最近のコメント