MVPen (Pegasus Mobile NoteTaker) の解析 (3)
MVPen をLinux から使う試みシリーズ。昨日までの内容がやや間違っていたので,補遺です。
※注意※ MVPen が故障する可能性があります
HID とのやりとりについて概略を知るためには,PSoCのUSB(HID)をつかってみる 文中でリンクされている Silicon Labs の AN249 の Human Interface Device Tutorial(PDF)が(英語ながら)よくまとまっていて非常にわかりやすいです。さらに Report Descriptor について詳しく知るためには,Human Interface Devices (HID) Information | USB-IF にある「Device Class Definition for HID 1.11」と「HID Usage Tables 1.12」を参照する必要があります。
昨日も書いたように,HID と PC の間のデータのやりとりは「Report」という構造でやりとりされるように抽象化されています。この Report の構造を定義するのが Report Descriptor で,HID デバイスは Device Descriptor や Interface Descriptor だけでなく Report Descriptor も返す必要があります。
Report を構造体とみなすと,Report Descriptor とは構造体の定義になります。
たとえば,マウスの Report Descriptor を C 風に書き下ろすと以下のようになります。
struct ApplicationCollection as Mouse { struct PhysicalCollection as Pointer { // Input as Buttons unsigned bool button1: 1; unsigned bool button2: 1; // padding unsigned bool pad: 6; // Input as X signed short X; // Input as Y signed short Y; }; };
さて,Report には「Usage Page」と「Usage」という非常に重要な2つの概念が存在します。
おおまかにいうと,「Usage」とは,Report やデータの意味を示すものです。具体的な Usage が示す意味は「Usage Page」によって切り替えることができます。逆にいうと,Usage Page でおおまかな「用途」を示して,Usage で具体的な「用途」を指定する,ということです。
実例を示します。
- Usage Page 0x01 - Generic Desktop Page
- Usage 0x02 - Mouse (Collection Appliation)
- Usage 0x30 - X 座標 (Dynamic Value)
- Usage Page 0x05 - Game Controls Page
- Usage 0x02 - Pinball Device (Collection Appliation)
- Usage 0x30 - Gun Bolt (Dynamic Value)
このように,同じ Usage の値に対しても,Usage Page が異なると違う意味をもつことになります。また,末尾の Collection Application や Dynamic Value という言葉からわかるとおり,Usage というのは「値の用途」だけでなく「Collection*1の用途」についても指定することができます。このように「Usage」がいろんな局面で適用されるので,当初混乱していました。
上記の例で「as
」と書いていますが,これは Usage(や Usage Page)で指定された「用途」です。
MVPen の Report Descriptor
ここまで理解できたところで,MVPen の Report Descriptor はどうなっているんだろうと思い,自力で取得して手で解析してみました。Report Descriptor を取得するためのプログラムは末尾に添付してあります。
Interface #0(制御用インタフェース)の Report Descriptor は,下記のような感じです。
06 a0 ff Usage Page = 0xffa0 (Vendor Page) 09 01 Usage = 0x01 (Vendor's 0x01) a1 01 Collection (0x01: Application) 09 02 Usage = 0x02 (Vendor's 0x02) 15 80 Logical Minimum = -128 (0x80) 25 7f Logical Maximum = 127 (0x7f) 75 08 Report Size = 8 (bits) 95 40 Report Count = 64 (0x40) 81 02 Input (Data, Variable, Absolute) 09 03 Usage = 0x03 (Vendor's 0x03) 15 80 Logical Minimum = -128 (0x80) 25 7f Logical Maximum = 127 (0x7f) 35 00 Physical Minimum = 0 (0x00) 45 ff Physical Maximum = 255 (0xff) 75 08 Report Size = 8 (bits) 95 08 Report Count = 8 (0x08) 91 08 Output (Data, Array, Absolute, Wrap) c0 End Collection (of Application)
シンプルなので,見ていただけると理解できると思います。
- Input は 8bit × 64
- Output は 8bit × 8
です。
おまけ?で,Interface #1(ユーザ入力デバイスとしてのインタフェース)の Report Descriptor をあげておきます。
05 0d Usage Page = 0x0d (Digitizers) 09 02 Usage = 0x02 (Pen) a1 01 Collection (0x01: Application) 85 08 Report ID = 0x08 09 20 Usage = 0x20 (Stylus) a1 00 Collection (0x00: Physical) 05 01 Usage Page = 0x01 (Generic Desktop) 09 30 Usage = 0x30 (X) 75 10 Report Size = 16 (bits) 95 01 Report Count = 1 55 0f Unit Exponent = 15 65 11 Unit = 17 35 00 Physical Minimum = 0 (0x00) 46 a0 00 Physical Maximum = 160 (0xa0) 16 e8 03 Logical Minimum = 1000 (0x03e8) 26 28 23 Logical Maximum = 9000 (0x2328) 81 02 Input (Data, Variable, Absolute) 09 31 Usage = 0x31 (Y) 35 00 Physical Minimum = 0 (0x00) 46 78 00 Physical Maximum = 120 (0x78) 15 00 Logical Minimum = 0 (0x00) 26 70 17 Logical Maximum = 6000 (0x1770) 81 02 Input (Data, Variable, Absolute) 05 0d Usage Page = 0x0d (Digitizers) 09 42 Usage = 0x42 (Vz) 15 00 Logical Minimum = 0 (0x00) 25 01 Logical Maximum = 1 (0x01) 75 01 Report Size = 1 (bits) 95 01 Report Count = 1 81 02 Input (Data, Variable, Absolute) 09 44 Usage = 0x44 (Vbry) 81 02 Input (Data, Variable, Absolute) 95 02 Report Count = 2 81 03 Input (Constant, Variable, Absolute) /* padding */ 09 32 Usage = 0x32 (Z) 95 01 Report Count = 1 81 02 Input (Data, Variable, Absolute) 95 03 Report Count = 3 81 03 Input (Constant, Variable, Absolute) /* padding */ c0 End Collection (of Physical) c0 End Collection (of Application) 05 01 Usage Page = 0x01 (General Desktop) 09 02 Usage = 0x02 (Mouse) a1 01 Collection (0x01: Application) 85 09 Report ID = 0x09 09 01 Usage = 0x01 (Pointer) a1 00 Collection (0x00: Physical) 05 09 Usage Page = 0x09 (Button) 19 01 Usage Minimum = 0x01 29 02 Usage Maximum = 0x02 15 00 Logical Minimum = 0 (0x00) 25 01 Logical Maximum = 1 (0x01) 75 01 Report Size = 1 (bits) 95 02 Report Count = 2 81 02 Input (Data, Variable, Absolute) 95 06 Report Count = 6 81 02 Input (Constant, Variable, Absolute) /* padding */ 05 01 Usage Page = 0x01 (General Desktop) 09 30 Usage = 0x30 (X) 09 31 Usage = 0x31 (Y) 15 81 Logical Minimum = -127 (0x81) 25 7f Logical Maximum = 127 (0x7f) 75 08 Report Size = 8 (bits) 95 02 Report Count = 2 81 06 Input (Data, Array, Relative) c0 End Collection (of Physical) c0 End Collection (of Application)
こちらのほうが Usage の意味についてとらえやすいかと思います。
このように,Digitizer としてのレポート((なぜ Vbry
があるのか不思議ですが))と,Mouse としてのレポートが定義されていることがわかります。
だから,Windows の場合,汎用のデジタイザ(タブレット?)ドライバが存在すれば,それ経由で入力を取得することが可能,かもしれません*2。「オンライン・ノートモード」に切り替えないとだめかもしれないけど。
HID Report Descriptor を取得するプログラム
libusb を使っています。通常 root 権限が必要かも。また,Linux の場合,カーネルの USB (HID) ドライバを detach する必要があるので(下記ソースをみるとわかりますが,libusb にそのための API が存在する)root 権限必須です。
/* getrepodesc.c */ #include <stdio.h> #include <stdlib.h> #include <usb.h> #define LINUX 1 static struct usb_device * get_usb_device(struct usb_bus *busses, unsigned vendor, unsigned product) { struct usb_bus *bus; for (bus = busses; bus; bus = bus->next) { struct usb_device *dev; for (dev = bus->devices; dev; dev = dev->next) { if (dev->descriptor.idVendor == vendor && dev->descriptor.idProduct == product) { return dev; } } } return NULL; } static int claim_usb_interface(usb_dev_handle *handle, int interface) { #ifdef LINUX if (! usb_claim_interface(handle, interface)) { fprintf(stderr, "successfully claimed interface\n"); return 0; } fprintf(stderr, "%s\n", usb_strerror()); fprintf(stderr, "detaching kernel driver\n"); if (usb_detach_kernel_driver_np(handle, interface)) { fprintf(stderr, "%s\n", usb_strerror()); return -1; } #endif if (! usb_claim_interface(handle, interface)) { fprintf(stderr, "successfully claimed interface\n"); return 0; } fprintf(stderr, "%s\n", usb_strerror()); return -1; } int main(int argc, char *argv[]) { struct usb_device *dev; usb_dev_handle *handle; int len; unsigned vendor = 0x0e20, product = 0x0101; int interface = 0; static char buf[4096]; if (argc > 1) interface = atoi(argv[1]); if (argc > 3) { vendor = strtol(argv[2], NULL, 16); product = strtol(argv[3], NULL, 16); } usb_init(); usb_set_debug(1); usb_find_busses(); usb_find_devices(); dev = get_usb_device(usb_get_busses(), vendor, product); if (! dev) { return 1; } handle = usb_open(dev); if (! handle) { fprintf(stderr, "%s\n", usb_strerror()); return 1; } if (claim_usb_interface(handle, interface)) { return 1; } if (dev->descriptor.bNumConfigurations > 0) { if (usb_set_configuration(handle, 0)) { fprintf(stderr, "%s\n", usb_strerror()); return 1; } } len = usb_control_msg(handle, USB_ENDPOINT_IN+1, USB_REQ_GET_DESCRIPTOR, (USB_DT_REPORT << 8) + 0, interface, buf, 4096, 1000); if (len > 0) fwrite(buf, 1, len, stdout); usb_close(handle); return 0; }
2008-08-08 追記
簡易パーサもつけときますね。
#!/usr/bin/perl use strict; use warnings; use IO::Handle; use IO::File; my %code = ( 0x80 => 'Input', 0x90 => 'Output', 0xb0 => 'Feature', 0xa0 => 'Collection', 0xc0 => 'EndCollection', 0x04 => 'UsagePage', 0x14 => 'LogicalMinimum', 0x24 => 'LogicalMaximum', 0x34 => 'PhysicalMinimum', 0x44 => 'PhysicalMaximum', 0x54 => 'UnitExponent', 0x64 => 'Unit', 0x74 => 'ReportSize', 0x84 => 'ReportID', 0x94 => 'ReportCount', 0xa4 => 'Push', 0xb4 => 'Pop', 0x08 => 'Usage', 0x18 => 'UsageMinimum', 0x28 => 'UsageMaximum', 0x38 => 'DesignatorIndex', 0x48 => 'DesignatorMinimum', 0x58 => 'DesignatorMaximum', 0x78 => 'StringIndex', 0x88 => 'StringMinimum', 0x98 => 'StringMaximum', 0xa8 => 'Delimiter', ); my $f = @ARGV ? IO::File->new(shift @ARGV, 'r') : IO::Handle->new_from_fd(fileno(\*STDIN), 'r'); die $! if ! $f; while (! $f->eof) { my $buf; $f->read($buf, 1) or die $!; my $c = ord $buf; printf {*STDOUT} '%02x ', $c; my $n = $c & 3; while ($n -- > 0) { $f->read($buf, 1) or die $!; my $d = ord $buf; printf {*STDOUT} '%02x ', $d; } $c = $c & 0xfc; if (exists $code{$c}) { print {*STDOUT} "\t", $code{$c}; } print {*STDOUT} "\n"; } $f->close();