6502tinymachines

純正パッドを USB か Bluetooth のキーボードに

印刷用: 図面一式「Original pad to a USB or Bluetooth keyboard, pad-ble v1」TM-NESB-003 版 M (PDF、英語)。

ブリッジ自身のパッド読み出しの後ろに、シフトレジスタの代わりにホストとの接続口を置いたもの。Pi も UNO もコンソールも無い。パッド一つ、ESP32 の基板一枚、抵抗一本。電話、タブレット、ノートには素のキーボードとして見える。それが、四十年前のコントローラでブラウザのエミュレータを、どちらの側にもアプリ無しで動かせる理由だ。

基板は Waveshare ESP32-P4-Module-DEV-KIT で、接続は USB だ。 どちらもこの文書を書いている間に変わった。その理由は下の「方向は二度変わった」にある。初期のシートが描いている ESP32-C6 は、一度も書き込みを受け付けなかった。

2026-09-21 記、2026-09-27 更新。

USB で組み上がり、動いている。2026-09-27。 信号どおりに P4 へ配線した純正パッドが、3.3 V で八つのボタンをすべて読み(「まず測る」の項目 4、2026-09-07 から開いていたものが閉じた)、Linux のホストがそれをキーのイベントとして受け取る: A は KEY_X、B は KEY_Z、Select は KEY_RIGHTSHIFT、Start は KEY_ENTER、十字キーは四つの矢印、同時押しも含めて。ホストはベンチの Pi で、電話はまだ試していない。 この基板での Bluetooth は組まれていないままだ。各層が何で、それぞれどう証明したかは pad-usb-protocol.md にある。

2026-09-27 までに測られていたのは回路ではなく基板だった。どの部品か、ブレッドボードのどこに座っているか、そのヘッダのピンの並び。その晩からは回路も測ってある: パッドは 3.3 V で応え、ホストはすべてのボタンをそのキーとして受け取る (下の「ベンチで試験した」と pad-usb-protocol.md)。どの測定も、どこで何によって読んだかを記してある。この二つは意図して分けてある。部品の測定と回路の計画を混ぜたページは、一方をもう一方に見せかけてしまうからだ。

なぜアダプタのシートではなくこれなのか

pad-adapter.svg が構想の全体だ。パッド二つ、モードスイッチ、セル、充電器、LDO、そして USB HID なら S3、BLE なら C6。その大半は注文中。これは部品がすべて引き出しにある部分集合で、bench-v1b が bench-v1 への注記ではなく独立したシートであるのと同じ理由で、独立したシートになっている。誰かが組むための図面は、その人が持っている部品を示していなければならない。さもないと、夜は持っていない部品を読み飛ばすことに費やされる。

部品を決めたもの。思い出したのではなく、読んだ

(C6 の設計。記録として残す。下の「方向は二度変わった」から先で置き換わった。組んだ基板は P4 だ。)

ブレッドボードの上の基板は ESP32-C6-DevKitC-1 v1.2 だった。これは 2026-09-21 に、部品表からではなくベンチの目でズーム 500 で確定した。シルクに RGB@IO8 とあり、C6 の devkit は RGB LED を GPIO8 に置き、S3 の devkit は GPIO48 に置く。どちらの基板も UART と USB と書かれた USB-C ポートを二つ持つので、ポートでは区別がつかず、LED でつく。

これで Bluetooth の問いは決まる。ファームウェアではなく、シリコンで。 C6 は USB キーボードにはなれない。USB と書かれたポートは USB-Serial-JTAG ブリッジで、その soc_caps.h は SOC_USB_SERIAL_JTAG_SUPPORTED を定義し SOC_USB_OTG_SUPPORTED を定義しない。Arduino コアは USBHIDKeyboard.h を後者で門にかけているので、このターゲットでは何もない物にコンパイルされる。インストール済みの esp32 コア 3.3.11 で確かめた。USB HID には S3、S2 か P4 が要り、S3 なら一枚で両方のモードになる。それが S3 を注文する唯一のまともな理由だ。

だから BLE キーボード。好みではなく、この基板にできる唯一の HID だ。

部品。すべて手元にある

記号部品注
U1ESP32-C6-DevKitC-1 v1.2すでにブレッドボード上
J1純正パッド一つケーブルのプラグ側
R110kパッドの D0 から 3V3 へ
C1100nFdevkit の 3V3 と GND の間

LDO も充電器もセルもスイッチも無い。devkit は与えられた USB 電源で動き、自身のレギュレータがパッドの動く 3V3 を作る。

配線。組むために描いた

一つの回路の三枚の図で、すべて同じネットリストから導かれているので、一枚にだけある配線は存在しない。

pad-ble v1: 回路図。一つのパッドを C6 へ。基板にできることとできないこと

pad-ble v1 を直角で: すべての配線を、置かれたままのパッケージの上に

pad-ble v1 をブレッドボードで: 何がどの穴に入るか

