USB CDC ACM DTR・RTSのデバッグ:SetControlLineState、シリアルポートオープン、ブートローダーリセット、データ欠落
USB CDC ACMのDTR/RTSコントロールライン状態、SetControlLineStateリクエスト、シリアルポートのオープン挙動、ブートローダーリセットのトリガ、データ欠落をデバッグする方法を解説します。
USB CDC ACMデバイスはシリアルポートに見えますが、「シリアルポートっぽい」バグの多くは、実はUSBクラス制御リクエストのバグです。CDC ACM DTR RTS、SetControlLineState USB、USB serial no data until DTR、Arduino resets when serial port opens、USB CDC bootloader reset、COM port opens but device does not respond といった言葉で検索されるのは、ポートは存在するが挙動がおかしい、という場面です。
Bus Scopeが活きるのは、DTR/RTSが魔法のアプリフラグではなく、ホストが送る「クラス固有のコントロールリクエスト」であり、ファームウェアがそのリクエストに反応して動いているからだ、ということをはっきり見せられる点です。
SetControlLineStateの役割
CDC ACMは、よく SetControlLineState と呼ばれるクラスリクエストを使います。これは次のようなコントロールライン状態を伝えます。
- DTR: Data Terminal Ready(データ端末レディ)
- RTS: Request To Send(送信要求)
多くのデバイスでは、これらのビットを従来のモデム的な用途以外にも使っています。ファームウェアによっては、DTRがアサートされてからストリーミングを開始したり、DTRのトグルでブートローダに入ったり、RTSをフロー制御のセマンティクスに使ったりします。
よくある症状
コントロールラインの問題は、次のような形で現れます。
- COMポートは開くが、データが一切来ない
- ターミナルプログラムが接続してからデバイスが送信を始め出す
- シリアルモニタを開くとファームウェアがリセットする
- ポートの開閉のたびにブートローダが現れる
- DTRが落ちるとデータが止まる
- RTS/CTSフロー制御の有効/無効で挙動が変わる
- Linuxツールでは動くがWindowsツールでは動かない
- Pythonスクリプトとターミナルエミュレータで挙動が違う
これらは、現場のエンジニアが障害を再現しようとする際に口にする症状とまさに一致します。
シリアルポートのオープン挙動
ホストアプリによって、ポートを開くときのDTR/RTSの扱いはさまざまです。
例:
- ターミナルエミュレータは即座にDTRをアサートする
- スクリプトはポートを開いてもDTRをfalseのままにする
- ファームウェア更新ツールはリセット信号としてDTRをトグルする
- ドライバはフロー制御設定に応じてRTSを決める
- アプリがポートを閉じるとDTRが予想外に落ちる
パケットトレースを見れば、アプリの「思い込み」ではなく実際のコントロールリクエストシーケンスを確認できます。
ブートローダへのリセットパターン
多くの開発ボードは、DTRまたはRTSの遷移を使ってブートローダモードにリセットします。ファームウェアアップロードには便利ですが、本番ツールで予期せぬ挙動になります。
失敗パターン:
- ログビューアを開くたびにデバイスがリセットする
- ファームウェアアップロードは成功するが通常のシリアル接続が失敗する
- デバイスがあるUSB識別子として現れ、リセット後にブートローダとして再列挙される
- リセット後にシリアル番号やプロダクト文字列が変わる
- アプリがポートハンドルを失う
Bus Scopeはコントロールリクエストと再列挙シーケンスを保全します。
DTRを待つ設計
ファームウェアによっては、意図的に「DTRが来るまでデータを送らない」という設計になっていることがあります。これにより、あるツールでは動かないのに別のツールでは動く、という見え方になります。
証拠:
- ホストがバルクまたはインタラプトエンドポイントを開く
- INデータが送られない
- ホストがDTR=trueでSetControlLineStateを送る
- デバイスが送信を始める
これはケーブルの問題でも、ドライバのバグでもなく、ファームウェアの仕様です。
RTSとフロー制御の混乱
RTSはハードウェアフロー制御に使われることがありますが、多くのUSB CDCデバイスには本物のモデム線は存在しません。それでもファームウェアがアプリケーションロジックにRTS状態を公開していることはあります。
問い:
- ホストはRTSをセットしているか?
- デバイスは送信前にRTSを要求しているか?
- ターミナルでハードウェアフロー制御を有効にするとリクエストのビットは変わるか?
- ファームウェアはRTSを無視しているが、ドキュメントにはRTS依存と書かれているか?
- RTSがブートローダやモード選択のシグナルとして使われていないか?
パケットの証拠があれば、推測ではなく事実で判断できます。
デバッグチェックリスト
次の流れで作業すると、原因に早くたどり着けます。
- 列挙をキャプチャする
- 失敗するアプリからシリアルポートを開く
- CDCクラス固有リクエストを記録する
- SetControlLineStateを探す
- DTR/RTSのビットをデコードする
- 動作するターミナルプログラムと比較する
- DTRのあとでデータが始まるかを確認する
- トグル後にリセットや再列挙が起きているかを確認する
- WindowsとLinuxのツールで挙動を比較する
- コントロールリクエストと最初のデータパケットを一緒に保全する
最終的な診断
USB CDC ACMのDTR/RTS問題は、「クラス制御シーケンス」の問題です。ポートは存在するしドライバのバインドも正しい——それなのにファームウェアは「アプリが一度も送ってこないコントロールライン状態」を待ち続けている、というケースです。
Bus Scopeは、SetControlLineState、DTR、RTS、シリアルオープン挙動、ブートローダリセット、データ欠落の原因を、USBプロトコルレベルで示します。