USB CDC です。立て続けに完成!完成!完成!!ですが、実際にある程度まで行くと芋づる式にできるようになってきます。最初はあんなにいろんなことに抵抗感があったのに...
実装するだけなら実はそんなに難しくなかった USB CDC です。
参考サイト
いくつもあります。よく参考にしたものたちを列挙しますね。
- USB CDCについて
- 一番参考にした記事。ディスクリプタやリクエストについて非常に簡潔に書かれています。
- 5.1 標準USBデスクリプタ定義(Standard USB Descriptor Definitions) - とりメモ
- デバイスディスクリプタの参考に。
- 6. Communications Class 固有のメッセージ(Communications Class Specific Messages) - とりメモ
- 規格書の和訳。ありがたや。
- [STM32メモ] USBでCDCを2つ持つ複合デバイスを作る ディスクリプタ編(IADとか) - Qiita
- CDC だけでなく、色々と参考になりました。
闇が深い??
実はそこまで闇は深くありませんでした。まあ確かに、トップダウン式に USB CDC で PSTN サブクラスの ACM をやって... と進めていくのは闇が深くて井戸型ポテンシャルのようです。
ただ、そこまでして学ぶのもなあ...とも思いますので、ひとまず出来合いのコード(ディスクリプタ)を参考に、その意味を考えていくという方式でやっていく分には、意外と難しくなかったです。
むしろ、USB HID ではレポートディスクリプタという複雑怪奇な階層型ディスクリプタを用意せねばならないという点で、そっち方が闇が深いというか、面倒くさいといった感じでしたね。
ディスクリプタ
コンフィギュレーションディスクリプタが長いですが、理解すればどうってことありません(理解すれば)。
下の例では、Send Break をサポートするデバイスとしてディスクリプタを構成しています。実装ではスルーしているという現状なんですけどね。
const USBDeviceDescriptor dev_desc_cdc = {
.bLength = DEVICE_DESCRIPTOR_LENGTH,
.bDescriptorType = DEVICE_DESCRIPTOR_TYPE,
.bcdUSB = USB_BCD_USB2_0, // USB2.0
.bDeviceClass = 2, // CDC
.bDeviceSubClass = 0, // unused
.bDeviceProtocol = 0, // unused
.bMaxPacketSize0 = 64,
.idVendor = 0xdead,
.idProduct = 0xbeef,
.bcdDevice = 0x100,
.iManufacturer = 2,
.iProduct = 3,
.iSerialNumber = 1,
.bNumConfigurations = 2
};
const uint8_t __attribute__((aligned(2))) conf_desc_cdc[] = {
// Configuration Descriptor
CONFIG_DESCRIPTOR_LENGTH,
CONFIG_DESCRIPTOR_TYPE,
67 & 0xFF, // .wTotalLength
67 >> 8, // same above
2, // .bNumInterfaces
1, // .bConfigurationValue,
0, // .iConfiguration
0xC0, // .bmAttributes
0x32, // MaxPower
// Interface Descriptor for Communications Class =======
INTERFACE_DESCRIPTOR_LENGTH,
INTERFACE_DESCRIPTOR_TYPE,
0, // bInterfaceNumber
0, // bAlternateSetting
1, // bNumEndpoints
0x02, // .bInterfaceClass(Communication Interface Class)
0x02, // .bInterfaceSubClass(ACM)
0x01, // bInterfaceProtocol(Common AT Commands)
0, // iInterface
/* Functional Descriptor[0] ----------------------------------------------- */
/* Header Functional Descriptor */
0x05, /* bLength: Endpoint Descriptor size */
0x24, /* bDescriptorType: CS_INTERFACE */
0x00, /* bDescriptorSubtype: Header Func Desc */
0x10, /* bcdCDC: spec release number */
0x00,
/* Functional Descriptor[1] ----------------------------------------------- */
/* Call Management Functional Descriptor */
0x05, /* bFunctionLength */
0x24, /* bDescriptorType: CS_INTERFACE */
0x01, /* bDescriptorSubtype: Call Management Func Desc */
0x00, /* bmCapabilities: D0+D1 (no call management supported) */
0x01, /* bDataInterface: Interface[1] */
/* Functional Descriptor[2] ----------------------------------------------- */
/* ACM Functional Descriptor */
0x04, /* bFunctionLength */
0x24, /* bDescriptorType: CS_INTERFACE */
0x02, /* bDescriptorSubtype: Abstract Control Management desc */
0x06, /* bmCapabilities: * */
/* Functional Descriptor[3] ----------------------------------------------- */
/* Union Functional Descriptor */
0x05, /* bFunctionLength */
0x24, /* bDescriptorType: CS_INTERFACE */
0x06, /* bDescriptorSubtype: Union func desc */
0x00, /* bMasterInterface: Communication class interface = Interface[0] */
0x01, /* bSlaveInterface0: Data Class Interface = Interface[1] */
// Endpoint Descriptor
ENDPOINT_DESCRIPTOR_LENGTH,
ENDPOINT_DESCRIOTPR_TYPE,
0x81, // .bEndpointAddress(IN, EP1)
0x03, // Interrupt Transfer
64, // .wMacPacketSize
0,
0x10, // bInterval
// Interface Descriptor for Data Class ================
INTERFACE_DESCRIPTOR_LENGTH,
INTERFACE_DESCRIPTOR_TYPE,
1, // bInterfaceNumber
0, // bAlternateSetting
2, // bNumEndpoints
0x0A, // .bInterfaceClass(CDC)
0x00, // .bInterfaceSubClass(None)
0x00, // bInterfaceProtocol(None)
0, // iInterface
// Endpoint Descriptor
ENDPOINT_DESCRIPTOR_LENGTH,
ENDPOINT_DESCRIOTPR_TYPE,
0x02, // .bEndpointAddress(OUT, EP2)
0x02, // Bulk Transfer
64, // .wMacPacketSize
0,
0x00, // bInterval
// Endpoint Descriptor
ENDPOINT_DESCRIPTOR_LENGTH,
ENDPOINT_DESCRIOTPR_TYPE,
0x83, // .bEndpointAddress(IN, EP3)
0x02, // Bulk Transfer
64, // .wMacPacketSize
0,
0x00, // bInterval
};
使用するエンドポイント
上のコードのコメントにもあるように、以下を使います。ダブルバッファを使えばもっとスマートなんですが。
- EP1: Interrupt, IN
- EP2: Bulk, OUT
- EP3: Bulk, IN
いわゆるシリアルデータの受け渡しは EP2, EP3 を使います。
動作
汚い文字列ですが、ハロワしてます。やりました。
〆
ということで、HID、HID コンポジットデバイス、CDC による Virtual COM ポートと3つの機能を実装できたので、大満足です。
ところで、ここまで実装したプロジェクトを中途半端に C++ で整形したものを GitHub に上げました。repo はこちら。若干ゴミファイルが残ってたりしますが、ご了承ください。使い方は察してもらえると幸いです。
何かわからないことがあればコメントでどうぞ。
それでは、今回でこのシリーズは本当の最終回になります。皆様ご覧頂きまして、誠にありがとうございました。