初投稿になります。よろしくお願いします。
ArduinoのUSBホストシールドにUSB接続マウスをつなげて、PCでその入力を扱おうと思いました。
USBホストシールドのライブラリには、マウスを扱うためのサンプルスケッチが用意されていましたが、これはブートプロトコルレポートを利用しているため、マウスの左、右、中ボタンとXY移動量しか扱えないようです。
今回は、マウスのホイールや追加ボタンも扱えるようにしたいため、マウスから送られるレポートを解析しプログラムを組む必要がありました。
レポートとレポートディスクリプタ
マウスをクリックしたり移動させると、そのたびに、マウスはレポートと呼ばれるひとまとまりのデータを送ってきます。
例えばマウスのブートプロトコルレポートは、下のような3バイトのデータになります。
このレポートのデータ構造をレポートディスクリプタと呼ぶそうです。
レポートディスクリプタは、マウスによって(ボタンの数が違ったりするので)異なると思われます。自分が使うマウスごとにレポートを解析する必要があります。
用意するもの
- Arduino Leonardo
- USBホストシールド
- LOGICOOL M500 マウス
- USB_Host_Shield_2.0 ライブラリ
ライブラリは下記で入手しました。
https://github.com/felis/USB_Host_Shield_2.0
今回、プログラムを組むうえで使ったマウスはロジクールのM500というもので、たまたま家にあったものですが、左右中ボタン、X1、X2ボタン、スクロールホイール、チルトホイールといったWindowsで標準で扱えるボタンをすべて兼ね備えたマウスなので、レポートの解析にはちょうどいいと思いました。
レポートの解析プログラム
マウスから送られるレポートの解析は、単純にレポートを2進数でシリアルモニタに表示し、各ボタンのクリックやXY移動を行って数値の変化を見ることで行うことにしました。
そのためのArduino用のプログラムを書く必要がありますが、USBホストシールドライブラリのJoyPadをあつかうサンプル「USBHIDJoystick」を土台とすると作りやすいことがわかりました。USBHIDJoystick の hidjoystickrptparser.cpp ファイルにJoyPadのレポートを読んでボタン押下をシリアルモニタに表示するプログラムが書かれています。この部分を特に変更し、レポートを1バイトごとにカンマで区切って8バイト目まで2進数で表示するようにしました。
プログラムは以下の3つのファイルからなります。私はC++プログラマではないので、見よう見まねで書いていることをご了承ください。
#include <usbhid.h>
#include <hiduniversal.h>
#include <usbhub.h>
// Satisfy IDE, which only needs to see the include statment in the ino.
#ifdef dobogusinclude
#include <spi4teensy3.h>
#endif
#include <SPI.h>
#include "hidmouserptparser.h"
USB Usb;
USBHub Hub(&Usb);
HIDUniversal Hid(&Usb);
HIDMouseEvents MouEvents;
HIDMouseReportParser Mou(&MouEvents);
void setup() {
Serial.begin(9600);
#if !defined(__MIPSEL__)
while (!Serial); // Wait for serial port to connect - used on Leonardo, Teensy and other boards with built-in USB CDC serial connection
#endif
Serial.println("Start");
if (Usb.Init() == -1)
Serial.println("OSC did not start.");
delay(200);
if (!Hid.SetReportParser(0, &Mou))
ErrorMessage<uint8_t > (PSTR("SetReportParser"), 1);
}
void loop() {
Usb.Task();
}
#if !defined(__HIDMOUSERPTPARSER_H__)
#define __HIDMOUSERPTPARSER_H__
#include <usbhid.h>
class HIDMouseEvents {};
#define RPT_LEN 8
class HIDMouseReportParser : public HIDReportParser {
HIDMouseEvents *mouEvents;
public:
HIDMouseReportParser(HIDMouseEvents *evt);
virtual void Parse(USBHID *hid, bool is_rpt_id, uint8_t len, uint8_t *buf);
};
#endif // __HIDMOUSERPTPARSER_H__
#include "hidmouserptparser.h"
HIDMouseReportParser::HIDMouseReportParser(HIDMouseEvents *evt) :
mouEvents(evt) {}
void HIDMouseReportParser::Parse(USBHID *hid, bool is_rpt_id, uint8_t len, uint8_t *buf) {
for (uint8_t i = 0; i < RPT_LEN; i++) {
Serial.print(buf[i], BIN);
Serial.print(", ");
}
Serial.println("");
}
シリアルモニタにレポートを表示する処理を hidmouserptparser.cpp の Parse メソッドに書きました。Parse メソッドはメインループの Usb.Task() 内部でレポートが送られるたびに実行されるようです。
これでArduinoに接続したマウスを操作するとシリアルモニタに2進数のレポートが表示されます。
#レポートの解析
上のプログラムを使ってM500マウスのレポート構造を解析します。マウスを操作してシリアルモニタの表示を見てみました。8ビット(1バイト)ごとにカンマ区切りしていますが、上位ビットの0は省略されるようです。
###ボタンとホイール
####左クリック
1, 0, 0, 0, 0, 0, 0, 0,
####離したとき
0, 0, 0, 0, 0, 0, 0, 0,
####X1ボタン
1000, 0, 0, 0, 0, 0, 0, 0,
####X2ボタン
10000, 0, 0, 0, 0, 0, 0, 0,
####スクロールホイールを上に回す
0, 0, 0, 0, 1, 0, 0, 0,
####スクロールホイールを素早く上に回す
0, 0, 0, 0, 10, 0, 0, 0,
####スクロールホイールを下に回す
0, 0, 0, 0, 11111111, 0, 0, 0,
####チルトホイールを左に傾ける
0, 0, 0, 0, 0, 11111111, 0, 0,
####チルトホイールを離す
0, 0, 0, 0, 0, 0, 0, 0,
ボタンを押した瞬間に該当するビットが1となるレポートが送られ、また離した瞬間に0となるレポートが送られるようです。ボタンの押下情報はすべて1バイト目にありました。
スクロールホイールは符号付1バイトで、回転速度も得られるようです。回転を止めたときに0となるレポートは送られません。チルトも符号付1バイトですが、中身はタクトスイッチなので速度は得られません。
###マウス移動
####左にゆっくり動かす
0, 11111111, 1111, 0, 0, 0, 0, 0,
####左に素早く動かす
0, 11100011, 1111, 0, 0, 0, 0, 0,
####右にゆっくり動かす
0, 1, 0, 0, 0, 0, 0, 0,
####上にゆっくり動かす
0, 0, 11110000, 11111111, 0, 0, 0, 0,
マウスのXY移動量は符号付12ビットで、2~4バイト目の3バイトに12ビットずつ分かれていました。
表示された情報をもとにレポート構造を解析すると下のようになりました。
##M500マウスのレポート構造
レポートは8バイトまで表示しましたが、今回使われていいたのは6バイトまででした。
#問題点と解決案
さて、これで使いたいマウスのレポートを読むことができるようになりました。しかし、実際に動かした人は気づいたと思いますが、問題点が1つ出てきてしまいました。
それはスクロールを同じ方向に続けて回転させるとレポートが表示されないということです。
おそらく Parse メソッドを実行する前に、レポートを前回のレポートと比較しており、同じデータなら Parse メソッドを実行しないようにしているのではないかと考えました。
USBホストシールドライブラリ内で Parse メソッドを実行しているところを探してみると、hiduniversal.cpp の367行目~の Poll メソッドの最後のほうに書かれていました。また395行目に
bool identical = BuffersIdentical(read, buf, prevBuf);
というところがあり前回のレポートと比較をしていました。
それから399~400行に
if(identical)
return 0;
とあり、前回のレポートと同じデータなら Parse メソッドを実行する前にループから抜けてしまっているようでした。
レポートが送られたときに必ず Parse メソッドを実行したいので399~400行をコメントアウトしました。
これで、連続でスクロールしてもレポートが表示されるようになりました。
しかしこのプログラムのためだけにライブラリを直接書き換えてしまうのはよくないので、オーバーライドしたほうがいいと思います。
今回はここまでです。次回はレポートの情報からPCをマウスで操作できるようにしたいと思います。
#参考リンク
https://www.circuitsathome.com/mcu/hid-support-for-usb-host-shield-library-2-0-released/
https://www.circuitsathome.com/mcu/usb/visualizing-hid-device-reports-and-report-descriptors/
https://www.circuitsathome.com/mcu/hid-joystick-code-sample/
http://wiki.onakasuita.org/pukiwiki/?レポートディスクリプタ