フォト
2025年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          
無料ブログはココログ

« 2016年10月 | トップページ | 2016年12月 »

2016年11月の8件の記事

2016年11月29日 (火)

Arduino M0互換機 Crowduino M0-SDを使ってみる

Aliexpressの特売日11月11日に衝動買いしたARM搭載のArduino M0互換機が到着。
製品名はCrowduino M0-SDというやつです。

Dscn6192

ELECROW製で箱入り、$11.55で購入しました。
オリジナル製品とちょっと異なり、microSDカードスロットがボード上にあります。

Dscn6193

Arduino Unoに比べると、プログラム領域が広いのが魅力です。
フラッシュメモリ 256kバイト、SRAM32kバイトあります。
ArduinoのARM版にはArduino Dueもありますが、こちらはコンパクトで使い勝手が良さそうです。

Arduino M0の売りとしては10ビットのDAコンバーター(DAC)が1チャンネル搭載していることです。
取りあえず、そのDACを利用した「Arduino Zero Simple audio player」を動かしてみました。

Dscn6176

開発環境はArduino.org版のArduino IDE 1.7.11を使います。

Ide

CC版と比べると使いにくいです。メニューの「ツール」をクリックすると、メニューが
表示されるまで3秒くらいかかります。

書込みも私のPCでドライバーが正しく動作していないようです。
試行錯誤して、IDEが「マイコンボードに書き込んでいます...」を表示したタイミングで
リセットボタンを押すと確実に書き込めました。

  追記:公式サイトにこの方法での書き込み案内がありました
    Rescue guide for microcontroller ATSAMD21G18A

   Writing

WAVファイルを作成し、SDカードに突っ込んでスケッチを書いて早速実行。
音は出たものの、音程がおかしい。
PCMで鳴らしているような感じでクリアではありません。
(Arduino Zero用なので正常に動かないのかも)

比較するためArduino Dueにて「Simple Audio Player」を試してみました。

Dscn6191

Arduino Dueでは極めてクリアな音で再生出来ました。
Arduino DueのDACは12ビットの分解能なので音が良いのは当然ですが差がありすぎます。
このArduino M0で音がおかしい件は今後調査していきたいと思います。

さて、このArduino M0、人気も無くてユーザーが少ないのか情報があまりありません。

使い勝手としてArduino UNOと異なるところも多いです。
USB経由の通信は、SerialUSBオブジェクトを使います。ピン端子のTXD、RXDは
Serual5オブジェクトを使います。

SerialUSBを使ったデバッグ情報出力も、なかなかうまくいかず、
下記の情報を見つけ、delay(6000)を入れると出力することが出来ました。
  ・ Arduino.org - Arduino M0 Serial ports and AD converter

USB経由のシリアル通信のイニシャル処理は次のようにします。

