「ファミコンカセットダンパー(M5Stamp S3 + 74HCT595/541/LVC245)に、 ROM を無効化した改造カートリッジ経由で YM2151 を載せてステレオ再生する」 プロジェクトの総集編。設計判断・デバッグ手法・電源トラブル・失敗談を残す。
- リポジトリ: https://github.com/GOROman/nes-rom-reader/tree/feature/ym2151
- YM3012 デコード編: https://gist.github.com/GOROman/f3d1bff3637fa44e87515eb329e50591
- PWM オーディオ編: https://gist.github.com/GOROman/c4f17fd6cc945bcbf83b9132127f95eb
当初はデータバス書き込みのために基板改造(74LVC245 の DIR 足上げ+ジャンパ)を 検討したが、最終的にはフルアドレスバス方式に落ち着いた:
- YM2151 の D0-7/A0//WR//IC を全部 CPU アドレス線(A0-A10) に割り当てる
- アドレス線は 74HCT595 が常時 5V で駆動している = レベル変換も方向制御も不要
- /WR のストローブすらアドレス線1本(シフトレジスタ3回更新)で作れる。 YM2151 はスタティックなのでパルス幅が数µsでも問題ない
- 結果: リーダー基板は完全無改造、ハンダ付けはカートリッジ側だけ
教訓: 「書き込みたい=データバスが要る」という思い込みを外すと、 既に5Vで常時駆動されている線が山ほどあった。
ROM のセレクト線をカットした市販カート(ポパイ/NROM)は、 60ピンエッジの万能キャリア基板になる:
- ROM の足=バスに繋がったままの太いハンダポイント(エッジフィンガーより遥かに楽)
- 未使用制御線(/ROMSEL、PPU /RD 等)は PWM 音声出力の配線にすら転用できる
- 28ピン ROM のピン配置は JEDEC 標準(11=D0,12=D1,13=D2,14=GND,15=D3…)。 24ピンの表で数えると全部ズレる(実際にやらかした。SH2 を GND に繋いでいた)
テスターより先に、診断コマンドを実装して数字で切り分けるのが結局速い:
- エッジカウンタ: 各信号線のエッジ数/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(電源断)が出た瞬間に「電源だ」と確定した
- フル演奏で rst=9 Brownout → バルクコンデンサ(470µF)で解消
- WiFi(SoftAP)を常時ONにしたら、送信スパイク(300-500mA)で
カート接続時だけブートループ。→
WiFi.setTxPower(7dBm)+ WiFi はコマンドでオンデマンド起動に変更 - カートの活線挿抜で USB が瞬断 → Mac がポートを過電流ロックして 「デバイスが見えない」状態に(ポート変更 or 再起動で復活)。 挿抜は必ず電源OFFで
- 空中配線(RC フィルタ等)の間欠ショートが最後の伏兵。 「揺らすと直る」「挿すと不安定」は大体これ。裸線は絶縁する
- 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 +コメントで割り切った
キャプチャ(2MHz ポーリング)とラッチ処理(デコード+PWM更新)は同居できないので:
- ワード先頭の捨てビット区間(3bit)を「処理時間の隠れ場所」にする
- 検証窓を厳しくしすぎると正常フレームの半分を捨てて音がガタガタになる (実際 47% まで落ちた)。「どこでビットが落ちるか」の物理を考えて窓を決める
MDX(X68000)も VGM(アーケード実機ログ)も、最終的には
(時刻, レジスタ, 値) のテキスト1行に変換してから共通プレイヤーで流す設計にした:
- MDX: portable_mdx の MXDRV に書き込みフックを1行足して抽出 (音源エミュレータ部分は捨てて「演奏ロジック」だけ使う)
- VGM: レジスタログそのものなので変換は素直
- ピッチ補正(KC/KF を 1/64 半音単位でシフト)もこの層でやると、 3.58MHz 収録のアーケード曲を 4MHz チップで正しい音程で鳴らせる
- 内蔵曲(電源ONで自動再生)も同じイベント列を C 配列に焼くだけ
行コマンド(Y 28 4A など)とは別に、改行不要の即時キーを用意した:
P=再生 S=停止(モード維持) Q=Quit 1-8=chミュート A=全ch復帰
L/R=左右出力 +/-=ピッチ±50kHz H=ヘルプ
- 演奏を止めずに効かせるには、再生ループ内でキーを消費する設計が必要
- Web UI(WiFi)からの操作も「仮想キー」として同じ経路に注入すると、 排他制御なしでシリアルと Web が共存できる
- 文字の衝突は「YMモード中だけ即時キー、通常時は従来の行コマンド」で回避
「動いた」と「正しい」は別物。 自動判別・検証窓・デコード位置… どれも一度は「動いているように見えた」が、生データや統計を取ると 間違っていた。疑ったら計測を足す。計測できないなら計測手段を先に作る。
そして: 同じ症状でも原因は毎回違う。「無音」だけで 配線ズレ・ハンダ不良・GND短絡・ROM暴走・検証窓バグ・電源・USB と 7種類の原因を踏んだ。前回の対処を反射的に繰り返さず、毎回切り分けから。