SNSへはこちら

STM32でUSBをベアメタル(11) - USBシリアル完成!

USB CDC です。立て続けに完成!完成!完成!!ですが、実際にある程度まで行くと芋づる式にできるようになってきます。最初はあんなにいろんなことに抵抗感があったのに...

実装するだけなら実はそんなに難しくなかった USB 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 を使います。

動作

汚い文字列ですが、ハロワしてます。やりました

ということで、HIDHID コンポジットデバイスCDC による Virtual COM ポートと3つの機能を実装できたので、大満足です。
ところで、ここまで実装したプロジェクトを中途半端に C++ で整形したものを GitHub に上げましたrepo はこちら。若干ゴミファイルが残ってたりしますが、ご了承ください。使い方は察してもらえると幸いです。
何かわからないことがあればコメントでどうぞ。

それでは、今回でこのシリーズは本当の最終回になります。皆様ご覧頂きまして、誠にありがとうございました。