HID Interrupt transferの動作確認

2008/09/08工作::USBimport

HID Report DescriptorとHostとの通信について(Interrupt Transfer)

デバイス設定:断りの無い限り,下記のとおりとする.

itemvalueremark
Poling cycle10 mSecInterface Descriptorで指定
EndPointBuffer size64 bytesInterrupt transfer

Report Descriptorで定義したサイズは固定長である.


転送サイズについて

Report Descriptorでは,Variable....という記載があったので,デバイス→ホスト間の転送サイズは任意であると考えていた.実際に動かして見ると調子が悪かったので,HIDは 原則固定長であると認識した.

実際にDevice/Host間で送受信データサイズを変えて確認した.ただし,いずれもデバイスのReport Descriptorでは 8bit 64count(64octet)とし,Interface Descriptorでは,EP sizeを64octetとしている.

Transfer directionfromtoRemark
IN transactionDEV: 64byte送信HOST: 64Bytes受信OK
OUT transactionHOST: 64byte送信DEV:64byte受信OK
IN transactionDEV: 1byte送信HOST: 64Bytes受信NG
IN transactionDEV: 1byte送信HOST: 1Bytes受信NG
IN transactionDEV: 64byte送信HOST: 1Bytes受信NG

Full Speed時は64byte(max)のユーザデータを送受信できるが,固定長となる.UARTの代替として使用する場合,もしくは64octetを超えるパケット通信の土管として使用する場合には,さらに一段ラッパーをかます必要がある.Low Speedもサポートするのであれば,パケットサイズが8octetになる.

 struct {
	Byte SizeOfData ;
	BYte Data[63] ;		// Low=63, High/Full=63
	}

PC側は,Report Descriptorを受け取って,capabilityとしてサイズを認識するので,データサイズは固定としない.デバイス側で,最大長となるように設定する.デバイスのメモリの都合・データ流量の都合により,削減する場合は,descriptor・interrupt transferの転送サイズをReport通りに修正するだけでよい.


取りこぼしについて(後方参照のこと!)

Interrupt転送は,デバイス-ホスト間でのデータ取りこぼしが起こりえないという話をどこかで見かけた.本当にそうなのかを検証してみた.*1

  • デバイスは複合デバイスとして実装した.同一USB portに複数のデバイスがぶら下がっているため,Interrupt転送を占有していない.← このため,最大負荷テストではない
  • PSoC側で1mSecの分周クロックを生成して*2,INパケットに積む
  • 毎パケット,+1したシーケンス番号を積んで,取りこぼしを検出する.
  • Host側は受信スレッドを作成し,受信開始からスレッドをまわす.取得したデータはQueueに積んでいく. 表示するときにQueueから吸い上げてListに突っ込む.
Poling Timing(Report)PO cycle実測結果Remark
10mSec8mSec取りこぼしなし周期が早いのは..?
1mSec2mSecかなりこぼす複合デバイスにしたので,最速では回らないか? スレッドで0秒寝かせているのが悪いのかもしれない.回りっぱなしでもトライする?.
2mSec2mSecかなりこぼします10/Sep.の追試実験用.条件よければOK.

※試験結果を随時追加していく.★疑問:GetInputReportBufferSize は なに?EPサイズでもreportサイズでもない.. reportの記憶回数でもない..



*1 : ホスト→デバイス方向は未確認

*2 : Sysclk=24MHz, VC1-VC2-VC3接続し,Timer8bitでカウント.VC1=15, VC2=16, VC3=100 → 1mSec

追記(10/Sep./2008)

同時期に,C++ Builderでホストプログラムを作成されている方より,取りこぼしはおきない旨,伺いました.Visual C#が癌なのかという疑いが出てきます.

ホスト側実装

前回の測定時のフローを示す.
1. 受信を Threadクラスを使ってぶん回す.ひたすらRead()を呼び出し続ける.
2. 受信したデータは,下記のクラスを用いてデータを積み上げる.

System.Collection.Generic.Queue<Byte>

このとき,スレッド内では,追加時にlock(Que)する.
3. Formスレッドで,上記クラスからenqueしてデータを取り出し,表示用のListControlに追加する.
このとき,lockなし,連続して吸い出している.

チェキ1

Formスレッドで,Queから読み出すたびに,Thread.sleep(0)を入れた.体感もっさりした気がする*3.取りこぼしは消えない.

追加情報

MSVSのmanualを見ると,動的に記憶領域を延ばすと書かれていた.追加時のオーバヘッドでもこぼしている可能性があるのだろう.
また,ロックの仕方に問題がありそう.本質的にスレッドセーフではないとあるので,記載どおりのlockを行う.
・・・ために,generic Queueではなく,Collection.Queueクラスを用いる.

チェキ2

ヘルプに記載のとおり,下記のlock()を適用する.

lock(myCollection.SyncRoot)

System.Collection.Generic.Queue<>ではなく,System.Collection.Queueを使ってみた.読み書き両方でlock()を使ったが,やはり駄目だ.
Byte[]の動的確保にも負荷がかかっているのだろう.開放処理もGCが走るだろうし.

チェキ3

表示させずにデータ取得させてから,表示してみる.
取りこぼしナシ.CPU負荷がそれなりにかかる模様*4

アプリケーション走行速度・データの扱い方による問題と思われる.

また,受信バッファは512byteまで指定可能のようだ.でかい値を放り込んでも拒絶される(32byte:report値に戻る?).
ただし,これによる救済措置が図れるのかは疑問である・・・.


結論

実は,チェキ1が終わった段階で,USB Snoopy Proを使用してPC側にデータが来ているかどうかを確認していた.
案の定,データが来ていることは見て取れたので,取りこぼしているのはドライバ~アプリのアタリであるとアタリがついていたのです.

そもそもHIDと言っている以上,ヒトの応答速度程度の情報を送受信するのが目的であるし,仕様なのかもしれない.
また,Report Descriptorを見ても判るように,固定長・ビット単位で意味を持たせたデータを送るのが目的であり,今回のようにストリームデータを送るのは想定外使用なのだろう.もし,確実に双方向のデータ通信を行いたいのであれば,ホスト~デバイス間でハンドシェイクを行う必要があるでしょう.

そこまでするならBulk転送で~とも思うのだけれど,それはそれでホスト側のドライバが面倒.汎用USBドライバやWinUSBといったものがあるようなので,それらの使用も視野に入れたい.
ひとまずHIDポート制御部分を切り出してコンポーネント化しておきたいところですね.

*3 : ListControlへの追加が遅くなっただけだろう

*4 : Core2Duo 2.4GHz(E6600)で30%超. VisualC# デバッグモードにて.Debug Writeも使用.