Arduino UnoでUSB HIDデバイスを作ってみた

Windowsで動く音量調整や再生/一時停止デバイスを前から作りたいなと考えていたがdrdnar / Arduino-HID-Remote をふと見つけArduinoでも簡単に実現できると知ったので試してみました. しかしこれはATMEGA32U4が搭載されているArduino限定で手元になかったのでATMEGA328Pで動くように工夫し動かしてみました.

ATMEGA328Pでキーボードデバイス

結構前にAVRデバイスで限定的ではあるがUSBデバイスを実現できるV-USBが話題になっていたのですがArduinoでも簡単に適用できるライブラリがあったのでありがたく使用します. サンプルにはキーボードがありました.
gloob / vusb-for-arduino

回路図はProject Log : Arduino USB, Arduino(ATmega328p)でV-USBキーボードテストを参考にしました. 手元に68Ωの抵抗がなかったため47Ωないしは100Ωで代替することができました. 面倒なのでユニバーサル基板に起こしました.

サンプルファイルであるUsbKeyboardDemo1.pdeを使用しテストしました. PlatformIOでビルドしましたが, そのままではエラーが吐かれてしまったので以下の行の宣言をcharからunsigned charに変更しました.
libraries/UsbKeyboard/utility/usbdrv.h#L492
libraries/UsbKeyboard/UsbKeyboard.h#L43

USBケーブルが悪いのか基板が悪いのかわかりませんがちゃんと動くまでに数時間かかってしまいました.
12ピンをGNDに落とすことで「hello world」が入力されるデバイスが実現できました.

ATMEGA328PでHID(Consumer)デバイス

音量の調整や再生/一時停止を可能にするデバイスはConsumer Pageを設定します.
https://www.usb.org/sites/default/files/documents/hut1_12v2.pdf
キーボードもHIDデバイス一種であり最低限の設定は既に出来上がっているのでキーボードを実現しているGeneric DesktopからCosumerに切り替えて必要なディスクリプタを記述します. 基本的にArduino-HID-RemoteのHIDRemote.cpp#L3をコピペします. 配列のサイズが変更されるのでlibraries/UsbKeyboard/usbconfig.h#L276を配列サイズに変更します.

今回はHID_REMOTE_PLAYからHID_REMOTE_MUTEを16bitで記述するのでuint16_tに変更しました. また, このディスクリプタではレポートIDを使用していることから対応したプログラムに変更する必要があります. 転送するデータの先頭にレポートIDを追加するだけで問題ありませんでした. バッファのサイズは必要に応じて変更しました.

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
32
  void sendKeyStroke(uint16_t key) {

while (!usbInterruptIsReady()) {
// Note: We wait until we can send keystroke
// so we know the previous keystroke was
// sent.
}

memset(reportBuffer, 0, sizeof(reportBuffer));

reportBuffer[0] = 4;//report id
reportBuffer[1] = key;
reportBuffer[2] = (key >> 8);

usbSetInterrupt(reportBuffer, sizeof(reportBuffer));

while (!usbInterruptIsReady()) {
// Note: We wait until we can send keystroke
// so we know the previous keystroke was
// sent.
}

// This stops endlessly repeating keystrokes:
memset(reportBuffer, 0, sizeof(reportBuffer));
usbSetInterrupt(reportBuffer, sizeof(reportBuffer));

}

//private: TODO: Make friend?
uchar reportBuffer[3]; // buffer for HID reports [ 1 modifier byte + (len-1) key strokes]

};

HID_REMOTE_PLAYやHID_REMOTE_PAUSEはアクティブなウィンドウのプレイヤー(Windows Media PlayerやiTunes)でのみ有効, HID_REMOTE_PLAY_PAUSEはバックグラウンドで再生していた場合でも有効でした.

HIDレポートディスクリプタのメモ

ディスクリプタを構築するツールが公式から提供されています.
HID Descriptor Tool

色々探せば解説されていますが, REPORT_SIZE, REPORT_COUNTの理解に時間がかかりました. REPORT_SIZEはUSAGEで使用するデータのフィールドをビットごとに指定しており, REPORT_COUNTはUSAGEの数を指定しています. データは記述した順に配置されていき今回は1ビットごとなのでこれだけであれば12ビットとなっています.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
//0か1の設定
0x15, 0x00, // Logical Minimum (0)
0x25, 0x01, // Logical Maximum (1)

//https://www.usb.org/sites/default/files/documents/hut1_12v2.pdf
//Table 17: Consumer Usage Page を参照
//PlayからRandom PlayまでのUsageを一度に指定
0x19, 0xb0, // Usage Minimum (Play)
0x29, 0xb9, // Usage Maximum (Random Play)
//On/Offなので1ビット
0x75, 0x01, // Report Size (1)
//0xb0~0xb9まで10個あるので
0x95, 0x0A, // Report Count (10)
0x81, 0x06, // Input (Data, Variable, Relative)

//VolumeUpとDownは0xe9と0xea
0x09, 0xe9, // Usage (Volume Up)
0x09, 0xea, // Usage (Volume Down)
//On/Offなので1ビット
0x75, 0x01, // Report Size (1)
//VolumeUp,Downで2個
0x95, 0x02, // Report Count (2)
0x81, 0x06, // Input (Data, Variable, Relative)

関連記事