どの穴かを言うのはブレッドボードのシートで、これには他の二枚に要らない事実が要った。devkit のヘッダに沿ったピンの物理的な並びだ。それは測ってあり (下記)、tools/breadboard.py に C6_HEADER として、USB 側の端から読んだ形で置いてある。

配線リスト

導体五本、パッド一つ。

パッドのプラグのピン信号リード線の色行き先
1GND黄GND
2CLK青GPIO3。C6 が駆動
3OUT0黒GPIO2。C6 が駆動
4D0緑GPIO6。10k で 3V3 へ
5+5V赤3V3。5 V ではない

色はブリッジ自身のもので、2026-09-22 にベンチで同じ五色だと確認した。 表は tools/wiring-diagram.py の LEAD = {1: yellow, 2: blue, 3: black, 4: green, 5: red} で、ブリッジのケーブルでテスタを当て、wiring-pad-ble.svg のパッドコネクタに印字してある。五色を別の順に並べた一覧は集合の一覧であってピン順の一覧ではなく、直すものは何も無かった。

それでも電源を入れる前にケーブルの導通を取ること。表が疑わしいからではなく、これはテスタを当てたのとは別のケーブルであり、ここでの規則は「色は証拠ではない」だからだ。抜いた状態で。2026-09-09 には、まだコンソールに挿さったケーブルに通した導通音がコンソールのプルアップを通って回り、何も共有していないピン同士が再現性をもって鳴った。

GPIO4、GPIO5、GPIO15 ではない。 それはこの仕事における S3 の番号で、C6 ではストラッピングピンだ。上の三本は firmware/bridge/bridge.ino がすでにパッドを読み出しているピンで、だから firmware/pad-ble は poll_pad を無編集で使い回せる。そして tools/check-sheets.py がシートとファームウェアを互いに突き合わせ、二度とずれないようにしている。

いまどこに座っているか。ベンチの目で測った

2026-09-22 に基板の目からズーム 500 と 1000 で、ブレッドボード自身に印字された列番号に対して読んだ:

devkit の PCB中央のブレッドボード。およそ列 37 から 56
そのヘッダ片側十六ピン、つまり十六列。USB コネクタは一端で PCB の残りからはみ出す
その行ピンは B と I。溝をまたぎ、A と J を空けている
ケーブル切ったパッドのケーブルが剥かれ、基板の低い列の端に置かれている。導体五本

一列以内の確度で信じている。ブリッジ自身の配置と全く同じだ (「印字された目印から数え、一列以内の確度で信じる」)。シートはヘッダを列 39 から 54 に描いており、それはこの読みをピン数に丸めたもの。回路の何もこの選択には依らず、相対位置にだけ依るので、都合のよい所に座らせて列はシートから読めばよい。

devkit の列で届く行は A と J だけだ。 C から H を覆うだけの幅があるので、その穴は下敷きになっている。下側ヘッダのピンへの配線はすべて行 J に、上側ヘッダのピンへの配線はすべて行 A に入る。この基板の上で devkit がチップと違う唯一の点で、シートのアドレスはすでにそれを織り込んでいる。

目はピンのラベルを読めず、それはいま記録された限界だ。 ラベルは 1 mm ほどで回転しており、ズーム 1000 でもこの作動距離で BRIO が分解できる大きさを下回る。devkit の盛り上がった面に合焦し直す (基板面の 18 に対してフォーカス 26) とかえってぼやけるので、フォーカスではなく分解能の問題だ。同じ目が基板 (BOARD) は完璧に読めており、その何倍も大きい RGB@IO8 のシルクから部品を特定できたのはそのためだ。

ヘッダ。測った

2026-09-22 に、ベンチの目には読めないので手で撮った基板の写真から読んだ。各行は USB 側の端から読む。 二つの USB-C コネクタがある端だ。G は基板自身のグラウンドの綴りで、複数回現れる。

行ピン。USB 側の端から
pad-ble の側NC, G, 5V, 3, 2, 11, 10, 8, 1, 0, 7, 6, 5, 4, RST, 3V3
反対側NC, G, 12, 13, G, 9, 18, 19, 20, 21, 22, 23, 15, RX, TX, G

片側十六本。この表は tools/breadboard.py の C6_HEADER に一度だけ置かれ、シートはそこから描かれる。

この回路に要るピンはすべて一つの行にある。 GPIO2、GPIO3、GPIO6、3V3 とグラウンドの一つが上の最初の行に揃っているので、基板の反対側へ渡る配線は無い。始める前に知っておく価値があり、当たり前のことではなかった。

その行が仕掛ける罠。 GPIO4 と GPIO5 は GPIO6 のすぐ隣に座り、GPIO8 は反対へ四つ先だ。三つとも C6 のストラッピングピンだ。その行で穴を一つ数え違えても得られるのは死んだ入力ではなく、起動しないかもしれない基板だ。端からではなく、印字されたラベルから数えること。

部品が変わった: 実際に動く基板は P4

