V-USB を使ってHIDデバイスを作る。
* なぜHIDか
USBデバイスを作る時、様々な形態が考えられる。
[[V-USBのドキュメント>http://vusb.wikidot.com/usb-device-classes]]によれば、例として次のような物がある。
<@list>
Custom class devices
全く新しいUSBデバイス
Standard HID class device
一般的な(マウスとかキーボードとか)USBデバイス
Custom HID class device
HIDだけど既存の種類じゃないデバイス
Vendor type requests sent to custom HID class device
HIDデバイスだけど、カスタムな通信をする
CDC class devices
USBシリアル
Other classes
その他
</@list>
本稿では、このうち「Custom HID class device」をオススメする。
HIDデバイスを作るメリットデメリットについても、[[V-USBのドキュメント>http://vusb.wikidot.com/usb-device-classes]]に記述がある。
(訳は筆者)
<@list>
メリット
Windowsではドライバが不要で、そのためインストールが不要
デメリット
やりとりするデータはReportDescriptorで宣言される必要がある。そのためデータは固定長である必要がある。
先頭にサイズを入れる事によって回避できる。AVR-Doperプロジェクトを見よ。
BSDなどのOSでは、OSがHIDデバイスを管理しているためlibusbが使えない。
WindowsとUNIX系OSでHIDを使うためのAPIが違う。Windowsではlibusbが使えない。
複雑になる。
</@list>
このように列挙するとデメリットの方が多いように見える。
しかし、これらのデメリットはそれぞれ解決できる。
1つ目のデメリットはドキュメントにもあるように先頭にサイズを入れればよい。
2つ目のデメリット、3つ目のデメリットについては、それぞれのOSでソフトウエアを作りなおさなければならない事を意味するが、一方でほとんどのOSでのHID操作APIは(USBを生で扱うAPIに比べれば)簡単になっており、また、各OSのHIDを抽象化するライブラリを作る事も可能ではないかと思われる。
3つ目のデメリットである実装が複雑になる点については、本稿で解説を行う。
* USBデバイスの作成(ソフトウエア編)
** 何を作るか?
後で書く
<@list>
コントロールエンドポイント(ENDPOINT 0)
ReportDescriptor
データ用エンドポイント(ENDPOINT 1)
</@list>
** V-USBの入手
V-USBソフトウエア本体は以下からダウンロードできる。
V-USBはGPLとコマーシャルライセンスのデュアルライセンスになっている。
GPLを選択すれば無料で使う事ができる。
http://www.obdev.at/products/vusb/index.html
今回はvusb-20090822.zipをダウンロードして使用した。
** プロジェクトの作成
まずはこれをコンパイルできるようにする。
私は[[AVR Studio 4>http://www.atmel.com/dyn/products/tools_card.asp?tool_id=2725]]と[[Win-AVR 20090313>http://winavr.sourceforge.net/download.html]]を利用した。
まず、AVR Studioを使ってプロジェクトを作成する。もちろんチップにはATTiny2313をチョイス(笑)
プロジェクトを作成したら、プロジェクトのオプションからクロックを設定しておく。
F_CPU定数が設定されていないと、V-USBのコンパイルに失敗する。
** V-USBの解凍
V-USBのパッケージから必要なファイルを取り出し、プロジェクトに追加する。
具体的にはusbdrvディレクトリをプロジェクトのディレクトリに入れる。
AVR Studioからusbdrvディレクトリの oddebug, usbdrv.c, usbdrvasm.asm をSource filesとして追加する。
また、oddebug.h, usbdrv.h, usbportability.hをヘッダファイルとして追加する。
この段階で次のような構成になっている。
<@list>
project directory
project.c
usbdrv
oddebug.c
oddebug.h
usbdrv.c
その他ファイル
</@list>
** usbconfig.h の作成
作成するUSB機器の設定を行う usbconfig.h を作成する。
このファイルのテンプレートが usbconfig-prototype.h というファイル名でusbdrvディレクトリにあると思うのでこれをコピーして使う。
プロジェクトにも追加しておこう。
<@list>
project directory
project.c
usbconfig.h <- NEW
usbdrv
oddebug.c
oddebug.h
usbdrv.c
その他ファイル
</@list>
変更すべき点は次の通り
*** ハードウエア設定
Hardware Configセクションを作るハードウエアに合わせて変更する。
PORTD2とPORTD4で通信をするのがデフォルトになっているので、
私は隣合うようにPORTD2とPORTD3にする事が多い。
*** HIDの設定
Functional Rangeセクションに設定する。
通信用のパイプであるENDPOINTを送受信用に作る。
これをENDPOINT 1と呼ぶ。
具体的にはUSB_CFG_HAVE_INTRIN_ENDPOINTを1にする。
これを使って受信できるように
USB_CFG_IMPLEMENT_FN_WRITEOUTを1にする。
また、最初からあるENDPOINT 0を使っても受信できるように。
USB_CFG_IMPLEMENT_FN_WRITEも1にする。
*** USB設定
また、USB機器自体を設定すべくDevice Descriptionセクションを書き変える。
USB機器はそれぞれユニークなIDを使う必要がある。
GPL版を使う人向けにはV-USBを作ってるObjective develipment社のIDが使っても良い事になっている。
詳しくはusbdrvディレクトリにあるUSB-IDs-for-free.txtを読もう。
まあ、自分で使うようの機器は、たまたまそのIDと同じ機器を自分が持っていない限り困らないので、特に気をつける必要はなさそう。
USB機器のIDは USB_CFG_VENDOR_ID と USB_CFG_DEVICE_ID で設定する。
まず、USB_CFG_VENDOR_IDだが、これはObjective development社のを使うならそのままでいい。
USB_CFG_DEVICE_ID は適宜設定しよう。
このIDは後でWindowsのデバイスマネージャから確認したり、PCからの接続時に使ったりするので、覚えておこう。
USB_CFG_VENDOR_NAMEやUSB_CFG_DEVICE_NAMEは適宜設定する。(しなくても動く)
つぎにこの機器をHIDだと認識させるための設定がある。
PCはUSB機器のタイプをクラスという形で判別する。
HIDの場合は機器全体のクラスを0に、インターフェースのクラスを3にする。
USB_CFG_DEVICE_CLASSに0を、USB_CFG_INTERFACE_CLASSに3を設定する。
最初にも触れたが、HIDはReportDescriptorという物が必要だ。
このReportDescriptorのサイズをこのファイルで設定する必要がある。
まだReportDescriptorを作ってないので、ここでは適当な値を USB_CFG_HID_REPORT_DESCRIPTOR_LENGTH に設定しておく。
** usbdrv.hの修正
usbdrv.hから先程のusbconfig.hをincludeしているのだが、
これのinclude先がusbdrvディレクトリになっている。
私はusbconfig.hをusbdrvの外に置いたので、変更しておく。
<@code>
#include "../usbconfig.h"
</@code>
** mainプログラムを書く。
やっと本体だ、本体はAVR Studioが自動生成した<project名>.cに書く事にする。
まず、V-USBをincludeする。あと、割り込みは必須っぽいのでこれも入れる。
<@code>
#include <avr/interrupt.h>
#include "./usbdrv/usbdrv.h"
</@code>
*** ReportDescriptorを作る
まず、ReportDescriptorを作る。
何度かちょこちょこ出てきているがReportDescriptorはHIDの通信内容を説明するためのものだ。
これを作るのがHIDを作る上での一つのポイントだ。
真面目にReportDescriptorを作るのであれば、USBの総本家であるusb.orgにHID descriptor Toolという物があり、GUIで作れる。
http://www.usb.org/developers/hidpage/
不真面目にいくなら、以下に8バイトのパケットを送受信する時のReportDescriptorの例を置いておくので、コピペしよう。
いずれにしろ、このReportDescrptorの長さをusbconfig.hに書く必要がある。
忘れずに。
**** 送受信8バイトパケット
<@code>
PROGMEM char usbHidReportDescriptor[34] = {
0x06, 0x00, 0xff, // USAGE_PAGE (Vendor Defined Page 1)
0x09, 0x01, // USAGE (Vendor Usage 1)
0xa1, 0x01, // COLLECTION (Application)
0x15, 0x00, // LOGICAL_MINIMUM (0)
0x26, 0xff, 0x00, // LOGICAL_MAXIMUM (255)
0x75, 0x08, // REPORT_SIZE (8)
0x95, 0x08, // REPORT_COUNT (8)
0x09, 0x00, // USAGE (Undefined)
0x81, 0x02, // INPUT (Data,Var,Abs)
0x15, 0x00, // LOGICAL_MINIMUM (0)
0x26, 0xff, 0x00, // LOGICAL_MAXIMUM (255)
0x75, 0x08, // REPORT_SIZE (8)
0x95, 0x08, // REPORT_COUNT (8)
0x09, 0x00, // USAGE (Undefined)
0x91, 0x02, // OUTPUT (Data,Var,Abs)
0xc0 // END_COLLECTION
};
</@code>
**** 受信(PC to HID)だけ8バイトパケット
<@code>
PROGMEM char usbHidReportDescriptor[21] = {
0x06, 0x00, 0xff, // USAGE_PAGE (Vendor Defined Page 1)
0x09, 0x01, // USAGE (Vendor Usage 1)
0xa1, 0x01, // COLLECTION (Application)
0x15, 0x00, // LOGICAL_MINIMUM (0)
0x26, 0xff, 0x00, // LOGICAL_MAXIMUM (255)
0x75, 0x08, // REPORT_SIZE (8)
0x95, 0x08, // REPORT_COUNT (8)
0x09, 0x00, // USAGE (Undefined)
0x91, 0x02, // OUTPUT (Data,Var,Abs)
0xc0 // END_COLLECTION
};
</@code>
**** 送信(HID to PC)だけ8バイトパケット
<@code>
PROGMEM char usbHidReportDescriptor[21] = {
0x06, 0x00, 0xff, // USAGE_PAGE (Vendor Defined Page 1)
0x09, 0x01, // USAGE (Vendor Usage 1)
0xa1, 0x01, // COLLECTION (Application)
0x15, 0x00, // LOGICAL_MINIMUM (0)
0x26, 0xff, 0x00, // LOGICAL_MAXIMUM (255)
0x75, 0x08, // REPORT_SIZE (8)
0x95, 0x08, // REPORT_COUNT (8)
0x09, 0x00, // USAGE (Undefined)
0x81, 0x02, // INPUT (Data,Var,Abs)
0xc0 // END_COLLECTION
};
</@code>
*** main関数(とデータ送信)
次にmain関数をかく、まず最低限バージョン
<@code>
int main(void){
usbInit();
sei();
for(;;){
usbPoll();
}
}
</@code>
まあ、usbInitしてusbPollしてるだけ。
次に送信もするバージョン
<@code>
static char reportBuffer[8];
int main(void){
usbInit();
sei();
for(;;){
usbPoll();
if(usbInterruptIsReady()){ // Can I send packet?
if(パケットを送る気があるか?) {
パケットの準備
usbSetInterrupt((void*)&reportBuffer, sizeof(reportBuffer));
}
}
}
</@code>
送るデータをreportBufferとして準備する。
usbInterruptIsReady()関数でUSBがデータを送れる状態にあるかを調べて、
送れるようであれば、usbSetInterrupt()関数で送る。
usbPoll()関数は10ms以内に呼ばなければならないらしい。
*** usbFunctionSetup関数
コントロールエンドポイント(ENDPOINT 0)に来たパケットを処理する関数だと思う(自信なし)
今回実装したい機能は次の3つ
<@list>
来たHIDパケットをusbFunctionWriteに処理させる。
GET_IDLEパケットを処理する
SET_IDLEパケットを処理する
</@list>
GET_IDLEとSET_IDLEは処理しないとWindowsで認識されないみたい。
それ以外のパケットは無視する。
<@code>
static uchar idleRate;
usbMsgLen_t usbFunctionSetup(uchar data[8]){
usbRequest_t *rq = (void *)data;
if((rq->bmRequestType & USBRQ_TYPE_MASK) == USBRQ_TYPE_CLASS){
if(rq->bRequest == USBRQ_HID_SET_REPORT){
return USB_NO_MSG;
}else if(rq->bRequest == USBRQ_HID_GET_IDLE){
usbMsgPtr = &idleRate;
return 1;
}else if(rq->bRequest == USBRQ_HID_SET_IDLE){
idleRate = rq->wValue.bytes[1];
}
}
return 0;
}
</@code>
*** usbFunctionWrite の実装
usbFunctionSetupで NO_MSGを返すとusbFunctionWriteが呼び出される。
ここで、実際のデータ処理をする。
<@code>
uchar usbFunctionWrite(uchar *data, uchar len)
{
dataを読み込んで処理する。
return len;
}
</@code>
*** usbFunctionWriteOut の実装
ENDPOINT 1に来たパケットがここに来る。
処理はusbFunctionWriteにまかせる。
<@code>
void usbFunctionWriteOut(uchar *data, uchar len)
{
usbFunctionWrite(data,len);
}
</@code>
* USBデバイスの作成(ハードウエア編)
* HIDデバイスのコントール
** Windows
HID用のAPIがある
特にAdministrator権限など不要で利用できる。
** FreeBSD
http://ikejima.org/diary/?date=20091024#p01 を参考に
** Linux
未調査
http://www.signal11.us/oss/hidapi/
これが使えそうだが、試してない。
* まとめ
HIDデバイスを作るのは簡単ぽいので作るといいですよ。
良くわかってない部分もあるので、ツッコミ等あれば掲示板まで。
http://bbs.ikejima.org/thread/43