V-USBを利用してHIDデバイスを作る
(C) Objective Development Software
2012年4月 AVRを使った製作物にV-USBを組み込もうと思いましたが、デバイス側もホスト側もソフトをどのように作ればよいのかよく分からなかったので、調べながら挑戦しました。 本記事はそのときのメモをまとめたものです。内容が偏っていたり、説明順的に読みにくいところもあると思いますが、ご了承ください。 ※HIDデバイス…ヒューマン・インターフェイス・デバイス・デバイス。気にしないでください。 【目次】
本記事は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機能を実現する技術です。
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ポートを増設してくれます。
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でした。
ファイル配置と設定
[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の設定内容を変更します。
もちろん実際に組み立てたデバイスもこの通りの配線です。
デフォルトは「0」、254byteまでの転送となっています。
レンタルサイトであればサブドメイン名まで必要でしょう。URLを「http://」からフルに書けば間違いありません。サイトやブログがない人はe-mailアドレスを書きます。詳細はusbdrvフォルダにあるUSB-IDs-for-free.txtを読んでください。
ホスト側のアプリはベンダーID/プロダクトIDに加え、シリアル番号まで見て個々のデバイスを判別します。 シリアル番号は数字だけでなくアルファベットも使えます。
デバイスの開発中、入出力仕様の変更に伴い、頻繁に書き換えることになると思います。書き換えを忘れるとデバイスにアクセスできません。注意してください。 |
デバイスの動作を確認する |
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デバイスは自発的に行動を起こすことはできず、いつでもホストからの命令を受けて行動を起こすもの、という決まりがあります。
USB規格でビットごとに意味づけられた値を、様々な項目ごとに設定するのですが、手作業ではやってられません。USBのサイトで公開しているサポートツールを利用します。本記事公開時点でのファイル名はdt2_4.zipでした。
|
データ転送の流れとデータのゲット/セット場所について | |||
デバイスは、ホストから受信するデータとホストへ送信するデータを、それぞれどの関数でゲット/セットするのか。 ホストからデバイスへレポートがリクエストされると、受信処理の関数からusbFunctionSetup()が呼び出されます。 そこで戻り値にUSB_NO_MSGを返すと、続けてusbFunctionRead(), usbFunctionWrite()が呼び出されます。 以降、ホストから要求されたデータ長に達するまで(今度はusbFunctionSetup()を経由せずに)usbFunctionRead(), usbFunctionWrite()が呼び出されます。 よって、データのゲット/セットはusbFunctionRead(), usbFunctionWrite()で行います。これらはそのための関数です。 usbFunctionRead(), usbFunctionWrite()で使う変数の初期化はusbFunctionSetup()で行います。
usbFunctionSetup()ではレポートタイプ(feature/input/output)とレポートIDが判別できるので、データのゲット先のバッファ/セット元のバッファを振り分けることができます。 [main.c] usbFunctionSetup()の記述例
|
データ送受信のサイズと戻り値について |
ホスト(PC)とV-USBデバイス間でやりとりするデータをレポートと呼びます。 データの最小単位はbyteではなくbitです。例えばボタンデバイスはon/offで1ビットあれば足ります。 1byte単位でデータを扱いたい場合、8bitを1個のデータとして定義すればよいです。 データ(レポート)の詳細はレポートデスクリプタで定義します。 例えばサンプルデバイスhid-dataは、main.c内のusbHidReportDescriptor[]で次のように定義しています。
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()の記述例
キーボードデバイスの場合、瞬間ごとにキーの押下状態が変わるわけではないので、ホスト側の判断によってアイドル時間を長くすることがあります。 マウスデバイスの場合、カーソルの俊敏な動きを捕捉するため、USBRQ_HID_SET/GET_IDLE処理は必要ありません。 USB_CFG_INTR_POLL_INTERVALで指定したインターバル間隔でデータが転送されます。 その他、hid-dataのようなデバイスでもUSBRQ_HID_SET/GET_IDLE処理は必要ありません。 |
データ転送の種類と処理間隔について | ||||
V-USBはコントロール転送とインタラプト転送をサポートします。 USBロースピードデバイスでのバルク転送は禁止されています。V-USB的にも負荷が大きいようです。
サンプルデバイスhid-dataはコントロール転送を利用して128byteのデータを送受信します。 USBメモリのようなデバイスをイメージしてデータをEEPROMに保存しています。 EEPROMへのアクセスのように時間がかかる処理をするときは注意が必要です。 デバイス内でどのような処理をしようともusbPoll()を50ms弱以内の間隔で呼び出す必要があります。
|
大きなサイズのデータを転送するには(1) | ||||||||
通常はSRAM上にバッファを確保してデータを処理することになります。 ATmega88PのSRAMサイズは1KB(1024byte)です。ATmega328Pだと2KB(2048byte)あります。 ここで、ATmega328Pを使い、SRAMにバッファを確保するとして、ホスト(PC)とV-USBデバイス間で1KB(1024byte)のデータを送受信することを考えてみます。 コントロール転送を利用する場合 V-USBのデフォルトでは、コントロール転送によるデータサイズは254byteまでです。 255byte以上を指定してはいけません。255はUSB_NO_MSGの定義値として使用されています。
hid-dataのレポートデスクリプタを書き換えて実験したところ、254byte正常に転送できました。 ※hid-dataはusbFunctionSetup()に'128'とハードコーディングしているので注意。そこも'254'に修正する。 254byteより大きくすることもできます。ただし16384byteまでです。 この場合、USB_NO_MSGは'sizeof(unsigned)-1'として定義されています。
以上より、1024bytのデータを転送するには次のようにします。
|
大きなサイズのデータを転送するには(2) | |||
インタラプト転送を利用する場合 レポートデスクリプタに項目[INPUT]関係を記述します。 例:
インタラプト転送するにはデバイスのmain.cのメインループ内で、usbPoll()の後にusbSetInterrupt()をコールする必要があります。インタラプト転送による一度の転送は8byteまでです。それより大きなデータを転送したいならコントロール転送を使えとのこと。
以上より、インタラプト転送で大きなサイズのデータを転送するには? → 向いていない。 |
レポートタイプの割り当て例 | ||||||||||||||||
レポートデスクリプタを記述することにより、様々なデータ転送のパターン(入出力の方向やデータサイズ)を組み込むことができます。以下に、実際にやりそうな例を示します。いずれも実験して動作確認できています。 (A) インタラプト転送(IN)を使用する
8byteを超えるサイズをインタラプト転送することもできますが(実験済み)、助言に従うことにします。 (B) サイズ別/目的別に分けたい コマンド(小サイズ:16byte)とデータ(大サイズ:512byte)のように使う例。
(C) 複雑な組み合わせ 入出力方向と各サイズを細かく設定する例。
下記のように割り当てたところ、目的の動作をしました。
V-USBは込み入ったレポートIDの使用には対応しきれないのかもしれません(?) 次のコメントはhid-dataについての話なのか、V-USBとしてそういうつもりで開発してきたということなのか、不明です。
レポートデスクリプタの記述例(番号は上記パターンに対応) (A)
(B)
(C)
|
サンプルプログラム | |||||
「レポートタイプの割り当て例 (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に戻っている) |
実験に用いた回路 | ||
|
◆ ◆ ◆ |
V-USBの使い方はサンプルデバイスが参考になります。これを改造・拡張する形で 大抵のものは作れそうです。より使いこなすにはUSBの知識が必要です。 と言っても「V-USBが使える程度に」と絞れば、多くを理解しなくても大丈夫です。 V-USBがUSBのコントロールをほとんどやってくれます。 HIDデバイスを作るにはレポートデスクリプタの書き方を理解する必要があります。 ですが複合デバイスのように複雑なものを作らなければ、適当でも何とかなります。 |
(C) 『昼夜逆転』工作室 | [トップページへ戻る] |