Upgrade to Pro — share decks privately, control downloads, hide ads and more …

ゲームコントローラやキーボードのファームウェアをSwiftで書く

 ゲームコントローラやキーボードのファームウェアをSwiftで書く

今日つくるもの
USBデバイスを実装する前に
通信の仕組み
接続の通知
USBデバイスの列挙
入力データの通信

Avatar for Kishikawa Katsumi

Kishikawa Katsumi

September 12, 2026

More Decks by Kishikawa Katsumi

Other Decks in Programming

Transcript

  1. 格闘ゲーム用コントローラー ‒ 部品リスト ゲームパッド キーボード ‒ Alpakka 1 Controller https://inputlabs.io/alpakka

    ‒ CROSSARROW60 ‒ 基板設計・はんだ付け必要 (写真の製品は組み立てのみ) ‒ 基板設計・はんだ付け必要 (写真の製品は組み立てのみ) ‒ ※マイコンはRP2040だが ‣ 筐体(箱であればなんでもいい) 別のボード(Marmota)のため Picoとはピン配置が異なる ‣ 基板設計・はんだ付け不要 ‒ キースイッチ、キーキャップは 別に用意する必要あり ‣ アーケードボタン ‣ 配線ケーブル ‣ Raspberry Pi Pico https://shop.yushakobo.jp/products/10753
  2. 章 タイトル 内容 1 概要 この文書の目的と構成、各章の概要についての紹介。 2 用語集 規格書で使われる用語と略語の定義集。Bit Stuf

    ng(ビット・スタッフ)などの技術 用語はここで説明されている。 3 バックグラウンド USB規格のゴールや対象となるアプリケーションについての説明。 4 アーキテクチャの全体像 ホスト・ハブ・デバイスからなるバス全体の構成と、データ転送・電源供給につい て。 エンドポイントとパイプという通信の考え方と、4種類の転送方式(コントロール/イ 5 USBのデータ・フロー・モデルの説明 ンタラプト/バルク/アイソクロナス)の説明。 7 電気的仕様 電気的仕様の規定。信号の電圧、NRZI符号化、クロック精度(±0.25%)など。ロゴ 認証で「チャプタ7テスト」と呼ばれる難関の基準。 8 プロトコル・レイヤー パケットの形式とやりとりの手順の規定。トランザクション、ACK/NAK/STALLの応 答、再送の仕組みなど。 9 USBデバイス・フレームワーク デバイスに求められる振る舞いの規定。列挙の状態遷移、標準リクエストへの応答、 ディスクリプタの形式。 10 USBホスト ホスト側のハードウェアとソフトウェアに求められる役割の規定。 11 HUB(ハブ)についての規格 ハブの動作の規定。ポートの管理や速度の異なるデバイスの混在の扱いなど。 fi 6 機械的仕様 コネクタとケーブルの形状・寸法・材質の規定。プラグの形やピン配置はここで決 まっている。
  3. 章 タイトル 内容 1 概要 この文書の目的と構成、各章の概要についての紹介。 2 用語集 規格書で使われる用語と略語の定義集。Bit Stuf

    ng(ビット・スタッフ)などの技術 用語はここで説明されている。 3 バックグラウンド USB規格のゴールや対象となるアプリケーションについての説明。 4 アーキテクチャの全体像 ホスト・ハブ・デバイスからなるバス全体の構成と、データ転送・電源供給につい て。 エンドポイントとパイプという通信の考え方と、4種類の転送方式(コントロール/イ 5 USBのデータ・フロー・モデルの説明 ンタラプト/バルク/アイソクロナス)の説明。 6 機械的仕様 コネクタとケーブルの形状・寸法・材質の規定。プラグの形やピン配置はここで決 まっている。 7 電気的仕様 電気的仕様の規定。信号の電圧、NRZI符号化、クロック精度(±0.25%)など。ロゴ 認証で「チャプタ7テスト」と呼ばれる難関の基準。 8 プロトコル・レイヤー パケットの形式とやりとりの手順の規定。トランザクション、ACK/NAK/STALLの応 全部読む! 答、再送の仕組みなど。 10 USBホスト ホスト側のハードウェアとソフトウェアに求められる役割の規定。 11 HUB(ハブ)についての規格 ハブの動作の規定。ポートの管理や速度の異なるデバイスの混在の扱いなど。 fi 9 USBデバイス・フレームワーク デバイスに求められる振る舞いの規定。列挙の状態遷移、標準リクエストへの応答、 ディスクリプタの形式。
  4. 章 タイトル 内容 この文書の目的と構成、各章の概要についての紹介。 知らない言葉を調べる 1 概要 2 用語集 規格書で使われる用語と略語の定義集。Bit

    Stuf ng(ビット・スタッフ)などの技術 用語はここで説明されている。 3 バックグラウンド USB規格のゴールや対象となるアプリケーションについての説明。 拾い読み 4 アーキテクチャの全体像 ホスト・ハブ・デバイスからなるバス全体の構成と、データ転送・電源供給につい て。 エンドポイントとパイプという通信の考え方と、4種類の転送方式(コントロール/イ 5 USBのデータ・フロー・モデルの説明 ンタラプト/バルク/アイソクロナス)の説明。 コネクタとケーブルの形状・寸法・材質の規定。プラグの形やピン配置はここで決 まっている。 6 機械的仕様 7 電気的仕様 拾い読み 8 プロトコル・レイヤー 電気的仕様の規定。信号の電圧、NRZI符号化、クロック精度(±0.25%)など。ロゴ 認証で「チャプタ7テスト」と呼ばれる難関の基準。 パケットの形式とやりとりの手順の規定。トランザクション、ACK/NAK/STALLの応 全部読む! 答、再送の仕組みなど。 10 USBホスト ホスト側のハードウェアとソフトウェアに求められる役割の規定。 11 HUB(ハブ)についての規格 ハブの動作の規定。ポートの管理や速度の異なるデバイスの混在の扱いなど。 fi 9 USBデバイス・フレームワーク デバイスに求められる振る舞いの規定。列挙の状態遷移、標準リクエストへの応答、 ディスクリプタの形式。
  5. Memory-Mapped I/O (MMIO) ソフトウェアからハードウェアを制御する仕組み import _Volatile let outputSet = VolatileMappedRegister<UInt32>(

    unsafeBitPattern: 0xD000_0014 // GPIO_OUT_SET ) let outputClear = VolatileMappedRegister<UInt32>( unsafeBitPattern: 0xD000_0018 // GPIO_OUT_CLR ) let ledMask: UInt32 = 1 << 25 while true { outputSet.store(ledMask) // GP25をHighにする delay() outputClear.store(ledMask) } delay() // GP25をLowにする
  6. Memory-Mapped I/O (MMIO) MMIOでは最適化によって処理が省略されると困る import _Volatile struct Register { let

    address: UInt32 /// Reads the register value. @inline(__always) func load() -> UInt32 { VolatileMappedRegister<UInt32>(unsafeBitPattern: UInt(address)).load() } } /// Writes a value to the register. @inline(__always) func store(_ value: UInt32) { VolatileMappedRegister<UInt32>(unsafeBitPattern: UInt(address)).store(value) }
  7. ̲Volatile Package SwiftでVolatileなメモリアクセスを実現する import _Volatile struct Register { let address:

    UInt32 /// Reads the register value. @inline(__always) func load() -> UInt32 { VolatileMappedRegister<UInt32>(unsafeBitPattern: UInt(address)).load() } } /// Writes a value to the register. @inline(__always) func store(_ value: UInt32) { VolatileMappedRegister<UInt32>(unsafeBitPattern: UInt(address)).store(value) }
  8. ̲Volatile Package SwiftでVolatileなメモリアクセスを実現する extension VolatileMappedRegister where Pointee == UInt32 {

    /// Perform a 32-bit volatile load operation from the target pointer. /// /// Do not use for inter-thread synchronization. @_transparent public func load() -> Pointee { UInt32(Builtin.atomicload_monotonic_volatile_Int32(_rawPointer)) } } /// Perform a 32-bit volatile store operation on the target pointer. /// /// Do not use for inter-thread synchronization. @_transparent public func store(_ value: Pointee) { Builtin.atomicstore_monotonic_volatile_Int32(_rawPointer, value._value) } https://github.com/swiftlang/swift/blob/6ee6d5bf4138eb59700463c032e83618d02e9919/stdlib/public/Volatile/Volatile.swift#L74-L90
  9. ̲Volatile Package コンパイラが直接LLVM IRを生成する Swift UInt32(Builtin.atomicload_monotonic_volatile_Int32(_rawPointer)) LLVM IR load atomic

    volatile i32, ptr %address monotonic atomicload 分割されない(atomic) monotonic 順序保証 volatile 省略や統合をしない Int32 32 bit幅で読み込む
  10. 差動通信 0と1はJ/Kの「変化」で表す 1 0 0 0 1 0 1 1

    0 0 1 0 1 1 0 K K J K J K J K J J K K K J J
  11. NRZI (Non Return to Zero Inversion)符号化 1 0 0 0

    1 0 1 1 0 0 1 0 1 1 0 K K J K J K J K J J K K K J J
  12. struct NRZIEncoder { private var line = true mutating func

    encode(_ bits: [Bool]) -> [Bool] { bits.map { bit in if !bit { line.toggle() } return line } } } struct NRZIDecoder { private var line = true mutating func decode(_ states: [Bool]) -> [Bool] { states.map { state in let bit = (state == line) // 変わっていない = 1、変わった = 0 } } } line = state return bit
  13. enum USB { ... static func initialize() { // 1.

    Reset to a clean slate before bringing it up (clk_usb assumes // Clocks.initialize has already run). Resets.reset(.usbctrl) Resets.unreset(.usbctrl) // 2. DPRAM holds undefined values right after power-on -> zero it out. DPRAM.clear() // 3. Controller core, wiring to the physical pins, and self-reported power. mainControl.store(controllerEnable) muxing.store(muxToPhySoftcon) power.store(vbusDetectOverride) // The Pico's detect pin isn't wired -> declare "power is present". // 4. Raise a flag on every EP0 completion / address 0 (accept newcomers). sieControl.store(ep0IntPerBuffer) addressEndpoint.store(0) // 5. The one bit that announces us. From here the host's script begins. sieControl.set(pullupEnable) } ... }
  14. enum USB { ... static func initialize() { // 1.

    Reset to a clean slate before bringing it up (clk_usb assumes // Clocks.initialize has already run). Resets.reset(.usbctrl) Resets.unreset(.usbctrl) // 2. DPRAM holds undefined values right after power-on -> zero it out. DPRAM.clear() マルチプレクサーを操作して // 3. Controller core, wiring to the physical pins, and self-reported power. USBコントローラーをPhysical Layerに繋ぐ mainControl.store(controllerEnable) muxing.store(muxToPhySoftcon) power.store(vbusDetectOverride) // The Pico's detect pin isn't wired -> declare "power is present". // 4. Raise a flag on every EP0 completion / address 0 (accept newcomers). sieControl.store(ep0IntPerBuffer) addressEndpoint.store(0) // 5. The one bit that announces us. From here the host's script begins. sieControl.set(pullupEnable) } ... }
  15. enum USB { ... static func initialize() { // 1.

    Reset to a clean slate before bringing it up (clk_usb assumes // Clocks.initialize has already run). Resets.reset(.usbctrl) Resets.unreset(.usbctrl) // 2. DPRAM holds undefined values right after power-on -> zero it out. DPRAM.clear() マルチプレクサーを操作して // 3. Controller core, wiring to the physical pins, and self-reported power. USBコントローラーをPhysical Layerに繋ぐ mainControl.store(controllerEnable) muxing.store(muxToPhySoftcon) power.store(vbusDetectOverride) // The Pico's detect pin isn't wired -> declare "power is present". // 4. Raise a flag on every EP0 completion / address 0 (accept newcomers). sieControl.store(ep0IntPerBuffer) addressEndpoint.store(0) D+をプルアップ // 5. The one bit that announces us. From here the host's script begins. sieControl.set(pullupEnable) } ... }
  16. enum USB { ... static func initialize() { // 1.

    Reset to a clean slate before bringing it up (clk_usb assumes // Clocks.initialize has already run). Resets.reset(.usbctrl) Resets.unreset(.usbctrl) // 2. DPRAM holds undefined values right after power-on -> zero it out. DPRAM.clear() // 3. Controller core, wiring to the physical pins, and self-reported power. mainControl.store(controllerEnable) muxing.store(muxToPhySoftcon) power.store(vbusDetectOverride) // The Pico's detect pin isn't wired -> declare "power is present". // 4. Raise a flag on every EP0 completion / address 0 (accept newcomers). sieControl.store(ep0IntPerBuffer) addressEndpoint.store(0) // 5. The one bit that announces us. From here the host's script begins. sieControl.set(pullupEnable) } ... }
  17. enum USB { ... デバッガーから実行など、コールドスタートとは static func initialize() { 限らないのでリセット状態に入れてからアンリセットする。

    // 1. Reset to a clean slate before bringing it up (clk_usb assumes // Clocks.initialize has already run). コントローラーはまだ動いていない。リセット状態を解除しただけ。 Resets.reset(.usbctrl) Resets.unreset(.usbctrl) // 2. DPRAM holds undefined values right after power-on -> zero it out. DPRAM.clear() // 3. Controller core, wiring to the physical pins, and self-reported power. mainControl.store(controllerEnable) muxing.store(muxToPhySoftcon) power.store(vbusDetectOverride) // The Pico's detect pin isn't wired -> declare "power is present". // 4. Raise a flag on every EP0 completion / address 0 (accept newcomers). sieControl.store(ep0IntPerBuffer) addressEndpoint.store(0) // 5. The one bit that announces us. From here the host's script begins. sieControl.set(pullupEnable) } ... }
  18. enum USB { ... デバッガーから実行など、コールドスタートとは static func initialize() { 限らないのでリセット状態に入れてからアンリセットする。

    // 1. Reset to a clean slate before bringing it up (clk_usb assumes // Clocks.initialize has already run). コントローラーはまだ動いていない。リセット状態を解除しただけ。 Resets.reset(.usbctrl) Resets.unreset(.usbctrl) DPRAMをゼロで初期化 // 2. DPRAM holds undefined values right after power-on -> zero it out. DPRAM.clear() // 3. Controller core, wiring to the physical pins, and self-reported power. mainControl.store(controllerEnable) muxing.store(muxToPhySoftcon) power.store(vbusDetectOverride) // The Pico's detect pin isn't wired -> declare "power is present". // 4. Raise a flag on every EP0 completion / address 0 (accept newcomers). sieControl.store(ep0IntPerBuffer) addressEndpoint.store(0) // 5. The one bit that announces us. From here the host's script begins. sieControl.set(pullupEnable) } ... }
  19. enum USB { ... デバッガーから実行など、コールドスタートとは static func initialize() { 限らないのでリセット状態に入れてからアンリセットする。

    // 1. Reset to a clean slate before bringing it up (clk_usb assumes // Clocks.initialize has already run). コントローラーはまだ動いていない。リセット状態を解除しただけ。 Resets.reset(.usbctrl) Resets.unreset(.usbctrl) DPRAMをゼロで初期化 // 2. DPRAM holds undefined values right after power-on -> zero it out. DPRAM.clear() USBコントローラーを動かす // 3. Controller core, wiring to the physical pins, and self-reported power. mainControl.store(controllerEnable) muxing.store(muxToPhySoftcon) power.store(vbusDetectOverride) // The Pico's detect pin isn't wired -> declare "power is present". // 4. Raise a flag on every EP0 completion / address 0 (accept newcomers). sieControl.store(ep0IntPerBuffer) addressEndpoint.store(0) // 5. The one bit that announces us. From here the host's script begins. sieControl.set(pullupEnable) } ... }
  20. enum USB { ... デバッガーから実行など、コールドスタートとは static func initialize() { 限らないのでリセット状態に入れてからアンリセットする。

    // 1. Reset to a clean slate before bringing it up (clk_usb assumes // Clocks.initialize has already run). コントローラーはまだ動いていない。リセット状態を解除しただけ。 Resets.reset(.usbctrl) Resets.unreset(.usbctrl) DPRAMをゼロで初期化 // 2. DPRAM holds undefined values right after power-on -> zero it out. DPRAM.clear() USBコントローラーを動かす // 3. Controller core, wiring to the physical pins, and self-reported power. mainControl.store(controllerEnable) muxing.store(muxToPhySoftcon) power.store(vbusDetectOverride) // The Pico's detect pin isn't wired -> declare "power is present". セットアップパケットの送受信に使うEP0の準備 // 4. Raise a flag on every EP0 completion / address 0 (accept newcomers). デバイス自身のアドレスをゼロに sieControl.store(ep0IntPerBuffer) addressEndpoint.store(0) // 5. The one bit that announces us. From here the host's script begins. sieControl.set(pullupEnable) } ... }
  21. enum USB { ... static func initialize() { // 1.

    Reset to a clean slate before bringing it up (clk_usb assumes // Clocks.initialize has already run). Resets.reset(.usbctrl) Resets.unreset(.usbctrl) // 2. DPRAM holds undefined values right after power-on -> zero it out. DPRAM.clear() // 3. Controller core, wiring to the physical pins, and self-reported power. mainControl.store(controllerEnable) muxing.store(muxToPhySoftcon) power.store(vbusDetectOverride) // The Pico's detect pin isn't wired -> declare "power is present". // 4. Raise a flag on every EP0 completion / address 0 (accept newcomers). sieControl.store(ep0IntPerBuffer) addressEndpoint.store(0) // 5. The one bit that announces us. From here the host's script begins. sieControl.set(pullupEnable) } ... }
  22. enum USB { ... static func initialize() { // 1.

    Reset to a clean slate before bringing it up (clk_usb assumes // Clocks.initialize has already run). Resets.reset(.usbctrl) Resets.unreset(.usbctrl) // 2. DPRAM holds undefined values right after power-on -> zero it out. DPRAM.clear() // 3. Controller core, wiring to the physical pins, and self-reported power. mainControl.store(controllerEnable) muxing.store(muxToPhySoftcon) power.store(vbusDetectOverride) // The Pico's detect pin isn't wired -> declare "power is present". // 4. Raise a flag on every EP0 completion / address 0 (accept newcomers). sieControl.store(ep0IntPerBuffer) addressEndpoint.store(0) // 5. The one bit that announces us. From here the host's script begins. sieControl.set(pullupEnable) } ... }
  23. Bus ResetとSetup while true { let status = USB.sieStatus.load() //

    Bus reset: "return to the initial state" if status & USB.busReset != 0 { USB.handleBusReset() ... } // SETUP: an 8-byte question arrived -> answer it if status & USB.setupReceived != 0 { USB.sieStatus.store(USB.setupReceived) // W1C (acknowledge receipt) let setup = Control.Setup.read() } } Enumeration.handle(setup) ...
  24. Bus ResetとSetup while true { let status = USB.sieStatus.load() //

    Bus reset: "return to the initial state" if status & (1 << 19) != 0 { USB.handleBusReset() ... } // SETUP: an 8-byte question arrived -> answer it if status & (1 << 17) != 0 { USB.sieStatus.store(1 << 17) // W1C (acknowledge receipt) let setup = Control.Setup.read() } } Enumeration.handle(setup) ...
  25. Bus ResetとSetup while true { let status = USB.sieStatus.load() //

    Bus reset: "return to the initial state" if status & (1 << 19) != 0 { USB.handleBusReset() ... } // SETUP: an 8-byte question arrived -> answer it if status & (1 << 17) != 0 { USB.sieStatus.store(1 << 17) // W1C (acknowledge receipt) let setup = Control.Setup.read() } } Enumeration.handle(setup) ...
  26. 〜略〜 SIE_STATUS: 0x00000015 SIE_STATUS: 0x00000015 SIE_STATUS: 0x00000015 SIE_STATUS: 0x00080001 SIE_STATUS:

    0x00000001 SIE_STATUS: 0x00000001 SIE_STATUS: 0x00000001 SIE_STATUS: 0x00010005 SIE_STATUS: 0x00010005 SIE_STATUS: 0x00010005 〜略〜
  27. 31 28 24 20 16 12 8 4 0 〜略〜

    SIE_STATUS: 0000 0000 0000 0000 0000 0000 0001 0101 SIE_STATUS: 0000 0000 0000 0000 0000 0000 0001 0101 SIE_STATUS: 0000 0000 0000 0000 0000 0000 0001 0101 SIE_STATUS: 0000 0000 0000 1000 0000 0000 0000 0001 SIE_STATUS: 0000 0000 1000 0000 0000 0000 0000 0001 SIE_STATUS: 0000 0000 1000 0000 0000 0000 0000 0001 SIE_STATUS: 0000 0000 1000 0000 0000 0000 0000 0001 SIE_STATUS: 0000 0000 0000 0001 0000 0000 0000 0101 SIE_STATUS: 0000 0000 0000 0001 0000 0000 0000 0101 SIE_STATUS: 0000 0000 0000 0001 0000 0000 0000 0101 〜略〜
  28. 31 28 24 20 16 12 8 4 0 〜略〜

    SIE_STATUS: 0000 0000 0000 0000 0000 0000 0001 0101 SIE_STATUS: 0000 0000 0000 0000 0000 0000 0001 0101 SIE_STATUS: 0000 0000 0000 0000 0000 0000 0001 0101 SIE_STATUS: 0000 0000 0000 1000 0000 0000 0000 0001 SIE_STATUS: 0000 0000 1000 0000 0000 0000 0000 0001 SIE_STATUS: 0000 0000 1000 0000 0000 0000 0000 0001 SIE_STATUS: 0000 0000 1000 0000 0000 0000 0000 0001 SIE_STATUS: 0000 0000 0000 0001 0000 0000 0000 0101 SIE_STATUS: 0000 0000 0000 0001 0000 0000 0000 0101 SIE_STATUS: 0000 0000 0000 0001 0000 0000 0000 0101 〜略〜
  29. 31 28 24 20 16 12 8 4 0 〜略〜

    SIE_STATUS: 0000 0000 0000 0000 0000 0000 0001 0101 SIE_STATUS: 0000 0000 0000 0000 0000 0000 0001 0101 SIE_STATUS: 0000 0000 0000 0000 0000 0000 0001 0101 SIE_STATUS: 0000 0000 0000 1000 0000 0000 0000 0001 SIE_STATUS: 0000 0000 1000 0000 0000 0000 0000 0001 SIE_STATUS: 0000 0000 1000 0000 0000 0000 0000 0001 SIE_STATUS: 0000 0000 1000 0000 0000 0000 0000 0001 SIE_STATUS: 0000 0000 0000 0001 0000 0000 0000 0101 SIE_STATUS: 0000 0000 0000 0001 0000 0000 0000 0101 SIE_STATUS: 0000 0000 0000 0001 0000 0000 0000 0101 〜略〜
  30. Bus Reset リセットをSerial Interface Engineが検知 while true { let status

    = USB.sieStatus.load() // Bus reset: "return to the initial state" if status & (1 << 19) != 0 { USB.handleBusReset() InputReport.reset() LedCommand.reset() ... } ... }
  31. Bus ResetとSetup 内部状態を初期値に戻す while true { let status = USB.sieStatus.load()

    // Bus reset: "return to the initial state" if status & (1 << 19) != 0 { USB.handleBusReset() InputReport.reset() LedCommand.reset() ... } ... }
  32. enum InputReport { nonisolated(unsafe) private static var dataPid = false

    nonisolated(unsafe) private static var mailboxEmpty = false static func reset() { dataPid = false mailboxEmpty = true } } 内部状態を保持する変数を初期値に戻すだけ。 ハードウェアの操作はなし。 ... enum LedCommand { nonisolated(unsafe) private static var dataPid = false nonisolated(unsafe) static var playerLampOn = false static func reset() { dataPid = false playerLampOn = false } } ...
  33. Bus Reset ビットフラグを下ろし、アドレスをゼロに設定する static func handleBusReset() { sieStatus.store(1 << 19)

    // W1C: only the flag written with a 1 clears addressEndpoint.store(0) // back to accepting at address 0 }
  34. Bus Reset ビットフラグを下ろし、アドレスをゼロに設定する static func handleBusReset() { sieStatus.store(1 << 19)

    // W1C: only the flag written with a 1 clears addressEndpoint.store(0) // back to accepting at address 0 }
  35. Bus Reset ビットフラグを下ろし、アドレスをゼロに設定する 19ビットをゼロに戻す static func handleBusReset() { sieStatus.store(1 <<

    19) // W1C: only the flag written with a 1 clears addressEndpoint.store(0) // back to accepting at address 0 }
  36. W1C (Write 1 to Clear) while true { let status

    = USB.sieStatus.load() // Bus reset: "return to the initial state" if status & (1 << 19) != 0 { sieStatus.store(1 << 19) // W1C: only the flag written with a 1 clears addressEndpoint.store(0) // back to accepting at address 0 ... } // SETUP: an 8-byte question arrived -> answer it if status & (1 << 17) != 0 { USB.sieStatus.store(1 << 17) // W1C (acknowledge receipt) let setup = Control.Setup.read() } } Enumeration.handle(setup) ...
  37. W1C (Write 1 to Clear) 1を書いたビットだけをゼロにする while true { let

    status = USB.sieStatus.load() // Bus reset: "return to the initial state" W1C: 書いたビットをクリアする if status & (1 << 19) != 0 { sieStatus.store(1 << 19) // W1C: only the flag written with a 1 clears addressEndpoint.store(0) // back to accepting at address 0 ... } // SETUP: an 8-byte question arrived -> answer it if status & (1 << 17) != 0 { USB.sieStatus.store(1 << 17) // W1C (acknowledge receipt) let setup = Control.Setup.read() } } Enumeration.handle(setup) ...
  38. W1C (Write 1 to Clear) 1を書いたビットだけをゼロにする 0000 0000 0000 1000

    0000 0000 0000 0001 0000 0000 0000 1000 0000 0000 0000 0000
  39. W1C (Write 1 to Clear) 1bitだけを書くということはできない 0000 0000 0000 1000

    0000 0000 0000 0001 0000 0000 0000 1000 0000 0000 0000 0000
  40. W1C (Write 1 to Clear) 更新が衝突すると上書きしてしまう 0000 0000 0000 1000

    0000 0000 0000 0001 0000 0000 0000 0000 0000 0000 0000 0001
  41. W1C (Write 1 to Clear) フラグを下ろすのはソフトウェア while true { let

    status = USB.sieStatus.load() // Bus reset: "return to the initial state" if status & (1 << 19) != 0 { sieStatus.store(1 << 19) // W1C: only the flag written with a 1 clears addressEndpoint.store(0) // back to accepting at address 0 ... } // SETUP: an 8-byte question arrived -> answer it if status & (1 << 17) != 0 { USB.sieStatus.store(1 << 17) // W1C (acknowledge receipt) let setup = Control.Setup.read() } } Enumeration.handle(setup) ...
  42. 〜略〜 SIE_STATUS: 0x00000015 SIE_STATUS: 0x00000015 SIE_STATUS: 0x00000015 SIE_STATUS: 0x00080001 BUS̲RESET

    SIE_STATUS: 0x00000001 SIE_STATUS: 0x00000001 SIE_STATUS: 0x00000001 SIE_STATUS: 0x00010005 SIE_STATUS: 0x00010005 SIE_STATUS: 0x00010005 〜略〜 CONNECTED 0001 0000 0000 0000 0101
  43. 〜略〜 SIE_STATUS: 0x00010005 SIE_STATUS: 0x00010005 SIE_STATUS: 0x00010005 SIE_STATUS: 0x00010005 SIE_STATUS:

    0x00010005 SIE_STATUS: 0x00010005 SIE_STATUS: 0x00010005 SIE_STATUS: 0x00010005 SIE_STATUS: 0x00010005 SETUP SIE_STATUS: 0x00030005 〜略〜 0011 0000 0000 0000 0101
  44. Setup while true { let status = USB.sieStatus.load() // Bus

    reset: "return to the initial state" if status & (1 << 19) != 0 { sieStatus.store(1 << 19) // W1C: only the flag written with a 1 clears addressEndpoint.store(0) // back to accepting at address 0 ... } // SETUP: an 8-byte question arrived -> answer it if status & (1 << 17) != 0 { USB.sieStatus.store(1 << 17) // W1C (acknowledge receipt) let setup = Control.Setup.read() } } Enumeration.handle(setup) ...
  45. SETUPリクエストの処理 0 1 2 3 4 5 6 7 方向・種類・宛先

    リクエストの種類 引数1 引数2 データの長さ bmRequestType bRequest wValue wIndex wLength 1 byte 1 byte 2 byte 2 byte 2 byte struct Setup { let bmRequestType: UInt8 let bRequest: UInt8 let wValue: UInt16 let wIndex: UInt16 let wLength: UInt16 }
  46. SETUPリクエストの処理 bmRequestType D7 D6-D5 Data Transfer Direction (データ転送方向) 0: OUT転送(ホスト

    → デバイス) Type (リクエストのタイプ) 00: Device(デバイス全体宛て) 1: IN転送(デバイス → ホスト) 01: Class(HID、オーディオ、CDCなどクラス固有) 10: Vendor(メーカーが独自に定義するコマンド) 11: Reserved(将来の拡張のための予約領域) D4-D0 Recipient (宛先・受信対象) 00000: Device(デバイス全体宛て) 00001: Interface(特定のインターフェース宛て) 00010: Endpoint(特定のエンドポイント宛て) 00011: Other(その他)
  47. SETUPリクエストの処理 ホストからデバイスへの関数呼び出し(RPC) bmRequestType - データの転送方向(ホスト→デバイス/デバイス→ホスト) - リクエストのタイプ(標準、クラス、ベンダー) - 宛先(デバイス、インターフェース、エンドポイント) bRequest

    関数名 具体的に何を実行するかを指定するコード 例: 0x06=GET_DESCRIPTOR, 0x05=SET_ADDRESSなど wValue bRequest に応じた引数 第1引数 ディスクリプタを要求する場合は「ディスクリプタのタイプとインデックス」 wIndex 主にインターフェース番号やエンドポイント番号の指定 wLength 続く「データステージ」で転送されるデータのバイト数を指定。データステー 入力データ・戻り値 ジがない場合は 0。 第2引数
  48. SET̲ADDRESS デバイスに個別のアドレスを割り当てる bmRequestType bRequest wValue 00 05 05 00 00

    00 00 00 5(新しいアドレス) SET̲ADDRESS ホスト → デバイス
  49. GET̲DESCRIPTOR(Device) Device Descriptor:基本情報を返す wLength 80 06 00 01 00 00

    12 00 18 byte Device Descriptor GET̲DESCRIPTOR デバイス → ホスト
  50. GET̲DESCRIPTOR(Configuration) Con guration Descriptor:基本情報を返す 80 06 00 02 00 00

    31 00 49バイト Con guration Descriptor GET̲DESCRIPTOR fi fi デバイス → ホスト
  51. SETUP: ゲームコントローラー ホストからのSETUP要求 応答と処理 ZLPで了承し、送信完了後に新しいアドレスを反映 GET̲DESCRIPTORDevice 最大wLengthまで返す 18B/VID・PID:045E:028E GET̲DESCRIPTORCon guration

    InterfaceやEndpointを説明するDescriptor群を返す 49B/XUSB Interface/EP1 IN+EP2 OUT GET̲DESCRIPTORReport 使用しないためSTALL SET̲CONFIGURATION(1) 宣言したEndpointを有効にし、ZLPで了承 EP1 INとEP2 OUTを有効化 その他・クラス固有要求 このサンプルではSTALL fi SET̲ADDRESS
  52. SETUP: キーボード ホストからのSETUP要求 応答と処理 SET̲ADDRESS ZLPで了承し、送信完了後に新しいアドレスを反映 GET̲DESCRIPTORDevice 最大wLengthまで返す 18B/VID・PID:CAFE:0002 InterfaceやEndpointを説明するDescriptor群を返す

    34B/HID Keyboard/HID Descriptor+EP1 IN 63BのHID Report Descriptorを返す GET̲DESCRIPTORCon guration GET̲DESCRIPTORReport SET̲REPORT 宣言したEndpointを有効にし、ZLPで了承 EP1 INを有効化 Caps LockなどのLED状態を受信し、ZLPで了承 SET̲IDLESET̲PROTOCOL このサンプルでは内容を利用せず、ZLPで了承 その他の要求 STALL fi SET̲CONFIGURATION(1)
  53. 入力の読み取り方の違い 入力機器 ハードウェアの構成 入力の読み取り方 格闘ゲーム用コントローラー 1個のボタンと1本のGPIOが1対1 各GPIOがHighかLowかを読む。内部プルアッ で対応 プを使うため、Lowなら押下 ゲームパッド

    ボタンに加えて、アナログス ボタンはGPIOから読む。スティックはADCで ティックの各軸をADCピンへ接続 電圧を数値に変換し、X軸・Y軸の値として使う 一般的なキーボード 多数のキーを、行と列に並べた キーマトリックスとして接続 行を1本ずつ選択して列を読み、反応した行と列 の交点から押されたキーを特定する
  54. USB 3.0ではなく2.0なのはなぜか? USB 3.0を実装するなら2.0も実装しなければならない ‒ USB 3.x対応ホストはUSB 2.0側も持つ USB 2.0機器とSuperSpeed機器は、同じポート上の別のバスで共存する

    ‒ ユーザー接続型のUSB 3.x機器にもUSB 2.0対応が求められる ただし、機器内部に固定されたSuperSpeed専用デバイスなどには例外がある ‒ 入力機器にSuperSpeedの帯域は必要ない 20バイトのレポートを1msごとに送っても、データ本体は約20kB/s Full-Speedの12Mbpsで十分な余裕がある ‒ USB 2.0は小さく、安価に実装できる マウス、キーボード、ゲームコントローラーなどの低帯域機器に適している
  55. 参考資料 ‒ Sample Code https://github.com/kishikawakatsumi/iosdc-2026-usb-examples ‒ Universal Serial Bus Speci

    cation https://www.usb.org/document-library/usb-20-speci cation ‒ RP2040 Datasheet https://pip-assets.raspberrypi.com/categories/814-rp2040/documents/RP-008371-DS-1-rp2040-datasheet.pdf ‒ Xbox Universal Serial Bus Protocol (XUSB) Interface Extension https://learn.microsoft.com/en-us/openspecs/windows̲protocols/ms-xusbi/c79474e7-3968-43d1-8d2f-175d47bef43e fi fi ‒ USB Complete http://janaxelson.com/usbc.htm