2026-09-24。 ここの図が前提にしている C6 は、一度も書き込みを受け付けなかった。同じベンチの Waveshare ESP32-P4-Module-DEV-KIT は最初の試みで接続し、ファームウェアを受け取り、独立した無線機が聞き取った BLE のアドバタイズを空中に出した。だから組み立てはその部品へ移り、この節はそのための配線だ。

選んだ根拠の測定は docs/esp32-part-choice.md にある。要点だけ言えば: P4 のダイには無線がまったく無く、モジュールが ESP32-C6 を無線として載せていて、Arduino コアの BLE クラスはちょうどその組み合わせを許すように条件が付いている。firmware/pad-ble はパッドの割り当てを一切変えずに esp32p4 向けにビルドできる。

五本の線

2026-09-24 に、この基板の Waveshare 自身の回路図 のコネクタ P6 を、ページを 900 dpi で描き出して読んだ。写真から読んだのではなく、それは意図してのことだ: この文書は、ピン配置を定義する図面からではなく回転した写真から導いたせいで、C6 の二つのヘッダ行を入れ替えたまま四つの版を出してしまった。

線ネット差す先それは
赤3V3P6 ピン 183V3
黄GNDP6 ピン 26GND
黒PAD_LATCHP6 ピン 22GPIO2
青PAD_CLKP6 ピン 20GPIO3
緑PAD1_D0P6 ピン 16GPIO6

どれも偶数ピンなので、五本とも一つの行に着き、ヘッダを横切る線は無い。しかも五本は連続していて、そう言うのが役に立つ:

6    3V3    3    2    0    GND

p16 p18 p20 p22 p24 p26

偶数の行で二つ目の 3V3 を見つければ、回路はその穴と周りの四つで、0 だけを飛ばす。 データ、電源、クロック、ラッチ、飛ばし、グラウンド。

五本が偶数なのは選んだことで、だからヘッダを横切る線が無い。それが一続きでもあることには、2026-09-25 に基板を配線するまで気づかなかった。そしてそれは効いてくる: ピン番号から配線したら、三本の信号線のうち二本が間違った穴に入った。黄は SCL に入った。基板が自分でプルアップしているピンなので、クロックはハードウェアと引っ張り合い、症状は沈黙になっていたはずだ。灰の一本は 0、ラッチの一つ先の穴に入った。どちらも見てではなく、測って見つけた。リード線の色は tools/bringup.py の LEADS、つまり 2026-09-09 に実際にテスタを当てたケーブルから引いたもので、誰かが覚えている色からではない: 版 E は五本のうち三本を間違えていた。測定から読まずに、色の集合から打ち込んだからだ。PAD1_D0 から 3V3 への 10k のプルアップは変わらず、今も基板の脇のブレッドボードに置く。

ピン 1 は Raspberry Pi が置く場所にない

ヘッダの形は Pi と同じだが、番号の振り方が逆だ:

5V    P6 ピン 1 と 3       Pi では 5V は 2 と 4
3V3   P6 ピン 2 と 18      Pi では 3V3 は 1 と 17
GND   5, 10, 13, 19, 26, 29, 33, 40

どのグラウンドも、Pi に慣れた手が伸びる場所から一本ずれている。Pi の HAT はこの基板に収まるが、動かない。 習慣からではなく、回路図から数えること。

ヘッダまで来ているのに使えないピンが十本

GPIO54  ピン 31   載っている C6 の RESET。取れば無線が死ぬ
GPIO45  ピン 39   MicroSD カードの電源スイッチ
GPIO53  ピン 36   スピーカーアンプの CTRL
GPIO36  ピン 23   BOOT_MODE2。ストラッピングピンで、プルアップされている
GPIO37  ピン 7    コンソール UART の TXD。Serial はここに住む
GPIO38  ピン 9    コンソール UART の RXD (二本とも 2026-09-25 に追加、rev G)

GPIO24 と GPIO25 (ピン 28 と 27) はフルスピードの USB の対(USB1P1、USB 1.1。2026-09-27 訂正: ここは高速と書いていた)で、USB と書かれた Type-C のソケットはそこに配線されている。GPIO7 と GPIO8 (ピン 4 と 6) はオーディオコーデックの I2C で、基板にもう 2.2k のプルアップが付いている。空いているものまで含めた全表は tools/p4_header.py と TM-NESB-003 の 5 枚目にある。

予備の二本は動かすしかなかった

C6 では MODE_SW と LED は GPIO10 と GPIO11 で、どちらも配線していないので何の負担も無かった。この基板ではその番号は ES8311 オーディオコーデックの I2S で、ヘッダにはまったく出ていない。ファームウェアはそれらを GPIO21 と GPIO20、ヘッダのピン 12 と 14 へ移す。空いていて、同じ偶数の行にある。どちらも配線しないが、両方とも宣言する。言及されないピンは沈黙だからだ。

この基板について証明されたこと、されていないこと

2026-09-24 に試験で証明したこと: 書き込めること、動くこと、その BLE アドレスが独立した受信機に届くこと、そしてパッドの割り当てに手を触れずにファームウェアがこの基板向けにコンパイルできること。2026-09-27: パッドも証明された、USB で、冒頭の状況に書いたとおりに。この基板での Bluetooth はまだだ。

