HID Interrupt transferの動作確認
HID Report DescriptorとHostとの通信について(Interrupt Transfer)
デバイス設定:断りの無い限り,下記のとおりとする.
item | value | remark |
---|---|---|
Poling cycle | 10 mSec | Interface Descriptorで指定 |
EndPointBuffer size | 64 bytes | Interrupt transfer |
Report Descriptorで定義したサイズは固定長である.
転送サイズについて
Report Descriptorでは,Variable....という記載があったので,デバイス→ホスト間の転送サイズは任意であると考えていた.実際に動かして見ると調子が悪かったので,HIDは 原則固定長であると認識した.
実際にDevice/Host間で送受信データサイズを変えて確認した.ただし,いずれもデバイスのReport Descriptorでは 8bit 64count(64octet)とし,Interface Descriptorでは,EP sizeを64octetとしている.
Transfer direction | from | to | Remark |
---|---|---|---|
IN transaction | DEV: 64byte送信 | HOST: 64Bytes受信 | OK |
OUT transaction | HOST: 64byte送信 | DEV:64byte受信 | OK |
IN transaction | DEV: 1byte送信 | HOST: 64Bytes受信 | NG |
IN transaction | DEV: 1byte送信 | HOST: 1Bytes受信 | NG |
IN transaction | DEV: 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 |
---|---|---|---|
10mSec | 8mSec | 取りこぼしなし | 周期が早いのは..? |
1mSec | 2mSec | かなりこぼす | 複合デバイスにしたので,最速では回らないか? スレッドで0秒寝かせているのが悪いのかもしれない.回りっぱなしでもトライする?. |
2mSec | 2mSec | かなりこぼします | 10/Sep.の追試実験用.条件よければOK. |
※試験結果を随時追加していく.★疑問:GetInputReportBufferSize は なに?EPサイズでもreportサイズでもない.. reportの記憶回数でもない..
追記(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ポート制御部分を切り出してコンポーネント化しておきたいところですね.