SSブログ

macOSからPi Picoを使う - その19 [Pi Pico]

TinyUSBがUSBのdescriptorをどう扱うかをサクッと見た。こうやって見てくると、ようするにUSBの規格をそのままstructとして実装した、というだけのようである。まあ当たり前かもしれないけど、それならそれでもう少しdecsriptorを書く手間を省いてくれるような工夫をしてくれてもいいような気がする。しかしdescriptorを書き慣れた人にはかえってそういうのがウザいのかもしれない...

15.3.5  device descriptorをどうやって返すか

まあこれでdescriptorができたとして、そのあとどうすればいいか、というとホストからのデバイスリクエストにこのdescriptorで応答しないといけない。勝手に応答するわけにはいかなくて、どうするかというと当然TinyUSBが用意していて、コールバック関数を書く形式になっている。
uint8_t const * tud_descriptor_device_cb(void);
この関数が呼ばれるとdevice descriptorを返すように書く。そうするとホストのデバイスリクエストへの応答が実行される。なぜかtusb_desc_device_tへのポインタではなく、バイト列(uint8_t *)として返すことになっている。このプロトタイプ宣言は
src/device/usbd.h
にある。
具体的にはexampleに似た記述がいっぱいあって
tusb_desc_device_t const desc_device =
{
    .bLength            = sizeof(tusb_desc_device_t),
    .bDescriptorType    = TUSB_DESC_DEVICE,
    .bcdUSB             = 0x0200,

    .bDeviceClass       = TUSB_CLASS_MISC,
    .bDeviceSubClass    = MISC_SUBCLASS_COMMON,
    .bDeviceProtocol    = MISC_PROTOCOL_IAD,

    .bMaxPacketSize0    = CFG_TUD_ENDPOINT0_SIZE,

    .idVendor           = 0xCafe,
    .idProduct          = USB_PID,
    .bcdDevice          = 0x0100,

    .iManufacturer      = 0x01,
    .iProduct           = 0x02,
    .iSerialNumber      = 0x03,

    .bNumConfigurations = 0x01
};

uint8_t const * tud_descriptor_device_cb(void)
{
  return (uint8_t const *) &desc_device;
}
のようになっている。structをリテラルで初期化するときメンバ名で指定できるようにC99規格ではなったのね。これは便利で読みやすい。20年も前のことなのに全然知らなかった。コールバックではこのポインタを返すだけでいい。

configuration descriptorとstring descriptorのコールバックも同じようにすればいいらしい。これもexampleに同じような記述が繰り返しあるのでわかる。

15.3.6  interface descriptorとendpoint descriptorの渡し方

これでだいたいわかった。でも実際に実装するためにはまだわからないことが残っている。まずinterface descriptorとendpoint descriptorをどうやって渡せばいいのかわからない。そのためのコールバックはない。

configuration descriptorを書けば決まってしまうか、というとそうではないはずで、どうなってるんだろう。しばらくわからなかった。

しかしUSBの規格ではconfiguration descriptorの読み出しのときに、それに続けてinterface descriptorとendpoint descriptorを読み出すことになっている。もともとconfiguration descriptorのwTotalLengthフィールドはそれらも含めたバイト数になっている。

ということは単にconfiguration descriptorを返すときにその本体だけでなく、ベタに続けてinterface descriptorとendpoint descriptorを書いておく、ということのようである。TinyUSBはそれを単にバイト列としてホストへ投げるだけらしい。

それでやっとわかった。ソースを最初に見たときはこれをどこで使うんだろう、と思っていたが、そのためのマクロがsrc/device/usbd.hに定義されている。

device classごとにあって例えばvendor specific class用には
//------------- Vendor -------------//
#define TUD_VENDOR_DESC_LEN  (9+7+7)

// Interface number, string index, EP Out & IN address, EP size
#define TUD_VENDOR_DESCRIPTOR(_itfnum, _stridx, _epout, _epin, _epsize)
....
とある。これはinterfaceがひとつと、そのendpointの入出力pipeがそれぞれひとつずつの一番簡単なもので、そのinterface descriptorとふたつのendpoint descriptorのバイト列を返すようになっている。CDCなんかの他のクラスではconfiguration descriptorのうしろにこのバイト列をpaddingなしで詰めて、コールバックで返せばいい、ということになっている。configuration descriptorにあるwTotalLengthフィールの値は、string descriptor以外は固定サイズなのでusbd.hにあるTUD_CONFIG_DESC_LENと、TUD_VENDOR_DESC_LENから自分で計算して入れろ、ということのようである。

まあ、それほど難しくはないけど、もうちょっと親切にしてもらってもいいのではないか、とは思う。例えば、structにデータを埋めて関数に渡すとhostに返すバイト列になるとか、ほんのちょっとしたことだけど、素人にとってわかりやすさは全然違うと思う。

そもそもこのmacroだと定義されたstructとは無関係になってる。初めてTinyUSBを見たときstructとmacroとどちらが入り口になっているのかわからない。そういえば、examplesのそれぞれに似たような記述が繰り返し現れるのを見て、こんなのなんとかすればいいのに、と思ったのを思い出した。まあプログラムしてると良くあることで、似たようなことをする関数をいくつも作ってしまうなんてことはある。そういうもんだ、といえばそういうものだけど。
nice!(0)  コメント(0) 

nice! 0

コメント 0

コメントを書く

お名前:
URL:
コメント:
画像認証:
下の画像に表示されている文字を入力してください。

この広告は前回の更新から一定期間経過したブログに表示されています。更新すると自動で解除されます。