[トップページへ戻る]

V-USBを利用してHIDデバイスを作る


(C) Objective Development Software

2012年4月
AVRを使った製作物にV-USBを組み込もうと思いましたが、デバイス側もホスト側もソフトをどのように作ればよいのかよく分からなかったので、調べながら挑戦しました。
本記事はそのときのメモをまとめたものです。内容が偏っていたり、説明順的に読みにくいところもあると思いますが、ご了承ください。
※HIDデバイス…ヒューマン・インターフェイス・デバイス・デバイス。気にしないでください。

【目次】
  • V-USBとは
  • V-USBでHIDデバイスを作る
  • V-USBファイルの配置とAVR Studioの設定
  • usbconfig.hを書き換える
  • デバイスの動作を確認する
  • 転送方向/レポートタイプ/サポートツール
  • データ転送の流れとデータのゲット/セット場所について
  • データ送受信のサイズと戻り値について
  • GetIdol/SetIdolリクエストについて
  • データ転送の種類と処理間隔について
  • 大きなサイズのデータを転送するには(1)
  • 大きなサイズのデータを転送するには(2)
  • レポートタイプの割り当て例
  • サンプルプログラム
  • 実験に用いた回路
【ご注意】
本記事はV-USBの内容を詳細に解説するものではなく、自分が初めてV-USBを使う(作り込む)にあたり、調べたこと、手掛かりにしたことを紹介するものです。これからV-USBを使おうとする人のヒントになればと思います。決して、本記事を読めばV-USBが使いこなせるようになる、というものではありません。


V-USBとは
PCには様々なデバイス(周辺機器)をUSB(Universal Serial Bus)で接続することができます。
そのようなデバイスには、USB機能を司る専用ICやUSB内蔵マイコンが使われています。
趣味の電子工作でも、USB内蔵のPICやAVRを使ってUSBデバイスを作ることができます。USB内蔵のPICは比較的よく出回っています。それに比べ、AVRの方はあまり出回っていません。しかし、USBを内蔵していないAVRマイコンでもUSBデバイスを作ることができます。V-USB技術がそれを可能にします。
V-USBはAVR上でファームウェア(マイコンに書き込むソフトウェア)によってUSB機能を実現する技術です。
Universal Serial Bus http://www.usb.org
Objective Development Software GmbH / V-USB http://www.obdev.at/products/vusb/index.html

V-USBの動作を知るためにソース(usbdrv.c)を読んで処理内容を追うこともあります。USBに関する基礎知識(クラス、インターフェイス、パケット、トランザクション、転送方式、といったこと)があると、ソースが理解しやすくなります。
USBに関する詳細は参考書を読んでください。概要だけなら、USBコントローラ内蔵ICの説明書が分かりやすくてお勧めです。メーカーのサイトで公開されています。※「USB HID コントローラ内蔵 pdf」などの用語で検索。
その際、IC固有の機能に関する説明を、USB規格の説明だと勘違いしないよう注意してください。

V-USBでHIDデバイスを作る
AVRで作ったものをPCに接続するために、AVR-CDCを利用することがよくあります。AVR-CDCシリーズはV-USBを応用したCDC(Communication Device Class)デバイスで、PCに仮想COMポートを増設してくれます。
Recursion Co., Ltd. / AVR-CDC http://www.recursion.jp/avrcdc/indexj.html

AVR-CDCを使うには、INFファイルで提供されるドライバをPCにインストールする必要があります。AVR-CDCに限らず、V-USBを利用したカスタムなデバイスは基本的にドライバを必要とします。デバイス開発者はドライバも作らなければなりません。
それに対し、HID(Human Interface Device)として規格化されているデバイス…キーボードやマウス、ジョイスティックなどは、PC(OS)がもともとドライバを持っているので利用者が別途インストールする必要はありません。
実はHIDに準拠したデバイスであれば、内容が何であれOS標準のドライバで動作させることができます。自作デバイスでもドライバの開発・インストールなしに、PCに接続することができるのです。

本記事ではV-USBを利用したHIDデバイスを作ります。具体的にはホスト(PC)との間で任意のデータ(byte配列)をやりとりするHIDデバイスを作ります。さらに、AVR-CDC/IOのような汎用I/O制御デバイスを作れればと思います。
※追記: AVR-HID/IO、完成しました。次回、ソフトウェア編の記事で公開する予定です。

