フォト
2017年3月
      1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31  
無料ブログはココログ

« Arduino Pro mini Pro Mini用USB HOSTシールドの調査 | トップページ | IchigoJamでLogicool製ワイヤレスキーボードを利用する(USB・PS/2変換) »

2016年11月10日 (木)

IchigoJamでポケモンキーボードを使ってみる(Bluetooth・PS/2変換)

前回調査したArduino pro mini+USB Hostシールドを使って、
IchigoJamで「ポケモンキーボード(Bluetoothキーボード)」を利用出来るようにしました。

IchigoJamからはPS/2キーボードと認識されて利用出来ます。
ポケモンキーボードの全てのキーが利用出来ます。

Dscn6077

IchigoJamから電源供給しています。
キーボードからの入力情報はPS/2キーボード用の端子 KBD1、KBD2に接続しています。
arduino      IchigoJam
  D2     =>     KBD2
  D3     =>     KBD1
  VCC   =>    VCC
  GND   =>    GND

Dscn6084

Dscn6078

動作の様子


システム構成的な図

If
                          ※図のGND => VCCは記載ミスです GND => GND が正しいです

処理としては、図のようなハードウェア&ソフトウェア構成となっています。
Bluetooth HIDのキーボード入力データをPS/2キーボードデータと信号に変換して
IchigoJamのPS/2キーボードとして利用出来るようにしています。

起動後のキーボードのキーを押しまくっているとペアリングが行われます。

利用しているハードウェア

1)Arduino pro mini 3.3V版(上)とUSB Hostシールド

  Dscn6030

 純正品は高いので2つともクローン製品を使っています。
  合計で1000円くらいです。

2)USB Bluetoothドングル
   BT-MicroEDR2X
   Bluetooth Ver.2.1+EDR対応 Micro サイズ USBアダプタ (Class2/10m)

   Dscn6080

   かなり前に買って放置していたものです。

   これ以外にElecom製のLBT-UAN05C2でも動作しました。   

   Dscn6081

スケッチ

スケッチを公開します。
   スケッチのダウンロード btkbd2ps2.zip (3.2K)

   ※ 2016/11/18 追記 最新版はgithubにて公開しています。
       https://github.com/Tamakichi/Arduino_USBToPS2

コンパイルはArduino IDE 1.6.12を利用しています。

本スケッチの他に別途、下記のライブラリが必要です。

  ・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

スケッチの全ソース
//
// btkbd2ps2 Bluetoothキーボード PS/2キーボード変換 for IchigoJam
// 作成者 たま吉さん
// 作成日 2016/11/09
//
// このスケッチの利用には以下のハードウェア(シールド)が必要です.
//  ・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 <BTHID.h>
#include <usbhub.h>

PS2dev keyboard(3,2);   // PS/2デバイス

// HID Usage ID (0x04 - 0x43) => PS/2 スキャンコード 変換テーブル
const uint8_t keytable1[64] PROGMEM = {
  0x1C,0x32,0x21,0x23,0x24,0x2B,0x34,0x33,0x43,0x3B,0x42,0x4B,0x3A,0x31,0x44,0x4D,
  0x15,0x2D,0x1B,0x2C,0x3C,0x2A,0x1D,0x22,0x35,0x1A,0x16,0x1E,0x26,0x25,0x2E,0x36,
  0x3D,0x3E,0x46,0x45,0x5A,0x76,0x66,0x0D,0x29,0x4E,0x55,0x54,0x5B,0x5D,0x5D,0x4C,
  0x52,0x0E,0x41,0x49,0x4A,0x58,0x05,0x06,0x04,0x0C,0x03,0x0B,0x83,0x0A,0x01,0x09,
};

// HID Usage ID (0x4A - 0x52) => PS/2 スキャンコード 変換テーブル
const uint8_t keytable2[9] PROGMEM = {
  0x6C,0x7D,0x71,0x69,0x7A,0x74,0x6B,0x72,0x75,
};

//
// HIDキーボード レポートパーサークラスの定義
//
class KbdRptParser : public KeyboardReportParser {
  protected:
    virtual uint8_t HandleLockingKeys(USBHID *hid, uint8_t key);
    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) {};
};

uint8_t KbdRptParser::HandleLockingKeys(USBHID *hid, uint8_t key) {
  uint8_t old_keys = kbdLockingKeys.bLeds;
  
  switch (key) {
    case UHS_HID_BOOT_KEY_NUM_LOCK:
      kbdLockingKeys.kbdLeds.bmNumLock = ~kbdLockingKeys.kbdLeds.bmNumLock;
      break;
    case UHS_HID_BOOT_KEY_CAPS_LOCK:
      kbdLockingKeys.kbdLeds.bmCapsLock = ~kbdLockingKeys.kbdLeds.bmCapsLock;
      break;
    case UHS_HID_BOOT_KEY_SCROLL_LOCK:
      kbdLockingKeys.kbdLeds.bmScrollLock = ~kbdLockingKeys.kbdLeds.bmScrollLock;
      break;
  }
  if (old_keys != kbdLockingKeys.bLeds && hid) {
    BTHID *pBTHID = reinterpret_cast<BTHID *> (hid); // A cast the other way around is done in BTHID.cpp
    pBTHID->setLeds(kbdLockingKeys.bLeds); // Update the LEDs on the keyboard
  }
  return 0;
}

