Skip to content

Instantly share code, notes, and snippets.

@GOROman
Created August 18, 2026 04:00
Show Gist options
  • Select an option

  • Save GOROman/7e344ad76edc7e8ff3eb31f334002e4c to your computer and use it in GitHub Desktop.

Select an option

Save GOROman/7e344ad76edc7e8ff3eb31f334002e4c to your computer and use it in GitHub Desktop.
ファミコンカセットダンパーでYM2151を鳴らすプロジェクトの知見総集編(設計判断・デバッグ手法・電源トラブル・失敗談)

ファミコンカセット吸い出し機で YM2151 を鳴らすまでに得た知見まとめ

「ファミコンカセットダンパー(M5Stamp S3 + 74HCT595/541/LVC245)に、 ROM を無効化した改造カートリッジ経由で YM2151 を載せてステレオ再生する」 プロジェクトの総集編。設計判断・デバッグ手法・電源トラブル・失敗談を残す。

1. 設計判断: 「基板を改造しない」縛りが最良の答えを生んだ

当初はデータバス書き込みのために基板改造(74LVC245 の DIR 足上げ+ジャンパ)を 検討したが、最終的にはフルアドレスバス方式に落ち着いた:

  • YM2151 の D0-7/A0//WR//IC を全部 CPU アドレス線(A0-A10) に割り当てる
  • アドレス線は 74HCT595 が常時 5V で駆動している = レベル変換も方向制御も不要
  • /WR のストローブすらアドレス線1本(シフトレジスタ3回更新)で作れる。 YM2151 はスタティックなのでパルス幅が数µsでも問題ない
  • 結果: リーダー基板は完全無改造、ハンダ付けはカートリッジ側だけ

教訓: 「書き込みたい=データバスが要る」という思い込みを外すと、 既に5Vで常時駆動されている線が山ほどあった。

2. 改造カートリッジという発想

ROM のセレクト線をカットした市販カート(ポパイ/NROM)は、 60ピンエッジの万能キャリア基板になる:

  • ROM の足=バスに繋がったままの太いハンダポイント(エッジフィンガーより遥かに楽)
  • 未使用制御線(/ROMSEL、PPU /RD 等)は PWM 音声出力の配線にすら転用できる
  • 28ピン ROM のピン配置は JEDEC 標準(11=D0,12=D1,13=D2,14=GND,15=D3…)。 24ピンの表で数えると全部ズレる(実際にやらかした。SH2 を GND に繋いでいた)

3. デバッグは「ファームに診断を足す」が最速だった

テスターより先に、診断コマンドを実装して数字で切り分けるのが結局速い:

  • エッジカウンタ: 各信号線のエッジ数/100ms。「12.4k=SH、50k超=φ1、0=断線」が 数字一発で分かる
  • デューティ計測: 50%ならクロック、100%なら断線でHi固定、0%ならGND短絡
  • /IC ホールドテスト: リセット中も走る信号(φ1)と止まる信号(SO)を区別できる
  • 生ワードダンプ: シフトレジスタの生値64個を吐かせてフォーマット位置を確定。 「オフセットを振って音量が変わった」程度の間接情報より、生データが正義
  • リセット理由(esp_reset_reason)を V コマンドに載せる: rst=9(Brownout)/rst=1(電源断)が出た瞬間に「電源だ」と確定した

4. 電源は最後まで敵だった(時系列)

  1. フル演奏で rst=9 Brownout → バルクコンデンサ(470µF)で解消
  2. WiFi(SoftAP)を常時ONにしたら、送信スパイク(300-500mA)で カート接続時だけブートループ。→ WiFi.setTxPower(7dBm) + WiFi はコマンドでオンデマンド起動に変更
  3. カートの活線挿抜で USB が瞬断 → Mac がポートを過電流ロックして 「デバイスが見えない」状態に(ポート変更 or 再起動で復活)。 挿抜は必ず電源OFFで
  4. 空中配線(RC フィルタ等)の間欠ショートが最後の伏兵。 「揺らすと直る」「挿すと不安定」は大体これ。裸線は絶縁する

5. ESP32 固有の罠(詳細は PWM 編 Gist)

  • USB-Serial-JTAG は受信バッファが溢れると黙って捨てる → ストリーミングにはアプリ層のクレジット式フロー制御が必須
  • Dedicated GPIO で 1 サイクル GPIO 読み(APB 経由の REG_READ では 2MHz 信号を取りこぼす)。バンドルは「配列順=ビット順」なので GPIO 昇順で作る
  • portDISABLE_INTERRUPTS の常用は禁物: IWDT が発火してパニック。 代わりに「取りこぼしをデータ検証で捨てる」設計にする (エッジ数検証+仕様上の禁止値チェック+median-3 の三段構え)
  • タスクWDT: idle だけ購読解除しても Arduino core 3.x の idle フックが "task not found" をログ洪水させる。全体 deinit +コメントで割り切った

6. リアルタイム性と検証窓のバランス

キャプチャ(2MHz ポーリング)とラッチ処理(デコード+PWM更新)は同居できないので:

  • ワード先頭の捨てビット区間(3bit)を「処理時間の隠れ場所」にする
  • 検証窓を厳しくしすぎると正常フレームの半分を捨てて音がガタガタになる (実際 47% まで落ちた)。「どこでビットが落ちるか」の物理を考えて窓を決める

7. ホスト側は「レジスタイベント列」に正規化すると全部繋がる

MDX(X68000)も VGM(アーケード実機ログ)も、最終的には (時刻, レジスタ, 値) のテキスト1行に変換してから共通プレイヤーで流す設計にした:

  • MDX: portable_mdx の MXDRV に書き込みフックを1行足して抽出 (音源エミュレータ部分は捨てて「演奏ロジック」だけ使う)
  • VGM: レジスタログそのものなので変換は素直
  • ピッチ補正(KC/KF を 1/64 半音単位でシフト)もこの層でやると、 3.58MHz 収録のアーケード曲を 4MHz チップで正しい音程で鳴らせる
  • 内蔵曲(電源ONで自動再生)も同じイベント列を C 配列に焼くだけ

8. UI は「1キー=1操作」の即時キーが正解だった

行コマンド(Y 28 4A など)とは別に、改行不要の即時キーを用意した:

P=再生 S=停止(モード維持) Q=Quit 1-8=chミュート A=全ch復帰
L/R=左右出力 +/-=ピッチ±50kHz H=ヘルプ
  • 演奏を止めずに効かせるには、再生ループ内でキーを消費する設計が必要
  • Web UI(WiFi)からの操作も「仮想キー」として同じ経路に注入すると、 排他制御なしでシリアルと Web が共存できる
  • 文字の衝突は「YMモード中だけ即時キー、通常時は従来の行コマンド」で回避

9. 一番大事だった教訓

「動いた」と「正しい」は別物。 自動判別・検証窓・デコード位置… どれも一度は「動いているように見えた」が、生データや統計を取ると 間違っていた。疑ったら計測を足す。計測できないなら計測手段を先に作る。

そして: 同じ症状でも原因は毎回違う。「無音」だけで 配線ズレ・ハンダ不良・GND短絡・ROM暴走・検証窓バグ・電源・USB と 7種類の原因を踏んだ。前回の対処を反射的に繰り返さず、毎回切り分けから。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment