LoginSignup
18
10

More than 3 years have passed since last update.

[STM32メモ] USBでCDCを2つ持つ複合デバイスを作る ディスクリプタ編(IADとか)

Last updated at Posted at 2020-02-25

9年ほど前に、STM32のUSBライブラリとかCDCの仕様を調べていたことがあったが、結局あまり触れずじまいだった。最近とある事情でCDCを2つ持つ(VCP: Virtual COM Portでつながる)USBデバイスを作りたくなり、資料を読み返している。

中でも難解だったのが、複合デバイス(Composite Device)を作る際に必須となるIAD(Interface Association Descriptor)。もう色々な人が解説をしているだろうから需要はないかもしれないが、自分の理解のために書き残す。(ちゃんとした文書に準拠した用語を使えていない箇所があるかもしれないので、仕様書を確認のこと)

サンプル

ディスクリプタを書いてCDCが2つある複合デバイスとして認識できるようにしたもの。一方のCOMポートは動作しない。STM32F072RBT6を搭載した、NUCLEO-F072RBボードで動作する。
https://github.com/stm32p103/stm32practice/releases/tag/0.0.1
image.png

参考文献

CDC: Communication Device Class

お題としてCDCを2つ持つ複合デバイスを作るにあたり、まずはCDCのサブクラスである、ACMのディスクリプタがどうなるかを把握する。

CDCとは

厳密な定義はCDCの仕様書に任せるとして、CDCは「USBで通信をする類のデバイスの総称である」くらいの理解で十分かと思う。「CDC仕様書: 3.4 Interface Definitions」によると、CDCは以下の2種類のインターフェースにより構成される。
image.png

  • Communications Class Interface
    • OUTエンドポイント: ホストからデバイスを管理するためのエンドポイント。(特別必要でなければEP0上でやり取りするので、必須ではない)
    • INエンドポイント: デバイスからホストへ通知するためのエンドポイント。
    • どちらも大抵はインタラプト転送を使う。CDCサブクラスの種類によって機能が異なる。デバイスにどんな機能があるかは、CDCクラス固有のDescriptor(Class-specific Descriptor)を使ってホストに伝える。
  • Data Class Interface
    • OUTエンドポイント: ホストからデバイスへのデータを送るエンドポイント。
    • INエンドポイント: デバイスからホストへデータを送るエンドポイント。
    • 「CDC仕様書: 3.5.2 Data Class Endpoint Requirement」によると、バルク転送またはアイソクロナス転送のペアであることが期待される。CDCの種類によっては使わない場合もある。

ACM: Abstract Control Model とは

シリアル通信を模擬してVCPをつかって通信したい場合は、CDCのサブクラスであるPSTN(Public Switched Telephone Network)の「Abstract Control Model」を使用する(PSTN仕様書: 3.2.2.1 Abstract Control Model Serial Emulation参照)。

ACMは、CDCの一般的な構成の通り、Communications Class InterfaceとData Class Interfaceから構成される。(以下PSTN仕様書の図参照)
image.png

CDCのディスクリプタ

CDCのデバイスと、その構成を説明するディスクリプタは下図のように対比することができる。ここで、ただInterface Descriptorを並べただけだと、Interface Descriptor[0]Interface Descriptor[1]のどちらがCommunication Class InterfaceData Class Interfaceに対応するのかが分からず、ホストはどちらにデータを送ってよいのか分からず困ってしまう。
image.png

デバイスと対比するためのイメージのためにディスクリプタを包含関係があるように描いているが、実際のディスクリプタは1つ1つ分かれているので注意する。

そこで、CDCを構成するインターフェースの役割を説明するのが、図中の赤で示したFunctional Descriptorである。Functional Descriptorには何種類かあり、中でもインターフェースの役割を説明するのに使われるのがUnion Functional Descriptorである。他にも、機能の有無を説明するACM(Abstract Control Model) Functional Descriptorがある。どういったFunctional Descriptorがあるかは、CDCやそのサブクラスの仕様書を参照すること。