方向は二度変わった。その理由

2026-09-25。 これは Bluetooth のアダプタとして始まり、USB のアダプタとして出る。どちらの移動も好みではなく測定に強いられたもので、その理屈は使い回せるので、どちらも残しておく価値がある。

一度目: C6 から P4 へ

ESP32-C6 は一度も書き込みを受け付けなかった。二晩続けて no serial data received が出て、ポート、ケーブル、配線、ModemManager はそれぞれ試験で外した。同じベンチの Waveshare ESP32-P4-Module-DEV-KIT は最初の試みで接続し、ファームウェアを受け取り、独立した無線機が聞き取った BLE のアドバタイズを空中に出した。それが docs/esp32-part-choice.md だ。

二度目: BLE から USB へ

BLE のファームウェアは P4 の上で落ちる。それもこのプロジェクトのコードの中ではない。

E rpc_core: Response not received for [0x15e](Req_GetCoprocessorFwVersion)
Guru Meditation Error: Core 1 panic'ed (Load access fault)

P4 には無線が無い。モジュールは ESP32-C6 を無線として載せ、SDIO 越しにそれに話しかける。ホスト側の esp-hosted 2.12.11 がその C6 にファームウェアの版を尋ねる。答えは返らず、失敗の経路は誰も埋めていないポインタを読む。いつも BLEDevice::init の 2881 ms 後で、試したどの変種でも同じだった。

五つの説明を試して、消した:

試したこと結果
ビルドの設定、16M フラッシュと PSRAM を有効変化なし
BLESecurity のブロックを外す変化なし
BLEDevice::init の前に delay(3000)落ちる時刻がちょうど 3000 ms ずれた
BLEDevice::init の後に delay(4000)そこまで届かない。落ちるのは init の中
GPIO の設定をすべて BLE の後へ変化なし

無線のプローブは同じタイムアウトに同じ遅れで当たり、それでも生き延びる。 それがこのバグの種類を名指しする: 失敗の経路は初期化されていないメモリを読み、死ぬかどうかはそのメモリに何があったか次第で、それはバイナリごとに違う。プローブは運がいいだけで、正しいわけではない。

だから原因は答えの返らない RPC で、まっとうな直し方は C6 側のスレーブファームウェアを基板の C6 UART ヘッダから入れ替えることだ。それはまだやっていない。

回避策ではなく USB にした理由

USB は、落ちている部品を避けて通るのではなく、取り除く: 無線も、コプロセッサも、SDIO の線も、ペアリングも無い。P4 は SOC_USB_OTG_SUPPORTED を宣言し、OTG の周辺回路を二つと UTMI の PHY を持つ。C6 にはどれもまったく無かったので、これはこの部品にしかできないことだ。それに、この作業の初日に頼まれていたのもこれだった。

firmware/pad-usb はフラッシュの 18% で素直に起動し、パッドを配線していない状態で B FF を出す。それで正しい: D0 はプルアップ無しで浮いているので八つのビットがすべて押された状態に読め、六つの枠を超えた分は黙ってキーを落とさずに DROPPED と報告する。プルアップを付けると B 00 と読み (2026-09-26)、2026-09-27 にはすべてのボタン、01 から 80 までと同時押しを読んだ。そのプルアップとして付いていた 10 オームの部品を 10k に替えてからだ。

その keymap.h は firmware/pad-ble/keymap.h へのシンボリックリンクなので、どちらのビルドも同じ八つのボタンを同じ八つのキーとして送る。tools/test-pad-keymap.sh はその一つのファイルを机の上でそれ自身のディスクリプタと照らし合わせ、tools/check-sheets.py はコピーを拒む。

変わらなかったもの

配線。どちらのビルドも GPIO2、GPIO3、GPIO6 を読む。firmware/bridge/bridge.ino がすでに使っているのと同じ割り当てなので、五本の線と、それが並ぶ一続きの穴は、どちらの移動にも影響されない。

例外が一つ、2026-09-30 に加わった: クラシックな ESP32。 DevKitC-1 が死んだので(どちらのソケットでも、どんなリセットでも ROM のバナーが出ない。下の「ベンチで試験した」を参照)、ESP32-WROOM-32 の devkit が BLE 版を引き受けた。このモジュールでは GPIO6 から GPIO11 はプログラムが走るフラッシュそのもので、GPIO3 は UART コンソールの受信線だ。だからこのビルドだけは GPIO25(ラッチ)、GPIO26(クロック)、GPIO27(データ)を読み、モードスイッチは GPIO4 のまま、devkit 自身の LED を GPIO2 で点ける。理由は pad-ble.ino のピンのブロックに書いてある。それらのピンがどこにあるかは 2026-09-30 に基板から読んだ。ピン配置のページからではなく、ブレッドボードに載った基板を持ち主が撮った写真からだ: 30 ピンの devkit で、USB のソケットが 1 行目の側にあり、ピンは 6 行目から 20 行目に入っている。各列を USB の側から読むと、D23 の側は 3V3(6 行目)、GND、D15、D2、D4、RX2、TX2、D5、D18、D19、D21、RX0、TX0、D22、D23(20 行目)。EN の側は VIN(6 行目)、GND、D13、D12、D14、D27、D26、D25、D33、D32、D35、D34、VN、VP、EN(20 行目)。したがってこの基板での五本の線は、パッドのプラグは前と同じで、こうなる:

