2022年1月15日土曜日

Arduino LeonardoでUSBキーボード作成

 Arduinoの中でもArduino LeonardoはパソコンのUSB外付けキーボードやUSB外付けマウスとして動かすことができるようなので,やってみました.

ハードウェア

以下の部品を購入しました.



今回使用したM5Stack用のカード型キーボードユニットは,元々M5Stack用なのですが,Grove Aポート(I2Cインターフェース)で接続できるため,Arduinoでも使用できるようなので,使ってみました.
I2Cのアドレスは0x5Fに固定されています.
以下のように接続します.キーボードユニットはGROVEシールドのI2Cのコネクタに接続しました.

プログラミング

第一段階:キーボードから入力したコードをシリアルコンソールに表示

まず,入力されたキーコードを確認するために,シリアルコンソールに表示しました.ソースコードは以下のとおりです.

/*
 * I2Ckbd1
 * 2022/1/13
 * M5Stack用のI2CキーボードからArduinoでキー読み込み
 * コードが来るのでシリアルコンソールに表示する
 */

#include <Wire.h>
// I2C kbd
#define AclSenAdrs 0x5F
unsigned char AclSen;

void setup() {
  Wire.begin();
  Serial.begin(9600);
}

void loop() {
  int byteAvailable;

  // ArduinoがI2Cのマスター側になる
  Wire.beginTransmission(AclSenAdrs);
  // レジスタアドレスを定義する
  Wire.write(0x00);
  Wire.endTransmission();

  // レジスタアドレスから1アドレス分のデータを読む
  Wire.requestFrom(AclSenAdrs, 1);
  // 読み込めるバイト数(常に1が返る)
  byteAvailable = Wire.available();
  if ( byteAvailable > 0) {
    AclSen = Wire.read();
    // 0が返って来た時はキーが押されていない
    // キーが押されていたらコードが来る
    if (AclSen > 0) {
      Serial.println(AclSen);
    }
  }
  delay(50);
}

プログラムを書き込んだ後でシリアルコンソールを表示すると,押したキーのコードが表示されます.

第二段階:パソコンに対してキーボードとしての動作を確認

次に,USBキーボードとしての動作を確認しました.ソースコードは以下のとおりです.

/*
 * I2Ckbd2
 * 2022/1/15
 * Arduino Leonardoを,USB接続したパソコンのkbd, mouseにする
 * 書き込んだ後は常にキー入力があるので,最後のコメント内にカーソル
 * を置いてArduinoをUSBに接続し,コンパイル・書き込みする
 */

#include <Keyboard.h>

void setup() {
  Keyboard.begin();
}

void loop() {
  Keyboard.print("Hello World");
  delay(1000);
}

/*
 
 */

一度このソースコードを書き込むと,USBでパソコンに繋いでいる間,ずっとキー入力があるので,次の書き込みがなかなかできません.なので,2回目以降はArduinoソフトウェアで,コメント欄を作成しておいて,ここにカーソルを置いてからArduinoを USBでパソコンと接続すると,入力されてくる文字列が全てコメントとなるので,次のプログラムが書き込み可能になります.

第三段階:特定のキーが入力されたらまとめて文字列をパソコンに入力

ソースコードは以下のとおりです.

/*
 * I2Ckbd3
 * 2022/1/15
 * Arduino Leonardoを,USB接続したパソコンのkbd, mouseにする
 * 'h'を押したら'Hello World'をキーボードとして入力する
 */

#include <Wire.h>
#include <Keyboard.h>
// I2C kbd
#define AclSenAdrs 0x5F
int AclSen;


void setup() {
  Wire.begin();
  Keyboard.begin();
}

void loop() {
  int byteAvailable;
  Wire.beginTransmission(AclSenAdrs);
  Wire.write(0x00);
  Wire.endTransmission();

  Wire.requestFrom(AclSenAdrs, 1);
  byteAvailable = Wire.available();
  if (byteAvailable > 0) {
    AclSen = Wire.read();
    if (AclSen > 0) {
      if (AclSen == 'h') {
        Keyboard.print("Hello World");        
      }
    }
  }
  delay(50);
}

自作キーボードとして便利なのが,特定のキーでまとまった文字列を入力できる機能を組み込めることです.ここでは,キーボードで'h'キーを押すと,パソコンに'Hello World'という文字が入力されます.

第四段階:通常のキーボードとして使用

最後に,通常のキーボードとして使えるようにする作業です.普通の表示可能文字はASCIIコードが送信されるのですが,制御コードなどはキーコードとASCIIコードが異なるので,調整する必要があります.最初に処理したのがEnterキーで,そのままキーを押すと0x0dが来ます.Macでやっているので,ターミナルなどでは0x0aを入力しないと改行コードとして認識されません.その他に上下左右の矢印キーを対応してみました.キーボードとして送信できるコードの一覧はここにあります.一方で,入力されるコードの一覧は製品のページにあります.これらを比較して,違うコードを置き換えます.とりあえず上下左右とEnter,ESCを対応しました.ソースコードは以下のとおりです.

/*
 * I2Ckbd3
 * 2022/1/15
 * Arduino Leonardoを,USB接続したパソコンのkbd, mouseにする
 * 押されたキーコードを送信
 * scanするキーコードはI2Ckbd1で確認する.以下のコードが来ている.
 * https://docs.m5stack.com/en/unit/cardkb
 * 送信できるキーコードは以下
 * https://www.arduino.cc/reference/en/language/functions/usb/keyboard/keyboardmodifiers/
 */

#include <Wire.h>
#include <Keyboard.h>
// I2C kbd
#define AclSenAdrs 0x5F

#define KEY_ESC 0xb1
#define KEY_UP_ARROW 0xda
#define KEY_DOWN_ARROW 0xd9
#define KEY_LEFT_ARROW 0xd8
#define KEY_RIGHT_ARROW 0xd7

void setup() {
  Wire.begin(AclSenAdrs);
  Keyboard.begin();
}

void loop() {
  Wire.beginTransmission(AclSenAdrs);
  Wire.write(0x00);
  Wire.endTransmission();
  
  Wire.requestFrom(AclSenAdrs, 1);
  while (Wire.available()) {
    unsigned char c = Wire.read();
    switch (c) {
    case 0x0d: // 13 Enter
      Keyboard.write(0x0a);
      break;
    case 0x1b: // 27 ESC
      Keyboard.write(KEY_ESC);
      break;
    case 0xb4: // 180 Left
      Keyboard.write(KEY_LEFT_ARROW);
      break;
    case 0xb5: // 181 Up
      Keyboard.write(KEY_UP_ARROW);
      break;
    case 0xb6: // 182 Down
      Keyboard.write(KEY_DOWN_ARROW);
      break;
    case 0xb7: // 183 Right
      Keyboard.write(KEY_RIGHT_ARROW);
      break;
    default:
      Keyboard.write(c);  
    }
  }
  delay(50);
}