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

# 概要
皆さん、<font color="Red">USB機器自作</font>したいですよね！
そのためにはまず、Aruduinoをキーボードと認識させるつまり、

**Arduinoを<font color="Red">HID</font>(Human interface device)として認識させる**

必要があります。認識してもらえるようになったら、キーコードと呼ばれる数値（キー入力に該当する情報）をマイコン側からPCに送れるようにプログラムすることでキーボード機器を作ることができます。
本記事では、HIDに認識させる方法から、認識後のプログラム法について説明します。

（2023/1/16追記）
現在、dfu-programmer-winの配布サイトが更新され、exeファイルがなく、各自ソースをビルドする必要があり難易度が上がっています。

(2024/7/19追記)
現在はこちらの[FLIP]というソフトを使ったほうがいい可能性が高いです（未検証）。

https://qiita.com/Kurogara/items/00796faed32e95df9bd0#_reference-a8479a15f1b59937f72f

### 参考記事

* ATmega16U2をDFUモードにし、HID機器と認識させるようにする方法

https://another.maple4ever.net/archives/2380/

* Arduino UnoでESCキー入力機器を作っている方（先にこちらを読むと良いと思います）

https://qiita.com/ssatoru/items/3919b0e46aed5525b078

ただし、Aruduino ProやLeonardを使えば、Keyboardというライブラリがあり、簡単に実装できる上に悩むこともないので、こっちを使った方がいいと思います。

# 環境
* Windows10
* 純正のArduino IDE
* Arduino UNO：Atmega328p（作業に入る前に、Arduino内のプログラムがSerialを使っていないものに書き換えてください）

重要な点として、
**atmega16u2がUSBインターフェースのもの**
を使ってください。
コンパチ品だとCH320等をUSBインターフェースに使っていますが、その場合は今回の方法は使えません。

![12.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/405484/b0f1c6b1-e71b-b172-7a8a-d5f8a8a01e4f.png)

赤丸の部品がないと今回の方法は使えません。

# 使用ソフトウェア（ソフトというよりコマンドプロンプト上で起動するもの）
dfu-programmer

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

windows用にコンパイラ済みの物があるので、ダウンロードと回答するだけでOKです。

(追記)久々に訪れたら、windowsのコンパイラ済みの物がなくなってました。


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

# ATmega16U2をHIDキーボードデバイスと認識させる方法
### デバイスマネージャを起動　＝　ウィンドウズマーク右クリック
関係ない話ですけど、windowsの設定画面どこに何があるかわかりにくいですよね。
デバイスマネージャーとか昔からあるやつと、windows10から追加された設定項目で2分化された感じで
新しくできた設定画面からじゃデバイスマネージャーとか出せないし・・・

### dfu-programmerをダウンロードする
前述のリンクからダウンロードしてわかりやすい所に保存してください。

### Arduino Unoのリセットピン下のピンを2つをジャンパーピンを使ってショートさせる
![ジャンパー.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/405484/68067ba2-5a2c-0333-61c1-74179b25946b.jpeg)
### パソコンに繫ぐ

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

![device.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/405484/cab79775-7cbe-92d0-fd00-dbdade9f6924.jpeg)

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

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

```C
cmd.exe
```
とWindowsマークのとなりの「ここに入力して検索」に打ち込み、コマンドプロンプトを起動します。

```C
cd "ダウンロードしたdfu-programmerの保存場所"/dfu-programmer-win-0.7.2　
```
でdfu-programmer.exeの有るファイルに移動（cdコマンドは人によって違います）

### ファームウェアをダウンロードしてくる
キーボード機器として認識させるファームウェアは

https://github.com/coopermaa/USBKeyboard

```
->firmware
->Arduiono-keyboard-0.3.hex 
```
です。

最初に入っている、Arduinoとしてのファームウェアは

```
->firmware
->Arduino-usbserial-uno.hex 
```
です。

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

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

![comand.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/405484/cccc7441-060f-ca28-e29f-8f0cb5a49c6f.jpeg)

```comond
dfu-programmer ATmega16U2 erase
```
とコマンドプロンプトに打ち込み、Enterで実行すると。
`Atmega16u2`に入っているたファームウェアを消します。

```comond
dfu-programmer.exe ATmega16U2 flash ./hex/Arduino-keyboard-0.3.hex
```
で新たにファームウェアを書き込みます。
`flash`の後の`./hex/`は`hex`というファイルに自分が保存したためです。自分が保存した場所に合わせてください。

この段階ではまだ、キーボードとして認識されていませんが、一度USBを外して刺し直すと
「HIDキーボード」として認識されます。
![keyborad.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/405484/44c413dc-82d0-b410-0497-ae6f8b840b74.jpeg)
ここで、`Atmega328p`の方のプログラムがSerialデータを送信していた場合、`Atmega16u2`がキー入力と勘違いしてPCにデータを送ることになってしまうのでマイコン側のプログラムの初期化が重要だったというわけです。

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

```comond
dfu-programmer ATmega16U2 erase
dfu-programmer.exe ATmega16U2 flash ./hex/Arduino-usbserial-uno.hex
```

![comand2.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/405484/37174dba-5929-1b9a-31a4-1cc79ab2af70.jpeg)
と入力し直せば元に戻せます。

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

送るUSBキーコードは次の表を参考にすると良いです。

http://www2d.biglobe.ne.jp/~msyk/keyboard/layout/usbkeycode.html

https://ja.wikipedia.org/wiki/%E3%82%B9%E3%82%AD%E3%83%A3%E3%83%B3%E3%82%B3%E3%83%BC%E3%83%89

まとめると
>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'だけを送るようにできる。

https://tepp91.github.io/contents/misc/remap-keyboard-with-scancode-map.html

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

```C++

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」と送ってくるデバイスができました。

### プログラムの書き込み
USBポートはキーボードとして認識してしまうためプログラムの書き込みはできなくなっています。
ArduinoISPという方法を使ってプログラムの書き込みは行ってください。

https://n.mtng.org/ele/arduino/ArduinoISP.html

書き込み毎にファームウェアをもとに戻す方法もありますが、大変だと思います。

### キーコードについて
`HID`のスキャンコードはよくわかりません。難しいし、いい情報が見当たらない。
Usageのpageは省略できるっぽいが、keyboardなら0x07が最初からいらないことや、一度32bit送ったら、再度0で書き換えないとずっとそのキーを入力し続けている判定になることなど、難しい。
とりあえず、BackSpaceをやるなら上のプログラムで、

```
putChar(0x2A);
```
Enterなら

```
putChar(0x28); 
```
でやれました。これで、巨大Enterも作れる。太鼓の達人コントローラも、アーケコンも作れるぞ！
ただ、遅延とかはまだよくわからない。

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

# 追記
2023/1/16 dfu-programerのwindows版がなくなりました。自分でコンパイルする必要がありそうです。

Arduino ProでKeybord・Mouseライブラリを使うことを強くお勧めします。

https://qiita.com/GANTZ/items/47b5f857a1322b32cfb2