パッド基板行
GNDGND、EN の側7
+5V3V3、D23 の側6
OUT0、ラッチD2513
CLKD2612
D0、10k で 3V3 へ引き上げD2711

三本の信号ピンは隣り合い、GND は同じ側でその四行下にある。D2 の devkit の青い LED が、パッドが押されている間の灯りだ。この基板には自分の図面が三枚ある。P4 のものと同じツールで作られ、同じヘッダのモジュールに照らして確かめられる: pad-ble-esp32.svg(回路図)、wiring-pad-ble-esp32.svg(すべての線を直角に、基板自身のラベルで)、breadboard-pad-ble-esp32.svg(ブレッドボード上の基板。その列番号は写真の行番号)、それにすべてのピンとその行を並べたヘッダのページ。rev M から TM-NESB-003 のシート 11 から 15 で、ヘッダが書いてある場所は tools/esp32_header.py ただ一つだ。

使い方

ホストは PWR USB TO UART ではなく、USB と書かれた Type-C のソケットに差す。UART のソケットはベンチのヘッドにつないだままにしておく。シリアルのログはそこから来て、書き込みもそこから行う。

2026-09-27 訂正: ジャンパは関係なく、最初のファームウェアはそのソケットでは動きようがなかった。 P4 にはハイスピードとフルスピードの USB コントローラがある。Arduino コアの USB クラスはキーボードをハイスピードの方に置き、それはこのキットでは、HOST/DEVICE のジャンパが動かすスイッチを通って USB-A の積み重ね J8 にしか届かず、しかもそのポートの 5 V は基板自身が駆動している。そこに差したホストは自分の電源を基板の電源にぶつけることになる。USB の Type-C のソケットは GPIO24/25 のフルスピードの対で、デバイスのポートとして組まれ(その 5 V はダイオードの後ろの入力)、既定ではチップ自身の USB-Serial-JTAG がそこで応える。最初のファームウェアで差すと、ホストに見えたのは 303a:1001、デバッグユニットで、キーボードは一度も見えなかった。firmware/pad-usb はいま TinyUSB をフルスピードのコントローラで自分で動かし、PHY 0(GPIO24/25)をそちらへ切り替える。P4 はその PHY を既定でデバッグユニットに渡しているからだ。理由、回路図の参照、測定は pad-usb-protocol.md の層 4 にある。

ベンチのパッドはレプリカだった

2026-09-26。 firmware/pad-usb を走らせると、この組み立ては B 00 を読み、A を押しても何も変わらなかった。パッドそのものを疑う前に、すべての接続を順に確かめた:

確かめたもの方法結果
ESP 側、穴ごとfirmware/header-probe二本を差し替えた後で正しい
中継、リード線からジャンパへオーナーの一覧を表と照合正しい
両レールテスタ3.3 V
パッドの電源リード中継の行でテスタ3.3 V
パッドのラッチリードテスタ、基板が high に保持3.3 V
D0 のプルアップ目視線ではなく本物の 10k
ラッチ high、クロック無しの D0firmware/pad-diag二分間押し続けて一度も動かなかった

そこでオーナーがコントローラに気づいた: 安いレプリカで、ブリッジに付いているパッドと同じ銘柄だ。ブリッジでは 5 V で動いている。 レプリカは本物の 4021 ではなく、それを真似た専用チップを載せていることが多く、そうしたチップは 5 V でしか動かないことがよくある。話はおそらくそれで全部だ。ただし測ってはいない: このレプリカを 5 V だけに載せてみた者はまだいない。

pad-diag の試験は、レプリカに対しては見かけより弱かった。本物の 4021 の振る舞い、つまりラッチを high に保つと D0 がボタン A に連続して従うことに頼っているからだ。模造チップはエッジでしか読み込まないかもしれないので、レプリカについて「反応無し」はどちらとも取れた。本物の 4021 なら公正な試験だ。

「まず測る」の項目 4 は純正パッドがこの組み立てに載るまで開いていて、2026-09-27 に閉じた: pad-diag で A を押すと、30 秒に 27 回、D0 が low に下がった。その晩の最初の試みは、覚えておく価値のある理由で失敗した。データのプルアップとして付けた抵抗が 10k ではなく 10 Ω だった。4021 は 10 Ω に逆らって線を引き下げられない(約 330 mA を吸い込まなければならない)ので、D0 は押すたびに少し下がるだけで閾値を越えず、それは応えないパッドとまったく同じに見える。チップを疑う前に、色帯を読むかテスターで測ること。

