Repository navigation
USB_Enumeration
USB エニュメレーション(列挙)の流れ。USBホストから見て;
- 接続検知と物理バス速度判定。USB 周辺機能が自動で行う
- USBアドレスの提供。⇨ SET_ADDRESS
- デバイス(装置)記述子の取得。⇨ GET_DESCRIPTOR
- 構成・インタフェース記述子の取得。⇨ GET_DESCRIPTOR
- 構成・インタフェースの選択と有効化。⇨ SET_CONFIGURATION、SET_INTERFACE
- OSドライバの読み込み・有効化。
ホスト-装置 トランザクションは 制御専用(Control)エンドポイント=EP0 を通じて行う。
EP0_OUT に着信する EP0_SETUP パケットは、必ず 8バイトからなる。ワード値はリトルエンディアン(下位バイト先行;以後共通)で表現される。
| 0 | 1 | 2,3 | 4,5 | 6,7 |
|---|---|---|---|---|
| bmRequestType | bRequest | wValue | wIndex | wLength |
| 要求形式 | 要求種別 | 機能選択値 | 指標値 | データ量 |
/* SETUPパケット 8byteを表現する構造体 */
typedef struct {
uint8_t bmRequestType;
uint8_t bRequest;
uint16_t wValue;
uint16_t wIndex;
uint16_t wLength;
} __attribute__((packed)) Setup_Packet;bmRequestType 要求種別 は 1+2+5 ビットのフィールドからなる。
| Bit | Selector | Value | Description |
|---|---|---|---|
| 7 | 転送方向 | 0 | ホスト⇨装置指令 |
| 1 USB_REQTYPE_DIRECTION_bm | 装置⇨ホスト要求 | ||
| 6,5 | 種別 | 0 USB_REQTYPE_STANDARD_gc | Standard |
| 1 USB_REQTYPE_CLASS_gc | Class | ||
| 2 USB_REQTYPE_VENDOR_gc | Vendor | ||
| 3 | 予約 | ||
| 4-0 | 受取先 | 0 USB_RECIPIENT_DEVICE_gc | Device |
| 1 USB_RECIPIENT_INTERFACE_gc | Interface | ||
| 2 USB_RECIPIENT_ENDPOINT_gc | Endpoint | ||
| 3 USB_RECIPIENT_OTHER_gc | Other | ||
| 4-31 | 予約 |
転送方向が 1であれば、wLength で示されたバイト数の DATA応答を、ホストに返さなければならない。そうでなければ ZLP(ゼロ長応答)を返すことで 1パケットのトランザクションを完了する。
-
wLengthを超える大きさの応答を返すべきではない。ホスト側では その大きさのバッファメモリしか用意していないと想定するべき。
以上の情報から、標準要求までのフローの実装コードは以下のように書き下せる。転送方向が0の場合は EP0_SETUP パケットに続いて追加の DATA領域(9バイト目以降、ディスクリプタの本体部分等)が存在しうるので、その転送(DATAバッファ充填)完了を確実にしてから先に進める。
-
SETUP割込は EP0_SETUP パケットの 8バイトが揃うとTRNCOMPL割込に先んじて発生する。 - ベンダー要求は USB標準には含まれず、装置固有の OSドライバが ホスト側で有効化されない限り使用されない。
ISR(USB0_TRNCOMPL_vect) {
USB0_INTFLAGSB |= USB_SETUP_bm;
if (bit_is_set(EP0_OUT.STATUS, USB_EPSETUP_bp)) {
/* EP0_OUT先頭バイトを読む */
uint8_t bmRequestType = EP0_SETUP.bmRequestType;
/* DIRECTION属性が偽なら制御OUT */
if (bit_is_clear(bmRequestType, USB_REQTYPE_DIRECTION_bp)) { /* bp=7 */
EP0_OUT_flush(); /* EP0_OUTに残りのDATAを読む(TRNCOMPL 解決) */
/* DATAが格納されるのは EP0_OUT_BUFFの位置:EP0_SETUP の後ろから */
}
/* REQTYPE属性をマスク抽出 */
bmRequestType &= USB_REQTYPE_TYPE_gm; /* gm=0x60 */
if (bmRequestType == USB_REQTYPE_STANDARD_gc) { /* gc=0x00 */
/* 標準要求を処理:制御エニュメレーション本体 */
control_request_standard();
}
else if (bmRequestType == USB_REQTYPE_CLASS_gc) { /* gc=0x20 */
/* クラス要求を処理:例えばUSB-CDCやHIDクラス */
control_request_class();
}
else if (bmRequestType == USB_REQTYPE_VENDOR_gc) { /* gc=0x40 */
/* 必要ならここにベンダー要求処理 */
control_request_vender();
}
}
/* 割込バルク転送 処理等 */
}標準要求の bRequest 要求種別 には、標準装置要求、標準インターフェース要求、標準エンドポイント要求 の 3分類がある。
/* 標準要求 応答例 */
void control_request_standard (void) {
uint8_t bRequest = EP0_SETUP.bRequest;
if (bRequest == USB_REQ_GetStatus) { /* 0x00 */
EP0_IN.CNT = EP0_SETUP.wLength;
USB_BUFF.wValue = 0;
}
else if (bRequest == USB_REQ_GetDescriptor) { /* 0x06 */
size_t _length = (size_t)memcpy_descriptor(&EP0_IN_BUFF, EP0_SETUP.wValue);
EP0_IN.CNT = EP0_SETUP.wLength < _length ? EP0_SETUP.wLength : _length;
}
else if (bRequest == USB_REQ_GetConfiguration) { /* 0x08 */
EP0_IN.CNT = 1;
EP0_IN_BUFF[0] = get_configuration();
}
else if (bRequest == USB_REQ_SetConfiguration) { /* 0x09 */
EP0_IN.CNT = 0;
set_configuration((uint8_t)EP0_SETUP.wValue);
}
else if (bRequest == USB_REQ_GetInterface) { /* 0x0A */
EP0_IN.CNT = 1;
EP0_IN_BUFF[0] = get_interface((uint8_t)EP0_SETUP.wIndex);
}
else if (bRequest == USB_REQ_SetInterface) { /* 0x0B */
EP0_IN.CNT = 0;
set_interface((uint8_t)EP0_SETUP.wIndex, (uint8_t)EP0_SETUP.wValue);
}
else if (bRequest == USB_REQ_SetAddress) { /* 0x05 */
/* Initiate EP0_IN and, if there is a response, activate the assigned address. */
EP0_IN.CNT = 0;
EP0_IN_flush(); /* STATUS (ZLP) sending */
EP0_IN_pending(); /* wait */
USB0_ADDR = EP0_SETUP.wValue; /* Get 7bit USB Device Address */
return;
}
else if (bRequest == USB_REQ_ClearFeature) { /* 0x01 */
EP0_IN.CNT = 0;
clear_feature();
}
else if (bRequest == USB_REQ_SetFeature) { /* 0x03 */
EP0_IN.CNT = 0;
set_feature();
}
else if (bRequest == USB_REQ_SynchFrame) { /* 0x0C */
/* Isochronous 転送デバイスのみ実装 */
EP0_IN.CNT = 2;
USB_BUFF.wValue = USB_EP_WORK.FRAMENUM;
}
EP0_IN_flush(); /* DATA (ZLP) sending */
}標準装置要求は、8種類ある。うち 一般的な(HID や CDC といった)装置実装では使用されない SET_DESCRIPTOR を除く 7種類はすべて実装され、応答できなくてはならない。
| bmRequestType | bRequest | wValue | wIndex | wLength | Data |
|---|---|---|---|---|---|
| 1 00 00000 | 0x00 GET_STATUS | 0 | 0 | 2 | 装置状態値 |
| 0 00 00000 | 0x01 CLEAR_FEATURE | 機能選択値 | 0 | 0 | - |
| 0 00 00000 | 0x03 SET_FEATURE | 機能選択値 | 0 | 0 | - |
| 0 00 00000 | 0x05 SET_ADDRESS | 装置アドレス | 0 | 0 | - |
| 1 00 00000 | 0x06 GET_DESCRIPTOR | 記述子種別/指標番号 | 0または言語ID | * | 記述内容 |
| 0 00 00000 | 0x07 SET_DESCRIPTOR | 記述子種別/指標番号 | 0または言語ID | * | 記述内容 |
| 1 00 00000 | 0x08 GET_CONFIGURATION | 0 | 0 | 1 | 構成番号 |
| 0 00 00000 | 0x09 SET_CONFIGURATION | 構成番号 | 0 | 0 | - |
wIndex 指標値 にインタフェース番号が指定される 標準インターフェース要求は、5種類ある。
| bmRequestType | bRequest | wValue | wIndex | wLength | Data |
|---|---|---|---|---|---|
| 1 00 00001 | 0x00 GET_STATUS | 0 | IF番号 | 2 | IF状態値 |
| 0 00 00001 | 0x01 CLEAR_FEATURE | 機能選択値 | IF番号 | 0 | - |
| 0 00 00001 | 0x03 SET_FEATURE | 機能選択値 | IF番号 | 0 | - |
| 1 00 00001 | 0x0A GET_INTERFACE | 0 | IF番号 | 1 | 代替IF値 |
| 0 00 00001 | 0x11 SET_INTERFACE | 代替設定値 | IF番号 | 0 | - |
このときの wIndex は、上位バイトは予約値で、下位バイト(Bit[7-0])だけが有効。番号0はホスト状態が未設定であることを表す。
| Bit | Selector | Value |
|---|---|---|
| 15-8 | 予約 | 0 |
| 7-0 | インターフェース番号 | 0x00-0xFF |
wIndex 指標値 にエンドポイント番号が指定される 標準エンドポイント要求は、4種類ある。うち SYNCH_FRAME は Isochronous 転送装置でのみ使用される。
| bmRequestType | bRequest | wValue | wIndex | wLength | Data |
|---|---|---|---|---|---|
| 1 00 00010 | 0x00 GET_STATUS | 0 | エンドポイント番号 | 2 | エンドポイント状態値 |
| 0 00 00010 | 0x01 CLEAR_FEATURE | 機能選択値 | エンドポイント番号 | 0 | - |
| 0 00 00010 | 0x03 SET_FEATURE | 機能選択値 | エンドポイント番号 | 0 | - |
| 1 00 00010 | 0x12 SYNCH_FRAME | 0 | エンドポイント番号 | 2 | フレーム番号 |
このときの wIndex 指標値 は、エンドポイント番号と方向を指示する。
| Bit | Selector | Value | Description |
|---|---|---|---|
| 15-8 | 予約 | 0 | |
| 7 | 転送方向 | 0 | ホスト⇨装置(OUT) |
| 1 | 装置⇨ホスト(IN) | ||
| 6-4 | 予約 | 0 | |
| 3-0 | EP番号 | 0-15 |
GET_STATUS は(どの要求分類でも)wLength で求められる 2バイトの状態値を常に返却する必要がある。後述の詳細な実装を必要としないなら、すべての分類で単純に 0 を返せば良い。
if (bRequest == USB_REQ_GetStatus) { /* 0x00 */
EP0_IN.CNT = 2; /* = EP0_SETUP.wLength */
USB_BUFF.wValue = 0; /* Response Value */
EP0_IN_flush(); /* DATA sending */
}この要求での返値は、以下のビットフィールドからなる。
| Bit | Selector | Value | Description |
|---|---|---|---|
| 15-2 | 予約 | 0 | |
| 1 | REMOTE_WAKEUP | 0 | 不可 |
| 1 | 可能 | ||
| 0 | SELF_POWERED | 0 | バスパワー |
| 1 | セルフパワー |
給電形態ビット(SELF_POWERED)は、ホスト側から VUSB 給電を遮断したとしても、装置が支障なく動作し続けられるかを表明する。
遠隔起動ビット(REMOTE_WAKEUP)は、スリープ/サスペンド状態の ホストPCをレジュームできることを表明する。これは 装置要求 SET_FEATURE の当該ビットの結果を反映すべきだ。
この要求は将来用に予約されている。要求された場合は(要求インタフェース番号に関わらず)常に 2バイトの 0を返せば良い。
| Bit | Selector | Value |
|---|---|---|
| 15-0 | 予約 | 0 |
この要求には、要求されたエンドポイントの活動状態を 最下位 1ビットで返却する。
| Bit | Selector | Value | Description |
|---|---|---|---|
| 15-1 | 予約 | 0 | |
| 0 | ENDPOINT_HALT | 0 | 活動 |
| 1 | 停止 |
この指令は USB装置に 7ビットの USBアドレスを供給する。指示された USBアドレスは次回の(SOFから始まる)通信フレームから使用可能になるため、これを設定反映する前に ZLP応答を完遂しなければならない。
if (bRequest == USB_REQ_SetAddress) { /* 0x05 */
EP0_IN.CNT = 0;
EP0_IN_flush(); /* STATUS (ZLP) sending */
EP0_IN_pending(); /* wait */
USB0_ADDR = EP0_SETUP.wValue; /* Get 7bit USB Device Address */
return;
}この指令の wIndex 指標値 は、以下のように解釈する。そして指定された機能区分を既定状態にリセットする。例えば ENDPOINT_HALT 指令ならば、当該エンドポイントの STALED や NAK応答状態を解除して再度正常応答可能にするということである。
- 装置要求:常にゼロ -
wValueのみを使用 - インターフェース要求:インターフェース番号
- エンドポイント要求:エンドポイント番号
ホストに対しては ZLP で応答する。
if (bRequest == USB_REQ_ClearFeature) { /* 0x01 */
EP0_IN.CNT = 0;
clear_feature();
EP0_IN_flush();
}この指令の wValue 機能選択値 は、下位バイトを以下のように解釈する。インターフェース要求に対しては常に 0であるはずだ。
| wValue.low | Selector | Target |
|---|---|---|
| 0 | ENDPOINT_HALT | エンドポイント要求 |
| 1 | DEVICE_REMORE_WAKEUP | 装置要求 |
| 2 | TEST_MODE | 装置要求 |
装置要求での TEST_MODE は、wValue 機能選択値 の上位バイトを以下のように解釈する。TEST_MODE は High-Speed 以上の USBデバイス認証試験を行うために、実装が必要とされる。
| wValue.high | Selector | Description |
|---|---|---|
| 0x00 | Reserved | (テストモード終了) |
| 0x01 | Test_J | D+/D-線を Jステート状態にする |
| 0x02 | Test_K | D+/D-線を Kステート状態にする |
| 0x03 | Test_SE0_NAK | D+/D-線を SE0 状態にし、かつ NAK を返す |
| 0x04 | Test_Packet | 特定のテスト信号パターンを送信し続ける |
| 0x05 | Test_Force_Enable | 受信信号をそのまま送り返す(loopback) |
| 0x06-0x3F | Reserved | (標準テストセレクタ) |
| 0x3F-0xBF | Reserved | (未定義) |
| 0xC0-0xFF | Reserved | (ベンダー定義テストセレクタ) |
- Low/Full-Speed USBデバイスに対してはこの規約はなく、コンプライアンス認証試験だけが要求されるので TEST_MODE の実装は必要ない。
- Test_K は、装置側からの REMORE_WAKEUP 信号送信(
URESUME)に等価。 - Test_J は、装置接続開始時の D+/D- プルアップ有効=アイドル状態(
ATTACH)に等価。 - Test_SE0_NAK は、アイドル+全NAK 応答状態(
GNACK)と換言できる。 - つまりこの3種類は、AVR DU 系統の場合はレジスタ設定で不完全ながら対応可能。ただし
SETUP転送も塞ぐGNAUTOは、通信での解除手段も失うことに注意。
この指令は CLEAR_FEATURE とは逆に、指定の機能区分をそうあるように変更することが期待される。wIndex 指標値 と wValue 機能選択値 の様式は CLEAR_FEATURE と同じ。
ホストに対しては ZLP で応答する。
if (bRequest == USB_REQ_SetFeature) { /* 0x03 */
EP0_IN.CNT = 0;
set_feature();
EP0_IN_flush();
}- インターフェース要求に対する指令は、当該インターフェース番号を新たな既定値として有効にすることを求める。現在選択されている当該インタフェース状態は インターフェース要求の GET_STATUS で取得される。これは前述のように普通は常に
0である。 - エンドポイント要求に対する ENDPOINT_HALT 指令は、CLEAR_FEATURE 指令がなされるまで、当該エンドポイントを通信不可(停止)に維持すべきだ。現在の当該エンドポイント状態は エンドポイント要求の GET_STATUS で取得される。実際には停止状態のエンドポイントに対して ホスト側は何もしてこなくなるので、装置側は何の対応もしなくても問題はない。
- 装置要求に対する DEVICE_REMORE_WAKEUP 指令は、装置の遠隔起動機能を有効にする。現在の装置状態は 装置要求の GET_STATUS で取得される。実際には(装置側が REMORE_WAKEUP に対応していてもいなくても)何の対応もしなくても実害はない。
- 装置要求に対する TEST_MODE 指令は、CLEAR_FEATURE 指令が指示されるまで(あるいは別の TEST_MODE 状態に変更されるまで)装置を指示された状態で維持すべきだ。これは前述したように Low/Full-Speed 装置では実装されていなくても支障はなく、単に無視してよい。
- 装置要求に対する CLEAR_FEATURE 指令の
wValue == 0セレクタは、TEST_MODE 終了と解釈してよい。
この 装置要求 には、0x00 以外の 現在選択されている 構成番号 を 1バイトで返さなければならない。
if (bRequest == USB_REQ_GetConfiguration) { /* 0x08 */
EP0_IN.CNT = 1;
EP0_IN_BUFF[0] = get_configuration();
EP0_IN_flush();
}| Bit | Selector | Result |
|---|---|---|
| 7-0 | 構成番号 | 0x01-0xFF |
この 装置指令 は、0x00 以外の 構成番号 が wValue の下位バイトに指定される。wIndex には 常に 0 が指定される。
ホストに対しては ZLP で応答する。
if (bRequest == USB_REQ_SetConfiguration) { /* 0x09 */
EP0_IN.CNT = 0;
set_configuration((uint8_t)EP0_SETUP.wValue);
EP0_IN_flush();
}| Bit | Selector | wValue |
|---|---|---|
| 7-0 | 構成番号 | 0x01-0xFF |
この インターフェース要求 には、0x00 以外の(現在有効である)インターフェース番号 を 1バイトで返さなければならない。wValue には 常に 0 が指定される。
if (bRequest == USB_REQ_GetInterface) { /* 0x0A */
EP0_IN.CNT = 1;
EP0_IN_BUFF[0] = get_interface((uint8_t)EP0_SETUP.wIndex);
EP0_IN_flush();
}| Bit | Selector | wIndex |
|---|---|---|
| 7-0 | インターフェース番号 | 0x01-0xFF |
この インターフェース指令 は、wIndex の下位バイトに インターフェース番号 が、下位バイトに 代替インタフェース番号 が指定される。これらは インターフェース記述子の bInterfaceNumber と bAlternateSetting を反映している。
ホストに対しては ZLP で応答する。
if (bRequest == USB_REQ_SetInterface) { /* 0x0B */
EP0_IN.CNT = 0;
set_interface((uint8_t)EP0_SETUP.wIndex, (uint8_t)EP0_SETUP.wValue);
EP0_IN_flush();
}| Bit | Selector | wIndex |
|---|---|---|
| 15-8 | 代替インターフェース番号 | 0x01-0xFF |
| 7-0 | インターフェース番号 | 0x01-0xFF |
この エンドポイント要求 は、Isochronous 転送形態でのみ実装する必要がある。wIndex で示されるエンドポイントの、最新のフレーム番号(実際は 12ビットのみ有効)を 2バイトで返さなければならない。
if (bRequest == USB_REQ_SynchFrame) { /* 0x0C */
EP0_IN.CNT = 2;
USB_BUFF.wValue = USB_EP_WORK.FRAMENUM;
EP0_IN_flush();
}| Bit | Selector | Result |
|---|---|---|
| 11-0 | フレーム番号 | 0x000-0x7FF |
実際には、エンドポイント毎に個別のフレーム番号があるわけではなく 装置全体で共通の通番であるから、FRAMENUM 仮想レジスタの値を返すだけで良い。ホスト側はこれを見て Isochronous 転送パケットの途中欠損(ホストが送ったのに装置が受け取れなかった)を知ることができる。
この 装置指令は、wIndex と wValue で指定される構成記述(ディスクリプタ)を、wLength の長さを持つ Data で上書きすることを求める。しかし一般に構成記述は "読み出し専用" であるので、USB 規約上は存在する機能ではあるものの、使用される場面は稀、あるいは皆無である。仮に使用されても無視して ZLP応答で済ませればよい。
if (bRequest == USB_REQ_SetDescriptor) { /* 0x07 */
EP0_IN.CNT = 0;
EP0_IN_flush();
}- 稀な用途としては、装置の 既定の 言語ID 変更や、シリアル番号文字列の書き換えがありうる。
この 装置要求は、さまざまな構成記述子(ディスクリプタ)の返答を要求する。USB エニュメレーションの根幹。
詳細は別項。⇨ USB 記述子
Twitter(X): @askn37
BlueSky Social: @multix.jp
GitHub: https://github.com/askn37/
Product: https://askn37.github.io/
Copyright (c) 2026 askn (K.Sato) multix.jp
Released under the MIT license
https://opensource.org/licenses/mit-license.php
https://www.oshwa.org/
multix.jp/てくにかるむ(休眠中)
Multix Zinnia Product SDK [*AVR]
AVR.JP(日本語訳)
AVR-LIBC(日本語訳)