USBデバイスを作る – 最初のガジェット制作のための総合ガイド
(popovicu.com)- USBデバイスを初めて作る際に必要な流れを、物理配線からPCB、USB 2.0の速度、プロトコル層、STM32実習まで一度に追えるよう整理している
- USB 2.0の基本接続は +5V、GND、D+、D- の4本で、
D+とD-は差動ペアとして一緒に1ビットを伝送する - PCBでは差動ペアの長さ合わせ、近接配置、インピーダンスが重要だが、12 Mbit/sのfull speedプロトタイプは比較的寛容に動作する場合がある
- 実習ではNUCLEO-F103RBで
PA12をUSB_DP、PA11をUSB_DMに設定し、CDC仮想COMポートとして認識されるUSB serialデバイスを作り、1入力でLEDを点灯させる - STM32CubeIDE方式はUIベースのコード生成とボイラープレートとの結合が負担になりやすく、LinuxベースのUSBデバイス実装はより標準化されたAPIと分離された構造を提供できる
USBを最初に実装するときの範囲
- 目標は、コンピューターに挿すと認識されるUSBデバイスを最も単純な形で最後まで作ってみること
- ここでいうデバイスはコンピューターの機能を拡張する周辺機器を指し、コンピューターはhostと呼ぶ
- 範囲には、USBデバイスとhostの間の物理接続から、host側でUSBデバイスと相互作用する簡単なアプリケーションまで含まれる
- 権威ある仕様解説というより、最も単純なE2EのUSBデバイス制作プロジェクトと参考資料の索引に近い
USBの基本概念
- USBは、複数の電子機器間でデータ交換と電源供給を可能にする業界標準である
- USBはシリアルバスなので、ビットは並列ではなく1つずつバス上を伝送される
- 現代のバスは主にシリアル方式であり、USBはhostとdeviceの間でビットをシリアルにやり取りする仕組みとして理解できる
- USBは、2つの装置を接続してビットをやり取りする物理仕様だけでなく、データ転送と電力供給のための通信プロトコルも含む
- 説明の焦点はUSB 2.0に置かれている
USB配線と差動ペア
- 一般的なUSB 2.0接続は、最低4本の配線で説明できる
+5 V: hostがdeviceに電力を供給する線D-,D+: 2本が一緒に動作して1ビットを伝送する差動ペアGND: グラウンド
- 接続によっては
IDのような追加ピンがある場合もあるが、実習では上記4本のみを扱う -
USB-Cに関する注意点
- USB-Cも内部的には差動ペアを使うが、向きを変えて挿しても動作しなければならない点などで、一般的なUSB 2.0の説明とは違いがある
- USB-Cコネクタを使うからといって、速度やUSBバージョンが決まるわけではない
- USB-CデバイスであってもUSB 2.0であることもあれば、より新しいUSB 3.0デバイスであることもある
- 以降の実習ではUSB-Cはこれ以上扱わない
-
差動ペアがノイズを減らす仕組み
- 単一の線は
GNDと比較した電圧でビット値を表すが、長い有線接続ではノイズの影響が大きくなりうる - 差動ペアは2本の線を使い、片方の線は
V、もう片方は-Vを伝送する - 受信側は2本の線の電圧差を見る
- 2本の線に同じノイズ
Vnが加わると単純化して考えると、(V + Vn) - (-V + Vn) = 2Vとなり、ノイズが相殺される - このモデルは非常に単純化した説明であり、差動ペアをより深く理解するにはZach PetersonのAltium YouTube動画を参照できる
- Video 15
- 単一の線は
PCBでUSBを扱うとき
- 自分でハードウェアを作らず開発ボードを使うならPCBの節は飛ばしてよいが、USBコネクタをPCBに載せる際の基本原則は有用である
- USBコネクタのライブラリ部品には前述のピンがあり、差動ペアはマイクロコントローラーやSoCの対応する隣接ピンへ配線する必要がある
- 差動ペア配線の基本原則は3つある
D+からチップのplusピンまで行くトレースと、もう一方のトレースの長さを同じにそろえる- 2本のトレースを互いに非常に近く配置する
- 信号に合ったインピーダンスを考慮する
- 2本の線がほぼ同じ環境を通るようにするため、先の単純モデルで2本の線に同じノイズが乗ると仮定できる
- インピーダンス計算は、製造元の計算機に目標インピーダンス、グラウンドプレーンとの距離、差動ペア間距離などを入力し、必要なトレース幅を求める方法で対応できる
- Zach Petersonによる差動ペアおよびUSB配線の動画が参考資料として含まれている
USB 2.0の速度
- USB 2.0だからといって速度が1つに固定されるわけではない
- USB 2.0には、たとえば2つの速度レベルがある
- full speed: 12 Mbit/s
- high speed: 480 Mbit/s
- deviceとhostは接続時にどの速度を使うか決める必要がある
- PCB上でhigh speedを安定して達成するには、インピーダンス整合が重要である
- 基本的なプロトタイプでは12 Mbit/sのfull speedで十分な場合があり、USBコネクタからチップまでのトレースが短ければ、トレース幅などに対して比較的寛容であることもある
プロトコルとソフトウェア層
- USBではハードウェアだけでなく、hostとdeviceの両方のソフトウェア層もあわせて考える必要がある
- Linuxの観点からUSBを説明する約45分の動画が参考資料として含まれている
- この動画では、USBフレーム、endpoint、configuration、1つのdeviceが複数のUSB機能を実行できる仕組みなどを扱っている
- USBをデバイスたちのネットワークのように考える視点が重要である
- host OSがあらゆるUSBデバイス用ドライバーを個別実装するのは非現実的なので、OSは複数のUSB device classを認識する
- mass storage device
- serial device
- そのほか多数のクラス
- 実習デバイスは、hostから見るとserial portデバイスとして動作する
STM32でUSB serial portデバイスを作る
- 実習デバイスは、hostから要求を受けるとLEDを点灯する単純なUSBデバイスである
- hostはこのデバイスをserial port deviceとして認識する
- 実装方法は大きく2つに分かれる
- USB対応マイクロコントローラーを使う
- Linuxを実行できるSoCを使い、カーネルに多くの処理を任せる
- 実習では単純さのため、マイクロコントローラー方式を使う
-
NUCLEO-F103RBボード
- 開発ボードにはNUCLEO-F103RBを使う
- 米国ではDigikeyのような場所で10ドル強で購入できる
- このボードは2つの部分がつながった構造になっている
- 小さい側はprogrammer領域、大きい側は実際にプログラムするmain MCU領域である
- ボードの標準USB接続はmain MCUではなくprogrammer側のMCUにつながっている
- このprogrammerはST-LINKプロトコルを使ってコンピューターとUSBメッセージをやり取りし、その後main MCUを書き込む
- STM32ベースのPCBを自作するなら、ボード上にチップを1つだけ置き、USBで書き込んだ後に同じUSBポートをデバイスロジックに使うこともできる
- STM32ベースPCBとUSB書き込みの参考資料としてPhil’s Labの動画が含まれている
- Video 19
- Video 20
実際のUSBポート配線
- Nucleoボードの標準USB接続はmain MCUにつながっていないため、main MCUのUSBポートを直接hostへ接続する必要がある
- プログラムを書き込んだ後は、ST-LINK programmerをコンピューターから外し、main MCUには実際のUSBポートから電源を供給する
- ソフトウェア作成にはSTM32CubeIDE、ボードへの書き込みにはSTM32CubeProgrammerを使う
-
必要なピンと設定
- USB 2.0デバイスを作るために必要なピンは4つである
- hostから来る5V電源
GNDD+D-- NUCLEO-F103RBでmain MCU側USBポートから給電を受けるには、
JP5ジャンパーを変更してボードがexternal 5 V供給を受けるよう設定する必要がある - STM32CubeIDEでは次のようにピンを設定する
PA12→USB_DPPA11→USB_DM- このボードのチップはUSB接続のために外付けの1.5 kΩプルアップ抵抗を必要とする
- 抵抗は
PA12を3.3Vへpull-upする - breadboardでこの回路を構成する
- MacBook Proと各ピンを接続するため、AmazonのUSB breakout cableを使い、terminal blockを外してUSBピンを露出させたうえで、jumper wireでNucleoボードとbreadboardに接続した
- 差動ペアの長さ合わせやインピーダンス制御を厳密に守らなくても、この実習では12 Mbit/s接続が可能だった
USB CDCソフトウェアを書く
- CubeIDEでUSBピンを設定するとclock設定に関する通知が表示され、CubeIDEに自動処理させるオプションを選べる
Pinout & ConfigurationのMiddleware and Software Packs配下でUSB_DEVICEを設定する- 実習で重要なモードは次のとおり
Communication Device Class (Virtual Port Com)
- この設定により、Nucleoボードはhostから見るとCDC serial portデバイスとして動作する
- hostはこのクラス情報に基づいて、custom deviceと通信するための適切なドライバーを設定できる
- CubeIDEはCコードを生成し、
main.cには次の初期化呼び出しが含まれる
MX_USB_DEVICE_Init();
- LEDを点灯させるため、
CDC_Receive_FSルーチンに次のコードを追加する
/* USER CODE BEGIN 6 */
if (Buf[0] == '1') {
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_5, 1);
}
- このHAL呼び出しは、port Aのpin 5に接続されたオンボードLEDを点灯する
- 手順ごとの参考動画は次のとおり
フラッシュと実行
- ELFファイルをビルドした後、CubeProgrammerでボードにコードを書き込む
- その後programmerを外し、前述の方法でexternal 5V給電を使うようにボードを配線する
- ボードの電源が入ると、OSのdevice managerにはCOM portまたはSerial portに似た項目として表示されるはずである
- CubeIDEの
USB_DEVICEmiddlewareメニューでDevice Descriptorの値を変更すると、OSのdevice managerに表示されるcustom device nameを設定できる - Mac OSでは、新しいデバイスは
/devファイルシステム配下に現れる- 例のパス:
/dev/tty.usbmodem497A0F6739561
- 例のパス:
- Linuxでは
/dev/ttyUSB0のような名前で見えることがある - Minicomでserial deviceに接続する例は次のとおり
minicom --device /dev/tty.usbmodem497A0F6739561
- 接続後、キーボードで
1を入力するとNucleoボードの緑色LEDが点灯する
STM32方式とLinux方式のソフトウェアの違い
- 実習の結果は、主要なOSで認識されるUSB serial portデバイスを作ることである
- STM32CubeIDEベースの方式には、ソフトウェア工学の観点で不便さがある
- UIメニューをクリックして大量のボイラープレートを生成しなければならない
InitUsbDevice(UsbClass.CDC)のように、コード上で柔軟にパラメータ化されたライブラリを使う方式ではない- 生成コードがユーザーコードと強く結合され、コードレビューが難しくなることがある
- 新しいバージョンが出たときにボイラープレートをどう更新するかが明確でない
- 現在の設定はSTM32エコシステムに強く縛られている
- LinuxがUSB deviceとして動作する仕組みは、よりクリーンなアプローチとして評価される
- Linux APIはより堅牢で標準化されている
- pseudo-fileやsystem callとの相互作用に基づいて実装できる
- user spaceとkernel spaceが分離されている
- LinuxをHAL層のように見ることもできる
- ただし、軽量で安価かつ量産しやすいUSBデバイスが必要な場合、Linux SoCは重すぎることがあり、多くの用途では過剰になりうる
- bare metalのUSBデバイス開発には、よりportableでopinionatedすぎないフレームワークがあるとよい、という結論につながる
1件のコメント
Hacker News の意見
USB 入門記事としてはとてもよいが、ST マイクロコントローラの利用にかなり偏っていて、最近の ESP32 エコシステムのように USB デバイスを簡単につないで使える方式に比べると、手順やツールチェーンがずっと多く見える。
また、差動ペアは自分でいくつも USB ボードを作ってきた立場からすると、初心者が大きく心配する部分ではなく、主に高速な作業で重要になる。
Arduino/ESP でよく使われる USB コントローラ IC が多くの細部を処理してくれるので、最初のデバイスを作る人に計算までさせるのはやりすぎに感じる。
初心者にとって素早く楽しめるプロジェクトで、一番面倒だったのは木製ケースを削ることだった。
実際に計算したことはほとんどなく、使っている ECAD ソフトウェアが差動ペアルーティングとインピーダンス信号解析ツールを提供している。
それでもトレースを非常に短く保てば、たいていは大きな問題にはならない。
ふつうはUSB 内蔵マイクロコントローラばかり使ってきた。
かなり前に USB 適合性試験をしたことがあるが、よく問題を見かけた項目のひとつが突入電流試験だった。
基本的には 5V 側のバイパスコンデンサが多すぎる場合で、記事ではこの点を見かけなかったように思う。
高速デジタル設計に意識が向きがちだが、適合性では、ときどきあまり格好よく見えない部分が足を引っ張る。
最新規格でどう動作するのかは知らないが、今も試験はあるようで、記事自体はよかった。
https://compliance.usb.org/index.asp?UpdateFile=Electrical#:...
トランジスタをいくつか使って電流リミッタを作ることはできるだろうが、温度保護のような機能が付いた、よりよい集積ソリューションがありそうだ。
USB-C について記事の内容に補足すると、CC ピンを適切な抵抗につなぐ必要がある。
そうしないと、おそらく動作しない可能性が高い。
それから、差動配線とインピーダンスは USB 2.0 ではそれほど大ごとではない。
トレース長を同じくらいにそろえ、無理のない形で直接つなぎ、できれば互いに近づけておけばよい。
長さやトレース幅の微調整、インピーダンス制御、RF のベストプラクティスまで過度に気にする必要はなく、単にネットを接続すればよい。
多くの MCU では USB PHY ピンとコネクタの間に直列抵抗も必要になる。
24mil ではなく 20mil のトレースを使うべきだったからといって基板を作り直すことまではおそらくないが、USB 2.0 の差動ペアレイアウトは負担が低い部類でも、きちんとやろうと努めるのはよい練習になる。
ARM プロセッサのように、はんだ付けが難しい部品が心配なら、必ずしもそこまで大きな部品を使う必要はない。
STM32 は性能が必要なときにはよいが、小さな作業にはより小さなコントローラのほうが向いている場合がある。
たとえば VUSB は、小さな Atmel マイクロコントローラで USB をビットバンギングするライブラリだ: https://www.obdev.at/products/vusb/index.html
学生に Linux カーネルモジュールプログラミングを教えるときに使ったサンプルボードの回路図もある: https://gitlab.cs.fau.de/i4/passt/passtboard-v2
ファームウェアは http://www.poempelfox.de/ds1820tousb/ および https://gitlab.cs.fau.de/i4/passt/ds1820tousb。
Arduino 風のプログラミングを好むなら、インクルードするライブラリを数行書くだけで USB デバイスのように使えるボードも多く、たとえば https://www.az-delivery.de/en/products/digispark-board がある。
たとえば NUCLEO-F429ZI がある: https://www.st.com/en/evaluation-tools/nucleo-f429zi.html
筆者が使った NUCLEO-F103RB と非常によく似ているが、内蔵プログラマ/デバッガ用の上側 USB コネクタに加えて、下側にもマイクロコントローラへ直接接続された USB コネクタがある。
ESD 保護などの選定を参考にしたいなら、ボードの回路図もダウンロードできる。
ハードウェア USB を内蔵した TSSOP-20 で、単価は約 0.81 ドル。
ただしソフトウェア面は少し難しいかもしれない。
https://www.wch-ic.com/products/CH32V203.html?
MCUでベアメタルUSBコードを書いてみたことがあるが、SPIやI2Cのような単純なデジタルプロトコルと比べると、かなり衝撃的だった。
物理層とデータリンク層はCANよりはるかに複雑というほどではないが、その先ですぐにディスクリプタ、エンドポイント、ドライバ設定の壁にぶつかる。
USBは最初からPC向けのプラグ&プレイエコシステムとして設計されていて、その特徴がはっきり出ている。
可能ならベンダー提供のソフトウェアを最大限使うのが、間違いなく良い道だ。
うっすら覚えているコツとしては、高スループットにはバルク転送を使い、アイソクロナス転送には目も向けないほうがいい。
USBはマスター/スレーブプロトコルなので、最大スループットが出ない場合は、たいていホストであるPC側に原因がある。
ライセンスであるLGPLが要件に合うなら、libusbはかなり使いやすい。
ベンダードライバを使わないなら、ハードウェアUSBプロトコルアナライザが本当に役に立つし、USB in a NutShellはプロトコル理解用のWeb参考資料として悪くない: https://www.beyondlogic.org/usbnutshell/usb1.shtml
STM32でUSBを扱うついでに聞きたいのだが、1フレームで64バイト超の受信をどうサポートするのか知っている人はいるだろうか。
今はソフトウェアで64バイトフレームを繰り返し処理しているが、もっと大きくできることは知っている。
たしか最大1MBまでだったように思う。
問題は、Reference Manualではこれらの設定が通常のレジスタではなく、ある種の疑似レジスタとして載っていることだ。
簡単な回避策があるのか知りたい。
OTGではないUSBペリフェラルの場合の話。
どのSTM32のことを言っているのかは分からないが、stm32g4はフルスピードのみ対応している。
主にESP32を使っているが、いろいろなプロジェクトに合う安価なハックがある。
とても速く簡単なカスタムコントローラが必要なら、捨てられたUSBキーボードのコントロール基板を外して使うのも検討に値する。
はんだ付けの代わりに導電性接着剤で接点に線を付け、グルーガンでしっかり固定すればよい。
スペースバー入力を送るアーケードボタン式の1ボタンゲームコントローラを安価ながらかなり頑丈に作ったことがあり、デバウンスなどはすべて処理され、コードは不要だった。
USB 3対応開発ボードがあるのか知りたい。
USB-Cモニターシンクをプロトタイピングしようとしているが、USBでDisplayPortを受けられるだけの性能を持つボードを見つけるのに苦労している。
すでにDisplayPort信号を受けられるか、外部モニターへルーティングできることが前提だ。
USBビルボードデバイスを実装し、正しいオルタネートモードを通知すればよい。
動作自体には任意だが、記憶では仕様上は必要だった。
そうすればUSB-CプラグにDisplayPort信号が出て、適切なAUX線をDisplayPortコネクタに接続すればよい。
価格も妥当に見える。
2年前に似たものを探したときは、価格がもう1桁高かった。
Raspberry PiをPCに挿して、https://github.com/xairy/raw-gadgetで仮想USBデバイスを作っている。
今はプロプライエタリソフトウェアをだますために、MTPカメラをエミュレートする用途に使っている。
Raspberry Pi ZeroとLinuxカーネルの複合USBで、USBデバイスをいくつかプロトタイピングしてみた。
少なくともストレージデバイスとシリアルデバイスはかなり簡単に動作させられた。
Raspberry Piで複合カーネルモジュールを初期化するシェルスクリプトのようなものが必要で、定型コードはカーネル文書で見つけられる。
pikvmも興味深いプロジェクトだ。
PCに接続すると、USB接続がキーボードやマウスのように動作するだけでなく、システム起動に使えるUSBドライブにもなり得る。
インストール作業にはかなり興味深いやり方だ。