純正パッドと、その色がレプリカの色でない理由

オーナーは純正パッドを開けて写真を撮った。チップには MN4021B とあり、Panasonic の CMOS 4021 なので、pad-diag はこれには公正な試験だ。ケーブルは Nintendo 自身の色を使っている:

信号P6 ピンレプリカのリード線純正のリード線純正を当てる 4021 のピン
電源18赤白16, VDD
グラウンド26黄茶8, VSS
ラッチ22黒橙9, P/S
クロック20青赤10, CLOCK
データ16緑黄3, Q8

赤と黄は両方の列にあり、それぞれで別の信号を意味する。 赤はレプリカの電源で純正のクロック、黄はレプリカのグラウンドで純正のデータだ。純正をレプリカの色で配線するとそのデータ出力がグラウンドのレールに着き、チップは high を出すたびに自分の出力を短絡させる。

純正の列は Nintendo の文書にある配色で、このパッドではまだ測っていない。 パッドが開いていればチップに手が届くので、電源を近づける前に、各線を最後の列の 4021 のピンにテスタで当てること。ピン 1 は切り欠きの下の角で点が付いている。1 から 8 が片側に並び、9 から 16 が反対側を戻るので、16 は切り欠きの隣に来る。

tools/p4_header.py は両方の列を持ち、純正のチップのピンがそれぞれの役目の 4021 のピンであること、そして二つの色が本当にぶつかることを確かめている。だから上の警告が黙って古くなることは無い。

ケーブルの色は信号ではない

この組み立てで色を事実と取り違えたのは、これで三度目だ。最初はリード線の色を、テスタを当てたケーブルからではなく一覧から打ち込んだ。次にその表をブリッジのケーブルの測定と結び付け、このケーブルも表していると思い込んだ。そして今、二つのパッドが同じ色の名前を別の信号に使っていると分かった。信号で配線し、導通で確かめること。色はどの線を手に取るかの手がかりであって、それが何を運ぶかの手がかりでは決してない。

まず測る

何かに電源を入れる前に、この順で:

  1. プラグをどこにも挿さずにパッドのケーブルの導通を取る。 「まず測る」項目 1 は 2026-09-09 の出来事のためにある。まだコンソールに挿さったケーブルに通した導通音はコンソール自身のプルアップとポートバッファを通って回り、何も共有していないピンが鳴る。最初の一回はポートのピン二本を一本のリード線に載せ、再現し、それは測定器がしゃべっていたのだった。結果は上の色の表の脇に書くこと。
  2. 純正パッドが 3V3 でボタンに従う。 これは「まず測る」項目 4 で、2026-09-07 から開いていて、2026-09-27 に閉じた: 純正パッドの MN4021B は 3V3 でボタンに従う。firmware/pad-diag の下で 30 秒に 27 回の押下、それから firmware/pad-usb の下ですべてのボタン。4021 は 3 から 18 V 定格の CMOS 部品なのでデータシートは「はい」と言っていた。このパッドは四十年物で純正でないものも混じっているから、測ったのだ。3V3 で動かないパッドには、直し方がアダプタのシートに描いてある。74LVC245 とパッドへの 5 V で、245 は手元にある。レプリカは 5 V で測っていない。

このアダプタのどの版も項目 2 の上に立っていた。ほかの何かを信じる前にそれを測ったのはそのためだ。

組む順序

  1. パッドを一つだけ配線する。GND、OUT0 を GPIO2 へ、CLK を GPIO3 へ、D0 を GPIO6 へ、電源を 3V3 へ、そして GPIO6 から 3V3 へ 10k。
  2. firmware/pad-usb を書き込み、UART のソケットを 115200 で見張る (tools/pad-watch.py)。変わるたびに B <byte> を印字する。
  3. 最初の灯りは、そのバイトが親指について来ることで、ホストは要らない。 動けばハードウェアの半分は済みで、その先はすべてソフトウェアだ。動かなければ、どれだけ USB を積んでも助けにならず、答えは「まず測る」項目 2 (か抵抗の色帯) にある。
  4. それからようやく、USB と書かれた Type-C のソケットにホストを挿す。tinymachines NES Pad、普通のキーボードとして列挙される。(無線が応える基板では firmware/pad-ble が代わりに NES Pad として広告する。P4 の上では init で落ちる。下を見よ。)

ホストに見えるもの

ブートキーボードのレポート配置を持つキーボード、レポート ID 1(ブートプロトコルのキーボードではない。2026-09-27 訂正: ブートのレポートは ID を持たないので、BIOS やブートプロトコルしか話さない KVM には見えない。完全なオペレーティングシステムならどれにでも見える。pad-usb-protocol.md を参照)、ブラウザのエミュレータが既定にする配置。A は x、B は z、Select は右シフト、Start はエンター、十字キーは矢印。Select がキー枠ではなく修飾キーなのは、右シフトが修飾キーだからだ。修飾キーの状態を別に追うホストは、さもないとどのキーボードも作らない形でシフトが押されているのを見ることになる。