V-USBファイルの配置とAVR Studioの設定
Objective DevelopmentのサイトからV-USBのモジュールをダウンロードします。
本記事公開時点での最新版はvusb-20120109.zipでした。
Download V-USB http://www.obdev.at/products/vusb/download.html

ファイル配置と設定
  1. AVR Studioで目的のプロジェクトを作成します。※以下、AVR Studio 5.1のこととする。
  2. V-USBのusbdrvフォルダを、フォルダごとAVR Studioのプロジェクトへコピーします。
  3. usbdrvフォルダのusbconfig-prototype.hをコピーし、usbconfig.hにリネームします。
    ※リネームしたusbconfig.hをusbdrvフォルダからプロジェクトのフォルダに移動してもよい。
    その場合、usbdrv.hにあるinclude記述がusbconfig.hの位置を正しく指すよう書き換える。
  4. AVR Studioのソリューションエクスプローラーでプロジェクト名を右クリック→追加→既存の項目。
    usbdrv.cとusbdrvasm.Sを追加します。※デバッグ用マクロを使うならoddebug.cも。
  5. AVR Studioのソリューションエクスプローラーでプロジェクト名を右クリック→プロパティ。
    下記のように設定します。
Cのモジュールに対する設定 [AVR/GNU C Compiler]
[Symbols]
F_CPU=12000000UL ※12MHzの例。実際は製作するデバイスに合わせた値で。
[Directories]
プロジェクト内のusbdrvディレクトリを指定する。「../usbdrv」のように設定される。
[Optimization](指定は任意)
-Os

アセンブラのモジュールに対する設定 [AVR/GNU Assembler]
[General]
プロジェクト内のusbdrvディレクトリを指定する。「../usbdrv」のように設定される。
[Symbols]
F_CPU=12000000UL ※12MHzの例。実際は製作するデバイスに合わせた値で。

usbconfig.hを書き換える
ダウンロードしたV-USBのモジュールのvusb-20120109/examplesフォルダにサンプルデバイスがいくつか入っています。このうち、目的のものに近い「hid-data」を改造する形で進めることにします。素(す)のusbconfig.hを書き換えるより分かりやすいからです。なお、V-USBを組み込むAVRはATmega88Pとしました。

製作するデバイスに合わせ、hid-dataのusbconfig.hの設定内容を変更します。

/* ---------------------------- Hardware Config ---------------------------- */
#define USB_CFG_IOPORTNAME D
#define USB_CFG_DMINUS_BIT 3
#define USB_CFG_DPLUS_BIT 2
V-USBの標準回路に従い、USBのD-線をPD3に、D+線をPD2に接続しました。
もちろん実際に組み立てたデバイスもこの通りの配線です。

/* --------------------------- Functional Range ---------------------------- */
#define USB_CFG_HAVE_INTRIN_ENDPOINT 1
インタラプト転送を利用する場合は「1」にします。コントロール転送だけを利用する場合は「0」にします。

/* --------------------------- Functional Range ---------------------------- */
#define USB_CFG_LONG_TRANSFERS 1
コントロール転送で一度に254byteより大きなサイズのデータを転送する場合は「1」にします。
デフォルトは「0」、254byteまでの転送となっています。

/* -------------------------- Device Description --------------------------- */
//#define USB_CFG_VENDOR_NAME 'o', 'b', 'd', 'e', 'v', '.', 'a', 't'
//#define USB_CFG_VENDOR_NAME_LEN 8

#define USB_CFG_VENDOR_NAME 'm', 'y', 's', 'i', 't', 'e', '.', 'c', 'o', 'm'
#define USB_CFG_VENDOR_NAME_LEN 10
* ALWAYS define a vendor name containing your Internet domain name if you use
* obdev's free shared VID/PID pair. See the file USB-IDs-for-free.txt for
* details.
ルールに則ってVenderID/ProductIDペアを使用するならば、ベンダー名(デバイスの製造者)を自サイトのドメイン名に変更することができます。ベンダー名はホスト側のアプリでデバイス情報を取得すると見ることができます。
レンタルサイトであればサブドメイン名まで必要でしょう。URLを「http://」からフルに書けば間違いありません。サイトやブログがない人はe-mailアドレスを書きます。詳細はusbdrvフォルダにあるUSB-IDs-for-free.txtを読んでください。

