V-USB を使ってHIDデバイスを作る。
USBデバイスを作る時、様々な形態が考えられる。
V-USBのドキュメントによれば、例として次のような物がある。
本稿では、このうち「Custom HID class device」をオススメする。
HIDデバイスを作るメリットデメリットについても、V-USBのドキュメントに記述がある。
(訳は筆者)
このように列挙するとデメリットの方が多いように見える。
しかし、これらのデメリットはそれぞれ解決できる。
1つ目のデメリットはドキュメントにもあるように先頭にサイズを入れればよい。
2つ目のデメリット、3つ目のデメリットについては、それぞれのOSでソフトウエアを作りなおさなければならない事を意味するが、一方でほとんどのOSでのHID操作APIは(USBを生で扱うAPIに比べれば)簡単になっており、また、各OSのHIDを抽象化するライブラリを作る事も可能ではないかと思われる。
3つ目のデメリットである実装が複雑になる点については、本稿で解説を行う。
後で書く
V-USBソフトウエア本体は以下からダウンロードできる。
V-USBはGPLとコマーシャルライセンスのデュアルライセンスになっている。
GPLを選択すれば無料で使う事ができる。
http://www.obdev.at/products/vusb/index.html
今回はvusb-20090822.zipをダウンロードして使用した。
まずはこれをコンパイルできるようにする。
私はAVR Studio 4とWin-AVR 20090313を利用した。
まず、AVR Studioを使ってプロジェクトを作成する。もちろんチップにはATTiny2313をチョイス(笑)
プロジェクトを作成したら、プロジェクトのオプションからクロックを設定しておく。
F_CPU定数が設定されていないと、V-USBのコンパイルに失敗する。
V-USBのパッケージから必要なファイルを取り出し、プロジェクトに追加する。
具体的にはusbdrvディレクトリをプロジェクトのディレクトリに入れる。
AVR Studioからusbdrvディレクトリの oddebug, usbdrv.c, usbdrvasm.asm をSource filesとして追加する。
また、oddebug.h, usbdrv.h, usbportability.hをヘッダファイルとして追加する。
この段階で次のような構成になっている。
作成するUSB機器の設定を行う usbconfig.h を作成する。
このファイルのテンプレートが usbconfig-prototype.h というファイル名でusbdrvディレクトリにあると思うのでこれをコピーして使う。
プロジェクトにも追加しておこう。
変更すべき点は次の通り
Hardware Configセクションを作るハードウエアに合わせて変更する。
PORTD2とPORTD4で通信をするのがデフォルトになっているので、
私は隣合うようにPORTD2とPORTD3にする事が多い。
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機器自体を設定すべく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から先程のusbconfig.hをincludeしているのだが、
これのinclude先がusbdrvディレクトリになっている。
私はusbconfig.hをusbdrvの外に置いたので、変更しておく。
#include "../usbconfig.h"
やっと本体だ、本体はAVR Studioが自動生成した<project名>.cに書く事にする。
まず、V-USBをincludeする。あと、割り込みは必須っぽいのでこれも入れる。
#include <avr/interrupt.h> #include "./usbdrv/usbdrv.h"
まず、ReportDescriptor?を作る。
何度かちょこちょこ出てきているがReportDescriptor?はHIDの通信内容を説明するためのものだ。
これを作るのがHIDを作る上での一つのポイントだ。
真面目にReportDescriptor?を作るのであれば、USBの総本家であるusb.orgにHID descriptor Toolという物があり、GUIで作れる。
http://www.usb.org/developers/hidpage/
不真面目にいくなら、以下に8バイトのパケットを送受信する時のReportDescriptor?の例を置いておくので、コピペしよう。
いずれにしろ、このReportDescrptor?の長さをusbconfig.hに書く必要がある。
忘れずに。
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
};
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
};
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
};
次にmain関数をかく、まず最低限バージョン
int main(void){
usbInit();
sei();
for(;;){
usbPoll();
}
}
まあ、usbInitしてusbPollしてるだけ。
次に送信もするバージョン
static char reportBuffer[8];
int main(void){
usbInit();
sei();
for(;;){
usbPoll();
if(usbInterruptIsReady()){ // Can I send packet?
if(パケットを送る気があるか?) {
パケットの準備
usbSetInterrupt((void*)&reportBuffer, sizeof(reportBuffer));
}
}
}
送るデータをreportBufferとして準備する。
usbInterruptIsReady?()関数でUSBがデータを送れる状態にあるかを調べて、
送れるようであれば、usbSetInterrupt?()関数で送る。
usbPoll()関数は10ms以内に呼ばなければならないらしい。
コントロールエンドポイント(ENDPOINT 0)に来たパケットを処理する関数だと思う(自信なし)
今回実装したい機能は次の3つ
GET_IDLEとSET_IDLEは処理しないとWindowsで認識されないみたい。
それ以外のパケットは無視する。
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;
}
usbFunctionSetup?で NO_MSGを返すとusbFunctionWrite?が呼び出される。
ここで、実際のデータ処理をする。
uchar usbFunctionWrite(uchar *data, uchar len)
{
dataを読み込んで処理する。
return len;
}
ENDPOINT 1に来たパケットがここに来る。
処理はusbFunctionWrite?にまかせる。
void usbFunctionWriteOut(uchar *data, uchar len)
{
usbFunctionWrite(data,len);
}
HID用のAPIがある
特にAdministrator権限など不要で利用できる。
http://ikejima.org/diary/?date=20091024#p01 を参考に
未調査
http://www.signal11.us/oss/hidapi/
これが使えそうだが、試してない。
HIDデバイスを作るのは簡単ぽいので作るといいですよ。
良くわかってない部分もあるので、ツッコミ等あれば掲示板まで。
http://bbs.ikejima.org/thread/43