void setup() {
  SerialUSB.begin(115200);
  delay(6000);

また、各I/Oピンに流せる電流が7mAまでなのも注意が必要です。
サーボモーターの直接駆動は無理ですね。


参考文献
・Arduino.org Arduino M0 http://www.arduino.org/products/boards/arduino-m0
・Elecrow Crowduino M0-SD http://www.elecrow.com/wiki/index.php?title=Crowduino_M0-SD
・Arduino Zero Simple audio player https://www.arduino.cc/en/Tutorial/SimpleAudioPlayerZero

・なんでも独り言 - Arduino M0をもっと使いこなす http://ehbtj.com/electronics/arduino-m0-hacks/
・エレキジャック フィシカル・コンピューティング ArduinoM0を使ってみる(1)~(8)
ArduinoM0を使ってみる(1) http://www.eleki-jack.com/FC/2015/08/arduino-m01.html
ArduinoM0を使ってみる(2) http://www.eleki-jack.com/FC/2015/09/arduino-m02.html
ArduinoM0を使ってみる(3) http://www.eleki-jack.com/FC/2015/09/arduino-m03.html
ArduinoM0を使ってみる(4) http://www.eleki-jack.com/FC/2015/10/arduinom04.html
ArduinoM0を使ってみる(5) http://www.eleki-jack.com/FC/2015/10/arduinom05.html
ArduinoM0を使ってみる(6) http://www.eleki-jack.com/FC/2015/11/arduinom06.html
ArduinoM0を使ってみる(7) http://www.eleki-jack.com/FC/2015/11/arduinom06-1.html
ArduinoM0を使ってみる(8) http://www.eleki-jack.com/FC/2015/12/arduinom08.html


2016年11月19日 (土)

IchigoJam用 USBキーボード変換 Bluetooth・USBキーボードの両対応できました

現在取り組んでいる、USB Hostシールドを使った USB - PS/2変換、
Bluetooth版USB HID版が別スケッチだったのですが1つに統合して両対応にしました。

Dscn6165

Bluetoothドングル、有線・無線USBキーボードを自動認識して利用出来ます。
構成的には次のような感じです。

System

更にキーを押し続けた場合のキーリピート機能を追加しました。
これでほぼ完全にPS/2キーボードとして利用出来ます。

USBハブにも対応したのですが、現状は動かない場合がありおまけレベルです。

Arduino Uno環境では、BluetoothドングルとUSB HIDキーボード(有線・無線)が同時利用出来ました。
(同じ種類のデバイスの同時利用は出来ません。 例:USB HIDキーボード2台同時)

Dscn6162

Arduino pro miniではUSBの電流供給能力的に無理ですが..

スケッチ


次のような感じです。まだまだこれから修正を予定しています。
  ダウンロード USBKBD2PS2_v2.zip (5.4K)

最新版はgithubにて公開しています。
       https://github.com/Tamakichi/Arduino_USBToPS2
//
// USBKBD2PS2_v2 USBキーボード PS/2キーボード変換 for IchigoJam v2
// 作成者 たま吉さん
// 作成日 2016/11/11 , 最終修正日 2016/11/18
// 修正 2016/11/18 Bluetooth HIDとUSB HIDのスケッチの統合,キーリピート機能対応
//
// このスケッチの利用には以下のハードウェア(シールド)が必要です.
//  ・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)
//  ・MsTimer2 (http://playground.arduino.cc/Main/MsTimer2)
//

//******* 用途により、下記の定義を設定して下さい ***************************
#define MYDEBUG      0  // 0:デバッグ情報出力なし 1:デバッグ情報出力あり 
#define KB_CLK      A4  // PS/2 CLK  IchigoJamのKBD1に接続
#define KB_DATA     A5  // PS/2 DATA IchigoJamのKBD2に接続
//**************************************************************************

#include <ps2dev.h>
#include <BTHID.h>
#include <hidboot.h>
#include <usbhub.h>
#include <MsTimer2.h>

#define LOBYTE(x) ((char*)(&(x)))[0]
#define HIBYTE(x) ((char*)(&(x)))[1]

// キーリピートの定義
#define REPEATTIME      5   // キーを押し続けて、REP_INTERVALxREPEATTIMEmsec後にリピート開始
#define EMPTY           0   // リピート管理テーブルが空状態
#define MAXKEYENTRY     6   // リピート管理テーブルサイズ
#define REP_INTERVAL    100 // リピート間隔 150msec

#define MS_SIKIICHI     10

uint8_t keyentry[MAXKEYENTRY];    // リピート管理テーブル
uint8_t repeatWait[MAXKEYENTRY];  // リピート開始待ち管理テーブル
uint8_t enabled =0;               // PS/2 ホスト送信可能状態
PS2dev keyboard(KB_CLK, KB_DATA); // PS/2デバイス

//
// 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) {};
};

// 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){};
};
*/

class MouseRptParser : public MouseReportParser {
  protected:
    void OnMouseMove(MOUSEINFO *mi);
    void OnLeftButtonUp(MOUSEINFO *mi);
    void OnLeftButtonDown(MOUSEINFO *mi);
    void OnRightButtonUp(MOUSEINFO *mi);
    void OnRightButtonDown(MOUSEINFO *mi);
    void OnMiddleButtonUp(MOUSEINFO *mi);
    void OnMiddleButtonDown(MOUSEINFO *mi);
};