パッドは一つで、それは最初の一歩ではなく設計だ。 キーボードのレポートはキー枠を六つ運び、パッド二つは十を求めうるので、二つ目のパッドは読み出せても送れない。2026-09-23 までこの図面にはまさにそれをする一つが載っていた。配線され、プルアップされ、ピンを与えられ、捨てられていた。外れたのは、ケーブルを手にした人が、電話向けの単体キーボードになぜ二つも付いているのかと訊いたからで、それは正しい問いだった。このシート自身が掲げた目的が、それに対する反論だった。誰かが組むための図面は、その人が持っている部品を示していなければならない。

二人ならレポート ID を二つ持つゲームパッドモードで、それは pad-adapter.svg の仕事だ。ゲームパッドモードはどのみち、もう一つの理由で作っていない。iOS は汎用の HID ゲームパッドを拒み、MFi、Xbox、PlayStation、Switch Pro の配置しか受け付けない。キーボードモードが先になったのはそのためだ。

試験したもの、していないもの

意図して分けてある。二つの半分は比べられないからだ。

机の上で、パッドもホストも部屋に無い状態で試験した。 レポートディスクリプタとキー割り当ては firmware/pad-ble/keymap.h の素の C で、tools/test-pad-keymap.sh が同じヘッダをネイティブにコンパイルして動かす。76 のチェック。MUTATE=1 は現実的な不具合を二つ仕込み、失敗を十二起こす。ディスクリプタは自身の写しと比べるのではなく、ホストが解釈するのと同じ手順で項目ごとに解釈され、その解釈が言うレポートの大きさがコードが実際に書く大きさと比べられる。この二つを機械化する価値があるのは、悪いディスクリプタでもペアリングはでき、悪い割り当てでも文字は打てるからだ。どちらも自分からは名乗らない。

ベンチで試験した。2026-09-27、USB で: 配線、3.3 V でのパッド自身のタイミング、そしてすべてのボタンをそのキーとして受け取るホスト。ベンチで試験した。2026-09-28、無線で、二枚目の C6 で: これらの図面が囲んでいる DevKitC-1 とは別の C6 基板 (フラッシュ 8 MB、SD カードを探すメーカーのデモが入っていた。型番は基板から読み取ることになっている) が firmware/pad-ble を一回目で受け取った。ベンチヘッドから esptool で、その USB-Serial-JTAG 越しに書き込んだ。それは動き、Pi 自身の無線が NES Pad として広告しているのを聞いた。途中で二つのことが見つかり、どちらもここに前に書いてあったことの訂正だ:

  • スケッチには、P4 が決して届かなかった自身の不具合があった。 hid->manufacturer("tinymachines") は、引数なしの manufacturer() だけが作るキャラクタリスティックのポインタを通して書き、ライブラリはそのポインタを初期化しないままにする。BLEDevice::init を越えた最初の C6 では、それは setValue の最初のバイトでのロードアクセス例外で、起動のたびに起きた。引数一つの呼び出しはいまは manufacturer()->setValue(...) だ。P4 は init の中で、この行より前に落ちていた。だから隠れていた。
  • C6 のターゲットは、ビルドが CDCOnBoot=cdc と言わない限り Serial を UART のピンへ送る。 それが無いと USB のソケットには ROM の起動行しか出ず、ホストからの書き込みはタイムアウトし、全体が最初の print の前に固まったスケッチとまったく同じに読める。スケッチのビルド行はいまこのオプションを持っている。

両方そろって: # advertising as "NES Pad", 65 descriptor bytes、パッドのバイトはポーリングされて印字され、STATUS と KEYS は USB 越しに答える。まだ試験しておらず、動くと書いてはならないもの: この基板に配線したパッド、そしてそれとペアリングする電話。P4 の上ではビルドはまだ BLEDevice::init の 2881 ms 後に落ち、コンパイルは 60%。C6 では 55% でコンパイルが通る。

ベンチで試験した。2026-09-30、無線で、クラシックな ESP32 で。 図面が囲んでいる DevKitC-1 v1.2 は、書き込めないのではなく死んでいる: 2026-09-29 の夜、その UART ブリッジでは四つのボーレート、五通りのリセット手順、BOOT+RST の押しっぱなしのどれでも ROM のバナーが出ず、ネイティブのソケットには何も現れなかった。生きている C6 なら電源だけで ROM からそこに現れる。その代わりに、ベンチヘッドの USB につないだ普通の ESP32-WROOM-32 の devkit: CP2102 越しの esptool chip-id は ESP32-D0WD-V3(リビジョン v3.1)、フラッシュ 4 MB と読み、届いたときは AT ファームウェアが動いていた(at_customize パーティション、バージョン行は 2.4.0)。その ROM のバナーは、リセットを RTS で駆動したときにだけ出る。DTR のパルスだけでは何も出ず、一分ほどは二枚目の死んだ基板に見えた。firmware/pad-ble は試験用の Pi で esp32:esp32:esp32 向けに、既定のアプリパーティションの 83% でビルドでき、ベンチヘッドから同じブリッジ越しに 921600 で、0x0 への一つの結合イメージとして書き込まれ、起動した: キー割り当てが印字され、続いて # advertising as "NES Pad", 65 descriptor bytes と # pad ff link down、そしてベンチヘッド自身の無線が NES Pad を一覧に出した。この部品では BLEDevice::init は落ちない。USB 越しの P4 と違い、この基板の Serial は CP2102 そのものなので、B xx の行はベンチヘッドで 115200 で読める。