/* -------------------------- Device Description --------------------------- */
/*#define USB_CFG_SERIAL_NUMBER 'N', 'o', 'n', 'e' */
/*#define USB_CFG_SERIAL_NUMBER_LEN 0 */

#define USB_CFG_SERIAL_NUMBER 'A', 'B', '1', '2', '3', '4', '5'
#define USB_CFG_SERIAL_NUMBER_LEN 7
複数個の同種デバイスをPCに接続するとします。例えばこのhid-dataを3個作ってPCに接続します。このときVenderID/ProductIDはどれも同じなので、個別に認識することができません。そこでデバイスごとに異なるシリアル番号を埋め込みます。「AB001」「AB002」「AB003」のようにです。
ホスト側のアプリはベンダーID/プロダクトIDに加え、シリアル番号まで見て個々のデバイスを判別します。
シリアル番号は数字だけでなくアルファベットも使えます。

/* -------------------------- Device Description --------------------------- */
#define USB_CFG_HID_REPORT_DESCRIPTOR_LENGTH 22
デバイスのmain.cに記述されているレポートデスクリプタusbHidReportDescriptor[]の配列長を記述します。
デバイスの開発中、入出力仕様の変更に伴い、頻繁に書き換えることになると思います。書き換えを忘れるとデバイスにアクセスできません。注意してください。

デバイスの動作を確認する
AVR Studioでプロジェクトをビルドし、AVR(ここではATmega88P)に書き込みます。ヒューズビットの設定はサンプルデバイスhid-dataのMakefileファイルに記述されています。

サンプルデバイスのフォルダには、コマンドラインで動作する「hidtool」というホスト側アプリも入っています。このアプリの実行には「libusb」が必要です。しかし自分はワケあってlibusbを導入せず、同様のライブラリ、および、hidtoolの代わりとなるホスト側アプリをC#で作成しました。※libusb…USBデバイスにアクセスするための、ホスト側アプリのライブラリ。

hid-dataは128byteまでの任意のデータ(バイト配列)をホスト(PC)とデバイス(hid-data)間でやりとりできます。
ホストからデータを送るとデバイスはそれをEEPROMに保存します。ホストが読み出しを要求すると、デバイスはEEPROMからデータを読み出し、返します。
上記自作ツールを用いて、これらの動作が正常に行われることを確認しました。

転送方向/レポートタイプ/サポートツール
ビルド、実行、動作確認ができたところで、V-USBの動作内容を見てみます。
V-USBのソースusbdrv.c/.hを読むにあたり、気を付けなければならない重要なポイントがあります。

USBデバイスにおけるデータ転送の方向を表す言葉INPUT/OUTPUTは、常にホスト(PC)から見た方向です。
USBデバイスは自発的に行動を起こすことはできず、いつでもホストからの命令を受けて行動を起こすもの、という決まりがあります。
  • INPUTはホスト(PC)に入る方向 … ホストデバイス
  • OUTPUTは ホスト(PC)から出る方向 … ホストデバイス
ホストとデバイス間でやりとりするデータのことをレポートと呼びます。レポートには次の3タイプあります。
  • Feature Reports …デバイスのコンフィギュレーション情報を指定するレポート。エンドポイント0を使用する。
  • Input Reports …ホストへ入力するレポート。V-USBは入力方向のインタラプト転送にエンドポイント1を使用する。
  • Output Reports …ホストから出力するレポート。
HIDデバイスでは「レポートデスクリプタ」を記述してデータ転送の種類を設定します。
USB規格でビットごとに意味づけられた値を、様々な項目ごとに設定するのですが、手作業ではやってられません。USBのサイトで公開しているサポートツールを利用します。本記事公開時点でのファイル名はdt2_4.zipでした。
HID Descriptor Tool http://www.usb.org/developers/hidpage/

