This page is old version(Thu May 05 04:44:31 +0900 2011)
You can To this version.
(Need insert '245' to this box. )

V-USB を使ってHIDデバイスを作る。

なぜHIDか

USBデバイスを作る時、様々な形態が考えられる。
V-USBのドキュメントによれば、例として次のような物がある。

本稿では、このうち「Custom HID class device」をオススメする。
HIDデバイスを作るメリットデメリットについても、V-USBのドキュメントに記述がある。
(訳は筆者)

このように列挙するとデメリットの方が多いように見える。
しかし、これらのデメリットはそれぞれ解決できる。
1つ目のデメリットはドキュメントにもあるように先頭にサイズを入れればよい。
2つ目のデメリット、3つ目のデメリットについては、それぞれのOSでソフトウエアを作りなおさなければならない事を意味するが、一方でほとんどのOSでのHID操作APIは(USBを生で扱うAPIに比べれば)簡単になっており、また、各OSのHIDを抽象化するライブラリを作る事も可能ではないかと思われる。
3つ目のデメリットである実装が複雑になる点については、本稿で解説を行う。

USBデバイスの作成(ソフトウエア編)

何を作るか?


後で書く

V-USBの入手

V-USBソフトウエア本体は以下からダウンロードできる。
V-USBはGPLとコマーシャルライセンスのデュアルライセンスになっている。
GPLを選択すれば無料で使う事ができる。
http://www.obdev.at/products/vusb/index.html

今回はvusb-20090822.zipをダウンロードして使用した。

プロジェクトの作成

まずはこれをコンパイルできるようにする。
私はAVR Studio 4Win-AVR 20090313を利用した。

まず、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をヘッダファイルとして追加する。

この段階で次のような構成になっている。

usbconfig.h の作成

作成するUSB機器の設定を行う usbconfig.h を作成する。
このファイルのテンプレートが usbconfig-prototype.h というファイル名でusbdrvディレクトリにあると思うのでこれをコピーして使う。
プロジェクトにも追加しておこう。


変更すべき点は次の通り

ハードウエア設定

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として準備する。
usbInterruptIsReady?()関数でUSBがデータを送れる状態にあるかを調べて、
送れるようであれば、usbSetInterrupt?()関数で送る。

usbPoll()関数は10ms以内に呼ばなければならないらしい。

usbFunctionSetup?関数

コントロールエンドポイント(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;
}

usbFunctionWrite? の実装

usbFunctionSetup?で NO_MSGを返すとusbFunctionWrite?が呼び出される。
ここで、実際のデータ処理をする。

uchar usbFunctionWrite(uchar *data, uchar len)
{
  dataを読み込んで処理する。
  return len;
}

usbFunctionWriteOut? の実装

ENDPOINT 1に来たパケットがここに来る。
処理はusbFunctionWrite?にまかせる。

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


For Me

Recent