Union Functional Descriptorは、CDC仕様書: 5.2.3.2 Union Functional Descriptor で説明のあるように、以下のフォーマットになっている。これにより、ディスクリプタ全体に含まれるインターフェースの何番目がCommunication Class InterfaceData Class Interfaceに対応するかを説明できるようになっている。(表のSubordinate interfaceというのがData Class Interfaceになるインターフェースを指す)
image.png

CDCのサンプルコードのディスクリプタ

STM32CubeIDEでCDC(VCP)のコードを生成することができる。
image.png

コード生成を実行すると、Device Descriptorはusbd_desc.cに、Configuration Descriptor以降はusbd_cdc.cに配列が生成される。Configuration Descriptor以降を見てみると、以下のようになっている(コメントは編集している)。
image.png

usbd_cdc.c抜粋
/* USB CDC device Configuration Descriptor */
__ALIGN_BEGIN uint8_t USBD_CDC_CfgHSDesc[USB_CDC_CONFIG_DESC_SIZ] __ALIGN_END =
{
  /* Configuration Descriptor[0] -------------------------------------------- */
  0x09,                         /* bLength: Configuration Descriptor size     */
  USB_DESC_TYPE_CONFIGURATION,  /* bDescriptorType: Configuration             */
  USB_CDC_CONFIG_DESC_SIZ,      /* wTotalLength:no of returned bytes          */
  0x00,
  0x02,                         /* bNumInterfaces: 2 interface                */
  0x01,                         /* bConfigurationValue: Configuration value   */
  0x00,                         /* iConfiguration: Index of string descriptor */
  0xC0,                         /* bmAttributes: self powered                 */
  0x32,                         /* MaxPower 0 mA                              */

  /* Interface Descriptor[1] ------------------------------------------------ */
  /* Communication Class Interface                                            */
  0x09,                     /* bLength: Interface Descriptor size             */
  USB_DESC_TYPE_INTERFACE,  /* bDescriptorType: Interface                     */
  0x00,                     /* bInterfaceNumber: Number of Interface          */
  0x00,                     /* bAlternateSetting: Alternate setting           */
  0x01,                     /* bNumEndpoints: One endpoints used              */
  0x02,                     /* bInterfaceClass: Communication Interface Class */
  0x02,                     /* bInterfaceSubClass: Abstract Control Model     */
  0x01,                     /* bInterfaceProtocol: Common AT commands         */
  0x00,                     /* 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                                      */
  0x01,

  /* 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             */
  0x02,   /* 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[0] ------------------------------------------------- */
  0x07,                                /* bLength: Endpoint Descriptor size   */
  USB_DESC_TYPE_ENDPOINT,              /* bDescriptorType: Endpoint           */
  CDC_CMD_EP,                          /* bEndpointAddress: IN 2              */
  0x03,                                /* bmAttributes: Interrupt             */
  LOBYTE(CDC_CMD_PACKET_SIZE),         /* wMaxPacketSize:                     */
  HIBYTE(CDC_CMD_PACKET_SIZE),
  CDC_HS_BINTERVAL,                    /* bInterval:                          */

  /* Interface Descriptor[1] ------------------------------------------------ */
  /* Data class interface descriptor                                          */
  0x09,                              /* bLength: Endpoint Descriptor size     */
  USB_DESC_TYPE_INTERFACE,           /* bDescriptorType: Endpoint             */
  0x01,                              /* bInterfaceNumber: Number of Interface */
  0x00,                              /* bAlternateSetting: Alternate setting  */
  0x02,                              /* bNumEndpoints: Two endpoints used     */
  0x0A,                              /* bInterfaceClass: CDC                  */
  0x00,                              /* bInterfaceSubClass: -                 */
  0x00,                              /* bInterfaceProtocol: -                 */
  0x00,                              /* iInterface: -                         */

  /* Endpoint Descriptor[1] ------------------------------------------------- */
  0x07,                                /* bLength: Endpoint Descriptor size   */
  USB_DESC_TYPE_ENDPOINT,              /* bDescriptorType: Endpoint           */
  CDC_OUT_EP,                          /* bEndpointAddress: OUT 1             */
  0x02,                                /* bmAttributes: Bulk                  */
  LOBYTE(CDC_DATA_HS_MAX_PACKET_SIZE), /* wMaxPacketSize:                     */
  HIBYTE(CDC_DATA_HS_MAX_PACKET_SIZE),
  0x00,                                /* bInterval: ignore for Bulk transfer */

  /* Endpoint Descriptor[2] ------------------------------------------------- */
  0x07,                                /* bLength: Endpoint Descriptor size   */
  USB_DESC_TYPE_ENDPOINT,              /* bDescriptorType: Endpoint           */
  CDC_IN_EP,                           /* bEndpointAddress: IN 1              */
  0x02,                                /* bmAttributes: Bulk                  */
  LOBYTE(CDC_DATA_HS_MAX_PACKET_SIZE), /* wMaxPacketSize:                     */
  HIBYTE(CDC_DATA_HS_MAX_PACKET_SIZE),
  0x00                                 /* bInterval: ignore for Bulk transfer */
};