データ転送の流れとデータのゲット/セット場所について
デバイスは、ホストから受信するデータとホストへ送信するデータを、それぞれどの関数でゲット/セットするのか。
ホストからデバイスへレポートがリクエストされると、受信処理の関数からusbFunctionSetup()が呼び出されます。
そこで戻り値にUSB_NO_MSGを返すと、続けてusbFunctionRead(), usbFunctionWrite()が呼び出されます。
以降、ホストから要求されたデータ長に達するまで(今度はusbFunctionSetup()を経由せずに)usbFunctionRead(), usbFunctionWrite()が呼び出されます。
よって、データのゲット/セットはusbFunctionRead(), usbFunctionWrite()で行います。これらはそのための関数です。
usbFunctionRead(), usbFunctionWrite()で使う変数の初期化はusbFunctionSetup()で行います。
[usbdrv.h]
USB_PUBLIC usbMsgLen_t usbFunctionSetup(uchar data[8]);
* If the SETUP indicates a control-in transfer, you should provide the
* requested data to the driver. There are two ways to transfer this data:
* (1) Set the global pointer 'usbMsgPtr' to the base of the static RAM data
* block and return the length of the data in 'usbFunctionSetup()'. The driver
* will handle the rest. Or (2) return USB_NO_MSG in 'usbFunctionSetup()'. The
* driver will then call 'usbFunctionRead()' when data is needed. See the
* documentation for usbFunctionRead() for details.

usbFunctionSetup()ではレポートタイプ(feature/input/output)とレポートIDが判別できるので、データのゲット先のバッファ/セット元のバッファを振り分けることができます。
[main.c] usbFunctionSetup()の記述例
usbMsgLen_t usbFunctionSetup(uchar data[8])
{
 usbRequest_t *rq = (void *)data; /* wValue: ReportType (highbyte:[1]), ReportID (lowbyte:[0]) */
 (略)
 uint8_t reportType = rq->wValue.bytes[1]; //ReportType: input(1),output(2),feature(3)
 uint8_t reportID = rq->wValue.bytes[0]; //ReportID: 0,1,2,..
 (略)
}

データ送受信のサイズと戻り値について
ホスト(PC)とV-USBデバイス間でやりとりするデータをレポートと呼びます。
データの最小単位はbyteではなくbitです。例えばボタンデバイスはon/offで1ビットあれば足ります。
1byte単位でデータを扱いたい場合、8bitを1個のデータとして定義すればよいです。
データ(レポート)の詳細はレポートデスクリプタで定義します。
例えばサンプルデバイスhid-dataは、main.c内のusbHidReportDescriptor[]で次のように定義しています。
  • 項目 REPORT_SIZE (8) …1個のデータは8ビット
  • 項目 REPORT_COUNT (128) …全体サイズはデータ128個分
よって、レポート(データ配列)の大きさは 8bit * 128個 = 128byte

usbdrv.c内部の送受信バッファは8byteです。※usbTxBuf[]とusbRxBuf[]。受信枠は2倍サイズで確保している。
デバイスのプログラム内(上記例ではhid-data/main.c内)のusbFunctionRead(),usbFunctionWrite()は8byteずつデータを処理することになります。※データ全長が8byteの倍数でないなら、繰り返しの最後は8byte未満。
何byte送受信したか自前でカウントし、データ全長分を処理したかどうかを自前で判断します。

uchar usbFunctionRead(uchar *data, uchar len) の戻り値
引数'len'はバッファ'data[]'へ格納できるbyte数で、戻り値は実際にdata[]へ格納したbyte数です。true/false的な値ではなく処理したbyte数を返すのです。従って戻り値はlen以下の値となります。
lenの値を累積し、これまでに何バイト処理したか把握し、データの全体サイズを超えて処理しないようにします。

uchar usbFunctionWrite(uchar *data, uchar len) の戻り値
引数'len'はバッファ'data[]'から取り出せるbyte数です。自前で用意したSRAM上のバッファに格納するなど処理します。そしてlenの値を累積し、データの全体サイズに達したか(データを全て受け取ったか)判断します。
全てのデータを受け取ったとき、非0の値(true)を返します。まだ続きのデータがあるとき、0(false)を返します。

ホスト側は、デバイス内でデータが8byteずつ区切られていることを意識する必要はありません。
HidD_SetFeature(),HidD_GetFeature()でデータ全体を1回で送受信すればよいです。
HidD_GetInputReport(),HidD_SetOutputReport()も同様です。※これら4つはWindows標準のhid.dllに含まれている関数。

GetIdol/SetIdolリクエストについて
インタラプト転送の時間間隔はusbconfig.hで次のように定義します。
例: #define USB_CFG_INTR_POLL_INTERVAL 100 //100ms

