最近, USB CDCについて勉強したのでメモがてら書きます. (間違えてる箇所があったらご連絡ください.)

最初に: 幸福とは何か 


USBの規格を読まずに日々を過ごせること( ˘ω˘)スヤァ

USB CDCについて 


USB CDCとはUniversal Serial Bus Communications Device Classの略称で, USB上でデバイス間のデータのやりとりを行うための通信規格です. RS-232CとかがCDC-ACMの規格で通信をやっています.

具体的にはFT232やマイコンのデバッガとかがこの通信規格を使ってPCにデータを投げたり受け取りしてます. (TeraTermを開いてやるやつね

何を読むべきか 


ブログ記事とかもネットにたくさんありますが, (https://usb.org/)に公式のドキュメントが置いてるので, 正確なことが知りたい人はこれを読んでください.

また, こちらのブログでCDCについて大変分かりやすく翻訳されています.(私もお世話になりました. ありがとうございます.)
とりメモ: (https://sites.google.com/site/toriaezunomemo/home/communication-device-class)

以下の話は
Class definitions for Communication Devices 1.2 (https://www.usb.org/document-library/class-definitions-communication-devices-12)
を読んでマイコンのUSB機能とUARTを使ってFT232みたいな仮想COMポート(シリアル変換モジュール)みたいなのを作るときに必要になりそうなものをまとめていきます.

Images

Functional Overview 


CDCではCommunicatios Class InterfaceData Class Interfaceの2つを持っています.(CDC3章に記載)

Communications Class Interfaceでは管理と通知(これはoptionally)の2つを行っていてデバイスの設定や管理を行います. これはデバイスがコール管理を実装しているか否かで挙動が変わるので, 規格書の3.4.1を読んでください.

例えばデバイス側でコール管理をしていると管理や設定の情報はendpoint0越しにデータのやり取りをして, 通知はinterrupt endpointで行われます.

Data Class Interfaceでは実際の通信データのやり取りを行っていて, bulk endpointで行われます.

これらの実装はCommunication Class Interfaceとしてintterupt input endpointが1つ(otionallyでoutputのものが1つ.), bulk inputとbulk outputのそれぞれが1つという3つのendpointでの実装になります.

Descriptor 


DescriptorはPCからUSBケーブルをデバイスに接続した際にPCに対して送信されるもので, 「このデバイスはこういうデバイス(CDCであることや使うendpointとか)ですよー」みたいな情報が含まれています.

送信するデータはC言語でいう構造体みたいな形をとっていて, 「先頭から1バイト目に〇〇の設定データ, 2バイト目に〇〇の設定データ」といった形式になります. このとき, クラス固有の情報を記述するInterface Descriptorの各要素をFunctional Descriptorといいます. 実際に送信する際にはこれらFunctional Descriptorを続けて送信します.

シリアル変換モジュールを作る際, UART越しに対象のマイコンと通信するのでその制御のために, CDCではSubClassとしてPSTN(公衆電話回線) Device Modelのサブクラスである ACM(Abstract Control Model) を採用します. (ACMのプロトコルを使うとレガシーモデムとUSBデバイス間の橋渡しが可能になる.)

USB CDCとして使用するために以下のFunctional Descriptorが必要になります.

  • ヘッダ情報を示すHeader Functional Descriptor
  • 通信で使われるインターフェイスの番号について記述するUnion Functional Descriptor
    • これにはCDC以外のインターフェイス(HID, Audio)などのインターフェイスについてもまとめて記述できます.
  • 動作が認められた国なのかを識別するCountry Selection Functional Descriptor
    • (法規制目的なので今回は使いません.

また, PSTNサブクラスのACMを使用する際には以下の2つのFunctional Descriptorが必要になります.

  • コール管理(ATコマンドをどこから送るか)を示すCall Management Functional Descriptor
  • ACMでサポートするコマンドを示すAbstract Control Model Functional Descriptor

ACMについての詳細はUSBPSTNの3.2.2.1で詳しく書かれているのでそこを読んでください.
ディスクリプタはCDCの5章にCDCとして設定する際に送るサンプルも一緒に乗ってるので参考にしてみてください.

以下に実際にCDCとして設定する際のDescriptorの例を載せます.

Offset Field Size Value Description
0 bFunctionLength 1 0x05 Header Functional Descriptorのサイズ(USBのどの通信を使うか決める)
1 bDescriptorType 1 0x24 CS_INTERFACE(固定値です)
2 bDescriptorSubtype 1 0x00 Header Functional Descriptorであることを示す
3 bcdCDC 2 0x0110 BCD(binary-coded decimal)表記で書かれたCDCのバージョン.(1.2)
5 bFunctionLength 1 0x04 ACM Functional Descriptorのサイズ(ACMの設定)
6 bDescriptorType 1 0x24 CS_INTERFACE(固定値です)
7 bDescriptorSubtype 1 0x02 ACM Functional Descriptorであることを示す
8 bmCapabilities 1 0x0F 対応する機能の設定(全部盛り)
9 bFunctionLength 1 0x05 Union Functional Descriptorのサイズ(Unionの設定)
10 bDescriptorType 1 0x24 CS_INTERFACE(固定値です)
11 bDescriptorSubtype 1 0x06 Union Functional Descriptorであることを示す
12 bControlInterface 1 0x00 コントロールインターフェイス番号(ここではCommunications Class Interface)を設定
13 bSubordinateInterface0 1 0x01 それ以外のインターフェイス番号(ここではData Class Interface)を設定
14 bFunctionLength 1 0x05 Call Management Functional Descriptorのサイズ(コールの設定)
15 bDescriptorType 1 0x24 CS_INTERFACE(固定値です)
16 bDescriptorSubtype 1 0x01 Call Management Functional Descriptorであることを示す
17 bmCapabilities 1 0x03 コール管理を行うデータインターフェイス上でコマンドとデータを多重化する
18 bDataInterface 1 0x03 データとコマンドが送られるインターフェイス番号(Data Class Interfaceと同じ)

ちなみオフセットが8バイト目のbmCapabilitiesで送信する1バイトデータの各bitはそれぞれ

  • MSB=7 - 4: RESERVED(0にすること)
  • 3: NetWork_Connectionをサポート
  • 2: Send_Breakをサポート
  • 1: Set_Line_Coding, Set_Control_Line_State, Get_Line_Coding, Serial_Stateをサポート
  • 0: Set_Comm_Feature, Clear_CommFeature, Get_Comm_Featureをサポート

になります.

Communications Class Specific Messages 


これはCommunications Interface Classにおいてサポートしているリクエスト群です. これらのリクエストを使ってUART通信(半二重モデムなど)などの設定を行います.
例えば, FT232とかをPCに接続してTeraTermでUARTの通信速度やパリティビットの設定を行うとこのリクエストたちが送られます.

以下にCDCの通信でよく使われるものを書きます.

SerialState 


シリアル通信の状態をホスト側が要求するときに送られるリクエストでACM固有のリクエストです. (ACMのリクエストタイプは0xa1になります)
デバイス側はシリアル通信の状態(パリティエラーやTXRXが動作しているか否か)をホストに返します.

今でも電話のリングの状態を送っていたりします. (いい話)

リクエストコードは0x20でデバイス側は以下のような2バイトのデータを送ります.

Bits Field Description
MSB=15-7 RESERVED
6 bOverRun 受信したデータがデバイスのオーバーランによって破棄されたか否か
5 bParity パリティエラーが発生したか否か
4 bFraming フレーミングエラーが発生しているか否か
3 bRingSignal デバイスのリング状態
2 bBreak デバイスのブレーク状態
1 bTxCarrier 送信キャリアの状態. V.24の106信号やRS-232のDSR信号にあたります.
0 bRxCarrier 受信キャリアの状態. V.24の109信号やRS-232のDCD信号にあたります.

Set(Get)LineCoding 


シリアル通信の設定をホスト側が要求するときに送られるPSTN固有のリクエストです. (PSTNのリクエストタイプは0x21になります)
デバイス側はシリアル通信の設定(パリティビットや通信レート)をホストに返したり, ホストから送られた設定データを受け取ったりします.
それぞれSetLineCodingのリクエストコードは0x20, GetLineCodingのリクエストコードは0x21になります.

送られるデータ(Line Coding 構造体)は以下のようになります.

Offset Field Size Value Description
0 dwDTERate 4 Number 通信速度(bps)
4 bCharFormat 1 Number ストップビット(0:1bit, 1:1.5bit, 2:2bit)
5 bParityType 1 Number パリティビット(0:None, 1:Odd, 2:Even, 3:Mark, 4:Space)
6 bDataBits 1 Number データビットの長さ(5, 6, 7, 8, 16)

SetControlLineState 


RS-232のフロー制御の信号を送るPSTN固有のリクエストです. UART通信でいうところのRTSやDTRに相当します. リクエストコードは0x22です.

以下のような2バイトのデータをホストから送信します.

Bits Description
MSB=15-2 RESERVED
1 半二重モデムのためのキャリア制御. V.24の105信号やRS-232のRTSなどに対応しています. 0:キャリア不使用, 1:キャリア使用
0 DTEが存在するかをDCEに示す. V.24の108/2信号やRS-232のDTRに対応しています. 0:なし, 1:あり

SendBreak 


シリアル通信に対してブレイク信号を送るためのPSTN固有のリクエストです. リクエストコードは0x23です.
リクエストと一緒に送信される値(wValue)ミリ秒間ブレイク時間が継続されます.

ただし, wValueが0xFFffのとき, 次にwValueが0x0000のSendBreakが送られるまでブレイク時間は継続されます.

デバッグ 


USB製品のデバッグをするために実際に流れてるパケットを見るUSB Analyzerが必要になります. TOTAL PhaseがBeagle USBという高性能なUSB Analyzerを出していますが, 値段ェ…(https://www.totalphase.com/products/beagle-usb480/)
Images
おススメのものがあったら教えてください…

気になるところ 


Data Class Interfaceはプロプラエイタリでもよい 


USB CDCの3.4.2を見るとData Class Interfaceの通信はプロプラエイタリでもいいことが明示されてます.

実際にFT232の通信しているパケットを見ているとCDCに準拠しているものとは全く違うリクエストとかが飛んでいることが分かります. (これらを解釈するためにデバイスドライバーをPCにインストールするんですね.)

Tera TermやPuttyではSetControlLineStateに対応していない? 


CDCデバイスをTera TermやPuttyでフロー制御をON, OFFしても同じパケットが流れます. (なぜ?)
しかし, デバイスドライバーなどを要求するFT232とかをつなげてやると正しくパケットが変わってる.(なぜ?)

2020/03/15追記 

この問題は修正済みとのこと(圧倒的感謝)


こんな感じですね.

USBの規格はかなり昔から存在していて今では密林状態のようになっていますが, 規格自体は結構面白いのでぜひ読んでCDCデバイスを作ってみてください.