複合デバイス

前置きがだいぶ長くなったが、USBで定義されたCDCやMSD(Mass Storage Device)、HID(Human Interface Device)を複数備えるデバイスのディスクリプタをどう定義すればよいかをまとめる。

Device Descriptor

複合デバイスはその名の通り「複合デバイス」というデバイスクラスであるため、Device Descriptorは以下のようになる。デバイスクラスは0xEF: Miscellaneous Device(雑多なデバイス?)である。(IAD参考資料参照)
image.png

Configuration Descriptor

通常のデバイスと同様なので、割愛する。

IAD: Interface Association Descriptor

複合デバイスでは、コンフィギュレーションに含まれる複数のインターフェースが連携してデバイスに相当する機能を実現する。例えばCDCの場合、Communication Class InterfaceData Class Interface合わせて2つのインターフェースが連携して機能を実現する。CDCを2つ持つ複合デバイスであれば4つのインターフェースの2つずつが連携して、それぞれCDCの機能を実現する。

複合デバイスでは、コンフィギュレーションに含まれる複数のインターフェスに関して、「どれが連携してどんな機能を定義するか」を説明するために、IAD(Interface Associate Descriptor)というディスクリプタを用いる。これは、CDCでUnion Functional Descriptorを用いてインターフェースの役割を説明したのに似ている。そのイメージは以下のようになる。
image.png

IADの構成は以下のようになっている。連携するインターフェースはbFirstInterfacebInterfaceCountを用いて説明する。例えば0番ねのインターフェースから2つのインターフェースが連携するなら、それぞれ0x00,0x02となる。インターフェースが連携してどんな機能を実現するかは、bFunctionClassbFunctionSubClassbFunctionProtocolを用いて説明する。これはデバイスのクラス、サブクラス・プロトコルに相当するもので、例えばCDC(VCP)であればそれぞれ0x02,0x02,0x00になる。(IAD参考資料参照)
image.png

以降のディスクリプタ

複合デバイスではIADというディスクリプタを用いること以外は特別なことはなく、Interface DescriptorEndpoint Descriptorは通常のデバイスクラスの時と同様に記述すればよい。

試しに複合デバイスのディスクリプタを書く

STM32のCDCサンプルコードを少し改造するだけで、なんちゃって複合デバイスにすることができる。CDCを2つ搭載させるには、ディスクリプタだけでなく色々な所に手を加えないといけないので、ここではディスクリプタの説明にとどめる。

CDCが1つしかない複合デバイスにする

簡単な例としてCDCサンプルコードを改造し、以下のようなイメージでCDCが1つしかない複合デバイスを作ってみる。
image.png

Device Descriptorを変更する