void MouseRptParser::OnMouseMove(MOUSEINFO *mi){
/*  
  Serial.print("dx=");
  Serial.print(mi->dX, DEC);
  Serial.print(" dy=");
  Serial.println(mi->dY, DEC);
*/
  if (mi->dX > MS_SIKIICHI) {
    keyboard.write(0xE0);
    keyboard.write(0x74);
    delay(50); 
    keyboard.write(0xE0);
    keyboard.write(0xF0);
    keyboard.write(0x74);
  } else if (mi->dX < -MS_SIKIICHI) {
    keyboard.write(0xE0);
    keyboard.write(0x6B);    
    delay(50); 
    keyboard.write(0xE0);
    keyboard.write(0xF0);
    keyboard.write(0x6B);    
  }
  if (mi->dY > MS_SIKIICHI) {
    keyboard.write(0xE0);
    keyboard.write(0x72);        
    delay(50); 
    keyboard.write(0xE0);
    keyboard.write(0xF0);
    keyboard.write(0x72);        
  } else if (mi->dY < -MS_SIKIICHI) {
    keyboard.write(0xE0);
    keyboard.write(0x75);        
    delay(50); 
    keyboard.write(0xE0);
    keyboard.write(0xF0);
    keyboard.write(0x75);        
  }
}

void MouseRptParser::OnLeftButtonUp  (MOUSEINFO *mi){
  Serial.println("L Butt Up");
}

void MouseRptParser::OnLeftButtonDown (MOUSEINFO *mi){
  Serial.println("L Butt Dn");
}

void MouseRptParser::OnRightButtonUp  (MOUSEINFO *mi){
  Serial.println("R Butt Up");
}

void MouseRptParser::OnRightButtonDown  (MOUSEINFO *mi){
  Serial.println("R Butt Dn");
}

void MouseRptParser::OnMiddleButtonUp (MOUSEINFO *mi){
  Serial.println("M Butt Up");
}

void MouseRptParser::OnMiddleButtonDown (MOUSEINFO *mi){
  Serial.println("M Butt Dn");
}

USB      Usb;
USBHub  Hub1(&Usb);
USBHub  Hub2(&Usb);
USBHub  Hub3(&Usb);
USBHub  Hub4(&Usb);

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

KbdRptParser keyboardPrs;
MouseRptParser mousePrs;

HIDBoot<USB_HID_PROTOCOL_KEYBOARD>    HidKeyboard(&Usb);
HIDBoot<USB_HID_PROTOCOL_MOUSE>    HidMouse(&Usb);

uint8_t classType = 0;      
uint8_t subClassType = 0;
uint32_t next_time;

// 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
};

// PS/2 ホストにack送信
void ack() {
  while(keyboard.write(0xFA));
}

// PS/2 ホストから送信されるコマンドの処理
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 claerKeyEntry() {
 for (uint8_t i=0; i <MAXKEYENTRY; i++)
    keyentry[i] = EMPTY;
}

// リピート管理テーブルにキーを追加
void addKey(uint8_t key) {
 for (uint8_t i=0; i <MAXKEYENTRY; i++) {
  if (keyentry[i] == EMPTY) {
    keyentry[i] = key;  
    repeatWait[i] = REPEATTIME;
    break;
  }
 }
}

// リピート管理テーブルからキーを削除
void delKey(uint8_t key) {
 for (uint8_t i=0; i <MAXKEYENTRY; i++) {
  if (keyentry[i] == key) {
    keyentry[i] = EMPTY;
    break;
  }
 }  
}

//
// PS/2 makeコード送信
// 引数 key(IN) HID Usage ID
//
uint8_t sendKeyMake(uint8_t key) {
  // HID Usage ID から PS/2 スキャンコード に変換
  uint8_t code = 0;
  uint8_t pre = 0xff;

  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(code);
    return 1;
  } else if (pre == 1) {
    keyboard.write(0xE0);
    keyboard.write(code);    
  } else if (pre == 2) { // PrintScreenキー
    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);
  }
  return code;
}

