Arduino UNOを使った、キーボード機器製作法


概要

皆さん、USB機器自作したいですよね!
Arduinoでは、Serial.printでシリアルデータを送ることができますが、BackSpace,ESC,文字入力等のキーボード入力は対応していません。

対応させるには、ArduinoをHID(Human interface device)として認識させる必要があります。
本記事では、HIDに認識させる方法から、認識後のプログラム法について説明します。

参考にした記事を挙げておきます
ATmega16U2をDFUモードにし、HID機器と認識させるようにする方法
https://another.maple4ever.net/archives/2380/

Arduino UnoでESCキー入力機器を作っている方(先にこちらを読むと良いと思います)
https://qiita.com/ssatoru/items/3919b0e46aed5525b078

ただし、Leonardを使えば、Keyboardというライブラリがあり、簡単に実装できるので、難しい場合はLeonardを使うのが無難かも知れません。

環境

Windows10

Arduino UNO:Atmega328p(作業に入る前に、Arduino内のプログラムがSerialを使っていないものに書き換えてください)
:atmega16u2がUSBインターフェースのもの
コンパチ品だとCH320等をUSBインターフェースに使っていますが、その場合は今回の方法は使えません

使用ソフトウェア

dfu-programmer
http://dfu-programmer.github.io/

arduino IDE

ファームウェア
https://github.com/coopermaa/USBKeyboard

流れ

  1. ATmega16U2のファームウェアを書き換える
  2. ATmega16U2をHIDキーボードデバイスと認識させる
  3. ATmega328pから、USBキーコード(Serialデータ)をATmega16U2に送る
  4. 送られたキーコードがキーボード入力として認識される

ATmega16U2をHIDキーボードデバイスと認識させる方法

  1. デバイスマネージャを起動 = ウィンドウズマーク右クリック
  2. dfu-programmerをダウンロードする
  3. Arduino Unoのリセットピン下のピンを2つをジャンパーピンを使ってショートさせる
  4. パソコンに繫ぐ

繫いでもPCには認識されません
認識されないことを確認した後(音が鳴らないので)、ジャンパーピンを外すと認識される(音が鳴る)
認識された時は「不明なデバイス」と表示されます。
->このデバイスのプロパティを開き->ドライバーのリボン->ドライバーの更新
->コンピューターを参照してドライバーソフトウェアを検索
->dfu-programmerのファイルないの「dfu-programmer-win-0.7.2\dfu-prog-usb-1.2.2」を参照する
->ドライバーが当てられて、不明なデバイスから、「ATmega16U2」と認識されるようになる

部門としては[Libusb-win32 devices]の中に[ATmega16U2]が有ります。

5 . コマンドプロンプトで、dfu-programmer.exeを起動

cmd.exeとWindowsマークのとなりの「ここに入力して検索」に打ち込み、コマンドプロンプトを起動
->cd ./dfu-programmer-win-0.7.2 でdfu-programmer.exeの有るファイルに移動(cdコマンドは人によって違います)

6 . ファームウェアをダウンロードしてくる
キーボード機器として認識させるファームウェアは
https://github.com/coopermaa/USBKeyboard
->firmware->Arduiono-keyboard-0.3.hex です。
最初に入っている、Arduinoとしてのファームウェアは
->firmware->Arduino-usbserial-uno.hex です。

ダウンロードしたら、dfu-programmerのファイル内に移動させます。

  1. ファームウェアを書き込む

dfu-programmer ATmega16U2 erase

で最初に入っていたファームウェアを消します。

dfu-programmer.exe ATmega16U2 flash ./hex/Arduino-keyboard-0.3.hex

でファームウェアを書き込みます。
flashの後の./hex/は「hex」というファイルに保存したためです。直下に保存していれば、なくて良いです

この段階ではまだ、キーボードとして認識されていませんが、一度USBを外して刺し直すと
「HIDキーボード」として認識されます。

ここで、Atmega328pの方がSerialデータを送信していた場合、その影響が現れます。

Arduinoとして元に戻す場合は、ジャンパピンをさしてATmega16U2として再度認識させ、dfu-programmerで

と入力し直せば元に戻せます。

ATmega328pから、USBキーコード(Serialデータ)をATmega16U2に送る

次に、キー入力をする方法を説明します。
ATmega328pから、ATmega16U2にUSBキーコードをSerial.writeで送信すればキーボードとして入力したことになります。

送るUSBキーコードは次の表を参考にすると良いです。
http://www2d.biglobe.ne.jp/~msyk/keyboard/layout/usbkeycode.html

Usageの上位16ビットをUsage page下位16ビットをUsage IDと呼ぶ。
Usageは32ビットありこれをすべて送ると不経済であるため省略して送ることができる。
あるReportの集まりが共通のUsage Pageを持つUsageだった場合には、グループ化して表現でき、
下位16ビットのUsage IDだけを送信することができる。またUsage PageやUsage IDの上位ビットが0だった場合にはそれを省略できる。このためキーボードのUsageは、たとえば"A"キーは'0x00070004'ではなく'0x04'だけを送るようにできる。

サンプルとして、10秒おきに"hello world"と送信してくる機器にしてみましょう。
(実際にあったら超迷惑なBadUSBですが)


void setup() {
  Serial.begin(9600);
  delay(2000);
}

void loop() {

  putChar(0x0B);  // 一文字目大文字 できない
  putChar(0x2A);  //よってBackSpace
  Sheft(0x0B);    //H
  putChar(0x08);  //e
  putChar(0x0F);  //l
  putChar(0x0F);  //l
  putChar(0x12);  //o
  Sheft(0x1A);    //W
  putChar(0x12);  //o
  putChar(0x15);  //r
  putChar(0x0F);  //l
  putChar(0x07);  //d

  putChar(0x28);  //改行
  delay(10000);


}
void putChar(byte data) {
  for (byte j = 0; j <= 7; j++) {
    if (j == 2) {
      Serial.write(data);
    } else {
      Serial.write(0x00);
    }
  }

  for (byte j = 0; j <= 7; j++) {
    Serial.write(0x00);
  }
}
void Sheft(byte data) {
  for (byte j = 0; j <= 7; j++) {
    if (j == 6) {
      Serial.write(data);
    } else if (j == 2) {
      Serial.write(0xE5);
    } else {
      Serial.write(0x00);
    }
  }
  for (byte j = 0; j <= 7; j++) {
    Serial.write(0x00);
  }
}
void Ender() {
  for (byte j = 0; j <= 7; j++) {
    if (j == 2) {
      Serial.write(0x00);
    } else {
      Serial.write(0x00);
    }
  }
}

とりあえず、「HelloWorld」と送ってくるデバイスができました。
HIDのスキャンコードはよくわかりません。難しいし、いい情報が見当たらない。
Usageのpageは省略できるっぽいが、keyboardなら0x07が最初からいらないことや、一度32bit送ったら、再度0で書き換えないとずっとそのキーを入力し続けている判定になることなど、難しい。
とりあえず、BackSpaceをやるなら上のプログラムで、
putChar(0x2A);
Enterなら
putChar(0x28);
でやれる。これで、巨大Enterも作れる。太鼓の達人コントローラも、アーケコンも作れるぞ!
ただ、遅延とかはまだよくわからない。

長々とご閲覧ありがとうございました。