基本的にこの時間間隔で、ホストがデバイスに「データがあるなら送信せよ」と命令を送ります。
しかしこの値とは別に、ホスト側が指定した時間間隔でインタラプト転送を行う場合があります。デバイスはUSB_CFG_INTR_POLL_INTERVALの値とは関係なく、転送を一定時間待つことになります。
この仕組みはキーボードデバイスではサポート必須です。通常、キーボードデバイスではホスト(PCのOS)が値を指定してきます。この値を取得/設定するのがGetIdol/SetIdolリクエストです。

[main.c] usbFunctionSetup()の記述例
(前半省略)
}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];
}

キーボードデバイスの場合、瞬間ごとにキーの押下状態が変わるわけではないので、ホスト側の判断によってアイドル時間を長くすることがあります。
マウスデバイスの場合、カーソルの俊敏な動きを捕捉するため、USBRQ_HID_SET/GET_IDLE処理は必要ありません。
USB_CFG_INTR_POLL_INTERVALで指定したインターバル間隔でデータが転送されます。
その他、hid-dataのようなデバイスでもUSBRQ_HID_SET/GET_IDLE処理は必要ありません。

データ転送の種類と処理間隔について
V-USBはコントロール転送とインタラプト転送をサポートします。
USBロースピードデバイスでのバルク転送は禁止されています。V-USB的にも負荷が大きいようです。
[usbdrv.h]
Please note that the USB standard forbids bulk endpoints for low speed devices!
Most operating systems allow them anyway, but the AVR will spend 90% of the CPU
time in the USB interrupt polling for bulk data.

サンプルデバイスhid-dataはコントロール転送を利用して128byteのデータを送受信します。
USBメモリのようなデバイスをイメージしてデータをEEPROMに保存しています。
EEPROMへのアクセスのように時間がかかる処理をするときは注意が必要です。
デバイス内でどのような処理をしようともusbPoll()を50ms弱以内の間隔で呼び出す必要があります。
[usbdrv.h]
USB_PUBLIC void usbPoll(void);
/* This function must be called at regular intervals from the main loop.
* Maximum delay between calls is somewhat less than 50ms (USB timeout for
* accepting a Setup message). Otherwise the device will not be recognized.
* Please note that debug outputs through the UART take ~ 0.5ms per byte
* at 19200 bps.
*/

大きなサイズのデータを転送するには(1)
通常はSRAM上にバッファを確保してデータを処理することになります。
ATmega88PのSRAMサイズは1KB(1024byte)です。ATmega328Pだと2KB(2048byte)あります。
ここで、ATmega328Pを使い、SRAMにバッファを確保するとして、ホスト(PC)とV-USBデバイス間で1KB(1024byte)のデータを送受信することを考えてみます。

コントロール転送を利用する場合
V-USBのデフォルトでは、コントロール転送によるデータサイズは254byteまでです。
255byte以上を指定してはいけません。255はUSB_NO_MSGの定義値として使用されています。
[usbdrv.h]
USB_PUBLIC void usbPoll(void);
[usbdrv.h]
Maximum data payload:
Data payload of control in and out transfers may be up to 254 bytes. In order
to accept payload data of out transfers, you need to implement
'usbFunctionWrite()'.

hid-dataのレポートデスクリプタを書き換えて実験したところ、254byte正常に転送できました。
※hid-dataはusbFunctionSetup()に'128'とハードコーディングしているので注意。そこも'254'に修正する。

254byteより大きくすることもできます。ただし16384byteまでです。
この場合、USB_NO_MSGは'sizeof(unsigned)-1'として定義されています。
[usbconfig.h]
#define USB_CFG_LONG_TRANSFERS 0
/* Define this to 1 if you want to send/receive blocks of more than 254 bytes
* in a single control-in or control-out transfer. Note that the capability
* for long transfers increases the driver size.
*/
[usbconfig.h]
#if USB_CFG_LONG_TRANSFERS /* if more than 254 bytes transfer size required */
# define usbMsgLen_t unsigned
#else
# define usbMsgLen_t uchar
#endif
/* usbMsgLen_t is the data type used for transfer lengths. By default, it is
* defined to uchar, allowing a maximum of 254 bytes (255 is reserved for
* USB_NO_MSG below). If the usbconfig.h defines USB_CFG_LONG_TRANSFERS to 1,
* a 16 bit data type is used, allowing up to 16384 bytes (the rest is used
* for flags in the descriptor configuration).
*/

