USBプロジェクト - ファームウェアに立ち返る (6) [USB]
気の向くままに、プログラムを書きなおしてみました。
割り込みハンドラ
割り込みハンドラでは、状態を優先とした分岐をやめました。 SETUP, OUT, IN のそれぞれのトランザクションにより、doSetup(void), doEp0Out(void), doEp0In(void) の三つの関数で処理をさせます。
// Check RXD at EP0 if (UIR1_RXD0F) { // any data received via EP0? if (USR0_SETUP) { // SETUP packet? switch (doSetup()) { // go to setup case ERR_STALL: forceStall(); break; } } else { // or a normal packet switch (doEp0Out()) { // receive it case ERR_STALL: forceStall(); break; } } UCR0_RX0E = 1; // Activate EP0 Receiver again } // Check TXD at EP0 if (UIR1_TXD0F) { // has EP0 sent Data? switch (doEp0In()) { case ERR_STALL: forceStall(); break; } UCR0_RX0E = 1; // Activate EP0 Receiver again }
双方とも最後にRX0E=1が入っているのは、常時SETUPトランザクションを受け入れるためです。
それぞれの関数は、エラーコードを返してくるので、割り込みハンドラ内でSTALL処理を行います。 他の種類のエラー処理があるかも知れませんが、今はこれだけです。
//---------------------------------------------------------------------------- // Error code //---------------------------------------------------------------------------- #define ERR_NONE (0) #define ERR_STALL (1)
SETUPトランザクションの処理
doSetup(void)では、リクエスト・コードに従って、処理関数を呼び出します。 現在は、SET_ADDRESSリクエストだけ実装しています。
//---------------------------------------------------------------------------- // Process for SETUP transaction. //---------------------------------------------------------------------------- byte doSetup(void) { UIR2_RXD0FR = 1; // Acknowledge UCR0_RX0E = 0; // Turn off EP0 receiver copySetupBuffer(); // Copy SETUP parameters if(USR0_RP0SIZ != 8) { // SETUP Transaction must be Size=8 return ERR_STALL; // otherwise we have an Error Condition } switch (setupBuffer.bmRequestType & 0x60) { case 0x00: // Standard Request Decoder: switch (setupBuffer.bRequest) { case SET_ADDRESS: // 5 return setAddressSetup(); default: return ERR_STALL; } break; case 0x20: // Class Request Decoder: switch (setupBuffer.bRequest) { default: return ERR_STALL; } break; default: // Unsupported Request return ERR_STALL; } }
SET_ADDRESSリクエストのSETUP
SET_ADDRESSリクエストのSETUPトランザクションに対する処理を行います。
//---------------------------------------------------------------------------- // SET_ADDRESS Standard Device Request Handler // called by SETUP handler. // according to USB2.0 spec page 256 //---------------------------------------------------------------------------- byte setAddressSetup(void) { // Confirm STATE switch (usbState) { case US_DEFAULT: case US_ADDRESSED: break; default: return ERR_STALL; } // SET_ADDRESS transaction validation if (setupBuffer.wIndex != 0) return ERR_STALL; if (setupBuffer.wLength != 0) return ERR_STALL; if (setupBuffer.wValue >= 128) return ERR_STALL; // configure endpoint #0 to send an empty DATA1 // at the next IN Transaction UCR0 = 0b10100000; // ||||++++-- TP0SIZ = 0 reset packet size register // |||+------ RX0E = 0 disable EP0 receiver // ||+------- TX0E = 1 enable EP0 transmitter // |+-------- Reserved (0) // +--------- T0SEQ = 1 set TX sequence bit }
最初に状態を確認します。 DEFAULTおよびADDRESSED状態i以外の時の処理は、"not specified"(不定)なので、STALLを返すことにしました。
次にSETUPパラメータの値を確認します。 パラメータに誤りがあったら、STALLを返します。
INトランザクションの処理
INトランザクションは、doEp0In(void)関数で処理されます。 まだ、SET_ADDRESSリクエストのステータス・ステージの処理しか実装されていません。
//---------------------------------------------------------------------------- // Process for EP0-IN transaction. //---------------------------------------------------------------------------- byte doEp0In(void) { UIR2_TXD0FR = 1; // Acknowledge UCR0_TX0E = 0; // Turn off EP0 transmitter switch (setupBuffer.bmRequestType & 0x60) { case 0x00: // Standard Request switch (setupBuffer.bRequest) { case SET_ADDRESS: // 5 return setAddressStatus(); default: return ERR_STALL; } break; case 0x20: // Class Request switch (setupBuffer.bRequest) { default: return ERR_STALL; } break; default: // Unsupported Request return ERR_STALL; } }
SET_ADDRESSリクエストのSTATUS
ここだけは、ほとんど変わっていません。 ステータス・ステージが終了した事実が、SET_ADDRESSリクエストが正しく受信できたことを意味しているので、この関数ではエラーは発生しないはずです。
//---------------------------------------------------------------------------- // SET_ADDRESS Standard Device Request Handler // called by STATUS handler. // according to USB2.0 spec page 256 //---------------------------------------------------------------------------- byte setAddressStatus(void) { // A STATUS transaction was received. // Set the USB address. UADDR = (byte)setupBuffer.wValue | UADDR_USBEN_MASK; if (setupBuffer.wValue == 0) { // make a transition to DEFAULT usbState = US_DEFAULT; } else { // make a transition to ADDRESSED usbState = US_ADDRESSED; } // Reset the request code. setupBuffer.bRequest = REQUEST_COMPLETE; return ERR_NONE; }
今日のところは、こんな気分です。 明日は、また、変わるかも。
付録 : USBプロジェクト索引
- USBプロジェクト - ファームウェアに立ち返る (1)
- USBプロジェクト - ファームウェアに立ち返る (2)
- USBプロジェクト - ファームウェアに立ち返る (3)
- USBプロジェクト - ファームウェアに立ち返る (4)
- USBプロジェクト - ファームウェアに立ち返る (5)
- USBプロジェクト - ファームウェアに立ち返る (6)
- USBプロジェクト - ファームウェアに立ち返る (7)
- USBプロジェクト - ファームウェアに立ち返る (8)
- USBプロジェクト - ファームウェアに立ち返る (9)
- USBプロジェクト - ファームウェアに立ち返る (10)
- USBプロジェクト - ファームウェアに立ち返る (11)
- USBプロジェクト - ファームウェアに立ち返る (12)
- USBプロジェクト - ファームウェアに立ち返る (13)
- USBプロジェクト - HIDデバイス(1)
- USBプロジェクト - HIDデバイス(2)
- USBプロジェクト - HIDデバイス(3)
- USBプロジェクト - 複合デバイスを考えた
- USBプロジェクト - HIDデバイス(4)
- USBプロジェクト - HIDデバイス(5)
参考文献
USBハード&ソフト開発のすべて―USBコントローラの使い方からWindows/Linuxドライバの作成まで (TECHI―Bus Interface)
- 作者: インターフェース編集部
- 出版社/メーカー: CQ出版
- 発売日: 2006/07
- メディア: 単行本
USBターゲット機器開発のすべて―各種USBコントローラの使い方と基本ソフトウェアの作成法 (TECHI―Bus Interface)
- 作者:
- 出版社/メーカー: CQ出版
- 発売日: 2005/08
- メディア: 単行本
USBプロジェクト - ファームウェアに立ち返る (5) [USB]
正しいHIDデバイスを追い求めるシリーズ、五の段です。
SET_ADDRESSリクエストの処理
バス・リセットの次は、SET_ADDRESSリクエストが来るはずです。 このリクエストは、エンド・ポイント0にSETUPトランザクションとして届きます。 USBデバイスの状態に応じて、USB割り込みハンドラから以下の関数を呼び出します。
//---------------------------------------------------------------------------- // Process for Default state. //---------------------------------------------------------------------------- void doDefaultStateProc(void) { if(UIR1_RXD0F) { // any data received via EP0? UIR2_RXD0FR = 1; // Acknowledge UCR0_RX0E = 0; // Turn off EP0 receiver if(USR0_SETUP) { // SETUP packet? copySetupBuffer(); // Copy SETUP parameters if(USR0_RP0SIZ != 8) { // SETUP Transaction must be Size=8 forceStall(); // otherwise we have an Error Condition } else { if((setupBuffer.bmRequestType & 0x60) == 0) { // Standard Request Decoder: switch(setupBuffer.bRequest) { case SET_ADDRESS: // 5 setAddressSetup(); break; default: forceStall(); break; } } else { forceStall(); // Unexpected request } } } else { // or a normal packet forceStall(); // Unexpected transaction } UCR0_RX0E = 1; // Activate EP0 Receiver again } if(UIR1_TXD0F) { // has EP0 sent Data? UIR2_TXD0FR = 1; // Acknowledge UCR0_TX0E = 0; // Turn off EP0 transmitter setAddressStatus(); // Expected SET_ADDRESS } }
受信したSET_ADDRESSに対する処理は、setAddressSetup(void)関数で行われます。 続くステータス・ステージに備える処理もsetAddressSetup(void)関数で記述されています。
DEFAULT状態では、SET_ADDRESSリクエストしか期待されていません。 このため、他のリクエストおよび、SETUPトランザクション以外は、不正なトランザクションとみなされます。 その場合、STALLによる応答をすべく、forceStall(void)が呼び出されます。
//---------------------------------------------------------------------------- // Force STALL Condition for EP0 (both IN and OUT) // as a Response to an invalid SETUP Request // Flags will be auto-cleared by the next SETUP Token //---------------------------------------------------------------------------- void forceStall(void) { UCR3_OSTALL0 = 1; UCR3_ISTALL0 = 1; }
forceStall(void)関数は、ステータス・ステージのハンドシェイク・パケットとしてSTALL送るための設定を行います。 このマイコンには、RX0E=0としておくと、ハンドシェイク・パケットとして、NAKを返すという機能もあります。 サンプル・プログラムを見る限りでは、どうやらSTALL設定の方が優先されているようです。
EP0は、いつでもSETUPトランザクションを受け付けられるように、RX0E=1として受信機を有効にしています。 本当に、SETUPトランザクションが続けて発行されるような事態がありうるのだろうか?
SETUPトランザクションが続けてくる事態は、正しく実装されたホストでは、起こりえないそうです。 ただし、ホスト側にバグがあった場合に備える必要はありそうです。 もし、予期せずにSETUPトランザクションが届いたら、処理中のSETUPトランザクションを破棄するのが正しい実装だそうです。
DEFAULT状態では、SETUPステージの後のステータス・トランザクションの処理も行います。 ステータス・トランザクションが終了したら、USBアドレスを更新して、状態を遷移させます。 実際の処理は、setAddressStatus(void)で記述されます。
割り込みハンドラのディスパッチャ
一方、割り込みハンドラのディスパッチャは、このようになります。
switch (usbState) { case US_POWERED: case US_DEFAULT: doDefaultStateProc(); break; case US_ADDRESSED: case US_CONFIGURED: case US_SUSPENDED: default: ; }
まだ、DEFAULT状態以外の処理は、未実装です。
doDefaultStateProc(void)関数は、SET_ADDRESSリクエストにのみ反応しますが、GET_DESCRIPTORリクエストは、DEFAULT状態でも有効なリクエストなので、修正が必要です。 他のリクエストについても調べなくては。
このプログラムでは、状態による分岐を優先する書き方をしてみました。 どうしても状態コードとリクエスト・コードのマトリックス条件で処理を分けなくてはならないので、分岐が複雑になってしまいます。 もうちょっと、スマートにならないかな。
USB2.0の仕様書を読んでみると、リクエストごとに各状態で行うべき動作が記述されておりました。 どうも、仕様書と見比べてプログラムを作る分には、これに従ったほうが良さそうです。 次回、方針変更をする予定です。
SET_ADDRESS処理関数
SETUPトランザクションの終了時に実行される処理は、setAddressSetup(void)に記述されます。
//---------------------------------------------------------------------------- // SET_ADDRESS Standard Device Request Handler // called by SETUP handler. // according to USB2.0 spec page 256 //---------------------------------------------------------------------------- void setAddressSetup() { // SET_ADDRESS transaction validation if ( (setupBuffer.wIndex == 0) && (setupBuffer.wLength == 0) && (setupBuffer.wValue < 128) ) { // configure endpoint #0 to send an empty DATA1 // at the next IN Transaction UCR0 = 0b10100000; // ||||++++-- TP0SIZ = 0 reset packet size register // |||+------ RX0E = 0 disable EP0 receiver // ||+------- TX0E = 1 enable EP0 transmitter // |+-------- Reserved (0) // +--------- T0SEQ = 1 set TX sequence bit } else { // response for illegal SETUP packet. forceStall(); } }
まず、SETUPパケットで送信されたパラメータが正しい値であるかどうかを比較します。 SET_ADDRESSは、wValueに7ビットのUSBアドレスが入り、wIndexとwLengthには、0が入ります。 もし、入っている値が範囲外であれば、forceStall(void)で処理が正常に終了できなかったことを示します。
値が正しいことが確認されたら、次に発生するステータス・トランザクションに備えて、EP0送信機を起動します。
ステータス・トランザクションの終わりが、コントロール転送の無事終了を示しています。 SET_ADDRESSリクエストは、ここで初めて終了するので、ホストから指定されたUSBアドレスを設定することが出来ます。
//---------------------------------------------------------------------------- // SET_ADDRESS Standard Device Request Handler // called by STATUS handler. // according to USB2.0 spec page 256 //---------------------------------------------------------------------------- void setAddressStatus(void) { // A STATUS transaction was received. // Set the USB address. UADDR = (byte)setupBuffer.wValue | UADDR_USBEN_MASK; if (setupBuffer.wValue == 0) { // make a transition to DEFAULT usbState = US_DEFAULT; } else { // make a transition to ADDRESSED usbState = US_ADDRESSED; } // Reset the request code. setupBuffer.bRequest = REQUEST_COMPLETE; // Activate EP0 Receiver for next request. UCR0_RX0E = 1; }
アドレスとして、"0"が指定されていたら、DEFAULT状態に戻ります。 また、"0"以外の値が指定されていたら、ADDRESSED状態に進みます。 そして、次に発行されるSET_CONFIGURATIONリクエストを受信すべく、EO0受信機をイネーブルにします。
アドレスの変更がステータス・ステージまで待たされる理由は、USB2.0仕様書の256ページに書いてあります。 もし、SETUPステージであわててアドレスを変更したら、続くステータス・トランザクションは、アドレスが異なるために通信することができません。 かならず、トランスファが終了してからアドレスを変更しなくてはなりません。
setAddressStatus(void)が呼び出された後は、 次のセットアップ・トランザクションを待つためにRX0E=1としなくてはならないはずです。 ところが、サンプル・プログラムでもsetAddress(void)でEP0受信機がディセーブルされて以来、イネーブルされた形跡がありません。 何で、動いているように見えるんだろう?
私の見落としでした。 doDefaultStateProc(void)関数の最後でRX0E=1としておりました。
SETUPバッファ構造体に関する方針変更
どうもプログラムを書きにくいので、方針変更し、SetupBuffer構造体を変更しました。
//---------------------------------------------------------------------------- // Structure of Setup Packet sent during // SETUP Stage of Standard Device Requests // according to USB2.0 spec page 248 //---------------------------------------------------------------------------- typedef struct { byte bmRequestType; // Characteristics (Direction,Type,Recipient) byte bRequest; // Standard Request Code word wValue; // Value Field word wIndex; // Index or Offset Field word wLength; // Number of Bytes to transfer (Data Stage) } SetupBuffer;
従来、使用されていたiword構造体を廃止して、16-bit符号無し整数wordを使っています。 エンディアンの変更は、エンド・ポイントをバッファにコピーする時に行っています。
//---------------------------------------------------------------------------- // Copy SETUP packet into buffer. // Intel words are converted into MOTOROLA words. //---------------------------------------------------------------------------- void copySetupBuffer(void) { setupBuffer.bmRequestType = UE0D0; setupBuffer.bRequest = UE0D1; setupBuffer.wValue = (UE0D3 << 8) | UE0D2; setupBuffer.wIndex = (UE0D5 << 8) | UE0D4; setupBuffer.wLength = (UE0D7 << 8) | UE0D6; }
HC08のような純粋な8-bitマイコンに16-bit整数を扱わせるのは酷なんだけど、コンパイラにがんばってもらいましょう。
2008-03-24 23:14 コメントでの指摘事項および情報を追加。
付録 : USBプロジェクト索引
- USBプロジェクト - ファームウェアに立ち返る (1)
- USBプロジェクト - ファームウェアに立ち返る (2)
- USBプロジェクト - ファームウェアに立ち返る (3)
- USBプロジェクト - ファームウェアに立ち返る (4)
- USBプロジェクト - ファームウェアに立ち返る (5)
- USBプロジェクト - ファームウェアに立ち返る (6)
- USBプロジェクト - ファームウェアに立ち返る (7)
- USBプロジェクト - ファームウェアに立ち返る (8)
- USBプロジェクト - ファームウェアに立ち返る (9)
- USBプロジェクト - ファームウェアに立ち返る (10)
- USBプロジェクト - ファームウェアに立ち返る (11)
- USBプロジェクト - ファームウェアに立ち返る (12)
- USBプロジェクト - ファームウェアに立ち返る (13)
- USBプロジェクト - HIDデバイス(1)
- USBプロジェクト - HIDデバイス(2)
- USBプロジェクト - HIDデバイス(3)
- USBプロジェクト - 複合デバイスを考えた
- USBプロジェクト - HIDデバイス(4)
- USBプロジェクト - HIDデバイス(5)
参考文献
USBハード&ソフト開発のすべて―USBコントローラの使い方からWindows/Linuxドライバの作成まで (TECHI―Bus Interface)
- 作者: インターフェース編集部
- 出版社/メーカー: CQ出版
- 発売日: 2006/07
- メディア: 単行本
USBターゲット機器開発のすべて―各種USBコントローラの使い方と基本ソフトウェアの作成法 (TECHI―Bus Interface)
- 作者:
- 出版社/メーカー: CQ出版
- 発売日: 2005/08
- メディア: 単行本
USBプロジェクト - ファームウェアに立ち返る (4) [USB]
正しいHIDデバイスを追い求めるシリーズ、四の段です。
電源投入後の状態設定
USBデバイスは、到着するトランザクションに従って、状態を刻々と変化させて処理を行います。 そのため、プログラムは、ソフトウェアで実現したステートマシンとして動作します。 ステートマシンは、電源投入後にある特定の状態に入ります。 この状態を設定するのが、initUSB(void)という関数です。
//---------------------------------------------------------------------------- // Make a transition to powered but not reset yet. //---------------------------------------------------------------------------- void initUsb() { // set default address UADDR = 0; // disable Interrupts UIR0 = 0b00000000; // |||||||+-- RXD0IE = 0 disable RXD0 interrupt // ||||||+--- TXD0IE = 0 disable TXD0 interrupt // |||||+---- Reserved (0) // ||||+----- TXD1IE = 0 disable TXD1 interrupt // |||+------ RXD2IE = 0 disable RXD2 interrupt // ||+------- TXD2IE = 0 disable TXD2 interrupt // |+-------- SUSPEND = 0 disable Suspend function // +--------- EOPIE = 0 disable EOP interrupt // clear all Flags in UIR1 UIR2 = 0b11111111; // |||||||+-- RXD0FR = 0 clear RXD0 flag // ||||||+--- TXD0FR = 0 clear TXD0 flag // |||||+---- RESUMEFR = 1 clear RESUME flag // ||||+----- TXD1FR = 1 clear TXD1 flag // |||+------ RXD2FR = 1 clear RXD2 flag // ||+------- TXD2FR = 1 clear TXD2 flag // |+-------- RSTFR = 1 clear bus-reset flag // +--------- EOPFR = 1 clear EOP flag // initialize endpoint #0 UCR0 = 0b00000000; // ||||++++-- TP0SIZ = 0 reset packet size register // |||+------ RX0E = 0 disable EP0 receiver // ||+------- TX0E = 0 disable EP0 transmitter // |+-------- Reserved (0) // +--------- T0SEQ = 0 reset TX sequence bit // initialize endpoint #1 UCR1 = 0b00000000; // ||||++++-- TP1SIZ = 0 reset packet size register // |||+------ FRESUM = 0 disable Force RESUME // ||+------- TX1E = 0 disable EP1 transmitter // |+-------- STALL1 = 0 disable Force EP1 STALL // +--------- T1SEQ = 0 reset TX sequence bit // initialize endpoint #2 UCR2 = 0b00000000; // ||||++++-- TP2SIZ = 0 reset packet size register // |||+------ RX2E = 0 disable EP2 receiver // ||+------- TX2E = 0 disable EP2 transmitter // |+-------- STALL2 = 0 disable Force EP2 STALL // +--------- T2SEQ = 0 reset TX sequence bit // initialize others UCR3 = 0b01000100; // |||||||+-- ENABLE1 = 0 disable EP1 // ||||||+--- ENABLE2 = 0 disable EP2 // |||||+---- PULLEN = 1 enable pull-up at D- // ||||+----- Reserved (0) // |||+------ ISTALL0 = 0 disable EP0 Force STALL for IN // ||+------- OSTALL0 = 0 disable EP0 Force STALL for OUT // |+-------- TX1STR = 1 clear TX1ST flag // +--------- Reserved (0) // initialize force bits. UCR4 = 0b00000000; // |||||||+-- FDM = 0 disable Force D- port // ||||||+--- FDP = 0 disable Force D+ port // |||||+---- FUSBO = 0 disable Force USB output // +++++----- Reserved (00000) // make a transition usbState = US_POWERED; // powered, but not yet reset UADDR_USBEN = 1; // enable USB interface }
初期設定関数は、これまでのサンプル・プログラム例では、main(void)関数から呼び出されていました。 USB関連のレジスタを初期設定したら、ステートマシンの状態をUS_POWEREDに設定します。
次に発生するイベントは、「bus-resetイベントによる割り込み」です。 bus-resetイベントが発生すると、CONFIGレジスタのURSTDビット(USB Reset Disable)の状態によって異なる動作を行います。 URSTDがリセットされている場合、bus-resetイベントによりマイコンはリセットされます。 また、URSTDがセットされている場合、bus-resetイベントによるマイコンのリセットが禁止されているので、代わりにUSB割り込みが発生します。 bus-resetによるリセットおよび割り込みの両方を禁止する方法は無いようです。
USB割り込みハンドラを整備する
MC908JB16マイコンは、USBバス上のイベントやトランザクションの終了ごとに割り込みが発生します。 このため、プログラムは、割り込み発生により状態遷移を繰り返すステート・マシンとして実装されることになります。
//---------------------------------------------------------------------------- // USB Interrupt Handler // All Interrupt Sources of the JB16's integrated USB peripheral // will be treated by this ISR //---------------------------------------------------------------------------- void interrupt VectorNumber_USB isrUSB() { : }
このハンドラの中をうめていきます。 また、長くなりそうだ。
バス・リセットの検出
マイコンがbus-resetイベントを検出したら、割り込みが発生します。 割り込みハンドラの中には、このイベントを検出するディスパッチャを入れておきます。
if (UIR1_RSTF) { // USB Reset Signal State detected resetUsb(); // call bus-reset handler }
このバス・リセットというイベントは、発生する頻度が低いので、サンプル・プログラムでは、 一番後ろにディスパッチャが配置されています。
bus-resetイベントに対する処理は、以下の通りです。
//---------------------------------------------------------------------------- // USB bus-reset handler //---------------------------------------------------------------------------- void resetUsb(void) { initUsb(); // Soft Reset of USB Systems // INT enabled for: end of packet UIR0 = 0b00000011; // |||||||+-- RXD0IE = 1 enable RXD0 interrupt // ||||||+--- TXD0IE = 1 enable TXD0 interrupt // |||||+---- Reserved (0) // ||||+----- TXD1IE = 0 disable TXD1 interrupt // |||+------ RXD2IE = 0 disable RXD2 interrupt // ||+------- TXD2IE = 0 disable TXD2 interrupt // |+-------- SUSPEND = 0 disable Suspend function // +--------- EOPIE = 0 disable EOP interrupt // enable EP0 receiver only. UCR0_RX0E = 1; // EP0 Receive Enable // make a transition usbState = US_DEFAULT; // Device is powered and reset }
resetUsb()からは、initUsb()を呼び出して確実に初期化を行います。 そして、次に発生するイベントに対応する設定を行います。 次は、SET_ADDRESSリクエストが来るはずなので、EP0受信機をイネーブルし、さらにUS_DEFAULT状態に遷移します。
サンプル・プログラムでは、この段階ですべての割り込みを有効にしていました。 もちろん、各エンド・ポイントを有効にしなければ、割り込みが発生することはありません。 ひとつだけ不明なのは、End Of Packet (EOP)の扱いです。 ここで、有効にすべきか否か。 そもそも、サンプル・プログラムでは、まともに処理がされていなかったイベントです。
ひとまず、ここまで。
付録 : USBプロジェクト索引
- USBプロジェクト - ファームウェアに立ち返る (1)
- USBプロジェクト - ファームウェアに立ち返る (2)
- USBプロジェクト - ファームウェアに立ち返る (3)
- USBプロジェクト - ファームウェアに立ち返る (4)
- USBプロジェクト - ファームウェアに立ち返る (5)
- USBプロジェクト - ファームウェアに立ち返る (6)
- USBプロジェクト - ファームウェアに立ち返る (7)
- USBプロジェクト - ファームウェアに立ち返る (8)
- USBプロジェクト - ファームウェアに立ち返る (9)
- USBプロジェクト - ファームウェアに立ち返る (10)
- USBプロジェクト - ファームウェアに立ち返る (11)
- USBプロジェクト - ファームウェアに立ち返る (12)
- USBプロジェクト - ファームウェアに立ち返る (13)
- USBプロジェクト - HIDデバイス(1)
- USBプロジェクト - HIDデバイス(2)
- USBプロジェクト - HIDデバイス(3)
- USBプロジェクト - 複合デバイスを考えた
- USBプロジェクト - HIDデバイス(4)
- USBプロジェクト - HIDデバイス(5)
参考文献
USBハード&ソフト開発のすべて―USBコントローラの使い方からWindows/Linuxドライバの作成まで (TECHI―Bus Interface)
- 作者: インターフェース編集部
- 出版社/メーカー: CQ出版
- 発売日: 2006/07
- メディア: 単行本
USBターゲット機器開発のすべて―各種USBコントローラの使い方と基本ソフトウェアの作成法 (TECHI―Bus Interface)
- 作者:
- 出版社/メーカー: CQ出版
- 発売日: 2005/08
- メディア: 単行本
USBプロジェクト - ファームウェアに立ち返る (3) [USB]
正しいUSBデバイスの続きです。 デスクリプタを整備していきます。
デバイス・デスクリプタを整備する
USBデバイスが、自身の素性をホストに知らせるのが、デスクリプタと呼ばれるデータです。 まずは、デバイス・デスクリプタから。
//---------------------------------------------------------------------------- // Standard Device Descriptor // according to USB2.0 spec page 262 //---------------------------------------------------------------------------- typedef struct { byte bLength; // Size of this Descriptor in Bytes byte bDescriptorType; // Descriptor Type (=1) iword bcdUSB; // USB Spec Release Number in BCD byte bDeviceClass; // Device Class Code byte bDeviceSubClass; // Device Subclass Code byte bDeviceProtocol; // Device Protocol Code byte bMaxPacketSize0; // Maximum Packet Size for EP0 iword idVendor; // Vendor ID iword idProduct; // Product ID iword bcdDevice; // Device Release Number in BCD byte iManufacturer; // Index of String Desc for Manufacturer byte iProduct; // Index of String Desc for Product byte iSerialNumber; // Index of String Desc for SerNo byte bNumConfigurations; // Number of possible Configurations } DeviceDescriptor;
デバイス・デスクリプタは、固定長のデータです。 そのため、structで宣言する構造体で表現します。 構造体の内部構成は、USB2.0の仕様書の262ページに書いてあります。 仕様書は、 USB.orgの ここに あります。
でも、待てよ。 ファームウェアからデスクリプタに記述した内容を参照することなどは、まずありえない。 もし、必要なら、別の場所で定義した定数を使えば良いのだから。 従って、構造体など使わずに、ベタに8ビット符号無し整数列で書いても十分なんじゃないかな。
構造体の宣言で使用されているiwordは、"Intel Word"の意味です。 USBでは、LSBが先に配置されます。 そのため、モトローラ由来のアセンブラ・コンパイラとは相性が悪いため、iwordという構造体を定義して使っているというわけです。
//---------------------------------------------------------------------------- // Intel type WORD description. //---------------------------------------------------------------------------- typedef struct { // Data Type "Intel Word" byte lo; // (High/Low Byte swapped) byte hi; } iword;
デバイス・デスクリプタのデータは、ホストの要求に従って、送出するだけで、加工などは行いません。 そのため、ROMに配置します。
//---------------------------------------------------------------------------- // Device Descriptor Declaration //---------------------------------------------------------------------------- const DeviceDescriptor deviceDesc = { // Size of this Descriptor in Bytes sizeof(DeviceDescriptor), DT_DEVICE, // Descriptor Type (=1) {0x00, 0x02}, // USB Spec Release Number in BCD = 2.00 0, // Device Class Code (none) 0, // Device Subclass Code (none) 0, // Device Protocol Code (none) 8, // Maximum Packet Size for EP0 {0x00, 0x01}, // Vendor ID = 0x0100 {0x00, 0x00}, // Product ID = 0 {0x00, 0x00}, // Device Release Number in BCD 1, // Index of String Desc for Manufacturer 2, // Index of String Desc for Product 0, // Index of String Desc for SerNo 1 // Number of possible Configurations }; // end of DeviceDesc
ベンダIDとプロダクトIDは、仮の値が入れてあります。 ベンダIDを入手しない限り、このデバイスは正式なデバイスには成りえないのね。
ストリング・デスクリプタを整備する
デバイス・デスクリプタの中にストリング・デスクリプタのインデックスを入れるフィールドがあります。 ストリング・デスクリプタは、文字列を保持するためのデスクリプタで、ホストから指示されたインデックスにより特定の文字列をホストに返します。 ここでは、言語コードを持たせるインデックス0のデータの他に 二種類の文字列を用意しました。
//---------------------------------------------------------------------------- // String Descriptors // 0: Language code // 1: Manufacturer // 2: Product //---------------------------------------------------------------------------- // Language IDs const byte strDescLanguage[4] = { sizeof(strDescLanguage), DT_STRING, // Size, Type 0x09, 0x04 // LangID Codes }; // Manufacturer String const byte strDescManufacturer [24] = { sizeof(strDescManufacturer), DT_STRING, // Size, Type 'n',0,'o',0,'r',0,'i',0,'t',0, 'a',0,'n',0,'.',0,'o',0,'r',0, 'g',0 }; // Product String const byte strDescProduct [24] = { sizeof(strDescProduct), DT_STRING, // Size, Type 'L',0,'C',0,'D',0,' ',0,'D',0, 'i',0,'s',0,'p',0,'l',0,'a',0, 'y',0, }; // Table of String Descriptors const byte * const strDescTable[] = { strDescLanguage, strDescManufacturer, strDescProduct };
ストリング・デスクリプタで厄介なのは、ユニ・コードが使用されていることです。 このため、ASCII文字列も16ビットのデータとして記述しなければなりません。 逆に言うと、日本語フォントも使えるはずですが、今は見送りとしています。
コンフィグレーション・デスクリプタを整備する
コンフィグレーション・デスクリプタは、複数のデスクリプタの集合です。 それぞれのデスクリプタは、structで宣言して構造体としています。
//---------------------------------------------------------------------------- // Standard Configuration Descriptor // according to USB2.0 spec page 265 //---------------------------------------------------------------------------- typedef struct { byte bLength; // Size of this Descriptor in Bytes byte bDescriptorType; // Descriptor Type (=2) iword wTotalLength; // Total Length of Data for this Conf byte bNumInterfaces; // No of Interfaces supported by this Conf byte bConfigurationValue; // Designator Value for *this* Configuration byte iConfiguration; // Index of String Desc for this Conf byte bmAttributes; // Configuration Characteristics (see below) byte bMaxPower; // Max. Power Consumption in this Conf (*2mA) } ConfigurationDescriptor;
コンフィグレーション・デスクリプタの最初のデスクリプタは、コンフィグレーション・タイプです。
//---------------------------------------------------------------------------- // Standard Interface Descriptor // according to USB2.0 spec page 268 //---------------------------------------------------------------------------- typedef struct { byte bLength; // Size of this Descriptor in Bytes byte bDescriptorType; // Descriptor Type (=4) byte bInterfaceNumber; // Number of *this* Interface (0..) byte bAlternateSetting; // Alternative for this Interface (if any) byte bNumEndpoints; // No of EPs used by this IF (excl. EP0) byte bInterfaceClass; // Interface Class Code byte bInterfaceSubClass; // Interface Subclass Code byte bInterfaceProtocol; // Interface Protocol Code byte iInterface; // Index of String Desc for this Interface } InterfaceDescriptor;
続いて、インターフェース・デスクリプタ構造体の宣言です。
//---------------------------------------------------------------------------- // Standard HID Descriptor // according to HID1.11 spec page 22 //---------------------------------------------------------------------------- typedef struct { byte bLength; // Size of this Descriptor in Bytes byte bDescriptorType; // Descriptor Type (=5) iword bcdHID; // HID Class specification release number byte bCountryCode; // Hardware target country byte bNumDesriptors; // Number of HID class descriptors to follow byte bRepDescriptorType; // Report descriptor type iword wDescriptorLength; // Total length of Report descriptor } HidDescriptor;
HIDデスクリプタは、HIDデバイスに特有のデスクリプタです。
//---------------------------------------------------------------------------- // Standard Endpoint Descriptor // according to USB2.0 spec page 269 //---------------------------------------------------------------------------- typedef struct { byte bLength; // Size of this Descriptor in Bytes byte bDescriptorType; // Descriptor Type (=5) byte bEndpointAddress; // Endpoint Address (Number + Direction) byte bmAttributes; // Endpoint Attributes (Transfer Type) iword wMaxPacketSize; // Max. Endpoint Packet Size byte bInterval; // Polling Interval (Interrupt) in ms } EndpointDescriptor;
さらに、エンドポイント・デスクリプタが続きます。
これら、デスクリプタの配置順番は、HID仕様書の決められています。
//---------------------------------------------------------------------------- // Configuration descriptors // according to HID1.11 spec page 48 //---------------------------------------------------------------------------- const ConfigurationDescriptors configurationDescriptors = { //---------------------------------------------------------------------------- // Configuration Descriptor //---------------------------------------------------------------------------- { // Size of this Descriptor in Bytes sizeof(ConfigurationDescriptor), DT_CONFIGURATION, // Descriptor Type (=2) { sizeof(ConfigurationDescriptor) + sizeof(InterfaceDescriptor) + sizeof(HidDescriptor) + sizeof(EndpointDescriptor) * 2, 0x00}, // Total Length of Data for this Conf 1, // No of Interfaces supported by this Conf 1, // Designator Value for *this* Configuration 0, // Index of String Desc for this Conf 0xc0, // Self-powered, no Remote-Wakeup 1 // Max. Power Consumption in this Conf (*2mA) }, // end of ConfigDesc //---------------------------------------------------------------------------- // Interface Descriptor //---------------------------------------------------------------------------- { // Size of this Descriptor in Bytes sizeof(InterfaceDescriptor), DT_INTERFACE, // Descriptor Type (=4) 0, // Number of *this* Interface (0..) 0, // Alternative for this Interface (if any) 2, // No of EPs used by this IF (excl. EP0) 0x3, // IF Class Code (0x03 - HID) 0, // No Interface Subclass Code 0, // IF Protocol Code (0 = None) 0 // Index of String Desc for this Interface }, // end of InterfaceDesc //---------------------------------------------------------------------------- // HID Descriptor //---------------------------------------------------------------------------- { // Size of this Descriptor in Bytes sizeof(HidDescriptor), DT_HID, // Descriptor Type (=5) {0x01,0x01}, // HID class spec release 1.01 0x00, // Country code 0x01, // Number of descriptors DT_REPORT, // Descriptor type...? // Total size of report descriptor {sizeof(reportDesc),0} }, // end of __hidDesc //---------------------------------------------------------------------------- // Endpoint #1 Descriptor //---------------------------------------------------------------------------- { // Size of this Descriptor in Bytes sizeof(EndpointDescriptor), DT_ENDPOINT, // Descriptor Type (=5) 0x81, // Endpoint Address (EP1, IN) 0x03, // Interrupt transfer {0x08, 0x00}, // Max. Endpoint Packet Size 10 // Polling Interval (Interrupt) in ms }, // end of __endpoint1Desc //---------------------------------------------------------------------------- // Endpoint #2 Descriptor //---------------------------------------------------------------------------- { // Size of this Descriptor in Bytes sizeof(EndpointDescriptor), DT_ENDPOINT, // Descriptor Type (=5) 0x02, // Endpoint Address (EP2, OUT) 0x03, // Interrupt transfer {0x08, 0x00}, // Max. Endpoint Packet Size 10 // Polling Interval (Interrupt) in ms } // end of endpoint2Desc };
この構造体の宣言とデスクリプタ値の配置も、HIDのレポートデスクリプタのように、機械にやらせたいな。
付録 : USBプロジェクト索引
- USBプロジェクト - ファームウェアに立ち返る (1)
- USBプロジェクト - ファームウェアに立ち返る (2)
- USBプロジェクト - ファームウェアに立ち返る (3)
- USBプロジェクト - ファームウェアに立ち返る (4)
- USBプロジェクト - ファームウェアに立ち返る (5)
- USBプロジェクト - ファームウェアに立ち返る (6)
- USBプロジェクト - ファームウェアに立ち返る (7)
- USBプロジェクト - ファームウェアに立ち返る (8)
- USBプロジェクト - ファームウェアに立ち返る (9)
- USBプロジェクト - ファームウェアに立ち返る (10)
- USBプロジェクト - ファームウェアに立ち返る (11)
- USBプロジェクト - ファームウェアに立ち返る (12)
- USBプロジェクト - ファームウェアに立ち返る (13)
- USBプロジェクト - HIDデバイス(1)
- USBプロジェクト - HIDデバイス(2)
- USBプロジェクト - HIDデバイス(3)
- USBプロジェクト - 複合デバイスを考えた
- USBプロジェクト - HIDデバイス(4)
- USBプロジェクト - HIDデバイス(5)
参考文献
USBハード&ソフト開発のすべて―USBコントローラの使い方からWindows/Linuxドライバの作成まで (TECHI―Bus Interface)
- 作者: インターフェース編集部
- 出版社/メーカー: CQ出版
- 発売日: 2006/07
- メディア: 単行本
USBターゲット機器開発のすべて―各種USBコントローラの使い方と基本ソフトウェアの作成法 (TECHI―Bus Interface)
- 作者:
- 出版社/メーカー: CQ出版
- 発売日: 2005/08
- メディア: 単行本
USBプロジェクト - ファームウェアに立ち返る (2) [USB]
ターゲットを液晶モジュール制御に定めて、「正しいHIDデバイス」を作成します。 このデバイスは、インタラプトOUT専用になる予定です。
レポート・デスクリプタを作成する
最初にHIDの通信データ構造であるレポート・デスクリプタを考えます。 このBLOGのコメントでも指摘があったように、「勝手プロトコル」を使うつもりであれば、 レポート・デスクリプタの中身を細かく既定する必要はありません。 そこで、MC908JB16のエンド・ポイントを最大限に使えるように、送受信ともに8バイトのデータを送受信することにしました。 データの意味については、後で考えます。
USAGE_PAGE (Generic Desktop) 06 01 00 USAGE (Vendor Usage 1) 09 01 COLLECTION (Application) A1 01 LOGICAL_MINIMUM (0) 15 00 LOGICAL_MAXIMUM (255) 26 FF 00 REPORT_SIZE (8) 75 08 REPORT_COUNT (8) 95 08 USAGE (Vendor Usage 1) 09 01 INPUT (Data,Var,Abs) 81 02 USAGE (Vendor Usage 1) 09 01 OUTPUT (Data,Var,Abs) 91 02 END_COLLECTION C0
某掲示板にあったものをサイズだけ変更しています。 これのC言語書式をレポート・デスクリプタ構造体としてインスタンスします。
//---------------------------------------------------------------------------- // Report Descriptor // Generic 8-byte IN/OUT descriptor //---------------------------------------------------------------------------- const byte reportDesc[25] = { 0x06, 0x00, 0xff, // USAGE_PAGE (Generic Desktop) 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, 0x01, // USAGE (Vendor Usage 1) 0x81, 0x02, // INPUT (Data,Var,Abs) 0x09, 0x01, // USAGE (Vendor Usage 1) 0x91, 0x02, // OUTPUT (Data,Var,Abs) 0xc0 // END_COLLECTION };
ここまでは、機械がやってくれるのでラクチンです。
付録 : USBプロジェクト索引
- USBプロジェクト - ファームウェアに立ち返る (1)
- USBプロジェクト - ファームウェアに立ち返る (2)
- USBプロジェクト - ファームウェアに立ち返る (3)
- USBプロジェクト - ファームウェアに立ち返る (4)
- USBプロジェクト - ファームウェアに立ち返る (5)
- USBプロジェクト - ファームウェアに立ち返る (6)
- USBプロジェクト - ファームウェアに立ち返る (7)
- USBプロジェクト - ファームウェアに立ち返る (8)
- USBプロジェクト - ファームウェアに立ち返る (9)
- USBプロジェクト - ファームウェアに立ち返る (10)
- USBプロジェクト - ファームウェアに立ち返る (11)
- USBプロジェクト - ファームウェアに立ち返る (12)
- USBプロジェクト - ファームウェアに立ち返る (13)
- USBプロジェクト - HIDデバイス(1)
- USBプロジェクト - HIDデバイス(2)
- USBプロジェクト - HIDデバイス(3)
- USBプロジェクト - 複合デバイスを考えた
- USBプロジェクト - HIDデバイス(4)
- USBプロジェクト - HIDデバイス(5)
参考文献
USBハード&ソフト開発のすべて―USBコントローラの使い方からWindows/Linuxドライバの作成まで (TECHI―Bus Interface)
- 作者: インターフェース編集部
- 出版社/メーカー: CQ出版
- 発売日: 2006/07
- メディア: 単行本
USBターゲット機器開発のすべて―各種USBコントローラの使い方と基本ソフトウェアの作成法 (TECHI―Bus Interface)
- 作者:
- 出版社/メーカー: CQ出版
- 発売日: 2005/08
- メディア: 単行本
USBプロジェクト - USB制御液晶モジュール [USB]
インタラプトOUTパイプの使い方を教えてもらっているので、 せっかくだからMC908JB16で液晶モジュールを制御させます。 液晶モジュールには、サンライク社のSC1602Bを使用します。
端子の割り当て
SC1602Bには、4-bitバスモードと8-bitバスモードがあります。 MC908JB16には、多くの端子があるので、贅沢に8-bitモードを使用してみます。
SC1602B側 | USBDEV基板側 | ||
---|---|---|---|
# | 端子名 | # | 端子名 |
1 | VDD | CN4-14 | VDD |
2 | VSS | CN2-14 | VSS |
3 | VO | - | - |
4 | RS | CN2-5 | PTE0 |
5 | R/W | CN2-6 | PTE2 |
6 | E | CN2-11 | PTC1 |
7 | DB0 | CN2-1 | PTA0 |
8 | DB1 | CN2-2 | PTA1 |
9 | DB2 | CN2-3 | PTA2 |
10 | DB3 | CN2-4 | PTA3 |
11 | DB4 | CN2-7 | PTA4 |
12 | DB5 | CN2-8 | PTA5 |
13 | DB6 | CN2-9 | PTA6 |
14 | DB7 | CN2-10 | PTA7 |
VOは、チャージポンプを作って供給したいところですが、ひとまず半固定抵抗で間に合わせます。
基板上には、LEDが4個搭載されます。
# | 端子名 | LED |
---|---|---|
CN4-8 | PTD2 | 赤 |
CN4-9 | PTD3 | 黄 |
CN4-10 | PTD4 | 緑 |
- | PTD5 | 青(USBDEV上) |
ファームウェアとPC側制御プログラムは、まだ、これからです。 プロトコルは、どうしようかな。
USBプロジェクト - ファームウェアに立ち返る (1) [USB]
これまで、「HIDプロトコルを実装したサンプル・プロジェクト」を見よう見まねで使ってきました。 BLOGのコメントにかなり理解の助けになる情報を書いてもらいましたので、 ここで、このサンプル・プロジェクトがUSBデバイス、または、HIDデバイスとしてふさわしいかどうか検証しながら整備していこうと思います。 誤解などありましたら、どしどしコメントをください。 まずは、メモから。
USBの基本:パケット
USBケーブルを流れるすべてのデータは、パケットと呼ばれる単位で表現されます。
MC908JB16では、パケットの処理部分は、ハードウェアで行われ、ファームウェアで関与することはまずありません。
USBの基本:トランザクション (Transaction)
パケットには、 TOKEN, DATA, HANDSHAKE の三つの種類があります。 トランザクションは、これら三つのパケットからなるデータ(payload)送受のための最小単位です。 トランザクションは、 TOKEN パケットの種類によって SETUP, DATA, STATUS の三つの種類があります。
トランザクションは、ホストしか開始することが出来ません。 そのため、USBデバイスは、ホストから到着するトランザクションを延々と待つのが仕事になります。
MC908JB16の場合、トランザクションの終了により割り込みが発生します。 ファームウェアは、到着したトランザクションの種類により、適切な処理を行います。
割り込みが発生するのは、トランザクションの終了時です。 このため、ホストからデータを受け取るときには、受け取ったデータに従った処理を行う事になります。 ところが、ホストにデータを送る場合には、トランザクションの開始前に送信すべきデータをあらかじめバッファに入れておく必要があります。 そのためには、ホストから送られてくるトランザクションを解析して、次に要求されるであろうデータを予測し、しかるべきバッファに配置しておく必要があります。
USBの基本:ステージ (Stage)
トランザクションには、 SETUP, DATA, STATUS の三つの種類があります。 この種類の事をステージと呼んでいます。
USBの基本:トランスファ (Transfer)
トランスファは、任意の長さのデータを送受信するための通信単位です。 トランスファには、三つの種類があり、コントロール、インタラプト、バルクと呼ばれています。
コントロール・トランスファでは、 SETUP, DATA, STATUS のすべてのステージが使用されます。
SETUPステージでは、これから開始されるデータ転送に関する情報がホストから送られてきます。 MC908JB16の場合、 SETUP トランザクションが到着したことを示すフラグSETUPがあり、 SETUP トランザクションに特有の「これから開始される通信の予測」処理が始まります。
if(UIR1_RXD0F) { // any data received via EP0? if(USR0_SETUP) { // SETUP packet? do_setup(); // go to setup } else { // or a normal packet ep0_rxd(); // receive it } }
サンプル・プロジェクトの場合にも到着したトランザクションの種類によって処理が分かれているのがわかります。
SETUP ステージで次のデータの送受信方向が予測できるので、来るべき DATA ステージに備える事ができます。 DATA ステージでホストにデータを送信する必要がある場合には、 SETUP ステージの処理の中でバッファにデータを準備する必要があります。
最後の STATUS ステージでは、送受信されるデータがありません。 そのため、ハードウェアが適切(適当?)に処理を行います。
残る二つのトランスファ、インタラプトとバルクは、 DATA ステージだけで構成されます。 ホストが開始するトランザクションに対して単一方向にデータを送受信します。 サンプル・プロジェクトでは、エンド・ポイント1がインタラプト・トランスファとして実装されています。
if(UIR1_TXD1F) { // has EP1 sent Data? ep1_txd(); }
処理の内容は、要求に応じてデータをバッファに入れているだけです。
SETUPトランザクションでの処理
SETUP トランザクションでは、8バイトのデータが送信されてきます。 サンプル・プロジェクトでは、setup_bufferという構造体で表現されます。 SETUP トランザクションでの処理は、これから始まる一連のコントロール・トランスファがどんな種類のトランスファであるかを判断することです。 そのためのパラメータが、bmRequestTypeとbRequestの二つのフィールドに入っています。
typedef struct { byte bmRequestType; // Characteristics (Direction,Type,Recipient) byte bRequest; // Standard Request Code iword wValue; // Value Field iword wIndex; // Index or Offset Field iword wLength; // Number of Bytes to transfer (Data Stage) } setup_buffer;
bmRequestTypeの5,6ビット目には、USBとして備えるべき機能であるか、HIDとして備えるべき機能であるかという大分類情報が書かれています。 また、bRequestを検査することによって、トランスファの詳細を特定することが出来ます。
対応 | bmRequestType[6:5] | bRequest | 種別 |
---|---|---|---|
YES | 00 | 0x01 | CLEAR_FEATURE |
YES | 00 | 0x05 | SET_ADDRESS |
YES | 00 | 0x06 | GET_DESCRIPTOR |
YES | 00 | 0x09 | SET_CONFIGURATION |
NO | 01 | 0x01 | GET_REPORT |
YES | 01 | 0x09 | SET_REPORT |
HIDでは、必須とされているGET_REPORTは、実装されていません。 コメントに寄せられた情報によると、Windowsでは、インターフェース・デスクリプタにインタラプト・トランスファが宣言されていたら、GET_REPORTは、発行されないのだそうです。 まあ、今の所は、Windows専用ということにしておきましょう。
トランスファの種類によって処理を分ける部分の記述は、以下の通りです。 未知のトランスファに出会ったら、STALL宣言をすることによって、受け付けられなかったことを示します。
if((SetupBuffer.bmRequestType & 0x60) == 0x00) { // Standard Request Decoder: switch(SetupBuffer.bRequest) { case CLEAR_FEATURE: // 1 clearFeature(); break; case SET_ADDRESS: // 5 setAddress(); break; case GET_DESCRIPTOR: // 6 getDescriptor(); break; case SET_CONFIGURATION: // 9 setConfiguration(); break; default: forceSTALL(); break; } // endswitch } else if((SetupBuffer.bmRequestType & 0x60) == 0x20) { // Class request switch(SetupBuffer.bRequest) { case SET_REPORT: // 9 setClassReport(); break; default: forceSTALL(); break; } // endswitch } else { forceSTALL(); // Unknown request }
またまた、長くなったので続く…
付録 : USBプロジェクト索引
- USBプロジェクト - ファームウェアに立ち返る (1)
- USBプロジェクト - ファームウェアに立ち返る (2)
- USBプロジェクト - ファームウェアに立ち返る (3)
- USBプロジェクト - ファームウェアに立ち返る (4)
- USBプロジェクト - ファームウェアに立ち返る (5)
- USBプロジェクト - ファームウェアに立ち返る (6)
- USBプロジェクト - ファームウェアに立ち返る (7)
- USBプロジェクト - ファームウェアに立ち返る (8)
- USBプロジェクト - ファームウェアに立ち返る (9)
- USBプロジェクト - ファームウェアに立ち返る (10)
- USBプロジェクト - ファームウェアに立ち返る (11)
- USBプロジェクト - ファームウェアに立ち返る (12)
- USBプロジェクト - ファームウェアに立ち返る (13)
- USBプロジェクト - HIDデバイス(1)
- USBプロジェクト - HIDデバイス(2)
- USBプロジェクト - HIDデバイス(3)
- USBプロジェクト - 複合デバイスを考えた
- USBプロジェクト - HIDデバイス(4)
- USBプロジェクト - HIDデバイス(5)
参考文献
USBハード&ソフト開発のすべて―USBコントローラの使い方からWindows/Linuxドライバの作成まで (TECHI―Bus Interface)
- 作者: インターフェース編集部
- 出版社/メーカー: CQ出版
- 発売日: 2006/07
- メディア: 単行本
USBターゲット機器開発のすべて―各種USBコントローラの使い方と基本ソフトウェアの作成法 (TECHI―Bus Interface)
- 作者:
- 出版社/メーカー: CQ出版
- 発売日: 2005/08
- メディア: 単行本
USBプロジェクト - WindowsでHIDデバイスを扱う定石 (2) [USB]
昨日の続きです。 HIDクラス・デバイスを表す「パス名」のリストからスタートです。
STEP4 : 後片付けを忘れずに
前回、入手した「鍵のリスト」は、「パス名のリスト」が手に入った今となっては不要です。 不要になったからといって、放っておくと、メモリがどんどん消費されてしまいます。 不要になったら、きちんと捨てる。 これが、Winodwsの流儀です。
「鍵のリスト」を廃棄するための関数は、 SetupDiDestroyDeviceInfoList()です。
ここまでの手順は、 DeviceManagement.FindDeviceFromGuidにあります。 何も考えずに使わせてもらっても動きます。
STEP5 : お目当てのデバイスを探せ
「パス名のリスト」が手に入ったので、ここからお目当てのデバイスを探します。 探し出すためには、「パス名」を元にファイルを開き、そのファイルの属性(Attribute)を調べます。
ファイルを開く関数は、一般のファイルと同じCreateFile()です。 宣言は、"FileIODeclaration.vb"に記述されています。
ファイルの属性を取り出す関数は、HidD_GetAttributes()です。 「お目当てのデバイス」を見つけるための手がかりとして使っているのが、 ベンダID (VenderID) とプロダクトID (ProductID) です。 この二つのIDが一致したときに、デバイスが見つかったと判断します。
属性を調べ終わったら、ファイルを閉じておきます。 ファイルを閉じる時に使う関数は、CloseHandle()です。
STEP6 : デバイス取り外し時のイベントを受け付けろ
USBデバイスは、動作中に抜き差ししても良いことになっているので、 抜かれた時に発生するイベントを受信する必要があります。 イベントを受信するオブジェクトを指定する関数は、 RegisterDeviceNotification()です。
この関数の呼び出しには、いくつ物手順を踏む必要があります。 お手本プログラムの"hidclass_vs5"の場合は、 "DeviceManagement.RegisterForDeviceNOtifications()"というメソッドにすべて記述されていますので、 そのまま呼び出すだけで十分です。 イベントが発生したら、フォームのWndProc()というメソッドが呼び出されます。
STEP7 : パイプを開け
HIDデバイスとレポートを送受信するために使用されるのは、 WriteFileとReadFileという関数です。 そうです。普通のファイルの読み書きと何ら変わらないのです。
そのため、送受信に使用されるパイプを開くときも 普通のファイルと同様にCreateFile関数が使用されます。
STEP8 : データを送信する
データを送信する場合には、WriteFile関数を使用します。 お手本の"hidclass_vs5"では、 送信すべきレポートがOutputReportクラスとして定義されているので、 インスタンスを作成し、Writeメソッドを呼び出したらおしまいです。
STEP9 : データを受信する
データを受信する場合、ReadFile関数を使用します。 受信の場合には、送信の場合と異なり、データの受信が終わるまで次の処理に進むことが出来ないのが常です。 それまで待っているいるのも無駄なので、「データが受信できたら呼んでね。」という意味の コールバックと呼ばれる関数を定義し、InputReportクラスの Readメソッドを呼び出します。
これら送受信にかかる一連の操作は、 ExchangeInputAndOutputReports()に記述されています。 全部理解しなくても十分に使えています。
こうやって、まとめを書いてみると、レポートの送受信のところの理解がまだ甘い事に気が付きます。 特にDelegateが出てくると、とたんにややこしくなります。 まだ、修行が足りないな。
参考サイト
- HIDClass Devices
- Micro$oftの解説書は、ここにあります。 日本語版もあるのですが、迷訳なので、英語版の方がおすすめです。
- http://i-wanna-metamorphose.blogspot.com/2008/02/so-net-blogusb-reportdescri.html
- ここにもがんばっている方がいらっしゃいました。
- 改訂版 プロフェッショナルVB.NETプログラミング
- .NET版のVisual BASIC の使い方が解説されています。読めば読むほど「Javaみたい」だと思うのですよね。
USBプロジェクト - WindowsでHIDデバイスを扱う定石 (1) [USB]
ようやく、HIDデバイスを扱うためのPC側アプリケーションが書ける様になったので、HIDデバイスを操作するときの定石についてまとめておきます。 テキストは、EDNの記事、 Using the HID class eases the job of writing USB device drivers です。 日本語版が見つけられませんでした。 もしかしたら、日本には上陸しなかったのかな?
HIDは、ヒューマン・インターフェースのためにあるわけではない
この記事の言わんとするところは、「ヒューマン・インターフェース・デバイス(HID)」という言葉に惑わされること無く、なんにでも使ってしまえというところです。 実際、単純な入出力なら、ボタン入力とLED出力に偽装すれば、どんなものだってHIDで十分に実用になります。 しかも、HIDを使うだけで、プログラム開発が圧倒的に簡単になります。
STEP1 : HIDを探せ
HIDデバイスを扱うための第一歩は、HIDドライバを探すことです。 このステップは簡単です。 HidD_GetHidGuid()という関数を呼び出すとHIDデバイスの識別子(GUID)が入手できます。
関数を呼び出すために必要なこと
この関数は、hid.dllという動的リンク・ライブラリ(Dynamic Link Library : DLL)に入っています。 通常は、C:\WINDOWS\SYSTEM32ディレクトリを探すと見つかります。 そのため、「hid.dllにあるHidD_GetHidGuidを呼び出せ。」とプログラムに書けばすべて解決です。
ところが、いわゆる高級言語でプログラムを作成する場合、「HidD_GetHidGuidは、hid.dllに入っている。」と教えてやる必要があります。 Visual C++ でプログラムを作成する場合、この情報を持っているのが「LIBファイル」です。 そのため、プログラム開発には、関数がすぐそこにあるにもかかわらず、「Windows DDKに入っているhid.libファイル」を取り寄せなくてはなりません。 さらに、「HidD_GetHidGuidは、こんな風に使う。」という情報が「ヘッダファイル」という別のファイルに入っています。 そのため、「Windows DDKに入っているhid.hファイル」を取り寄せなくてはなりません。 このようにVisual C++では、必要なファイルが芋づる式に増えていきます。
一方、Visual BASICの場合はどうかというと、実は、「HidD_GetHidGuidは、こんな風に使う。」という情報を入れたファイルがそもそも存在しません。 では、どうするかというと、自分で書き起こすのです。 でも、ご安心ください。 世界中の人たちが「Visual BASICで関数を使いたい。」と考えているので、ありがたいことにその成果を利用させてもらうことができます。 私がプログラムのお手本にした"hidclass_vs5"の場合にも、関数呼び出しの方法を書いたファイル"HidDeclaration.vb"が用意されています。
<DllImport ("hid.dll")> Sub HidD_GetHidGuid _ (ByRef HidGuid as System.Guid) End Sub
必要なのは、これだけです。 しかも、この関数がどの"DLL"ファイルに入っているかという情報も含まれていますので、これだけの記述で十分なのです。 これと同じことがVisual C++でも出来ればよいのですけど。
というわけで、Visual BASICを使うときには、ライブラリ相当のテキストファイルが必要だけれど、Windows DDKを入手する必要はありません。
STEP2 : HIDクラスのデバイスをリストアップしろ
HIDクラスに所属するUSBデバイスは、オペレーティング・システムの作用により、PCに接続されたとたんにHIDドライバの管理下に置かれます。 そのため、HIDドライバに問い合わせをすると、現在接続中のHIDクラス・デバイスのリストを入手することができます。
このリストの問い合わせに使用される関数が、SetupDiGetClassDevs()です。 この関数は、"setupapi.dll"に入っています。 関数の宣言は、"DeviceManagementDeclarations.vb"を見てください。
この関数は、HIDクラス・デバイスにたどり着くための鍵のリストを返します。
STEP3 : 鍵からパス名を割り出せ
出来上がったリストから、アプリケーションが操作しようとしているデバイスを割り出さなくてはならないのですが、鍵にはデバイスを割り出すための詳細な情報は含まれていません。 このため、デバイスを特定する名前である「パス名」のリストに変換します。
パス名を求めるためには、関数SetupDiEnumDeviceInterfaces()とSetupDiGetDeviceInterfaceDetail()を使用します。 まず、SetupDiEnumDeviceInterfaces()でインターフェースを示す構造体を構築します。 そして、SetupDiGetDeviceInterfaceDetail()でその詳細なデータを引き出します。 何で、こんな二段構成になっているのかは、理解できません。
SetupDiGetDeviceInterfaceDetail()は、二回呼び出されます。 一回目は、得られるデータの大きさを調べるために呼び出されます。 そして、二回目は、あらかじめ十分な大きさの領域を確保して、そこにデータを書かせます。 領域を確保するには、ヒープ領域が便利です。 ヒープに書かせたデータをVisual BASICのStringにコピーしたら、ヒープは開放されます。
Visual BASICでヒープを割り当てる
Cの場合、ヒープを割り当てるためには、"malloc()"のような関数が使用されました。 ところが、ちょっと前のVisual BASICの場合、もともとヒープのような概念がないため、 何とか誤魔化すしか方法がありませんでした。
現在のVisual BASICは、かなりCに歩み寄っているようで、"Marshal.AllocHGlobal()"という関数(メソッド?)が提供されています。
長くなったので、続く…
USBプロジェクト - Windowsアプリケーションの作成 [USB]
PC側アプリケーションの作成で紆余曲折してきたこのシリーズですが、ついに最初のアプリケーションを作成することが出来ました。 やはり、鍵は、Visual BASIC が握っていたようで。 hidclass_vs5プログラムを幅広く利用させていただきました。
これがPC側アプリケーションだ
マイコン側のハードウェアは、写真の3ボタンマウスを使っています。 また、ファームウェアは、数日前に公開した"HID05"をそのまま使用しています。 アプリケーションの仕事は、チェックボックスの状態をUSBデバイスのLEDに伝え、ボタンの状態を採取してくることです。
プログラム中でオオチャクをしているので、チェックボックスが変更されたタイミングでボタンの状態を採取しています。 以下のファイルを"HidApp01.exe"という名前で保存すると実行ファイルが再現できますので、追試をなさりたい方はご利用ください。 ソースコードは、もう少し整備が進んでから公開します。
HidApp01.exe
参考文献
USBハード&ソフト開発のすべて―USBコントローラの使い方からWindows/Linuxドライバの作成まで (TECHI―Bus Interface)
- 作者: インターフェース編集部
- 出版社/メーカー: CQ出版
- 発売日: 2006/07
- メディア: 単行本
USBターゲット機器開発のすべて―各種USBコントローラの使い方と基本ソフトウェアの作成法 (TECHI―Bus Interface)
- 作者:
- 出版社/メーカー: CQ出版
- 発売日: 2005/08
- メディア: 単行本
USB プロジェクト - WindowsアプリケーションにはBASICが良く似合う [USB]
マイコン側に"Generic HID"デバイスが用意できたので、これを制御するPC側のアプリケーションを作成します。 色々とサンプル・アプリケーションを探し回ったのですが、なかなか適当なのがありません。 古い Visual C++ や Visual BASIC のプロジェクトは、たとえ変換できたとしても、現代のVS2008では、簡単には動きそうにありません。 その中で、Visual BASIC 2005 Express Edition で作ったHIDクラスというものがありました。
Visual BASIC 2005 でHIDを操作する
その期待させるようなアプリケーションがあるのは、ここです。 このページの上から4分の1ほどの所に"HIDClass"というリンクがあります。 コメント欄で hamayan さんに指摘をいただきましたが、 http://www.lvr.com/ というのは、有名なUSB本の原作者の方のサイトなのですね。
Usb Complete: Everything You Need To Develop Custom Usb Peripherals (Complete Guides Series)
- 作者: Jan Axelson
- 出版社/メーカー: Lakeview Research
- 発売日: 2005/06/15
- メディア: ペーパーバック
苦も無くビルト完了
さっそく、"hidclass_vs5.zip"をダウンロードして、"Visual BASIC 2008 Express Edition"で開いてみました。 いくつかオプションを指定するとプロジェクトが自動的に変換されます。 「無警告」でした。
ビルトすると、アプリケーションが出来ました。 実行ボタンを押してデバッグ開始です。
エラーが出る
アプリケーション画面で、Vender IDとProduct IDを入れて、"Find My Device"をクリックすると、ちゃんとデバイスを見つけてくれました。 ここで、"Send and Receive Data"にある"Once"をクリックすると、レポートの送信と受信が行われると期待していたのですが、エラーメッセージが出てしまいました。
何だか難しい日本語ですが、要するに配列の添え字が大きくなりすぎたようです。 問題発生箇所を確認したところ、私が作ったデバイスは、1バイトしかデータを送受信しないのに、2バイト分のデータを取り出そうとしたのが原因であることがわかりました。 そこで、プログラムを修正しました。 以下、1210行辺りの記述です。
lstResults.Items.Add("An Output report has been written.") 'Display the report data in the form's list box. Debug.WriteLine(" Output Report ID: " & OutputReportBuffer(0)) Debug.WriteLine(" Output Report Data:") lstResults.Items.Add(" Output Report ID: " & OutputReportBuffer(0)) lstResults.Items.Add(" Output Report Data:") txtBytesReceived.Text = "" For Count = 1 To UBound(OutputReportBuffer) '2 'Add a leading zero to values from 0 to F. If Len(Hex(OutputReportBuffer(Count))) < 2 Then ByteValue = "0" & Hex(OutputReportBuffer(Count)) Else ByteValue = Hex(OutputReportBuffer(Count)) End If Debug.WriteLine(" " & ByteValue) lstResults.Items.Add(" " & ByteValue) Next Count
再度、挑戦
プログラムを修正して、再度実行します。 "Autoincrement values"をチェックして、"Continuous"ボタンをクリックすると、 約1秒ごとにLEDのパターンが変化し、ボタンを押したときの反応も"Bytes Received"で 確認することが出来ます。
あとは、お好みの機能を実装すれば、PC側アプリケーションは、何とかなりますね。
参考文献
USBハード&ソフト開発のすべて―USBコントローラの使い方からWindows/Linuxドライバの作成まで (TECHI―Bus Interface)
- 作者: インターフェース編集部
- 出版社/メーカー: CQ出版
- 発売日: 2006/07
- メディア: 単行本
USBターゲット機器開発のすべて―各種USBコントローラの使い方と基本ソフトウェアの作成法 (TECHI―Bus Interface)
- 作者:
- 出版社/メーカー: CQ出版
- 発売日: 2005/08
- メディア: 単行本
USB Complete: The Developer's Guide (Complete Guides Series)
- 作者: Jan Axelson
- 出版社/メーカー: Lakeview Research
- 発売日: 2009/06
- メディア: ペーパーバック
USB プロジェクト - コンパイル済みプログラムなら何とか [USB]
MC9S08JMxxシリーズ用のサンプル・アプリケーションを利用する計画は、 コンパイラがうまく使えないために挫折してしまいました。 でも、同梱されていたコンパイル済みプログラムだったら、何とかつかえるはずです。 そこで、PIDとVIDをこのアプリケーションに合わせたターゲットを作成して、 何とか連動させようと考えました。
ターゲットの改造
ターゲットは、HIDインターフェースを搭載した PID=XXX; VID=XXXX (伏字)のデバイスです。 HIDの上には、"Generic HID"インターフェースを構築します。 使用した"ReportDescriptor"は、これです。
char ReportDescriptor[46] = { 0x06, 0x00, 0xff, // USAGE_PAGE (Vendor Defined Page 1) 0x09, 0x01, // USAGE (Vendor Usage 1) 0xa1, 0x01, // COLLECTION (Application) 0x05, 0x08, // USAGE_PAGE (LEDs) 0x09, 0x4b, // USAGE (Generic Indicator) 0x15, 0x00, // LOGICAL_MINIMUM (0) 0x25, 0x01, // LOGICAL_MAXIMUM (1) 0x75, 0x01, // REPORT_SIZE (1) 0x95, 0x04, // REPORT_COUNT (4) 0x91, 0x02, // OUTPUT (Data,Var,Abs) 0x75, 0x01, // REPORT_SIZE (1) 0x95, 0x04, // REPORT_COUNT (4) 0x91, 0x03, // OUTPUT (Cnst,Var,Abs) 0x05, 0x09, // USAGE_PAGE (Button) 0x19, 0x01, // USAGE_MINIMUM (Button 1) 0x29, 0x03, // USAGE_MAXIMUM (Button 4) 0x75, 0x01, // REPORT_SIZE (1) 0x95, 0x04, // REPORT_COUNT (4) 0x81, 0x02, // INPUT (Data,Var,Abs) 0x75, 0x01, // REPORT_SIZE (1) 0x95, 0x04, // REPORT_COUNT (4) 0x81, 0x03, // INPUT (Cnst,Var,Abs) 0xc0 // END_COLLECTION };
オリジナルから、ボタンの数を増やしました。 たぶん、このくらいは大丈夫だろう。
接続、点火
無限ループにボタンの状態とLEDの状態をやり取りするプログラムを書いたら、 度胸一発、接続、点火。 デバイスをつないだら、単なるHID準拠デバイスとして認識されました。
アプリケーションを起動する
フォルダ"C:\CMXUSB_LITE\usb-peripheral\pc-side\hid-led-demo\Debug"にある "hid-led-demo.exe"を実行します。 そして、開いたウィンドウのLEDボタンを押すと、見事にLEDが点灯しました。 また、ターゲットのボタンを押すとチェック・ボックスが反応しました。 めでたしめでたし。
やっぱり、アプリケーションを作ろう
実験できるのは、ここまで。 あとは、アプリケーション・プログラムを作るしかなさそうです。 でも、HIDが使えるのだったら、楽できそうだな。
プロジェクト・アーカイブは、以下のリンクを"HID05.zip"という名前で保存すると再現できます。
HID05.zip (20446 Bytes)
参考文献
USBハード&ソフト開発のすべて―USBコントローラの使い方からWindows/Linuxドライバの作成まで (TECHI―Bus Interface)
- 作者: インターフェース編集部
- 出版社/メーカー: CQ出版
- 発売日: 2006/07
- メディア: 単行本
USBターゲット機器開発のすべて―各種USBコントローラの使い方と基本ソフトウェアの作成法 (TECHI―Bus Interface)
- 作者:
- 出版社/メーカー: CQ出版
- 発売日: 2005/08
- メディア: 単行本
USB プロジェクト - 懲りずにWinUSBに手を出す [USB]
Windows Vistaには、WinUSBという仕掛けがあるらしい。 Windows XPでも使えるらしいので、手を出してみるか。
WinUSBのサンプル・プロジェクトを取り寄せる
奇特な方がいて、サンプル・プロジェクトを作ってくれています。 このサイトにVisual BASICとVisual C#で 記述したサンプルが置いてあります。 最初は、Visual BASICで試してみます。
コンパイルは、できました
Visual C++ のサンプルと違い、このサンプルはExpress Edition でもコンパイルができました。 おぉ、なかなか有望ではないか。 ここで、初めて"readme.doc"という取扱説明書を読みました。
まず、infディレクトリにある記述を使用するUSBデバイスに合わせて変更する必要があります。 「\USB\VID_何たら\PID_かんたら」 この記述は、以前UUSBDをかじったときに悩んだ形式ですね。 HIDデバイスの場合には、「\HID\VID_何たら\PID_かんたら」になってしまい、 結局HIDデバイスをUUSBDから操作することは出来なかったのでした。 今回も、同じ目に遭いそうな予感。
INFファイルのGUIDとアプリケーションのGUIDを合わせるとのこと。 これは、サンプルINFファイルのままを使うことにしました。
あとは、デバイス・ドライバのインストールで、WDKのファイルが必要だそうです。 何だ、結局、WDKは必要なのね。
HIDは、認識してくれない
INFファイルを書き換えて、デバイスもインストールして、 「さあ、デバイスを認識しろ」と"Find My Device"ボタンをクリックしましたが、音沙汰無し。 INFファイルをUSBからHIDに書き換えましたが、これも効果なし。 しかたがないので、インターフェース・デスクリプタのクラス・コードを0xFF(ベンダ独自インターフェース)に変更して、マイコンのプログラムを書き換えました。 すると、今度は認識は出来ました。
でも、通信プロトコルは全く合っていないので、通信そのものは成り立ちませんでした。 このサンプル・プログラムでは、通信プロトコルの低レベルな部分まで記述されているので、もしかしたら、デバイス・ドライバ並みのアプリケーションが必要になるのかもしれません。 それでは、本末転倒でしょう。
思わぬ収穫?
WinUSBサンプル・プログラムを見ていて、"SetupDiGetClassDevs"などのどこかで見たような関数の宣言が並んでいることに気がつきました。 前回、Visual C++ではどうしても解決できなかった、あのプログラムで使用されていた関数です。 どうやら、Windowsでプログラムを書くときには、Visual BASICの方が適しているようです。 さすがは、ビル。 何処まで行ってもBASICの呪縛からは逃れられないのですね。
これらの関数宣言を使えば、Visual C++のサンプルプログラムをVisual BASICで書き直して使うことが出来るかも知れません。 そうすると、HIDデバイスのまま、インターフェースが出来るかな?
おまけ
http://www.lvr.com のページを探っていて、「Visual BASIC 2005 Express Edition」で作成したHIDデバイスを扱うプログラムがあることに気がつきました。 これを使ってみるのもいいかな。
とにかく、色々と目移りしそうなサンプル・プログラムが沢山あることに気がつきました。 どれもこれもMicro$oftのサイトでは無いというところが、いかにもMicro$oftらしいではありませんか。
参考文献
USBハード&ソフト開発のすべて―USBコントローラの使い方からWindows/Linuxドライバの作成まで (TECHI―Bus Interface)
- 作者: インターフェース編集部
- 出版社/メーカー: CQ出版
- 発売日: 2006/07
- メディア: 単行本
USBターゲット機器開発のすべて―各種USBコントローラの使い方と基本ソフトウェアの作成法 (TECHI―Bus Interface)
- 作者:
- 出版社/メーカー: CQ出版
- 発売日: 2005/08
- メディア: 単行本
USB プロジェクト - デバイス・ドライバを入れ替える [USB]
hamayanさんも、お悩みになったように USBデバイスのプログラムを書き換えたとき、PC側は「PIDとVIDだけを確認して同じデバイスであると認識する」ので、PC側デバイス・ドライバは更新されません。 これでは、デバッグにならないので、PCに「昔のことは忘れて」もらいましょう。
以下、Windows XP の場合の操作例です。
デバイス・マネージャを呼び出す
デバイス・ドライバ入れ替えの第一歩は、デバイス・マネージャの呼び出しから始まります。
コントロールパネル → システム → ハードウェア → デバイスマネージャ
デバイスのツリーが表示されますので、ここから該当するデバイスを探します。 この時、 ツリーには接続されているデバイスしか表示されないので、 当該デバイスはUSBポートに接続しておいてください。
プロパティ・ダイアログを操作する
探し出したデバイスの所で右クリックし、プロパティ・ダイアログを出します。 詳細タブで「デバイスインスタンスID」を確認して当該デバイスであることを確認しておきましょう。
確認したらドライバ・タブを出します。 ここではドライバの変更に関するボタンが並んでいます。
デバイス・ドライバを削除するために、「削除」をクリックします。 HIDデバイスの場合、ここでUSBデバイスから採取された各種デスクリプタの情報が削除されているようです。 逆にいうと、PC側は、USBデバイスを抜き差ししただけでは、デスクリプタを再読み込みしたりしないようです。
USBデバイスを再認識させる
この工程は簡単です。 単にUSBデバイスを抜いて挿しなおします。 すると、オペレーティング・システムが新しいUSBデバイスを認識して、 勝手にデバイス・ドライバができます。
問題はどこに
Windows XPの場合には、以上の手順でデバイスの入れ替えが行われるようなのですが、問題は、当該デバイスが存在しないとドライバの削除も行えないというところです。 昔使った事があるデバイスドライバが山のように残されていて、すでに壊れた・捨てた場合には、二度と削除できないというすばらしいシステムです。
また、USBデバイスを挿しなおしただけではデスクリプタの読み込みや検証はされていないようなので、一度怪しいデバイスドライバを入れられたら、正規デバイスに対しても怪しいデバイスドライバが反応するので、危険な香りがします。
他の方法もある
デバイス・マネージャの右クリックメニューには、「ドライバの更新」や「ハードウェア変更のスキャン」という項目もあって、使える時には使えるようです。 ただ、「役に立たないこともある」ようなので、削除してしまった方が確実であろうと思います。
USBプロジェクト - サンプル・ソフトへの長すぎた道のり (挫折編) [USB]
ソースコードがあるからと、気軽に手を出したのですが。
CLRとは、なんぞや?
サンプル・プログラム同様にフォーム上のボタンを操作してLEDを制御するモデルを考えていたので、 GUIつきの空プロジェクトを作ろうと考えました。 いくつかプロジェクトを作成してみて、 Visual C++ Express Editionでフォームが出てくるのは、「新規作成→Visual C++→CLR→Windowsフォーム・アプリケーション」だけだったので、これを雛形として使います。
あとは、"hid_dev.cpp"などのソース・コードと"hid-lib"に入っているファイルをコピーしました。 まだ、"hid_dev.cpp"内の関数を使用するプログラムは書いていません。 これを度胸一発、ビルドすると、
warning LNK4248: 未解決の typeref トークン (0100001C) ('_HIDP_PREPARSED_DATA') です。イメージを実行できません。
をはじめとして、沢山のエラーが出てきました。 これは、ライブラリが指定されていないからだと考え、「プロパティ・ページ」の「リンカ;追加のライブラリ・ディレクトリ」に"hid-lib"ディレクトリを追加しましたが、状況は変わらず。
エラーメッセージを頼りに検索をかけた結果、 "__clrcall"関数から"__stdcall"関数を呼び出しているのが気に入らないご様子。 何ですか?"CLR"って?
敗北宣言
そもそも、英語でもわからないエラーメッセージが、日本語にするともっとわけがわからなくなるので、 英語版のメッセージを探しに行きました。
unresolved typeref token (token) for 'type'; image may not run
前方参照された「型」が気に入らないということなので、"_HIDP_PREPARSED_DATA"が あらかじめ正しく定義されていれば問題ないようです。 でも、この型の宣言がまったく見つかりません。 だいたい、これはリンカのエラーだよな。
というわけで、 現在のGUIベースプログラムと太古のデバイスドライバを組み合わせて使うのは、 割りに合わない労力が必要だと判断して、このあたりでやめます。 早い話、敗北宣言です。
それにしても、 古いソース・コードが通らなくなるような、改造はやめて欲しいな。
収穫が無いわけじゃない
今回の調査で、現代風にUSBデバイスをユーザレベルのプログラムで操作する方法として、 "WinUSB"という概念があることがわかりました。 UUBSDみたいなものが肥大化を続ける自称オペレーティング・システムに入るという事かな? こちらは、VB2005でのサンプルというものがみつかったので、敗北感から立ち直ったら、試してみます。
USBプロジェクト - サンプル・ソフトへの長い道のり(後編のつもりだった) [USB]
だんだんと手段が目的になりつつある。
WDKのインストール
前回は、WDKをダウンロードしたところで夜になったので、 今日はインストールからはじめます。 ダウンロードしたISOイメージから出来上がったCD-RWは、660MBほどの容量でした。 これをインストールしたところ、1.65GBの容量を占めるバケモノに変化してしまいました。 こいつも大喰らいだぁ。
サンプルプログラムに無かったファイルは、 "{install_directory}/inc/api"にヘッダ・ファイルが、 "{install_directory}/lib/wxp/i386"にライブラリ・ファイルがありました。 これを"hid-lib"ディレクトリに入れます。
Visual C++でコンパイル
それでは、気を取り直して新規に作り直したディレクトリのソリューションを開きます。 古いバージョンのソリューションだから、変換しますという表示が出て、変換されたようです。 いくつか警告が出ていましたが、
C++ コンパイラの準拠の変更によって、プロジェクトがビルドする前にコード変更が必要になる場合があります。
こんな感じの「日本語の活字で記述された宇宙語の警告」なので、さっぱりわかりません。 しまった、英語版を入れるべきだった。
実のところ、VisualC++に触るのは初めてです。 Windows系列のコンパイラは、Windows1.2の時に挫折して以来なので、 まったくの素人と言っても差し支えないかと思います。 よくわかんないので、とりあえず、"hid_dev.cpp"だけコンパイル。
include ファイルを開けません。'afxwin.h': No such file or directory
これは、MFCだかのライブラリが無いのが原因でしたね。 "stdafx.h"の中の"#include"をコメントアウトして再度挑戦。
fatal error C1083: include ファイルを開けません。'atlstr.h': No such file or directory
これは、"hid_dev.cpp"で"Cstring"クラスが使用されているのが原因なので、 使用するクラスを"std::string"に変更します。
static std::string msg;
今度は、"setupapi.h"ファイルで使われている"GUID"という型が宣言されていないという エラーが出てきました。 使用している箇所は、こんな風になっています。
#ifndef __LPGUID_DEFINED__ #define __LPGUID_DEFINED__ typedef GUID *LPGUID; #endif
それにしても、このVisualC++についてきたヘッダファイルは醜いですね。 パッチの当てすぎです。 これなんか「定義されていなかったら定義しろ」なんて記述が あちらこちらのヘッダファイルに散在しているという事のあらわれでしょう。 美しくない。
MSDNを調べた結果、GUIDというのは、"windows.h"というヘッダ・ファイルに出てくるらしいことが わかりました。 そこで、"hid_dev.cpp”にヘッダ・ファイル呼び出しを追加しました。
error C2039: 'GetString' : 'std::basic_string<_Elem,_Traits,_Ax>' のメンバではありません。
"CString"の代わりに"std::string"を使うことにしたので、 "GetString()"は、"c_str()"に変更します。 これで、hid_dev.cpp"はコンパイルできるようになりました。
GUIを作り直す
ここから、"hid_dev.cpp"以外の部分を変更していきます。 ところが、VisualC++のバージョンの違いから、GUI部分の変更が膨大になりそうなので、 いまはこの結果だけを使って、GUIは、VisualC++に新たに作成させることにしました。
USB プロジェクト - サンプル・ソフトへの長い道のり(前編) [USB]
今日は、USBプロジェクトのための資料集めの覚え書きです。
USB対応Flexisが発表された
2008年2月22日付けで、二つ目のFlexis™チップであるMCF51JM128が公開されました。 同時に評価ボードDEMOJMとそのデモソフトまで公開されました。 それが、CMX_USB-LITE: Complimentary USB stack by CMXです。 このサンプルソフトの中に"Generic HID Device"のマイコン側ファームウェアとPC側ファームウェアが入っていたので、読んでみる事にしました。
PC側ソフトウェアにはVisual C++が必要
PC側のソフトウェアは、Visual C++ で記述されていました。 それなら、Visual C++ Express Editionを持ってきて、すぐにコンパイルできるだろうと、 最新版のVisual Studio 2008 Express EditionのISOイメージをダウンロードして、インストールしてみました。 ISOイメージが1.1GBなので、DVD+RWに焼きました。 インストールに必要なディスク容量が約1GB。 すごい大喰らい。
Express Editionは、不完全だった
インストールして、さっそく、件のPC側ソフトウェアのプロジェクト(Micro$oft用語では、ソリューションと呼ぶらしい)を開いて、ビルドさせてみました。 すると、「afx??.hというヘッダが無い」と返してきました。
何のことだかわからないので、Micro$oftで検索をかけました。 その結果、「Express Editionには、MFCだかATLだかが含まれていないので、このヘッダは存在しない。」ということ事のようです。
わけあって、ライブラリが足りない
きっと、ユーザインタフェースの部分の話だろうと、 「#includeを適当に削除して」ビルドを続けると、今度は、「hid-libディレクトリにヘッダが無い」と返してきました。 hid-libディレクトリは、サンプル・プロジェクトに含まれているディレクトリです。 確かにhid-libディレクトリはありますが、その中には、ヘッダファイルはありません。 代わりにあるのは、"readme.txt"だけです。
"readme.txt"には、「著作権の問題があるから、ヘッダは入れられんかった。」と書いてあり、 「Windows DDKからコピーしてきてチョ。」とあります。 今度は、"Windows DDK"が必要なのですね。
Windows Driver Development Kit を訪ねて
普通は、ユーザ様にプログラムを作成してもらうのだから、 そのための技術資料はポロポロと落ちていそうなものですが、 そこはMicro$oft。 捜せど捜せど、「Windows DDK」がダウンロードできる場所に当たりません。 その中で、見つかったのが、 Which Windows Driver Kit and Test Kit to Useというページでした。
ふむふむ、「Windows Vista WDK」を捜せばよいのだな。 でも、やっぱり出てきません。 このページの中にはリンクがいくつかあって、その一つが、 How to Get the Windows Driver Kit and the Windows Logo Kitでした。
Micro$oft Connect という存在
このページの中には、"Windows Driver Kit"を入手するための二つの方法が紹介してあります。 一つは、「MSDN Subscriberからダウンロード」 "MSDN"が買えるのであれば苦労はしないから、これは論外。 もう一つは、「Micro$oft Connect Webサイトからダウンロード」という方法。 これは、何のサイトですか?
Micro$soft Connectは、 会員制のサイトで、 要するにMicro$oftのベータ版ソフトウェアを無償でデバッグしてやる奇特な人たちのためのサイトのようです。 何だか、だんだん腹が立ってきたぞ。
まあ、仕方が無いので、660MBの"WDK"をダウンロードしている間に このBLOGの文章が書き終わりました。
次回は、WDKのインストールから。
USB プロジェクト - 3ボタン・マウス [USB]
HIDのサンプル・プログラムを改造して、 ついに、3ボタン・マウスを作りました。
概要
この作品は、デジタル入力のみを用いて作られた3-ボタン・マウスです。 マウスの移動には、四つのタクト・スイッチを使います。 移動速度は、ファームウェアにより自由自在です。 ボタンも三つ付いています。 場所が余ったので、LEDによるインジケータも付けました。 今は、ろくな仕事を与えられていません。
キーとLEDの割り当て
キーとLEDは、それぞれポートを一つずつ占有しています。
種別 | 名前/位置 | ポート | 機能 |
---|---|---|---|
キー | 右 | PTA0 | ポインタを右に移動します。 |
キー | 上 | PTA1 | ポインタを上に移動します。 |
キー | 下 | PTA3 | ポインタを下に移動します。 |
キー | 左 | PTA4 | ポインタを左に移動します。 |
キー | ボタン1 | PTA7 | マウスの左ボタンです。 |
キー | ボタン2 | PTA5 | マウスの右ボタンです。 |
キー | ボタン3 | PTA6 | マウスの中央ボタンです。 |
LED | 左/赤 | PTD4 | ボタン1のインジケータです。 |
LED | 中/黄 | PTD3 | ボタン3のインジケータです。 |
LED | 右/緑 | PTD2 | ボタン2のインジケータです。 |
キー入力にPTA2が使われていないのは、間違いではありません。 USBDEV基板上で10kΩの抵抗によってプルダウンされているため、 プルダウンタイプのキー入力には使えなかったのです。 LEDには、それぞれ1kΩの電流制限抵抗を付けています。
即興で作成したので、回路図は、存在しません。
プログラム
プログラムは、キーボードのフリをしていたプログラムをもとに改造を施しています。
レポート・デスクリプタ以外で変更があったのは、 インターフェース・デスクリプタに含まれるプロトコルIDが、 マウス・デバイスになったところです。
{ // Size of this Descriptor in Bytes sizeof(interface_descriptor), DT_INTERFACE, // Descriptor Type (=4) 0, // Number of *this* Interface (0..) 0, // Alternative for this Interface (if any) 1, // No of EPs used by this IF (excl. EP0) 0x3, // IF Class Code (0x03 - HID) 1, // Boot Interface Subclass Code 2, // IF Protocol Code (2 = Mouse) 0 // Index of String Desc for this Interface }, // end of InterfaceDesc
これ以外の部分は、すべてキーボード・デバイスのものと同じです。
キーボードのインジケータで使われていたエンドポイントを通じたデータ受信の部分は、 おそらく使われることはないだろうと思いましたが、そのまま残してあります。 エンドポイントを通じたデータの送信の部分は、 送信するデータ長が8バイトであったものを3バイトにしてあります。
#define RX0_SIZE (8) // Byte count of RX0 extern volatile byte Rx0Buffer[RX0_SIZE]; #define TX1_SIZE (3) // Byte count of TX1 extern volatile byte Tx1Buffer[TX1_SIZE];
改造のためのヒント
現在は、ポインタの動作が1ドットずつの超低速です。 キーを押しっぱなしにすると、加速していくような仕掛けをファームウェアに仕込むと もっとマウスらしくなるかと思います。
また、LEDは、単にボタンに連動して点灯するようになっているので、 もっと賢い使い方が出来ないか、考えています。
USB プロジェクト - マウスのReportDescriptorを見てみよう [USB]
キーボード・デバイスのフリができたので、次はマウス・デバイスに挑戦します。 ハードウェアを作る前に、マウス向けReportDescriptorについて調べます。
HID Descriptor Toolにおまかせ
USB.orgで見つけた"HID Descriptor Tool"のサンプル・デスクリプタを見ながら、 考えます。
0x05, 0x01, // USAGE_PAGE (Generic Desktop) 0x09, 0x02, // USAGE (Mouse) 0xa1, 0x01, // COLLECTION (Application)
最初は、このデバイスの種類(マウス)を宣言します。 ここから、マウス・デバイスとしてのデータ構造が始まります。
0x09, 0x01, // USAGE (Pointer) 0xa1, 0x00, // COLLECTION (Physical)
PhysicalタイプのPointer COLLECTIONが宣言されます。 Physicalが何を意味するのか、今のところ不明です。
0x05, 0x09, // USAGE_PAGE (Button) 0x19, 0x01, // USAGE_MINIMUM (Button 1) 0x29, 0x03, // USAGE_MAXIMUM (Button 3) 0x15, 0x00, // LOGICAL_MINIMUM (0) 0x25, 0x01, // LOGICAL_MAXIMUM (1) 0x95, 0x03, // REPORT_COUNT (3) 0x75, 0x01, // REPORT_SIZE (1) 0x81, 0x02, // INPUT (Data,Var,Abs)
マウス・デバイスからPCに送られる1バイト目の下3ビットの構造です。 三つのビットがそれぞれボタン1から3に相当しています。
0x95, 0x01, // REPORT_COUNT (1) 0x75, 0x05, // REPORT_SIZE (5) 0x81, 0x03, // INPUT (Cnst,Var,Abs)
1バイト目の残りの5ビットには、定数データ"0"が入ります。
0x05, 0x01, // USAGE_PAGE (Generic Desktop) 0x09, 0x30, // USAGE (X) 0x09, 0x31, // USAGE (Y) 0x15, 0x81, // LOGICAL_MINIMUM (-127) 0x25, 0x7f, // LOGICAL_MAXIMUM (127) 0x75, 0x08, // REPORT_SIZE (8) 0x95, 0x02, // REPORT_COUNT (2) 0x81, 0x06, // INPUT (Data,Var,Rel)
2バイト目と3バイト目には、(X,Y)座標データが入ります。 それぞれ符号付8ビットの表現になっていて、 しかも相対値の値になるとされています。
0xc0, // END_COLLECTION 0xc0 // END_COLLECTION
最後にPointerとMouseの二つのCOLLECTIONを閉じておしまいです。
なんだか、キーボード・デバイスに比べて簡単に見えます。 扱うデータも3バイトの片方向通信です。 HIDのサンプル・プログラムを使うと、あっという間にできるかも。
USB プロジェクト - ReportDescriptorの解析 [USB]
HIDのフリをするプログラムが出来たので、何が書いてあるのか解析します。 (普通は、逆だな。)
参考文献
- Device Class Definition for Human Interface Devices (HID) Firmware Specification—6/27/01
- HID Usage Tables 10/28/2004 Version 1.12
ReportDescriptorを読む
ファームウェアには、HIDに特有のReportDescriptorと呼ばれるデータが入っていて、 ホストの要求に応じてデータを返します。 このデータは、PCとの送受信に使われるパケットのフォーマットを規定していて、 賢いホストさまが、構文解析を行ってデバイスごとに適切なパケットを送受信してくれるという ありがたい仕組みになっています。 サンプルプログラムのこの部分には、一切コメントが無かったので、 勉強がてら解析することにしました。
05 01 - Global Usage Page = $01 (Generic Desktop) 09 06 - Local Usage = $06 (Keyboard)
Usageは、このデバイスの用途を指定します。 ここでは、Keyboardが指定されています。 Usageは、32ビットのデータで、Usage Pageは、その上位16ビットを指します。 と、仕様書には書いてあるのだけど、これは8ビットデータだよな。 まあ、いいか。
A1 01 - Main Collection = $01 (Application)
ここから、"End Collection"までの部分でパケットのフォーマットを指定します。 実際に送受信されるパケットの構造は、HIDの仕様書に書いてあります。
05 07 - Global Usage Page = $07 (Keyboard/Keypad Page) 19 E0 - Local Usage Minimum = $E0 (Left Control) 29 E7 - Local Usage Maximum = $E7 (Right GUI) 15 00 - Global Logical Minimum = $00 (0) 25 01 - Global Logical Maximum = $01 (1) 75 01 - Global Report Size = $01 (1 bit) 95 08 - Global Report Count = $08 (8 fields) 81 02 - Main Input = $02 (no NULL position; preferred state; linear; no wrap; absolute; variable; data)
最初の部分は、8ビットの送信データです。 Hutで定義されている8個のキーの状態をPCに知らせます。
ビット位置 | Usageコード | 対応するキー |
---|---|---|
0 | $E0 | 左のCtrl |
1 | $E1 | 左のShift |
2 | $E2 | 左のAlt |
3 | $E3 | 左のGUI(PC/ATではWindowsキー) |
4 | $E4 | 右のCtrl |
5 | $E5 | 右のShift |
6 | $E6 | 右のAlt |
7 | $E7 | 右のGUI(PC/ATではメニューキー) |
このファーム・ウェアでは、 それぞれのキーの状態をKbBuffer[0]に入れると、 それをPCが引き取ってしかるべき動作を行ってくれます。
95 01 - Global Report Count = $01 (1 field) 75 08 - Global Report Size = $08 (8 bits) 81 01 - Main Input = $01 (no NULL position; preferred state; linear; no wrap; absolute; array; constant)
この部分は、KbBuffer[1]に相当します。 8ビットの定数データが入ってくるだけで、PCでは何も処理されないはずです。
95 05 - Global Report Count = $05 (5 fields) 75 01 - Global Report Size = $01 (1 bit) 05 08 - Global Usage Page = $08 (LED) 19 01 - Local Usage Minimum = $01 (Num Lock) 29 05 - Local Usage Maximum = $05 (Kana) 91 02 - Main Output = $02 (non volatile; no NULL position; preferred state; linear; no wrap; absolute; variable; data)
ここから、唐突にPCから送られてくるデータの定義が始まります。 この部分は、LedBufferの下位5ビットに相当するデータです。 ファーム・ウェアがLedBufferの内容に従って、「何か」を行います。 それぞれのビットは、以下のように割り当てられます。
ビット位置 | Usageコード | 対応するLED |
---|---|---|
0 | $01 | Num Lock |
1 | $02 | Caps Lock |
2 | $03 | Scroll Lock |
3 | $04 | Compose |
4 | $05 | Kana |
ComposeとKanaは、 最近のキーボードには見当たりませんね。 LEDには、"Spinning"や"Message Waiting"なんていうものもあるので、 「ディスク回ってます」「メールが来たよ」インジケータがすぐに出来てしまうかも知れません。
95 01 - Global Report Count = $01 (1 field) 75 03 - Global Report Size = $03 (3 bits) 91 01 - Main Output = $01 (non volatile; no NULL position; preferred state; linear; no wrap; absolute; array; constant)
次の部分で、残りの上位3ビットを定義しています。 この部分には、定数データ(0)が入ってくるはずです。
95 06 - Global Report Count = $06 (6 fields) 75 08 - Global Report Size = $08 (8 bits) 15 00 - Global Logical Minimum = $00 (0) 25 65 - Global Logical Maximum = $65 (101) 05 07 - Global Usage Page = $07 (Keyboard Keypad Page) 19 00 - Local Usage Minimum = $00 (no event) 29 65 - Local Usage Maximum = $65 (Application) 81 00 - Main Input = $00 (no NULL position; preferred state; linear; no wrap; absolute; array; constant)
再び、PCに送るデータの宣言が入っています。 ここは、KbBuffer[2]からKbBuffer[7]に相当する部分で、それぞれにUsageコードが入ります。 つまり同時に6個のキー入力を伝えることが出来るというわけです。 入れることのできるコードは、102種類で、101タイプ・キーボードのすべてのキーに対応しています。 どのキーも押されていなければ、$00(no event)を入れます。
C0 - Main End Collection
データ(Collection)の終端をあらわす印で、デスクリプタは、完結します。
HIDデバイスに関する情報はこちら
HIDに関する資料などは、このサイトにあります。
ここには、"HID Descriptor Tool"なんてものもあって、 16進数と格闘することなくデスクリプタを記述することの出来るツールなどがそろっています。 なんだ、これをそのままコピーしたのか。 納得です。
char ReportDescriptor[63] = { 0x05, 0x01, // USAGE_PAGE (Generic Desktop) 0x09, 0x06, // USAGE (Keyboard) 0xa1, 0x01, // COLLECTION (Application) 0x05, 0x07, // USAGE_PAGE (Keyboard) 0x19, 0xe0, // USAGE_MINIMUM (Keyboard LeftControl) 0x29, 0xe7, // USAGE_MAXIMUM (Keyboard Right GUI) 0x15, 0x00, // LOGICAL_MINIMUM (0) 0x25, 0x01, // LOGICAL_MAXIMUM (1) 0x75, 0x01, // REPORT_SIZE (1) 0x95, 0x08, // REPORT_COUNT (8) 0x81, 0x02, // INPUT (Data,Var,Abs) 0x95, 0x01, // REPORT_COUNT (1) 0x75, 0x08, // REPORT_SIZE (8) 0x81, 0x03, // INPUT (Cnst,Var,Abs) 0x95, 0x05, // REPORT_COUNT (5) 0x75, 0x01, // REPORT_SIZE (1) 0x05, 0x08, // USAGE_PAGE (LEDs) 0x19, 0x01, // USAGE_MINIMUM (Num Lock) 0x29, 0x05, // USAGE_MAXIMUM (Kana) 0x91, 0x02, // OUTPUT (Data,Var,Abs) 0x95, 0x01, // REPORT_COUNT (1) 0x75, 0x03, // REPORT_SIZE (3) 0x91, 0x03, // OUTPUT (Cnst,Var,Abs) 0x95, 0x06, // REPORT_COUNT (6) 0x75, 0x08, // REPORT_SIZE (8) 0x15, 0x00, // LOGICAL_MINIMUM (0) 0x25, 0x65, // LOGICAL_MAXIMUM (101) 0x05, 0x07, // USAGE_PAGE (Keyboard) 0x19, 0x00, // USAGE_MINIMUM (Reserved (no event indicated)) 0x29, 0x65, // USAGE_MAXIMUM (Keyboard Application) 0x81, 0x00, // INPUT (Data,Ary,Abs) 0xc0 // END_COLLECTION };
デスクリプタはここから来たらしい
HIDの仕様書の最後には、サンプル・デスクリプタが記載されています。 今回調べたファームウェアは、キーボードのサンプル・デスクリプタと一字一句同じでした。
2008-02-06 22:13:05 追記
他のUsageでLEDを操作できるか?
Message Waitingを使おうとしたのだけれど、失敗しました。 Telephonyに入っているところをみると、 どうやら「留守番電話にメッセージあり」という意味だったようです。
Spinningも使えませんでした。 こちらは、Mediaに入っているので、 「Mediaデバイス」のフリをさせないと情報が来ないのだろうと思います。
Muteもダメ。 これも、Consumerデバイスに分類されているので、 それなりのデバイスのフリをさせなくてはならないようです。
だったら、Keyboardに分類されている Shiftならいけるだろうと考えましたが、 そもそも、Shiftインジケータがどんな条件で点灯するのかがわからず、 操作できませんでした。
と、4連発で失敗してしまいました。 HIDインターフェースでLEDを光らせるのは、難しいようなので、 次は、マウス・インターフェースにでも挑んでみます。