😫

ESP32を通してUSBデバイスへの理解をちょっと深めた話

2022/12/16に公開

はじめに

本記事は「株式会社RetailAI X Advent Calendar 2022」の16日目の記事です。
昨日は@riseiさんの「迅速開発GoAdminでマスタ管理」でした。

本記事について

IoTデバイスを新規で開発する際、データのやり取りを行うため、従来使用されている機器と接続する必要がありました。それはです。IoTといえばWiFiや無線で接続しデータの送受信を行うイメージですが、要求仕様によって、例えばUSBといった有線でつなげるケースもあります。普段、USBデバイスの仕組みについて、深く知ること無くつかっていました。しかし調べてみると奥深くややこしいことがわかりました。
本記事ではマイコンのESP32を例に取り、USBデバイスとしてディスクリプタを変更することができるのか試してみたことを紹介します。

USBの規格はややこしい

USBデバイスのディスクリプタ

USBデバイスがどのような仕様なのかを示すのがディスクリプタです。
これは下記の表に示すディスクリプタで整理され、また下記のような親子関係をもつ階層構造で表されます。これまでマウスやキーボードといったUSBデバイスをPCなどに接続するとき、気にすることはありませんが、ディスクリプタで仕様でやり取りされています。
ディスクリプタの種類は非常に多く、何が何を指しているのかよくわからないですね。

デバイスディスクリプタ
  └コンフィグレーションディスクリプタ
    └インターフェイスディスクリプタ
       └エンドポイントディスクリプタ