まず、usbd_desc.cで定義されているDevice Descriptorを、CDCから複合デバイスに書き換える。以下の★のところがCDCサンプルコードから変えた個所となる。

usbd_desc.c
/** USB standard device descriptor. */
__ALIGN_BEGIN uint8_t USBD_FS_DeviceDesc[USB_LEN_DEV_DESC] __ALIGN_END =
{
  0x12,                       /*bLength */
  USB_DESC_TYPE_DEVICE,       /*bDescriptorType*/
  0x00,                       /*bcdUSB */
  0x02,
  0xEF,                       /*bDeviceClass: Miscellaneous Device Class ★*/
  0x02,                       /*bDeviceSubClass: Common Class ★*/
  0x01,                       /*bDeviceProtocol: Interface Association Descriptor ★*/
  USB_MAX_EP0_SIZE,           /*bMaxPacketSize*/
  LOBYTE(USBD_VID),           /*idVendor*/
  HIBYTE(USBD_VID),           /*idVendor*/
  LOBYTE(USBD_PID_FS),        /*idProduct*/
  HIBYTE(USBD_PID_FS),        /*idProduct*/
  0x00,                       /*bcdDevice rel. 2.00*/
  0x02,
  USBD_IDX_MFC_STR,           /*Index of manufacturer  string*/
  USBD_IDX_PRODUCT_STR,       /*Index of product string*/
  USBD_IDX_SERIAL_STR,        /*Index of serial number string*/
  USBD_MAX_NUM_CONFIGURATION  /*bNumConfigurations*/
};

Configuration Descriptorを変更する

次に、usbd_cdc.cで定義されているConfiguration Descriptorを書き換える。(実際には順番に制約はないらしいのだが)ここでは、CDCサンプルのディスクリプタに対して、IADをConfiguration DescriptorInterface Descriptor[0]の間に追加する。配列サイズを決めるUSB_CDC_CONFIG_DESC_SIZは、IADで8バイト増加することから67Uから75Uに書き換える。USBの速度(FS/HS/その他)ごとにConfiguratgion Descriptorがあるので、全部変えるように気を付ける。