//
// キー押しハンドラ
// 引数
//  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 = 0;
  if (key >= 0x04 && key <= 0x43) {
    code = pgm_read_byte(keytable1 + key - 0x04);
  } else if (key >= 0x4A && key <= 0x52) {
    code = pgm_read_byte(keytable2 + key - 0x4A);    
    pre = 0xE0;
  } else if (key == 0x87) {
    code = 0x51;    
  } else if (key == 0x89) {
    code = 0x6A;    
  }

  // PS/2キーの発行
  if (pre) keyboard.write(pre);
  keyboard.write(code);
}

//
// コントロールキー変更ハンドラ
// 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 <= 0x43) { // keytable1を使った変換
    code = pgm_read_byte(keytable1 + key - 0x04);
  } else if (key >= 0x4A && key <= 0x52) { // keytable2を使った変換
    code = pgm_read_byte(keytable2 + key - 0x4A);    
    pre = 0xE0;
  } else if (key == 0x87) {
    code = 0x51;    
  } else if (key == 0x89) {
    code = 0x6A;    
  }

  // PS/2キーの発行
  if (pre) keyboard.write(pre);
  keyboard.write(0xF0);  
  keyboard.write(code);
};

// HIDマウス レポートパーサークラスの定義
class MouseRptParser : public MouseReportParser {
  protected:
    virtual void OnMouseMove(MOUSEINFO *mi) {};
    virtual void OnLeftButtonUp(MOUSEINFO *mi){};
    virtual void OnLeftButtonDown(MOUSEINFO *mi){};
    virtual void OnRightButtonUp(MOUSEINFO *mi) {};
    virtual void OnRightButtonDown(MOUSEINFO *mi){};
    virtual void OnMiddleButtonUp(MOUSEINFO *mi){};
    virtual void OnMiddleButtonDown(MOUSEINFO *mi){};
};

USB   Usb;
BTD   Btd(&Usb);
BTHID bthid(&Btd, PAIR, "0000");
BTHID hid(&Btd);

KbdRptParser keyboardPrs;
MouseRptParser mousePrs;
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;
  }
}

void setup() {
  Serial.begin(115200);
  while(keyboard.write(0xAA)!=0);
  
  if (Usb.Init() == -1) {
    Serial.print(F("\r\nOSC did not start"));
    while (1); // Halt
  }

  bthid.SetReportParser(KEYBOARD_PARSER_ID, &keyboardPrs);
  bthid.SetReportParser(MOUSE_PARSER_ID, &mousePrs);
  bthid.setProtocolMode(USB_HID_BOOT_PROTOCOL); // Boot Protocol Mode
  bthid.setProtocolMode(HID_RPT_PROTOCOL); // Report Protocol Mode
  Serial.println(F("Start."));
}

void loop() {
  unsigned char c;  // ホストからの送信データ
  if( (digitalRead(3)==LOW) || (digitalRead(2) == LOW)) {
    while(keyboard.read(&c)) ;
    keyboardcommand(c);
  }  
  Usb.Task();
}

ポケモンキーボード以外のBluetoothキーボードも利用出来ると思います。
ただし、現状はポケモンキーボードに無いキーは未対応です。
カタカナ/ひらがなキー、無変換、変換キー、テンキーは未対応です。

サイズ的に中央に配置出来そうです。
最終的にはこの形態で利用出来るよう、やってみます。

Dscn6083

2016/11/10 追記

取りあえず、ブレッドボードを付かない形態に変更しました。
ちょっと使いやすくなりました。

Dscn6093

参考文献

・USB HID to PS/2 Scan Code Translation Table
  http://download.microsoft.com/download/1/6/1/161ba512-40e2-4cc9-843a-923143f3456c/translate.pdf
・USBキーボードのキーコード
http://www2d.biglobe.ne.jp/~msyk/keyboard/layout/usbkeycode.html
・Device Class Definition for Human Interface Devices (HID)
  http://www.usb.org/developers/hidpage/HID1_11.pdf
・HID Usage Tables
  http://www.usb.org/developers/hidpage/Hut1_12v2.pdf
・NXP USB and PS/2 Multimedia Keyboard Interface
 http://www.nxp.com/files/microcontrollers/doc/ref_manual/DRM014.pdf

・Sazanami Online PS/2 インターフェイスの研究
  http://hp.vector.co.jp/authors/VA037406/html/ps2interface.htm
・PS/2キーボードインターフェース
  http://www.technoveins.co.jp/technical/keyboard/
・O-Family JIS配列 PS/2 キーボード スキャンコード表
  http://www.ne.jp/asahi/shared/o-family/ElecRoom/AVRMCOM/PS2_RS232C/KeyCordList.pdf


関連記事
IchigoJamでポケモンキーボードを使ってみる (16/11/10) この記事
arduinoでPS/2キーボードのエミュレーションを行う その2 (15/04/23)
arduinoでPS/2キーボードのエミュレーションを行う (15/04/22) 

« Arduino Pro mini Pro Mini用USB HOSTシールドの調査 | トップページ | IchigoJamでLogicool製ワイヤレスキーボードを利用する(USB・PS/2変換) »

arduino」カテゴリの記事

IchigoJam」カテゴリの記事

コメント

コメントを書く

(ウェブ上には掲載しません)

トラックバック

この記事のトラックバックURL:
http://app.cocolog-nifty.com/t/trackback/571408/64469082

この記事へのトラックバック一覧です: IchigoJamでポケモンキーボードを使ってみる(Bluetooth・PS/2変換):

« Arduino Pro mini Pro Mini用USB HOSTシールドの調査 | トップページ | IchigoJamでLogicool製ワイヤレスキーボードを利用する(USB・PS/2変換) »