同じ夜、ベンチヘッドがこのビルドの最初のホストになった。対話なしの bluetoothctl pair は AuthenticationFailed で失敗し、bluetoothd は No agent available for request type 2 と言い、デバイスを信頼済みだがボンドなしの状態に残す。その状態では BlueZ が再接続を繰り返す: 基板は十五秒で # linked を 126 回印字し、その間ヘッドの HID の読み出しは暗号化が無いために失敗し、STATUS の答えの一つは 0xff が六十四バイトで返ってきた。同じような連発がもう一度、FORGET の答えの前に来て、# unlinked, advertising again の行が一秒を埋めたことも一度あった。三つとも、条件をそろえても再現しない: ポートを三度開いてもバイトもリセットも出ず、十秒までの休止のあとの STATUS は毎回きれいに返り、ヘッドからの接続一回と切断一回はそれぞれちょうど一行を印字した。コアはこの部品を電源管理なしでビルドするので、クロックの切り替えも説明にならない。三度見て、一度も再現せず、そのとおりに記録する。先にエージェントを登録すれば(agent NoInputNoOutput、default-agent、それから pair)、Just Works のペアリングはボンドし、ヘッドには uhid の上に NES Pad のキーボードができる(バス 0005、e502:0100、アピアランス 0x03c1)。それ以降は接続一回で # linked が一つ印字され、STATUS はリンクが落ちている状態で五回中五回、上がっている状態でもう一度、きれいに答える。パッドが配線されていないとバイトは ff、八つすべてが押された状態で、これは修飾キーとしての右シフトと、六つの枠に対する七つのキーで、dropped 1。ヘッドはキーを一つも受け取らなかった。その最初のレポートはホストが購読する前に出るからだ。基板は、ボンドはしたが信頼せず切断したままに、意図して残した: 浮いたデータ線は、パッドが載るまで何にも文字を打ち込んではならない。まだ試験していないもの: GPIO25、26、27 に配線したパッド、そして電話とのペアリング。

一番時間を食う罠 (BLE 版のみ)

書き込み直しはこの基板のボンド記憶を消しうるが、電話は自分の側を持ったままだ。片側だけのボンドはペアリングを拒み、電話にもシリアルポートにも何も報告しない。FORGET コマンドはこちら側を消し、それからはっきりと、ホスト側でもデバイスを忘れるように言う。片方だけやるのが失敗だからだ。

2026-09-30 に両方向で測った。クラシックな ESP32 で、ベンチヘッドをホストにして。 結合イメージを書き込むと基板のボンド記憶は空になり、まだボンドを持っているヘッドは接続して Connection successful と報告するが、何も動かない: 手がかりは bluetoothd のジャーナルだけで、HID の読み出しから Request attribute has encountered an unlikely error の行が四つ出て、古い uhid のキーボードはヘッドのキャッシュから一覧に残り続ける。bluetoothctl remove をしてからエージェント付きでペアリングすれば、またボンドする。逆の向きでは、ボンドが一つ入った基板での FORGET は # 1 bond(s) were stored here と rc 0 を答え、ヘッドの次の接続は Connected: no に落ちた。remove と三度目のペアリングでまたボンドした。その日より前、FORGET はこの部品では何もしていなかった: スケッチは NimBLE の下でしかボンドを消しておらず、クラシックな ESP32 のコアは Bluedroid で、そこではボンドを一つずつ並べて消す。スケッチはいまその分岐を持っている。

未決。open-items.md に記録

  • USB のアダプタはまだ電話に会っていない。これまでのホストは Pi だけだ。
  • BLE 版はまだパッドに会っておらず、ホストはベンチヘッド一つだけだ(2026-09-30、ESP32-WROOM-32 で)。P4 の上では基板上の C6 のファームウェアが応えるまで会えない(上を参照)。二枚目の C6 の上では広告しており (2026-09-28)、GPIO2、3、6 のパッドと電話を待っている。ESP32-WROOM-32 の上では広告しており (2026-09-30)、GPIO25、26、27 のパッドと電話を待っている。
  • ベンチの目は devkit のシルクを読めない。上のヘッダの並びを手で読んだのはそのためだ。次の devkit でも同じだろう。
  • 遅延は未測定。推測より測る価値がある。このベンチにはそれができるからだ。ブリッジはすべての読み出しにラッチ番号を刻むので、その数は head.log と bridge.log の引き算一回だ。