//
// PS/2 breakコード送信
// 引数 key(IN) HID Usage ID
//
uint8_t sendKeyBreak(uint8_t key) {
  // HID Usage ID から PS/2 スキャンコード に変換
  uint8_t code = 0;
  uint8_t pre = 0xff;
 
  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);
  }
  return code;
}

// リピート処理(タイマー割り込み処理から呼ばれる)
void sendRepeat() {
  // HID Usage ID から PS/2 スキャンコード に変換
  uint8_t code = 0;
  uint8_t pre, key;
  
  for (uint8_t i=0; i < MAXKEYENTRY; i++) {
    if (keyentry[i] != EMPTY) {
      key = keyentry[i]; 
      if (repeatWait[i] == 0) {
        sendKeyMake(key);
    } else {
        repeatWait[i]--;          
      }
    }
  }
}

//
// ロックキー(NumLock/CAPSLock/ScrollLock)ハンドラ
//

uint8_t KbdRptParser::HandleLockingKeys(USBHID *hid, uint8_t key) {
  if (classType == USB_CLASS_WIRELESS_CTRL) {
    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
    }
  } else {
    return KeyboardReportParser::HandleLockingKeys(hid, key);   
  }
  return 0;
}


//
// キー押しハンドラ
// 引数
//  mod : コントロールキー状態
//  key : HID Usage ID 
//
void KbdRptParser::OnKeyDown(uint8_t mod, uint8_t key) {
  MsTimer2::stop();
#if MYDEBUG==1
  Serial.print(F("DN ["));  Serial.print(F("mod="));  Serial.print(mod,HEX);
  Serial.print(F(" key="));  Serial.print(key,HEX);  Serial.println(F("]"));
#endif
  if (sendKeyMake(key))
    addKey(key);
  MsTimer2::start();
}

//
// コントロールキー変更ハンドラ
// 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) {
  MsTimer2::stop();
#if MYDEBUG==1
  Serial.print(F("UP ["));  Serial.print(F("mod="));  Serial.print(mod,HEX);
  Serial.print(F(" key="));  Serial.print(key,HEX);  Serial.println(F("]"));
#endif
  if (sendKeyBreak(key)) // HID Usage ID から PS/2 スキャンコード に変換  
    delKey(key);  
  MsTimer2::start();
}

//
// インターフェースクラスの取得
//
uint8_t getIntClass(byte& intclass, byte& intSubClass ) {
  uint8_t buf[ 256 ];
  uint8_t* buf_ptr = buf;
  byte rcode;
  byte descr_length;
  byte descr_type;
  unsigned int total_length;

  uint8_t flgFound = 0;
  
  //デスクプリタトータルサイズの取得
  rcode = Usb.getConfDescr( 1, 0, 4, 0, buf );
  LOBYTE( total_length ) = buf[ 2 ]; HIBYTE( total_length ) = buf[ 3 ];
  if ( total_length > 256 ) {
    total_length = 256;
  }
  
  rcode = Usb.getConfDescr( 1, 0, total_length, 0, buf ); 
  while ( buf_ptr < buf + total_length ) { 
    descr_length = *( buf_ptr );
    descr_type = *( buf_ptr + 1 );

    if (descr_type == USB_DESCRIPTOR_INTERFACE) {
      // 最初のインタフェースの取得
      USB_INTERFACE_DESCRIPTOR* intf_ptr = ( USB_INTERFACE_DESCRIPTOR* )buf_ptr;  
      intclass = intf_ptr->bInterfaceClass;
      intSubClass = intf_ptr->bInterfaceSubClass;
      flgFound = 1;
      break;
    }
    buf_ptr = ( buf_ptr + descr_length );    //advance buffer pointer
  }
  return ( flgFound );
}