usbd_cdc.c
/* USB CDC device Configuration Descriptor */
__ALIGN_BEGIN uint8_t USBD_CDC_CfgFSDesc[USB_CDC_CONFIG_DESC_SIZ] __ALIGN_END =
{
    /* Configuration Descriptor[0] -------------------------------------------- */
    0x09,                         /* bLength: Configuration Descriptor size     */
    USB_DESC_TYPE_CONFIGURATION,  /* bDescriptorType: Configuration             */
    USB_CDC_CONFIG_DESC_SIZ,      /* wTotalLength:no of returned bytes          */
    0x00,
    0x02,                         /* bNumInterfaces: 2 interface                */
    0x01,                         /* bConfigurationValue: Configuration value   */
    0x00,                         /* iConfiguration: Index of string descriptor */
    0xC0,                         /* bmAttributes: self powered                 */
    0x32,                         /* MaxPower 0 mA                              */

    /* IAD: Interface Association Descriptor[0] ------------------------------- */
    0x08,                         /* bLength: IAD size                          */
    0x0B,                         /* bDescriptorType: IAD                       */
    0x00,                         /* bFirstInterface: Interface[0]              */
    0x02,                         /* bInterfaceCount: 2 interfaces              */
    0x02,                         /* bFunctionClass: CDC Class                  */
    0x02,                         /* bFunctionSubClass: ACM                     */
    0x00,                         /* bFunctionProtocol: -                       */
    0x00,                         /* iFunction: -                               */

    /* Interface Descriptor[1] ------------------------------------------------ */
    /* Communication Class Interface                                            */
    0x09,                     /* bLength: Interface Descriptor size             */
    USB_DESC_TYPE_INTERFACE,  /* bDescriptorType: Interface                     */
    0x00,                     /* bInterfaceNumber: Number of Interface          */
    0x00,                     /* bAlternateSetting: Alternate setting           */
    0x01,                     /* bNumEndpoints: One endpoints used              */
    0x02,                     /* bInterfaceClass: Communication Interface Class */
    0x02,                     /* bInterfaceSubClass: Abstract Control Model     */
    0x01,                     /* bInterfaceProtocol: Common AT commands         */
    0x00,                     /* 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                                      */
    0x01,

    /* 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             */
    0x02,   /* 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[0] ------------------------------------------------- */
    0x07,                                /* bLength: Endpoint Descriptor size   */
    USB_DESC_TYPE_ENDPOINT,              /* bDescriptorType: Endpoint           */
    CDC_CMD_EP,                          /* bEndpointAddress: IN 2              */
    0x03,                                /* bmAttributes: Interrupt             */
    LOBYTE(CDC_CMD_PACKET_SIZE),         /* wMaxPacketSize:                     */
    HIBYTE(CDC_CMD_PACKET_SIZE),
    CDC_HS_BINTERVAL,                    /* bInterval:                          */

    /* Interface Descriptor[1] ------------------------------------------------ */
    /* Data class interface descriptor                                          */
    0x09,                              /* bLength: Endpoint Descriptor size     */
    USB_DESC_TYPE_INTERFACE,           /* bDescriptorType: Endpoint             */
    0x01,                              /* bInterfaceNumber: Number of Interface */
    0x00,                              /* bAlternateSetting: Alternate setting  */
    0x02,                              /* bNumEndpoints: Two endpoints used     */
    0x0A,                              /* bInterfaceClass: CDC                  */
    0x00,                              /* bInterfaceSubClass: -                 */
    0x00,                              /* bInterfaceProtocol: -                 */
    0x00,                              /* iInterface: -                         */

    /* Endpoint Descriptor[1] ------------------------------------------------- */
    0x07,                                /* bLength: Endpoint Descriptor size   */
    USB_DESC_TYPE_ENDPOINT,              /* bDescriptorType: Endpoint           */
    CDC_OUT_EP,                          /* bEndpointAddress: OUT 1             */
    0x02,                                /* bmAttributes: Bulk                  */
    LOBYTE(CDC_DATA_HS_MAX_PACKET_SIZE), /* wMaxPacketSize:                     */
    HIBYTE(CDC_DATA_HS_MAX_PACKET_SIZE),
    0x00,                                /* bInterval: ignore for Bulk transfer */

    /* Endpoint Descriptor[2] ------------------------------------------------- */
    0x07,                                /* bLength: Endpoint Descriptor size   */
    USB_DESC_TYPE_ENDPOINT,              /* bDescriptorType: Endpoint           */
    CDC_IN_EP,                           /* bEndpointAddress: IN 1              */
    0x02,                                /* bmAttributes: Bulk                  */
    LOBYTE(CDC_DATA_HS_MAX_PACKET_SIZE), /* wMaxPacketSize:                     */
    HIBYTE(CDC_DATA_HS_MAX_PACKET_SIZE),
    0x00                                 /* bInterval: ignore for Bulk transfer */
};

実行してみる

上記のようにディスクリプタを修正してソフトを書きこみ実行すると、作成したUSBデバイスが複合デバイスとして認識されることが分かる。(ベンダID:1155 = 0x0483から見つけられる)
image.png

CDCが2つある複合デバイスにする

実際に動作させるにはコーディングが必要だが、ひとまずCDCが2つあると認識させるようディスクリプタを書き換える。Device Descriptorは前の例のままで、Configuration Descriptorを変更する。

Configuration Descriptorを変更する

CDCが1つある複合デバイスのConfiguration Descriptorに、もう一つCDCを追加する。以下コードのIAD: Interface Association Descriptor[1]からが追加したCDCのディスクリプタとなる。Configuration Descriptor全体として、インターフェースの総数(2→4)やディスクリプタ長(75→141)などが変わっているので不整合が無いように注意して編集する。