デバイス・ディスクリプタ
フィールド名 サイズ(バイト) 説明
bLength 1 ディスクリプタのサイズ
bDescriptor 1 ディスクリプタのタイプ
bcdUSB 2 BCD表現のUSB仕様リリース番号
bDeviceClass 1 クラス・コード(0:クラスなし、0xFF:ベンダ、1~0xFE:特定)
bDeviceSubClass 1 サブクラス・コード
bDeviceProtocol 1 プロトコル・コード(0:固有プロトコルを使用せず、0xFF:ベンダ特有)
bMaxPacketSize0 1 エンドポイント0の最大パケット・サイズ
idVendor 2 ベンダID(USB IFが割り当てる)
idProduct 2 プロダクトID(ベンダが割り当てる)
bcdDevice 1 BCD表現のデバイスのリリース番号
iManufacturer 1 製造者を表すストリング・ディスクリプタへのインデックス
iProduct 1 製品を表すストリング・ディスクリプタへのインデックス
iSerialNumber 1 デバイスの製造番号を表すストリング・ディスクリプタへのインデックス
bNumConfigurations 1 構成可能な数
コンフィグレーション・ディスクリプタ
フィールド名 サイズ(バイト) 説明
bLength 1 ディスクリプタのサイズ(0x09で固定)
bDescriptor 1 ディスクリプタのタイプ(0x02で固定)
wTotalLength 2 構成全体(構成,インターフェース,エンドポイント,その他のディスクリプタ)の長さ
bNumInterfaces 1 構成の持つインターフェースの数
bConfigurationValue 1 SetConfigurationで,この構成を選択するための引き数値(1以上)
iConfiguration 1 構成を表すストリング・ディスクリプタへのインデックス
bmAttributes 1 構成の特性.ビット単位で意味付け(D7:“1”、D6:自己電源、D5:リモート・ウェークアップ、D4~D0:予約(0)
bMaxPower 1 最大バス電力電力消費量を2mA単位で指定
インターフェース・ディスクリプタ
フィールド名 サイズ(バイト) 説明
bLength 1 ディスクリプタのサイズ(0x09で固定)
bDescriptorType 1 ディスクリプタのタイプ(0x04で固定)
bInterfaceNumber 1 構成の中で,このインターフェースを表すインデックス番号(0ベース)
bAlternateSetting 1 SetInterfaceで,代替設定を選択するための引き数値
bNumEndpoints 1 (エンドポイント0 を除く)インターフェースの持つエンドポイント数
bInterfaceClass 1 クラス・コード(0:クラスなし,0xFF:ベンダ,1~0xFE:特定)
bInterfaceSubClass 1 サブクラス・コード
bInterfaceProtocol 1 プロトコル・コード(0:固有プロトコル使用せず、0xFF:ベンダ固有)
iInterface 1 このインターフェースを表すストリング・ディスクリプタへのインデックス
エンドポイント・ディスクリプタ
フィールド名 サイズ(バイト) 説明
bLength 1 ディスクリプタのサイズ(0x07で固定)
bDescriptorType 1 ディスクリプタのタイプ(0x05で固定)
bEndpointAddress 1 エンドポイント・アドレス.ビット単位で意味付け(D7 :方向 0:OUT,1:IN、D6~D4 :予約(0)、D4~D0 :エンドポイント番号)
bmAttributes 1 属性(ビット単位で意味付け)(D1~D0:転送タイプ (0:コントロール 1:アイソクロナス 2:バルク 3:割り込み)、D5~D2はアイソクロナス・エンドポイントのみで使用、D3~D2:同期タイプ(0:同期なし、1:非同期、2:アダプティブ、3:同期)、D5~D4:ユーセージ・タイプ(0:データ・エンドポイント、1:フィードバック・エンドポイント、2:従属的なフィードバック・エンドポイント、3:(予約)))
wMaxPacketSize 2 ペイロード・サイズ指定(ビットで意味付け)D10~D0:最大パケット・サイズD12~D11:µフレームあたりの追加的なトランザクション数(HSのアイソクロナスと割り込みのみ)wMaxPacketSize 2 0:追加なし(1トランザクション/µフレーム)1:一つ(2トランザクション/µフレーム)2:二つ(3トランザクション/µフレーム)3:未使用(予約)
bInterval 1 データ転送のエンドポイントをポーリング間隔FS/LS割り込み:ms単位(フレーム数)で指定HSアイソクロナス/割り込み:µ フレーム単位で2(N-1)の N を指定(たとえば,bIntervalが4の場合,8µフレームに1回ポーリング)bInterval 1 FSアイソクロナス:1ms単位(フレーム数)で2(N-1)のNを指定HSバルク/コントロール:エンドポイントの最大NAKレートを µ フレーム単位で指定.値0はOUT/DATAトランザクションでNAK応答しないことを意味
ストリング・ディスクリプタ
フィールド名 サイズ(バイト) 説明
bLength 1 ディスクリプタのサイズ(N+2)
bDescriptorType 1 ディスクリプタのタイプ(0x03で固定)
bString N UNICODE文字列
デバイス・クラスの種類とクラス・コード
クラス仕様 デバイスクラス・コード インターフェース・クラス・コード
Firmware Update - FEh(サブクラス01h)
IrDA/USB Bridge - FEh(サブクラス02h)
Audio Interface 00h 01h
Communication Device 02h -
CDC Control Interface - 02h
CDC Data Interface - 0Ah
HID 00h 03h
HUB 09h 09h
Mass Storage 00h 08h
Monitor HIDと同じ HIDと同じ
Power devices HIDと同じ HIDと同じ
Physical - 05h
Printer - 07h
ベンダ独自 - FFh

あるUSBデバイスドライバに認識されるには

あるUSBデバイスドライバに認識されるには、ディスクリプタのどのパラメーターを合わせ込めば良いのでしょうか?必要そうなパラメーターを考えたところ、下記のディスクリプタが重要なのではないかと考えました。

- デバイス・ディスクリプタ
  - idVendor(ベンダーID:VID):ベンダID(USB IFが割り当てる)
  - idProduct(プロダクトID:PID):プロダクトID(ベンダが割り当てる)
- インターフェース・ディスクリプタ 
  - bInterfaceClass:デバイスの種類

VIDやPIDは、USBデバイスをPCに接続したときに接続された機器を特定するために使用されます。
bInterfaceClassでは、USBデバイスの種類、例えばHID(Human Interface Device)なのかCDC(Communication Device Class)なのかといったことを示します。
bInterfaceClassは下記のように定義されています。

https://wiki.onakasuita.org/pukiwiki/?bInterfaceClass

ESP32を例にとっていろいろ試してみる

シリアル変換モジュール「CP2102」のVID/PIDを書き換えてみる

ESP32にはシリアル変換モジュール「CP2102」が基板にのっています。
こちらのモジュールは、そのメーカであるSilicon Labs社のツールで一度に限ってVID・PIDをヘンコすることができます。こちらのやり方は、下記の記事で説明しています。

https://zenn.dev/takudooon/articles/4146826005bf0a

bInterfaceは変更できるのか?

シリアル変換モジュール「CP2102」のbInterfaceはベンダー独自の「0xFF」となっているようです(下記資料の21ページ目を参照)。

https://www.silabs.com/documents/public/application-notes/an978-cp210x-usb-to-uart-api-specification.pdf

こちらは変更できないようです。
そのため、CDCドライバからの制御は行うことができません。

https://www.macnica.co.jp/business/semiconductor/support/faqs/silicon_labs/128737/

まとめ

シリアル変換モジュール「CP2102」がのっているESP32に限ってですが、マイコンが文鎮化するリスクはありますが、VID・PIDを変更することができます。ただし、bInterfaceClassはベンダー独自で変更することができません。そのため、CDCでやり取りすることを想定したシステムに対してドライバを介して、データを送受信する際、VID・PIDを合わせこんだだけでは通信は難しいです。またそれ以外のディスクリプタも相違もドライバの認識に関係してくると考えられます。
現実的に考えると、既存のハードウェアに対して有線かつ後付でUSBを介してデータ通信を行うような機器構成で、汎用的なマイコンを使ってシステムを実現できるよう取り組むことは、開発のハードルが高そうです。

参考資料

次は...

次回は@UtaMoriさんの記事です。
ぜひ、ご覧ください!

Discussion