V-USB を使ってHIDデバイスを作る。
[http://www.roulette-casino.com/ Roueltte Casino] なぜHIDか
USBデバイスを作る時、様々な形態が考えられる。
V-USBのドキュメントによれば、例として次のような物がある。
- Custom class devices
- Standard HID class device
- 一般的な(マウスとかキーボードとか)USBデバイス
- Custom HID class device
- Vendor type requests sent to custom HID class device
- CDC class devices
- Other classes
[http://www.geldspielautomaten.tv/ Automatenspiele]- Custom class devices
- Standard HID class device
- 一般的な(マウスとかキーボードとか)USBデバイス
- Custom HID class device
- Vendor type requests sent to custom HID class device
- [http://www.casino-spiele.info/ Casino Spiele]
- CDC class devices
- Other classes
本稿では、このうち「Custom HID class device」をオススメする。
HIDデバイスを作るメリットデメリットについても、
V-USBのドキュメントに記述がある。
(訳は筆者)
- メリット
- Windowsではドライバが不要で、そのためインストールが不要
- デメリット
- やりとりするデータはReportDescriptor?で宣言される必要がある。そのためデータは固定長である必要がある。
- 先頭にサイズを入れる事によって回避できる。AVR-Doperプロジェクトを見よ。
- BSDなどのOSでは、OSがHIDデバイスを管理しているためlibusbが使えない。
- WindowsとUNIX系OSでHIDを使うためのAPIが違う。Windowsではlibusbが使えない。
- 複雑になる。
このように列挙するとデメリットの方が多いように見える。
しかし、これらのデメリットはそれぞれ解決できる。
1つ目のデメリットはドキュメントにもあるように先頭にサイズを入れればよい。
2つ目のデメリット、3つ目のデメリットについては、それぞれのOSでソフトウエアを作りなおさなければならない事を意味するが、一方でほとんどのOSでのHID操作APIは(USBを生で扱うAPIに比べれば)簡単になっており、また、各OSのHIDを抽象化するライブラリを作る事も可能ではないかと思われる。
3つ目のデメリットである実装が複雑になる点については、本稿で解説を行う。
USBデバイスの作成(ソフトウエア編)
何を作るか?
後で書く
- コントロールエンドポイント(ENDPOINT 0)
- データ用エンドポイント(ENDPOINT 1)
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をヘッダファイルとして追加する。
http://www.roulette-casino.com/この段階で次のような構成になっている。
- project directory
- project.c
- usbdrv
- oddebug.c
- oddebug.h
- usbdrv.c
- その他ファイル
usbconfig.h の作成
作成するUSB機器の設定を行う usbconfig.h を作成する。
このファイルのテンプレートが usbconfig-prototype.h というファイル名でusbdrvディレクトリにあると思うのでこれをコピーして使う。
プロジェクトにも追加しておこう。
- project directory
- project.c
- usbconfig.h <- NEW
- usbdrv
- oddebug.c
- oddebug.h
- usbdrv.c
- その他ファイル
変更すべき点は次の通り
ハードウエア設定
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の外に置いたので、変更しておく。
#include "../usbconfig.h"
mainプログラムを書く。
やっと本体だ、本体は
AVR Studioが自動生成した
<project名>.cに書く事にする。
まず、V-USBをincludeする。あと、割り込みは必須っぽいのでこれも入れる。#include <avr/interrupt.h>
#include "./usbdrv/usbdrv.h"
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バイトパケット
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
};
受信(PC to HID)だけ8バイトパケット
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
};
送信(HID to PC)だけ8バイトパケット
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関数(とデータ送信)
次に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として準備する。
usb
InterruptIsReady?()関数でUSBがデータを送れる状態にあるかを調べて、
送れるようであれば、usb
SetInterrupt?()関数で送る。
usbPoll()関数は10ms以内に呼ばなければならないらしい。
usbFunctionSetup?関数
コントロールエンドポイント(ENDPOINT 0)に来たパケットを処理する関数だと思う(自信なし)
今回実装したい機能は次の3つ
- 来たHIDパケットをusbFunctionWrite?に処理させる。
- GET_IDLEパケットを処理する
- SET_IDLEパケットを処理する
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;
}
usbFunctionWrite? の実装
usb
FunctionSetup?で NO_MSGを返すとusb
FunctionWrite?が呼び出される。
ここで、実際のデータ処理をする。
uchar usbFunctionWrite(uchar *data, uchar len)
{
dataを読み込んで処理する。
return len;
}
usbFunctionWriteOut? の実装
ENDPOINT 1に来たパケットがここに来る。
処理はusb
FunctionWrite?にまかせる。
void usbFunctionWriteOut(uchar *data, uchar len)
{
usbFunctionWrite(data,len);
}
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