以上より、1024bytのデータを転送するには次のようにします。
  • usbconfig.h の USB_CFG_LONG_TRANSFERS を 1 にする。
  • レポートデスクリプタの定義 usbHidReportDescriptor[] で REPORT_COUNT を 0x0400 (1024) にする。
hid-dataではこのようになります。
0x95, 0x80, //REPORT_COUNT (128)

0x96, 0x00, 0x04, //REPORT_COUNT (1024) ※2byteでLo-Hiの順。※先頭の値も変わる。
usbHidReportDescriptor[22]

usbHidReportDescriptor[23] ※レポートデスクリプタのサイズが1byte増える。

大きなサイズのデータを転送するには(2)
インタラプト転送を利用する場合
レポートデスクリプタに項目[INPUT]関係を記述します。
例:
0x09, 0x00, //USAGE (Undefined)
0x75, 0x08, //REPORT_SIZE (8) …データ1個は8ビット(1バイト)
0x95, 0x08, //REPORT_COUNT (8) …データ個数は8個(計8バイト)
0x81, 0x02, //INPUT (Data,Var,Abs) …意味的にはBuf属性を付けた方がよいかもしれない

インタラプト転送するにはデバイスのmain.cのメインループ内で、usbPoll()の後にusbSetInterrupt()をコールする必要があります。インタラプト転送による一度の転送は8byteまでです。それより大きなデータを転送したいならコントロール転送を使えとのこと。
[usbdrv.h]
USB_PUBLIC void usbSetInterrupt(uchar *data, uchar len);
/* This function sets the message which will be sent during the next interrupt
* IN transfer. The message is copied to an internal buffer and must not exceed
* a length of 8 bytes. The message may be 0 bytes long just to indicate the
* interrupt status to the host.
* If you need to transfer more bytes, use a control read after the interrupt.
*/

以上より、インタラプト転送で大きなサイズのデータを転送するには? → 向いていない。

レポートタイプの割り当て例
レポートデスクリプタを記述することにより、様々なデータ転送のパターン(入出力の方向やデータサイズ)を組み込むことができます。以下に、実際にやりそうな例を示します。いずれも実験して動作確認できています。

(A) インタラプト転送(IN)を使用する
feature: 128byte
input: 4byte
前出のコメント通り、インタラプト転送は8byte以内とします。ここでは仮に4byte。
8byteを超えるサイズをインタラプト転送することもできますが(実験済み)、助言に従うことにします。

(B) サイズ別/目的別に分けたい
コマンド(小サイズ:16byte)とデータ(大サイズ:512byte)のように使う例。
feature reportID=1: 16byte
feature reportID=2: 512byte
一番大きなサイズ1つを定義して全ての役割で共用してもよいですが、数バイト転送したいだけのときに何百バイトものデータを転送するのは無駄です。レポートIDを付与するとサイズ違いの同種レポートを設定することができます。

(C) 複雑な組み合わせ
入出力方向と各サイズを細かく設定する例。
コマンド(小サイズ:16byte)をホストからデバイスへ出力。
データ(大サイズ:256byte)をホスト-デバイス間で入出力。
ステータス(8byte)をデバイスからホストへ随時通知。
下記のように割り当てたところ、目的の動作をしませんでした。インタラプト転送が発生しませんでした(ホストのReadFile()に値が来ない)。※レポートデスクリプタの書き方が悪かったのかもしれないし、自分が考え違いをしているのかもしれない。
feature reportID=1: 16byte
feature reportID=2: 256byte
input: 8byte
※inputに適当なレポートIDを付与しても動作は変わらず。

下記のように割り当てたところ、目的の動作をしました。
input: 8byte …ホストへの入力専用
output: 16byte …ホストからの出力専用
feature: 256byte …ホスト-デバイス間で双方向

