前回のポケモンキーボードのスケッチを修正して一般的なUSBキーボードに対応しました。
USBワイヤレスキーボードをPS/2インターフェースに変換します。
利用したキーボードはLogicool K270です。
以前 amazonで1,290円で購入したのですが、今は値上がりしてますね。

IchigoJamはaitendoのai.Jam(ファームウェア V1.2.1)を使っています。
純正品IchigoJam Tと互換性があります。
IchigoJamとの接続はArduino pro miniのGND、VCCの他、
A4、A5ピンをPS/2用の通信に利用し、IchigoJamのKBD1、KBD2に接続しています。
Arduino pro miniではA4、A5端子がボード上の変なところにあります。
これはこれで、場合によっては接続しやすいです。

動いている様子
システム構成的な図
USB HIDに対応しています。有線キーボードも利用出来ます。
処理としては、図のようなハードウェア&ソフトウェア構成となっています。
USB HIDのキーボード入力データをPS/2キーボードデータと信号に変換して
IchigoJamのPS/2キーボードとして利用出来るようにしています。
利用しているハードウェア
1)Arduino pro mini 3.3V 8MHz版(上)とUSB Hostシールド
VCCに3.3Vを供給するのであれば、5V 16MHz版のArduino pro mini も利用出来ます。
私は最終的にIchigoJamのUSBコネクタに繋げる予定で、5V=>3.3Vの電圧変換が出来る
3.3V版を利用しています。
制約事項
・USBポートからの電源供給は3.3Vで電流もあまり流せません。
動かないキーボードもあると思います。
・現状、マルチメディアキーには対応していません。
・起動直後、なぜか、IchigoJamに"@"文字が送られていまいます。
IchigoJamとのPS/2キーボード接続のイニシャル処理でIchigoJamがコマンドではなく
文字として認識しているようです。
接続完了の目印になるので、これは放置しておきます。
スケッチ
スケッチを公開します。
ダウンロード USBKBD2PS2.zip (3.2K) (2016/11/19 バグがあり差し換えました)
※ 2016/11/18 追記 最新版はgithubにて公開しています。
https://github.com/Tamakichi/Arduino_USBToPS2
コンパイルはArduino IDE 1.6.12を利用しています。
本スケッチの他に別途、下記のライブラリが必要です。
//
// USBKBD2PS2 USBキーボード PS/2キーボード変換 for IchigoJam
// 作成者 たま吉さん
// 作成日 2016/11/11
// 修正日 2016/11/19 OnKeyUp()のバグ修正
//
// このスケッチの利用には以下のハードウェア(シールド)が必要です.
// ・USB Host Shield
//
// このスケッチのコンパイルには以下のライブラリが必要です.
// ・PS/2デバイス ps2dev(ps2dev.zip) - an interface library for ps2 host
// PS2 mouse interface for Arduino(http://playground.arduino.cc/ComponentLib/Ps2mouse)
// ・USB_Host_Shield_2.0 (https://github.com/felis/USB_Host_Shield_2.0)
//
#include <ps2dev.h>
#include <hidboot.h>
#include <usbhub.h>
#define KB_CLK A4 // IchigoJamのKBD1に接続
#define KB_DATA A5 // IchigoJamのKBD2に接続
PS2dev keyboard(KB_CLK, KB_DATA); // PS/2デバイス
// HID Usage ID (0x04 - 0x67) => PS/2 スキャンコード 変換テーブル
const uint8_t keytable1[] PROGMEM = {
0x1C,0, 0x32,0, 0x21,0, 0x23,0, 0x24,0, 0x2B,0, 0x34,0, 0x33,0, // 0x04 - 0x0B
0x43,0, 0x3B,0, 0x42,0, 0x4B,0, 0x3A,0, 0x31,0, 0x44,0, 0x4D,0, // 0x0C - 0x13
0x15,0, 0x2D,0, 0x1B,0, 0x2C,0, 0x3C,0, 0x2A,0, 0x1D,0, 0x22,0, // 0x14 - 0x1B
0x35,0, 0x1A,0, 0x16,0, 0x1E,0, 0x26,0, 0x25,0, 0x2E,0, 0x36,0, // 0x1C - 0x23
0x3D,0, 0x3E,0, 0x46,0, 0x45,0, 0x5A,0, 0x76,0, 0x66,0, 0x0D,0, // 0x24 - 0x2B
0x29,0, 0x4E,0, 0x55,0, 0x54,0, 0x5B,0, 0x5D,0, 0x5D,0, 0x4C,0, // 0x2C - 0x33
0x52,0, 0x0E,0, 0x41,0, 0x49,0, 0x4A,0, 0x58,0, 0x05,0, 0x06,0, // 0x34 - 0x3B
0x04,0, 0x0C,0, 0x03,0, 0x0B,0, 0x83,0, 0x0A,0, 0x01,0, 0x09,0, // 0x3C - 0x43
0x78,0, 0x07,0, 0x00,2, 0x7E,0, 0x00,3, 0x70,1 ,0x6C,1, 0x7D,1, // 0x44 - 0x4B
0x71,1, 0x69,1, 0x7A,1, 0x74,1, 0x6B,1, 0x72,1 ,0x75,1, 0x77,0, // 0x4C - 0x53
0x4A,1, 0x7C,0, 0x7B,0, 0x79,0, 0x5A,1, 0x69,0, 0x72,0, 0x7A,0, // 0x54 - 0x5B
0x6B,0, 0x73,0, 0x74,0, 0x6C,0, 0x75,0, 0x7D,0, 0x70,0, 0x71,0, // 0x5C - 0x63
0x61,0, 0x2F,1, 0x37,1, 0x0F,0, // 0x64 - 0x67
};
// HID Usage ID (0x87 - 0x94) => PS/2 スキャンコード 変換テーブル
const uint8_t keytable2[] PROGMEM = {
0x51,0, 0x13,0, 0x6A,0, 0x64,0, 0x67,0, 0x27,0, 0x00,0, 0x00,0, // 0x87 - 0x8E
0x00,0, 0xF2,0, 0xF1,0, 0x63,0, 0x62,0, 0x5F,0, // 0x8F - 0x94
};
//
// HIDキーボード レポートパーサークラスの定義
//
class KbdRptParser : public KeyboardReportParser {
protected:
virtual void OnControlKeysChanged(uint8_t before, uint8_t after);
virtual void OnKeyDown(uint8_t mod, uint8_t key);
virtual void OnKeyUp(uint8_t mod, uint8_t key);
virtual void OnKeyPressed(uint8_t key) {};
};
//
// キー押しハンドラ
// 引数
// mod : コントロールキー状態
// key : HID Usage ID
//
void KbdRptParser::OnKeyDown(uint8_t mod, uint8_t key) {
Serial.print(F("DN [")); Serial.print(F("mod=")); Serial.print(mod,HEX);
Serial.print(F(" key=")); Serial.print(key,HEX); Serial.println(F("]"));
// HID Usage ID から PS/2 スキャンコード に変換
uint8_t code = 0;
uint8_t pre;
if (key >= 0x04 && key <= 0x67) {
code = pgm_read_byte(keytable1 + (key - 0x04)*2);
pre = pgm_read_byte(keytable1 + (key - 0x04)*2 + 1);
} else if (key >= 0x87 && key <= 0x94) {
code = pgm_read_byte(keytable2 + (key - 0x87)*2);
pre = pgm_read_byte(keytable2 + (key - 0x87)*2 + 1);
}
//Serial.print(F("PS2 [")); Serial.print(F("code=")); Serial.print(code,HEX);
//Serial.print(F(" pre=")); Serial.print(pre,DEC); Serial.println(F("]"));
// PS/2キーの発行
if (pre == 0) {
keyboard.write(code);
} else if (pre == 1) {
keyboard.write(0xE0);
keyboard.write(code);
} else if (pre == 2) { // Breakキー
keyboard.write(0xE0);
keyboard.write(0x12);
keyboard.write(0xE0);
keyboard.write(0x7C);
} else if (pre == 3) { // Pauseキー
keyboard.write(0xE1);
keyboard.write(0x14);
keyboard.write(0x77);
keyboard.write(0xE1);
keyboard.write(0xF0);
keyboard.write(0x14);
keyboard.write(0xF0);
keyboard.write(0x77);
}
}
//
// コントロールキー変更ハンドラ
// SHIFT、CTRL、ALT、GUI(Win)キーの処理を行う
// 引数 before : 変化前のコード USB Keyboard Reportの1バイト目
// after : 変化後のコード USB Keyboard Reportの1バイト目
//
void KbdRptParser::OnControlKeysChanged(uint8_t before, uint8_t after) {
MODIFIERKEYS beforeMod;
*((uint8_t*)&beforeMod) = before;
MODIFIERKEYS afterMod;
*((uint8_t*)&afterMod) = after;
// 左Ctrlキー
if (beforeMod.bmLeftCtrl != afterMod.bmLeftCtrl) {
if (afterMod.bmLeftCtrl) {
keyboard.write(0x14); // 左Ctrlキーを押した
} else {
keyboard.write(0xF0); // 左Ctrltキーを離した
keyboard.write(0x14);
}
}
// 左Shiftキー
if (beforeMod.bmLeftShift != afterMod.bmLeftShift) {
if (afterMod.bmLeftShift) {
keyboard.write(0x12); // 左Shiftキーを押した
} else {
keyboard.write(0xF0); // 左Shiftキーを離した
keyboard.write(0x12); //
}
}
// 左Altキー
if (beforeMod.bmLeftAlt != afterMod.bmLeftAlt) {
if (afterMod.bmLeftAlt) {
keyboard.write(0x11); // 左Altキーを押した
} else {
keyboard.write(0xF0); // 左Altキーを離した
keyboard.write(0x11); //
}
}
// 左GUIキー(Winキー)
if (beforeMod.bmLeftGUI != afterMod.bmLeftGUI) {
if (afterMod.bmLeftGUI) {
keyboard.write(0xE0); // 左GUIキーを押した
keyboard.write(0x1F);
} else {
keyboard.write(0xE0); // 左GUIキーを離した
keyboard.write(0xF0);
keyboard.write(0x1F);
}
}
// 右Ctrlキー
if (beforeMod.bmRightCtrl != afterMod.bmRightCtrl) {
if (afterMod.bmRightCtrl) {
keyboard.write(0xE0); // 右Ctrlキーを押した
keyboard.write(0x14);
} else {
keyboard.write(0xE0); // 右Ctrlキーを離した
keyboard.write(0xF0);
keyboard.write(0x14);
}
}
// 右Shiftキー
if (beforeMod.bmRightShift != afterMod.bmRightShift) {
if (afterMod.bmRightShift) {
keyboard.write(0x59); // 右Shiftキーを押した
} else {
keyboard.write(0xF0); // 右Shiftキーを離した
keyboard.write(0x59);
}
}
// 右Altキー
if (beforeMod.bmRightAlt != afterMod.bmRightAlt) {
if (afterMod.bmRightAlt) {
keyboard.write(0xE0); // 右Altキーを押した
keyboard.write(0x11);
} else {
keyboard.write(0xE0); // 右Altキーを離した
keyboard.write(0xF0);
keyboard.write(0x11);
};
}
// 右GUIキー
if (beforeMod.bmRightGUI != afterMod.bmRightGUI) {
if (afterMod.bmRightGUI) {
keyboard.write(0xE0); // 右GUIキーを押した
keyboard.write(0x27);
} else {
keyboard.write(0xE0); // 右GUIキーを離した
keyboard.write(0xF0);
keyboard.write(0x27);
}
}
}
//
// キー離し ハンドラ
// 引数
// mod : コントロールキー状態
// key : HID Usage ID
//
void KbdRptParser::OnKeyUp(uint8_t mod, uint8_t key) {
// HID Usage ID から PS/2 スキャンコード に変換
uint8_t code = 0;
uint8_t pre = 0;
if (key >= 0x04 && key <= 0x67) {
code = pgm_read_byte(keytable1 + (key - 0x04)*2);
pre = pgm_read_byte(keytable1 + (key - 0x04)*2 + 1);
} else if (key >= 0x87 && key <= 0x94) {
code = pgm_read_byte(keytable2 + (key - 0x87)*2);
pre = pgm_read_byte(keytable2 + (key - 0x87)*2 + 1);
}
// PS/2キーの発行
if (pre == 0) {
keyboard.write(0xF0);
keyboard.write(code);
} else if (pre == 1) {
keyboard.write(0xE0);
keyboard.write(0xF0);
keyboard.write(code);
} else if (pre == 2) {
keyboard.write(0xE0);
keyboard.write(0xF0);
keyboard.write(0x7C);
keyboard.write(0xE0);
keyboard.write(0xF0);
keyboard.write(0x12);
}
}
int enabled =0;
void ack() {
while(keyboard.write(0xFA));
}
// ホストから送信されるコマンドの処理
int keyboardcommand(int command) {
unsigned char val;
switch (command) {
case 0xFF: ack();// Reset: キーボードリセットコマンド。正しく受け取った場合ACKを返す。
while(keyboard.write(0xAA)!=0);
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;
}
}
USB Usb;
//USBHub Hub(&Usb);
HIDBoot<USB_HID_PROTOCOL_KEYBOARD> HidKeyboard(&Usb);
uint32_t next_time;
KbdRptParser Prs;
void setup() {
Serial.begin( 115200 );
while(keyboard.write(0xAA)!=0);
if (Usb.Init() == -1)
Serial.println(F("OSC did not start."));
// USBデバイス情報の取得
delay(200);
while(1) {
Usb.Task();
if ( Usb.getUsbTaskState() == USB_STATE_RUNNING ) {
USB_DEVICE_DESCRIPTOR buf;
byte rcode;
rcode = Usb.getDevDescr( 1 , 0, 0x12, ( uint8_t *)&buf );
if (rcode) {
Serial.println(F("USB Devide Error."));
} else {
Serial.print(F("class:")); Serial.println(buf.bDeviceClass, HEX);
Serial.print(F("sub class:")); Serial.println(buf.bDeviceSubClass, HEX);
}
break;
}
}
//delay( 200 );
next_time = millis() + 5000;
HidKeyboard.SetReportParser(0, &Prs);
Serial.println(F("Start."));
}
void loop() {
unsigned char c; // ホストからの送信データ
if( (digitalRead(KB_CLK)==LOW) || (digitalRead(KB_DATA) == LOW)) {
while(keyboard.read(&c)) ;
keyboardcommand(c);
}
Usb.Task();
}
スケッチ的には実は大したことやってないです。ライブラリが大半の処理をやってくれます。
2つのプロトコル(USB HIDとPS/2)を繋げただけです。
USB HIDインターフェスのキー入力情報(HID Usage ID)をPS/2のキー入力に変換する
部分が本スケッチのかなめです。
この変換にはテーブル(keytable1[]、keytable2[])を用いて行っています。
ただし、CTRL、Shift、ALT、GUIキーはHID Usage IDではなくビット(ON/OFF)情報での
取得となるので、KbdRptParser::OnControlKeysChanged()で別処理を行っています。
スケッチはBluetooth版と類似点が多いです。なんとか統合したいと思います。
最終的にはケースに収納して、USBコネクタに接続して使いたいのですが..
とりあえず、手持ちの発泡スチロールボードで作って見たけど...
厚さが5mmあるのでデカくなった.. ダサい.. Otz 3Dプリンター欲しいですね。
aitendoの「プラケースwith基板 [2P-E44.5]」が使えるかもしれないので注文しました。
(厚さがダメかも...)

スケッチはArduino UNO+USB HOSTシールドでも動くことを確認しました。
IchigoJamの入力端子KBD1、KBD2には5Vが入力されますが、IchigoJam(LPC1114)は、
5Vトレラントなので問題なしです。
比べるとArduino UNOだとデカいですね。
最近のコメント