usbd_cdc.c
/* USB CDC device Configuration Descriptor */
__ALIGN_BEGIN uint8_t USBD_CDC_CfgFSDesc[USB_CDC_CONFIG_DESC_SIZ] __ALIGN_END =
{
    /* Configuration Descriptor[0] -------------------------------------------- */
    0x09,                         /* bLength: Configuration Descriptor size     */
    USB_DESC_TYPE_CONFIGURATION,  /* bDescriptorType: Configuration             */
    USB_CDC_CONFIG_DESC_SIZ,      /* wTotalLength:no of returned bytes          */
    0x00,
    0x04,                         /* bNumInterfaces: 4 interfaces               */
    0x01,                         /* bConfigurationValue: Configuration value   */
    0x00,                         /* iConfiguration: Index of string descriptor */
    0xC0,                         /* bmAttributes: self powered                 */
    0x32,                         /* MaxPower 0 mA                              */

    /* IAD: Interface Association Descriptor[0] ------------------------------- */
    0x08,                         /* bLength: IAD size                          */
    0x0B,                         /* bDescriptorType: IAD                       */
    0x00,                         /* bFirstInterface: Interface[0]              */
    0x02,                         /* bInterfaceCount: 2 interfaces              */
    0x02,                         /* bFunctionClass: CDC Class                  */
    0x02,                         /* bFunctionSubClass: ACM                     */
    0x00,                         /* bFunctionProtocol: -                       */
    0x00,                         /* iFunction: -                               */

    /* Interface Descriptor[1] ------------------------------------------------ */
    /* Communication Class Interface                                            */
    0x09,                     /* bLength: Interface Descriptor size             */
    USB_DESC_TYPE_INTERFACE,  /* bDescriptorType: Interface                     */
    0x00,                     /* bInterfaceNumber: Number of Interface          */
    0x00,                     /* bAlternateSetting: Alternate setting           */
    0x01,                     /* bNumEndpoints: One endpoints used              */
    0x02,                     /* bInterfaceClass: Communication Interface Class */
    0x02,                     /* bInterfaceSubClass: Abstract Control Model     */
    0x01,                     /* bInterfaceProtocol: Common AT commands         */
    0x00,                     /* 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                                      */
    0x01,

    /* 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             */
    0x02,   /* 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[0] ------------------------------------------------- */
    0x07,                                /* bLength: Endpoint Descriptor size   */
    USB_DESC_TYPE_ENDPOINT,              /* bDescriptorType: Endpoint           */
    CDC_CMD_EP,                          /* bEndpointAddress: OUT 2             */
    0x03,                                /* bmAttributes: Interrupt             */
    LOBYTE(CDC_CMD_PACKET_SIZE),         /* wMaxPacketSize:                     */
    HIBYTE(CDC_CMD_PACKET_SIZE),
    CDC_HS_BINTERVAL,                    /* bInterval:                          */

    /* Interface Descriptor[1] ------------------------------------------------ */
    /* Data class interface descriptor                                          */
    0x09,                              /* bLength: Endpoint Descriptor size     */
    USB_DESC_TYPE_INTERFACE,           /* bDescriptorType: Endpoint             */
    0x01,                              /* bInterfaceNumber: Number of Interface */
    0x00,                              /* bAlternateSetting: Alternate setting  */
    0x02,                              /* bNumEndpoints: Two endpoints used     */
    0x0A,                              /* bInterfaceClass: CDC                  */
    0x00,                              /* bInterfaceSubClass: -                 */
    0x00,                              /* bInterfaceProtocol: -                 */
    0x00,                              /* iInterface: -                         */

    /* Endpoint Descriptor[1] ------------------------------------------------- */
    0x07,                                /* bLength: Endpoint Descriptor size   */
    USB_DESC_TYPE_ENDPOINT,              /* bDescriptorType: Endpoint           */
    CDC_OUT_EP,                          /* bEndpointAddress: OUT 1             */
    0x02,                                /* bmAttributes: Bulk                  */
    LOBYTE(CDC_DATA_HS_MAX_PACKET_SIZE), /* wMaxPacketSize:                     */
    HIBYTE(CDC_DATA_HS_MAX_PACKET_SIZE),
    0x00,                                /* bInterval: ignore for Bulk transfer */

    /* Endpoint Descriptor[2] ------------------------------------------------- */
    0x07,                                /* bLength: Endpoint Descriptor size   */
    USB_DESC_TYPE_ENDPOINT,              /* bDescriptorType: Endpoint           */
    CDC_IN_EP,                           /* bEndpointAddress: IN 1              */
    0x02,                                /* bmAttributes: Bulk                  */
    LOBYTE(CDC_DATA_HS_MAX_PACKET_SIZE), /* wMaxPacketSize:                     */
    HIBYTE(CDC_DATA_HS_MAX_PACKET_SIZE),
    0x00,                                /* bInterval: ignore for Bulk transfer */

    /* IAD: Interface Association Descriptor[1] ------------------------------- */
    0x08,                         /* bLength: IAD size                          */
    0x0B,                         /* bDescriptorType: IAD                       */
    0x02,                         /* bFirstInterface: Interface[2]              */
    0x02,                         /* bInterfaceCount: 2 interfaces              */
    0x02,                         /* bFunctionClass: CDC Class                  */
    0x02,                         /* bFunctionSubClass: ACM                     */
    0x00,                         /* bFunctionProtocol: -                       */
    0x00,                         /* iFunction: -                               */

    /* Interface Descriptor[2] ------------------------------------------------ */
    /* Communication Class Interface                                            */
    0x09,                     /* bLength: Interface Descriptor size             */
    USB_DESC_TYPE_INTERFACE,  /* bDescriptorType: Interface                     */
    0x02,                     /* bInterfaceNumber: Number of Interface          */
    0x00,                     /* bAlternateSetting: Alternate setting           */
    0x01,                     /* bNumEndpoints: One endpoints used              */
    0x02,                     /* bInterfaceClass: Communication Interface Class */
    0x02,                     /* bInterfaceSubClass: Abstract Control Model     */
    0x01,                     /* bInterfaceProtocol: Common AT commands         */
    0x00,                     /* iInterface: -                                  */

    /* Functional Descriptor[4] ----------------------------------------------- */
    /* Header Functional Descriptor                                             */
    0x05,   /* bLength: Endpoint Descriptor size                                */
    0x24,   /* bDescriptorType: CS_INTERFACE                                    */
    0x00,   /* bDescriptorSubtype: Header Func Desc                             */
    0x10,   /* bcdCDC: spec release number                                      */
    0x01,

    /* Functional Descriptor[5] ----------------------------------------------- */
    /* Call Management Functional Descriptor                                    */
    0x05,   /* bFunctionLength                                                  */
    0x24,   /* bDescriptorType: CS_INTERFACE                                    */
    0x01,   /* bDescriptorSubtype: Call Management Func Desc                    */
    0x00,   /* bmCapabilities: D0+D1 (no call management supported)             */
    0x03,   /* bDataInterface: Interface[3]                                     */

    /* Functional Descriptor[6] ----------------------------------------------- */
    /* ACM Functional Descriptor                                                */
    0x04,   /* bFunctionLength                                                  */
    0x24,   /* bDescriptorType: CS_INTERFACE                                    */
    0x02,   /* bDescriptorSubtype: Abstract Control Management desc             */
    0x02,   /* bmCapabilities: *                                                */

    /* Functional Descriptor[7] ----------------------------------------------- */
    /* Union Functional Descriptor                                              */
    0x05,   /* bFunctionLength                                                  */
    0x24,   /* bDescriptorType: CS_INTERFACE                                    */
    0x06,   /* bDescriptorSubtype: Union func desc                              */
    0x02,   /* bMasterInterface: Communication class interface = Interface[2]   */
    0x03,   /* bSlaveInterface0: Data Class Interface = Interface[3]            */

    /* Endpoint Descriptor[3] ------------------------------------------------- */
    0x07,                                /* bLength: Endpoint Descriptor size   */
    USB_DESC_TYPE_ENDPOINT,              /* bDescriptorType: Endpoint           */
    0x84,                                /* bEndpointAddress: IN 4              */
    0x03,                                /* bmAttributes: Interrupt             */
    LOBYTE(CDC_CMD_PACKET_SIZE),         /* wMaxPacketSize:                     */
    HIBYTE(CDC_CMD_PACKET_SIZE),
    CDC_HS_BINTERVAL,                    /* bInterval:                          */

    /* Interface Descriptor[3] ------------------------------------------------ */
    /* Data class interface descriptor                                          */
    0x09,                              /* bLength: Endpoint Descriptor size     */
    USB_DESC_TYPE_INTERFACE,           /* bDescriptorType: Endpoint             */
    0x03,                              /* bInterfaceNumber: Number of Interface */
    0x00,                              /* bAlternateSetting: Alternate setting  */
    0x02,                              /* bNumEndpoints: Two endpoints used     */
    0x0A,                              /* bInterfaceClass: CDC                  */
    0x00,                              /* bInterfaceSubClass: -                 */
    0x00,                              /* bInterfaceProtocol: -                 */
    0x00,                              /* iInterface: -                         */

    /* Endpoint Descriptor[4] ------------------------------------------------- */
    0x07,                                /* bLength: Endpoint Descriptor size   */
    USB_DESC_TYPE_ENDPOINT,              /* bDescriptorType: Endpoint           */
    0x03,                                /* bEndpointAddress: OUT 3             */
    0x02,                                /* bmAttributes: Bulk                  */
    LOBYTE(CDC_DATA_HS_MAX_PACKET_SIZE), /* wMaxPacketSize:                     */
    HIBYTE(CDC_DATA_HS_MAX_PACKET_SIZE),
    0x00,                                /* bInterval: ignore for Bulk transfer */

    /* Endpoint Descriptor[5] ------------------------------------------------- */
    0x07,                                /* bLength: Endpoint Descriptor size   */
    USB_DESC_TYPE_ENDPOINT,              /* bDescriptorType: Endpoint           */
    0x83,                                /* bEndpointAddress: IN 3              */
    0x02,                                /* bmAttributes: Bulk                  */
    LOBYTE(CDC_DATA_HS_MAX_PACKET_SIZE), /* wMaxPacketSize:                     */
    HIBYTE(CDC_DATA_HS_MAX_PACKET_SIZE),
    0x00                                 /* bInterval: ignore for Bulk transfer */
};

実行してみる

マイコンにプログラムを着込んで実行すると、以下のようにデバイスマネージャから複合デバイスとして認識されることが確認できる。
image.png

また、USBコネクタを抜き差しすると、COM3とCOM9が追加・削除されることが確認できる。(5はST-LINK内蔵、COM1は元々ついている別のデバイスのCOMポート)。
image.pngimage.png

なお、この時点ではディスクリプタで「こんなデバイスがあります」とPCに認識させただけに過ぎないため、一方のCOMポートは動作しない。この後、ちゃんとコーディングしていくことで使えるようにしていくが、それはまた別の機会に...。

まとめ

USBの複合デバイスのディスクリプタの作り方をまとめた。複合デバイスを作るにはまずDevice Descriptorを複合デバイスのクラス(0xEF)にする。そして、Configuration Descriptor以下で、インターフェースのまとまりごとに、IADを用いて「どのインターフェースが連携してどんな機能を実現するか」を記述すればよい。実際に動作させるにはコーディングが必要だが、ディスクリプタを正しく書ければひとまず認識させることはできる。

備考

原文をみると、Communications Class Interfaceと書かれていたのだが、本文や図で間違ってCommunication Class Interfaceと書いてしまっているので注意。

18
10
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
18
10