V-USBは込み入ったレポートIDの使用には対応しきれないのかもしれません(?) 次のコメントはhid-dataについての話なのか、V-USBとしてそういうつもりで開発してきたということなのか、不明です。
[hid-data/main.c/usbHidReportDescriptor[]]
/* Since we define only one feature report, we don't use report-IDs (which
* would be the first byte of the report).



レポートデスクリプタの記述例(番号は上記パターンに対応)
(A)
PROGMEM char usbHidReportDescriptor[14+6+6+1] = {
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)bit

0x95, 0x80, // REPORT_COUNT (128)
0x09, 0x00, // USAGE (Undefined)
0xb1, 0x02, // FEATURE (Data,Var,Abs)

0x95, 0x04, // REPORT_COUNT (4)
0x09, 0x00, // USAGE (Undefined)
0x81, 0x02, // INPUT (Data,Var,Abs)

0xc0 // END_COLLECTION
};

(B)
PROGMEM char usbHidReportDescriptor[14+8+9+1] = {
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)bit

0x85, 0x01, // REPORT_ID (1)
0x95, 0x10, // REPORT_COUNT (16)
0x09, 0x00, // USAGE (Undefined)
0xb1, 0x02, // FEATURE (Data,Var,Abs)

0x85, 0x02, // REPORT_ID (2)
0x96, 0x00, 0x02, // REPORT_COUNT (512)
0x09, 0x00, // USAGE (Undefined)
0xb1, 0x02, // FEATURE (Data,Var,Abs)

0xc0 // END_COLLECTION
};

(C)
PROGMEM char usbHidReportDescriptor[14+6+6+7+1] = {
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)bit

0x09, 0x00, // USAGE (Undefined)
0x95, 0x08, // REPORT_COUNT (8)
0x81, 0x02, // INPUT (Data,Var,Abs)

0x09, 0x00, // USAGE (Undefined)
0x95, 0x10, // REPORT_COUNT (16)
0x91, 0x02, // OUTPUT (Data,Var,Abs)

0x09, 0x00, // USAGE (Undefined)
0x96, 0x00, 0x01, // REPORT_COUNT (256)
0xb1, 0x02, // FEATURE (Data,Var,Abs)

0xc0 // END_COLLECTION
};

サンプルプログラム
「レポートタイプの割り当て例 (C) 複雑な組み合わせ」のソースを示します。
hiddev1.c usbconfig.h ※拡張子を.txtに変更してあります。
※Shift-JIS, [TAB]=4

全体ダウンロードはこちら。
hiddev1.zip ・上記ソースとMakefile
・それをATmega88P用にビルドしたhiddev1.hex
・動作確認用ホスト側アプリ(下記コンソールアプリ) ※.NET Framework3.5が必要。
2012/08 上記zipファイル更新 動作確認環境: WindowsXP(32bit)/SP3, Windows7(64bit)/SP1

動作内容
  • featureレポートで256byte送受信する。
  • outputレポートでホストからデバイスへ16byte送信する。
  • PC0〜PC5をスイッチ・オンすると(GNDとショート)、PORTCの状態がインタラプト転送でホストへ通知される。
テスト結果 (ホスト側 C#自作アプリで動作テストした結果を表示)


まず、featureレポートで256byte送受信テストしています。0x00〜0xFFを送信し、受信できたことを表しています。
次に、outputレポートで16byte送信テストしています。
続いてfeatureレポートでデータを受信し、16byte正しく送信できていたことを確認しています。
最後に、インタラプト転送(IN)の受信テストをしています。
PC2をGNDとショートした瞬間、「00 3B 22 ... 88」を受信しました。0x3B→0b00111011(PC2が0になっている)
PC2をオープンにした瞬間、「00 3F 22 ... 88」を受信しました。0x3F→0b00111111(PC2が1に戻っている)

実験に用いた回路
ATmega88P
Xtal: 12MHz + 22pF
ZD: 3.6V
RD+,RD-: 68Ω
Rpull-up: 1.5kΩ



◆ ◆ ◆
V-USBの使い方はサンプルデバイスが参考になります。これを改造・拡張する形で
大抵のものは作れそうです。より使いこなすにはUSBの知識が必要です。
と言っても「V-USBが使える程度に」と絞れば、多くを理解しなくても大丈夫です。
V-USBがUSBのコントロールをほとんどやってくれます。
HIDデバイスを作るにはレポートデスクリプタの書き方を理解する必要があります。
ですが複合デバイスのように複雑なものを作らなければ、適当でも何とかなります。


(C) 『昼夜逆転』工作室 [トップページへ戻る]
inserted by FC2 system