void setup() {
  Serial.begin( 115200 );
  while(keyboard.write(0xAA)!=0);
  if (Usb.Init() == -1) {
    Serial.println(F("OSC did not start."));
    while (1); // Halt    
  }
  
  next_time = millis() + 5000;

  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
  //HidKeyboard.SetReportParser(0, &Prs);
  HidKeyboard.SetReportParser(0, &keyboardPrs);
  HidMouse.SetReportParser(0, &mousePrs);

  claerKeyEntry();
  MsTimer2::set(REP_INTERVAL, sendRepeat); 
  MsTimer2::start();
    
  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();
  if ( (classType == 0) && (Usb.getUsbTaskState() == USB_STATE_RUNNING) )  {  
    // デバイスクラス情報の取得
    getIntClass(classType, subClassType) ;
#if MYDEBUG == 1  
    Serial.print(F("class="));  Serial.println(classType, HEX);
    Serial.print(F("subclass="));  Serial.println(subClassType, HEX);
#endif
  }
  
#if MYDEBUG == 1  
  static uint8_t prevSts = 0xFF;
  if (Usb.getUsbTaskState() != prevSts) {
    prevSts = Usb.getUsbTaskState();
    Serial.print(F("sts="));
    Serial.println(prevSts, HEX);
  }
  if (Serial.available()) {
    Serial.read();
    for (uint8_t i=-0; i < MAXKEYENTRY; i++) {
      Serial.print(F("keyentry["));
      Serial.print(i,DEC);
      Serial.print(F("]="));
      Serial.println(keyentry[i],HEX);
    }
  }
#endif
}

現状、試行錯誤中の中途半端なマウス対応のコードを含んでいます。
削除してゲームパッド対応を予定しています。

2016年11月16日 (水)

IchigoJamでLogicool製ワイヤレスキーボードを利用する(USB・PS/2変換) その2

前回からの続きです。

ケースを製作しました。
aitendoのUSBケースは結局、小さすぎて使えませんでした。
事前に作成していたダサいケースを加工して利用することにしました。

とりあえず、表面を平らにして表面に何か貼ろうと構想。

Dscn6138


ホームセンターで購入したコルクシールを貼ることにしました。
はさみで切って貼ることが出来ます。

Dscn6157

貼り付けると、いい感じになりました。
適当なサイズに切って、貼った後にはさみではみ出た部分をカットしました。
中が発泡スチロールボードとはもう分かりません。

Dscn6151

中に入れるモジュール

IchigoJamのUSBポート経由でVCC、GND、KBD1、KBD2を接続します。
電源はUSB 5V(VCC)はArduino pro mini のRAW端子に接続することで3.3Vに変換して
利用しています。

USBケーブルは百円ショップのUSB延長ケーブルを利用しました。
最初太いケーブルをチョイスしたのですが、固くて曲げにくいため細いケーブルに変更しました。

Dscn6150

ケースの中に入れるとこんな感じです。
いい感じ! 上出来です。

Dscn6152


IchogoJamに接続の様子

ケースに使った発泡スチロールボードが5mmと厚いため、一回り大きくなりましたが、
良しとします。

Dscn6156

IchigoJam周辺

邪魔にならず、IchigoJamの全ての端子が利用出来るのでこの形態で正解でした。

Dscn6154


最後に

ケースに収納すると可搬性も良くなり実用性が増しますね。
発泡スチロールボードを使ったケース作成も意外といいです。
カッターナイフでお手軽に作成できます。

IchigoJamのケースも作ってみような?

2016年11月13日 (日)

IchigoJamでLogicool製ワイヤレスキーボードを利用する(USB・PS/2変換)

前回のポケモンキーボードのスケッチを修正して一般的なUSBキーボードに対応しました。
USBワイヤレスキーボードをPS/2インターフェースに変換します。


利用したキーボードはLogicool  K270です。
以前 amazonで1,290円で購入したのですが、今は値上がりしてますね。

  Dscn6124

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端子がボード上の変なところにあります。
これはこれで、場合によっては接続しやすいです。

  Dscn6121

動いている様子

 

システム構成的な図

Photo

USB HIDに対応しています。有線キーボードも利用出来ます。

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


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

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

  Dscn6127

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を利用しています。

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

  ・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

スケッチの全ソース
//
// 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プリンター欲しいですね。

Dscn6129

aitendoの「プラケースwith基板 [2P-E44.5]」が使えるかもしれないので注文しました。
(厚さがダメかも...)

Photo_2



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

比べるとArduino UNOだとデカいですね。

Dscn6132


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) 

2016年11月 7日 (月)

Arduino Pro mini Pro Mini用USB HOSTシールドの調査

Arduino pro mini用のUSB Hostシールドを見つけ、入手し動作確認しました。

購入時は$5.78でしたが、現時点では$7.60に値上がりしています。

Mini USB Host Shield Support Google ADK Android For Arduino UNO MEGA Duemilanove
Aliexpress


実際の製品


Arduino Pro mini が上(下でもよかも?)に乗っかるサイズです。
この製品はクローン製品です。オリジナルはCircuits製です。

Dscn6030

オリジナル製品は、本家サイト関連の情報Circuits@Homeを見ると下図のような感じです。

Board
(画像はCircuits@Homeより拝借しています)

オリジナル製品は左上部の5V供給対応のためのジャンパーがありますが、
クローン製品にはそれがありません。

オリジナル、クローン製品とも3.3V稼働がデフォルトです。
5V対応するには、ジャンパーの設定を行い、5V電源をVBUSに別途供給する配線が
必要となります。クローン製品にはそれが出来ないことに注意です。


USBの規格的には、VBASの電圧は4.4V以上との規定があります。
なんで、こんな仕様にしているんですかね。

ですので、このモジュールを使うには3.3Vで稼働するUSB機器に限定します。
ちょっと厳しい条件ですね。


このオリジナル製品に関する情報
  ・Circuits@Home
  ・felis/USB_Host_Shield_2.0 (GitHub)
  ・TKJ Electronics
  ・USB Host Shield 2.0


まずはArduino Unoで利用してみる


Arduino pro miniにはんだ付けして使う前にArduino Unoにて動作確認しました。

上にArduino pro miniを乗せられるようにしつつ、ブレッドボードにてArduino Unoにて
利用できるよう、ピンヘッダを付けました。

Dscn6039

BlueThoothキーボードを使ってみる

pqi製のBluetootドングルを使ってみます。
秋葉原で購入した安価キーボードとペアリングして使ってみます。

Dscn6040

ライブラリをfelis/USB_Host_Shield_2.0から入手し、そのライブラリのサンプルスケッチを
そのまま利用して動作確認を行います。
利用したスケッチは"BTHID"です。

取りあえず、動作しました。

Terminal

本命のArduino pro mini 3.3Vで利用してみる

ピンがハの字に沿っていたおかげて(ははんだ付け精度が悪いのが幸いして)、
がっちりはまり、はんだ付け無しで利用出来ました(これは良い♪)。

Dscn6048

以前作成したPS/2エミュレーションを利用して、IchigoJamのPS/2キーボードとしての
利用を試みます。

Dscn6063

一部のキーは動作しませんが、動作しました。

念のためUSBポートの電圧を測ると、昇圧されることなく3.3Vです。
これだと、規定外なので"動いてラッキー"ですね。
でも、手持ちの無線キーボード、有線キーボードを試すと3.3Vでも結構動きます。
(当然、動かないものもありました。特に古い製品)。

Dscn6065

動いている様子(動画)


取りあえず動きましたが、3.3Vという条件ですのでかなり相性的な問題があると思います。


2016年11月 5日 (土)

Arduino Pro mini Pro Mini 3.3V/5V Adjustable 16MHzの調査

Arduino Pro miniのクローン製品で3.3Vと5Vで利用出来る製品を入手しました。
購入先はeBayです。


調査開始

基板の左側の半田が乗っているJumper SMDで3.3Vと5Vを切り替えることが出来るようです。

Dscn6012

基板に乗っている電圧レギュレータはKBAAの刻印があります。
調べてみるとMICREL(Microchip)のMIC5205と分かりました。
外付け抵抗にて任意の電圧を出力出来るようです。
以下はデータシートからの抜粋です。

Pinconfig

Adjust

確かに普通のPro miniよりも抵抗が多めに乗っています。
こちらの回路図を見ると、この製品の電源部は次のようになっているようです。

Schematics


実際に電圧を切り替えてみる

3.3Vで利用したいため、ジャンパスイッチ(Jumper SMD)を早速切り替えてみました。
半田を除去しただけではダメみたいです。結線されているようです。

Dscn6014

ナイフでパターンをカットします。

Dscn6015

カットした後、念のためテスターで通電が無いことを確認の上、3.3V側の設定にします。

Dscn6017

ちゃんと切り替わっているかを確認します。
RAWに乾電池4本の6Vを供給し、VCCの出力電圧を確認してみます。

Dscn6020

RAWに供給している電圧

Dscn6018

VCCから出力される電圧

Dscn6019



ちゃんと動作しているようです。電圧を切り替えられるのはちょっと便利です。
電圧レギュレータは150mAまで流せるようです。

もっとも、Arduino pro miniのクローン製品で 16MHz 3.3V ってのも売っているので
最初からそっちを手に入れる選択もありますね。

2016年11月 4日 (金)

ArduinoによるI2C EEPROMのエミュレーション (6)

(2016/12/23 更新・追記)

以前作成したIchigoJam用のEEPROMエミュレーションの小型化を検討し、OpenLogが
使えるのではないかと思い、OpenLogのクローン製品を購入してチャレンジしました。

Aliexpressでは$6.80位で購入できます。

Aliexpress

そして、届いた製品はこんな感じです。

Dscn5974

Dscn5975

基板の片面にAtmega328と水晶発振子 16MHz等が搭載、その裏はmicroSDカードスロットです。

このOpenLogは名称の通りmicroSDカードにシリアル通信経由のログを記録することに
特化したモジュールです。
全ての端子の利用は出来ず、microSDカードスロットの他にシリアル通信、SPI通信しか利用出来ません。
I2Cが利用出来ないのはちょっと残念ですね。

一応、Arduinoのブートローダーが乗っており、スペック的にはArduino Pro miniのように
スケッチの書込みが可能なはずです。


私が目的とするEEPROMエミュレーションにはI2Cが必要なのですがこのままでは
利用出来ませんので強引に信号を取り出します。

Dscn5977

実はこの結線間違てます。詳細は後ほど説明します。 ^^


ユニバーサル基板上のレイアウトを決め、早速実装にかかります。

Photo

出来上がりはこんな感じです。

Dscn5983

IchigoJamに乗せるとこんな感じです。いい感じ!

Dscn5981

スケッチの書込みにはブートローダの書込みが必要

さて、シリアル通信経由でスケッチを書込みましょう。
やってみると、書き込みエラーが発生して書き込みできず。

デフォルトで乗っているOpenLogのプログラムでのシリアル通信は出来ているので、
シリアル接続には問題ないと判断し、ブートローダーが怪しいと思い、
Arduino IDE 1.6.9環境にてブートローダーを書き換えたところ、問題なくスケッチ書き込みhが出来るようになりました。

デフォルトのブートローダーが古かったようです。
ブートローダーの書込みはシリアル経由では出来ないので、SPI接続にて行います。
ラベルの無いSPI端子は次のような割り付けになっています。

MISOの穴はmicroSDカードスロットの樹脂が被さっており、ピンセットとカッターで
樹脂部を削って穴を確保しました。

Spi

書込みのために一時的にSPI端子を仮はんだ付けして端子を確保しました。
書込み機器としてUSBtynyISPを使いました。

Dscn5993

これで、ArduinoPro miniとしてスケッチ書き込みが出来るようになりました。

Ide

早速接続して、動作確認。

Dscn5986

プログラムを保存しようとするとエラーが発生して保存できません。

Ioerror

デバッグ機能にてSDカードは認識しているがI2C通信が出来ていない模様。
調べると、I2Cの信号取り出しの結線が1ピンずれてました(下図が正しいピンです)。

I2c

半田付けを取り除いて、修正を試みましたが無理でした。
ここで諦めるのもなんなので、OpenLogクローンを別に買うことにしました。

この失敗作はシリアル通信、SPI通信、ANA4(SDA)は使えるのでSPIを使う表示機器の
制御実験にても使うことにします。

作業は注文したOpenLogクローンが届くまで中断です。

(2016/12/23 追記)

再チャレンジにて完成しました。
問題なく動作しました。

Dscn6285

Dscn6283


« 2016年10月 | トップページ | 2016年12月 »