Arduino Mega 2560 フラッシュライター(SST39SF040 / .gbc 書き込み)
対象: issue #16 / 焼くもの: .gbc(MBC5・128KB または 256KB・CGB 専用・セーブなし)/ 焼き先: SST39SF040(PDIP-32 / PLCC-32 / TSOP-32・512KB・5V)
NES 版からの流用範囲
NES 版 nes_aiDopagaki/firmware/mega-writer/mega-writer.ino + tools/flashnes.py は どちらも無改造で流用できる(ソースを実読して確認。コード上の NES 依存は extract_ines_prg() だけで、 これは --ines を付けたときにしか呼ばれない)。Mega は 5V ロジック、SST39SF040 も 5V 単一電源なので レベル変換も不要。
ただし運用上の差分が 3 つある。
| # | 差分 | 対応 |
|---|---|---|
| 1 | 512KB 充填イメージの生成ツールが新規に要る | tools/make_gb_flash_bin.py を実装済み(本 issue で追加)。NES 版 make_flash_bin.py は CLI が <nes> --outdir で出力名固定 + iNES マジック必須のため流用できない |
| 2 | ヘッダ検査が GB 固有 | 任天堂ロゴ $0104-0133 / ヘッダチェックサム $014D / $0143 / $0147 / $0148 / $0149 を焼く前に検査する |
| 3 | 512KB 充填の要否がジャンパ方式で変わる | 方式 1(pin1→GND)なら充填は保険。方式 2(Audio pin as WE)なら 512KiB 全域が実際に使える |
- ファームウェア:
nes_aiDopagaki/firmware/mega-writer/mega-writer.ino(無変更) - ホスト CLI:
nes_aiDopagaki/tools/flashnes.py(無変更。⚠--inesを付けない) - 512KB 充填イメージ生成:
tools/make_gb_flash_bin.py✅ 実装済み(本 issue で追加)
ブレッドボード配線表
フラッシュは DIP-32(JEDEC 標準ピン配置)。ノッチ(半月の切り欠き)を上にして、左上が 1 番ピン、反時計回りに 32 番まで。NES 版と完全に同一。
| フラッシュ DIP-32 ピン | 信号 | Mega 2560 ピン |
|---|---|---|
| 1 | A18 | D40 |
| 2 | A16 | D38 |
| 3 | A15 | D37 |
| 4 | A12 | D34 |
| 5 | A7 | D29 |
| 6 | A6 | D28 |
| 7 | A5 | D27 |
| 8 | A4 | D26 |
| 9 | A3 | D25 |
| 10 | A2 | D24 |
| 11 | A1 | D23 |
| 12 | A0 | D22 |
| 13 | DQ0 | D42 |
| 14 | DQ1 | D43 |
| 15 | DQ2 | D44 |
| 16 | VSS | GND |
| 17 | DQ3 | D45 |
| 18 | DQ4 | D46 |
| 19 | DQ5 | D47 |
| 20 | DQ6 | D48 |
| 21 | DQ7 | D49 |
| 22 | /CE | D50 |
| 23 | A10 | D32 |
| 24 | /OE | D51 |
| 25 | A11 | D33 |
| 26 | A9 | D31 |
| 27 | A8 | D30 |
| 28 | A13 | D35 |
| 29 | A14 | D36 |
| 30 | A17 | D39 |
| 31 | /WE | D52 |
| 32 | VDD | 5V |
規則性: A0..A18 → D22..D40(番号順)、DQ0..DQ7 → D42..D49、/CE → D50、/OE → D51、/WE → D52。Mega の 2×18 ピンヘッダ(D22–D53)だけで完結する。
⚠ D41 と D53 は意図的に未使用。 どちらかに線が挿さっていたらハーネスが 1 列ずれている。NES 版で失敗原因 No.1 だった項目で、ライタが同一である以上 GB 版でもそのまま当てはまる。5V・GND の穴も同様に、計画外の場所にワイヤがあれば 1 列ズレを疑う。
補足:
- 電源は Mega の 5V / GND ピンから供給する(書き込み電流は数十 mA 程度)。
- VDD–VSS 間にパスコン 0.1µF を(できるだけチップの近くに)入れると安定する。
- /CE・/OE・/WE はファームウェアが常時駆動するためプルアップは必須ではないが、10kΩ で 5V にプルアップしておくとリセット中の誤書き込み保護になる。
PLCC-32 品を焼く場合(経路 A・保留)
PLCC32 → DIP32 変換ソケットを噛ませ、その DIP 側をブレッドボードに挿す。上の配線表はそのまま使える(PLCC-32 のピン番号は DIP-32 と完全に一致する。Microchip DS20005022F)。⚠ チップの斜めに落とした角を PLCC ソケットの斜め角に合わせること。
TSOP-32 品を焼く場合(経路 D = 完成形。必須)
完成形は TSOP-32 なので、この経路は避けて通れない(旧稿の「非推奨」は撤回)。
⚠ TSOP-32 のピン番号は DIP-32 と一致しない。 巡回シフトの関係にある。
TSOP pin = ((DIP pin + 7) mod 32) + 1 # DIP 1 → TSOP 9、DIP 32 → TSOP 8
DIP pin = ((TSOP pin + 23) mod 32) + 1鏡像でも逆順でもなく、DIP の pin 25–32 のブロックが TSOP の先頭に回り込む。頭の中で変換しようとせず、必ずデータシートの図と突き合わせること。
TSOP32 → DIP32 書込みアダプタ(TL866 系プログラマ用アダプタ等)を噛ませれば、上の DIP-32 配線表がそのまま使える。アダプタが番号変換を吸収する。
🚧 アダプタの入手性・価格・型番は未確認。 旧稿の「変換ソケットの入手性が悪い」は未検証の推測だったので撤回する。AliExpress / TL866 系アダプタを実検索し、型番・価格・納期を BOM(docs/cart-mod.md E-2 #15)に記入すること。経路 D の成立条件はここ。
⚠ SST39SF040 の 32L TSOP は EOL(Microchip PCN BLAS-18EIYY441、最終発注 2024-09-15 / 最終出荷 2024-12-31)。流通在庫が無ければ Am29F040B TSOP-32 Standard(発注記号 E) に切り替える。⚠ F = Reverse はピン番号が完全逆順なので絶対に使わない。
セットアップ
ファームウェア書き込み(NES 版と同一。既に焼いてあるなら不要)
bashbrew install arduino-cli # 未導入なら arduino-cli core install arduino:avr arduino-cli compile --fqbn arduino:avr:mega ../nes_aiDopagaki/firmware/mega-writer arduino-cli upload -p /dev/tty.usbmodem1101 --fqbn arduino:avr:mega ../nes_aiDopagaki/firmware/mega-writerホスト側の準備
bashpip install pyserialポート名は
ls /dev/tty.usbmodem*で確認する。
使い方
PORT=/dev/tty.usbmodem1101
FLASHNES=../nes_aiDopagaki/tools/flashnes.py
# 1. チップ ID の確認(配線チェックを兼ねる。必ず最初に実行する)
python3 $FLASHNES --port $PORT id
# → ID 0xBF 0xB7 SST39SF040 (MX29F040 なら ID 0xC2 0xA4 MX29F040)
# 2. .gbc から 512KB 充填イメージを作る
python3 tools/make_gb_flash_bin.py build/aidopagaki.gbc build/flash/gb_512k.bin
# → input 262144 bytes (256 KiB) x2 -> 524288 bytes
# 3. 消去→書き込み→ベリファイ を一括実行(⚠ --ines は付けない)
python3 $FLASHNES --port $PORT write build/flash/gb_512k.bin
# → 約 40 分かかる
# 書き込み済み内容の照合のみ
python3 $FLASHNES --port $PORT verify build/flash/gb_512k.bin
# フラッシュ全体の吸い出し
python3 $FLASHNES --port $PORT dump readback.bin --length 524288⚠ --ines を付けてはいけない。 .gbc に iNES ヘッダは無いので、付けると not an iNES file (missing 'NES\x1a' magic) で即座に落ちる。落ちる分には安全である(prepare_payload() は --ines を先に適用し、extract_ines_prg() がマジック不一致で必ず ValueError を投げるため、--offset を併用しても評価される前に止まる)。
⚠ 実際に危険なのは逆のケース: --ines を付けずに --offset 16 を指定してしまうこと。この場合は何のエラーも出ないまま先頭 16 バイトが欠けた ROM が焼き上がり、任天堂ロゴもヘッダも壊れて実機がハングする。--offset は原則として指定しない。
なぜ 512KB に充填するのか
.gbc は 128KB か 256KB、フラッシュは 512KB なので、そのまま焼くと上位が未書き込み(0xFF)で残る。**同一イメージを 512KB 全域に繰り返す(ミラー配置する)**ことで、上位アドレスがどこを指しても正しいデータが読める。NES 版 tools/make_flash_bin.py(PRG 32KB × 16 回)と同じ考え方。
⚠ ただし方式 1(docs/cart-mod.md §5)では、A18 は GND 固定なので上位 256KB は最初から使われない。 充填は「pin 1 → GND のジャンパ 1 本が失敗したときの保険」でしかない。
⚠ A17(= MBC5 の RA17、方式 1・256KiB でのバンク最上位)の配線ミスは充填では救えない。 充填が守ってくれるのは A18 だけである。
⚠ 充填は必ず rgbfix の後に行う。 逆順にすると、先頭コピーだけ $0148 や $014D が書き換わってミラー同士が不一致になり、充填の意味が消える。
docs/cart-mod.md のジャンパ表どおり pin 1(A18)を GND に落としてあれば充填は不要で、下位だけ焼けばよい。既定は充填とし、時間を惜しむ場合だけ次のようにする:
# ROM サイズちょうどだけ焼く(⚠ pin 1 → GND のジャンパが確実であること)
# 256KiB ROM の場合(約 20 分)
python3 $FLASHNES --port $PORT write build/aidopagaki.gbc --length 262144
# 128KiB ROM の場合(約 10 分)— または --length を省略する
python3 $FLASHNES --port $PORT write build/aidopagaki.gbc --length 131072⚠ --length は実 ROM サイズを超えられない。 128KiB の ROM に --length 262144 を指定すると ValueError: length 262144 out of range (payload is 131072 bytes) で落ちる。 --length を省略すればファイル全体が焼かれるので、迷うなら省略する。
tools/make_gb_flash_bin.py の仕様
目的: .gbc を検査したうえで、512KB 全域に同一データをミラー配置した生バイナリを作る。
呼び出し:
python3 tools/make_gb_flash_bin.py <input.gbc> <output.bin>| 引数 | 必須 | 説明 |
|---|---|---|
<input.gbc> | ✅ | 入力 ROM。128KiB / 256KiB / 512KiB のいずれか(131072 / 262144 / 524288 バイト) |
<output.bin> | ✅ | 出力先パス。親ディレクトリが無ければ作る |
⚠ NES 版 make_flash_bin.py の CLI(<nes> --outdir、出力名固定、iNES マジック必須)とは互換性がない。 雛形として参照はするが、そのままでは使えない。
出力: ちょうど 524288 バイト(512KiB)の生バイナリ。入力を 524288 / len(input) 回だけ単純に連結する(128KiB → 4 回、256KiB → 2 回、512KiB → 1 回)。
標準出力(1 行):
input 262144 bytes (256 KiB) x2 -> 524288 bytes焼く前に必ず行う検査(1 つでも失敗したら書き出さずに非 0 で終了する):
| # | 検査 | 失敗時に何が起きるか |
|---|---|---|
| 1 | 入力サイズが 512KiB を割り切ること(524288 % len == 0) | 黙ってパディングしない。 中途半端なミラーは実機で化ける |
| 2 | $0104-$0133 の任天堂ロゴが正しいこと | ブート ROM が検証してその場でハングする |
| 3 | $014D のヘッダチェックサムが $0134-$014C の総和から計算した値と一致すること | 同上。「実機で真っ白/ロゴで止まる」の主因 |
| 4 | $0143 = $C0(CGB 専用) | CGB 専用機能が期待どおりに有効化されない |
| 5 | $0147 = $19(MBC5・RAM なし) | MBC が違うとバンク切り替えが機能しない |
| 6 | $0149 = $00(RAM なし) | セーブ回路の無い基板と食い違う |
| 7 | $0148 が $02(128KiB)または $03(256KiB)であること | ⚠ $04 以上は方式 1 の配線では原理的に動作しない(→ docs/cart-mod.md §5)。焼く前にエラーで止める |
⚠ 検査 7 は方式 1 を前提とした制約である。方式 2(Audio pin as WE)で組んだカートに焼くなら $04(512KiB)も動くので、そのときは検査 7 を緩める --allow-512k を付ける。既定は方式 1 に合わせて厳しくしておく。
根拠: Pan Docs – The Cartridge Header
所要時間
| 対象 | 所要時間 | 根拠 |
|---|---|---|
| 512KB 充填イメージ(書込み + ベリファイ) | 約 40 分 | NES 版 2026-08-13 の実機実測。約 240 バイト/秒 |
| 256KB のみ(書込み + ベリファイ) | 約 20 分 | 240 B/s からの計算値。GB 実機では未計測(要確認) |
| 128KB のみ(書込み + ベリファイ) | 約 10 分 | 同上。未計測(要確認) |
| チップ消去 | SST39SF040 で 1 秒未満 / MX29F040 で数十秒 | NES 版実測 |
ボトルネックは digitalWrite ベースの GPIO 操作。進捗表示が出ていれば正常なので途中で中断しないこと。 書き込み中はブレッドボード・配線・USB ケーブルに触れない。途中失敗したチップは部分的に書き込まれた状態になるため、リトライは必ず消去込みの write(--no-erase なし)で行う。
シリアルプロトコル(参考)
115200bps・行ベース。V(バージョン)、I(ID 読み出し)、E(チップ消去)、W <addr> <len>(バイナリ書き込み、256 バイトごとに ACK、完了で OK <checksum>)、R <addr> <len>(バイナリ読み出し)。詳細は nes_aiDopagaki/firmware/mega-writer/mega-writer.ino 冒頭のコメントを参照。
⚠ 実装上の要点(2026-08-13 の実機検証で修正済み、commit ea67bde): ファームウェアは 256 バイトのチャンクを一旦 RAM に全部受信してからフラッシュに書き込む。1 バイトずつ「受信→書き込み」すると、書き込み処理(約 1ms/バイト)が受信速度(115200bps ≒ 87µs/バイト)に追いつかず、Mega の 64 バイトのハードウェア受信バッファが溢れてデータが黙って失われる(症状: 先頭 60〜70 バイトだけ書き込まれて ERR rx timeout)。GB でも同じファームウェアを使うので、この修正が入った版であることを最初に確認する。
トラブルシュート: ID が読めない時の確認順
⚠ これは実機検証前の「予測順」である。 並びは失敗確率の高い順で、根拠は (a) NES 版 2026-08-13 の実機検証で実際に踏んだ順、(b) GB 版で新規に増えた要素はリスクが高いという仮定、の 2 点。実機作業後に、実際に踏んだ順へ書き直すこと。
id が ID 0xFF 0xFF UNKNOWN や ID 0x00 0x00 UNKNOWN を返す場合、以下の順で切り分ける。
- コネクタ・ハーネスの挿さり具合(まずここ。実績 No.1): ジャンパ線を 2×18 ヘッダのコネクタブロックにまとめて Mega に挿す構成の場合、ブロックの半挿し・斜め挿しで全信号が同時に死に、配線が全部正しくても
0xFF 0xFFになる。NES 版 2026-08-13 の実機検証で ID が読めなかった実原因はこれだった。両端を均等に、奥までまっすぐ押し込んで再実行する。計画上空きのはずの穴(5V・GND・D41・D53)にワイヤが挿さっていたら 1 列ズレている。 - 変換アダプタ(PLCC32→DIP32 / TSOP32→DIP32): GB 版で新規に増えた要素なので 2 番目に疑う。
- チップの斜めに落とした角(PLCC)または pin 1 マーク(TSOP)とアダプタ側の指標が合っているか
- チップがソケットに最後まで座っているか(PLCC は途中で止まりやすい)
- アダプタの DIP ピンがブレッドボードに全 32 本入っているか(1 本折れて基板下に曲がっていることがある)
- ⚠ TSOP の場合、アダプタが番号変換をしてくれる前提でいること。素の TSOP を DIP 配線表どおりに繋いでも動かない
- 電源: フラッシュ pin 32 (VDD)–pin 16 (VSS) 間をテスターで実測して約 5V あるか。ブレッドボードの電源レール分割(中央で分かれているタイプ)に注意。プローブが穴に入らないときは、同じ縦列の空き穴に抵抗の足やジャンパ線を挿してその金属部に当てる(ブレッドボードは縦 1 列 5 穴が導通している)。
- チップの向き: ノッチ位置を再確認。逆挿しは発熱するので即座に外す。⚠ SST39SF040 には半月ノッチが無いロットがある(NES 版 2026-08-13 検証の個体がそう)。その場合はパッケージ端寄り・幅中央の丸いくぼみ(●)がある端が pin1/pin32 側。端から離れた位置の丸マークは成形時の跡なので向きの判定に使わない。
- 制御 3 線: /CE(pin22)→D50、/OE(pin24)→D51、/WE(pin31)→D52 の 3 本を最優先で確認する。ここが 1 本でも違うと ID は絶対に読めない。
0xFF 0xFF→ チップが応答していない(/CE か /OE の配線・電源を疑う)。0x00 0x00→ バスが駆動されていない/短絡の可能性。
- データバス 8 本: DQ0..DQ7(pin13,14,15,17,18,19,20,21)→ D42..D49 の順序ずれ。ID の値が「毎回同じだが期待値と違う」ならビット順の入れ替わりを疑う(例: 0xBF がビット逆転で 0xFD に見える等)。
- アドレスバス: ID コマンドは
$5555/$2AAAへの書き込みを使うため、A0..A14 のどれかが違っていてもコマンドが届かず0xFFになる。配線表と 1 本ずつ突き合わせる(特に pin 1=A18 / pin 2=A16 / pin 30=A17 / pin 31=/WE の並びは間違えやすい)。 - 接触不良: ブレッドボードとジャンパワイヤの品質。チップを軽く押さえながら
idを再実行して値が変わるなら接触不良。 - チップ自体: 別の個体に差し替えて再確認。⚠ 3.3V 版の SST39VF040 は 5V では使用不可(ID も読めないか、読めても書けない)。秋月で ¥150 で売っているのはこの
VF版なので、手元の袋の型番を目視すること。SFとVFの 2 文字違い。
上記でも解決しない場合は、V コマンド(python3 -c 等でシリアルに直接送信)でファームウェアが応答するかを確認し、応答がなければ USB ケーブル・ポート名・ファームウェア書き込み自体を疑う。
トラブルシュート: write が ERR rx timeout で失敗する
id は成功するのに write が erase 直後の転送で ERR rx timeout になる場合、2026-08-13 より前のファームウェア(1 バイトずつ受信しながら書き込む実装)が Mega に残っている可能性が高い。受信バッファ溢れで先頭 60〜70 バイトだけ書き込まれて止まるのが特徴(dump で確認できる)。最新の mega-writer を再アップロードすること。
切り分けに使えるテク:
dumpで先頭 512 バイトを吸い出し、期待データと突き合わせると「どこまで書けたか」「何も書けていないか」が分かる。GB ROM なら$0104から任天堂ロゴ(CE ED 66 66 CC 0D ...)が見えるはずなので、目視で当たりがつけやすい。- 一度でも部分書き込みされたチップに同じデータを重ね書きすると、フラッシュはビットを 1→0 にしか変えられないため
ERR program failになる。これは故障ではなく正常な動作。消去込みのwriteでリトライすれば良い。
トラブルシュート: ベリファイまで通ったのに実機で映らない
⚠ これも実機検証前の「予測順」である。 失敗確率の高い順に並べてある。
- 挿している本体(まずここ): 実機確認は GBC か GBA で行う。本 ROM は CGB 専用(
$0143 = $C0)で、CGB 専用のパレット・VRAM バンク・倍速コードは DMG では正しく動かない。⚠ なお DMG / MGB のブート ROM は$0143を見ない(検証するのはロゴと$014Dだけ)ので、「DMG で真っ白 = ヘッダ不正」とは限らない。原因を増やさないため GBC / GBA から始める。 - フラッシュ pin 31(/WE)の処理: 本改造で唯一の致命傷ポイント。
- 方式 1: pin 31 ⇔ +5V(穴 32)が導通し、pin 31 ⇔ 穴 31 が切れていることの両方を確認する
- 方式 2: pin 31 ⇔ カートエッジ pin 31 が導通し、47kΩ 経由で +5V に上がっていることを確認する
- パッド(MBC5 の RA18)に繋がったままだと常時書き込みモードに入る(→
docs/cart-mod.md§5)
- ヘッダチェックサム
$014D: ブート ROM が検証してハングする。rgbfix -C -m 0x19 -r 0x00 -v -p 0xFFを通し忘れていないか。make_gb_flash_bin.pyの検査を通していれば除外できる。 - A18 / A17 の配線:
- 方式 1 なら pin 1(A18)が GND に落ちていること。⚠ ただし 512KB 充填イメージを焼いてあれば A18 が何であっても読めるので、充填済みならこの項目は除外できる。
- ⚠ 充填で救えるのは A18 だけ。「256KiB ROM で後半 8 バンクだけ化ける」症状なら A17(pin 30 / パッド 30)を疑う。 方式 1 ではここがバンク最上位で、切れていると 128KiB 相当の挙動になる。
- 方式 2 なら pin 1 ⇔ パッド 31(RA18)のクロス配線が導通していること。
- ROM サイズとジャンパ方式の不整合: ⚠ 方式 1 の配線に
$0148 = $04(512KiB)の ROM を焼いていないか。 原理的に動かない。$03以下にするか、方式 2 へ組み替える。 - パッドのはんだ不良・ブリッジ: 32 箇所を拡大鏡で見る。フラックス残りで導通しているように見えることがあるので、洗浄してから測る。
- 書き込み内容が熱で消えていないか: 🚧 はんだ付けの熱で書き込み済みフラッシュの内容が失われるかは未検証。GBxCart RW または Mega で読み戻してベリファイする。
- パッド剥がれ: ROM 除去時に剥がれた信号を MBC5 のピンから直接引く(→
docs/cart-mod.mdの MBC5 ピン対応表)。 - ドナー基板の取り違え: 基板シルクを読み直す。ROM が 44 本なら TSOP-II で、そもそも本手順の対象外。
- ROM 自体: PyBoy で最後まで動くか確認する。ここまで来て初めて ROM を疑う。
実機検証の記録
🚧 未実施。 実機作業を行ったら、以下を日付入りで埋めること(NES 版 nes_aiDopagaki/docs/writer.md の同節に相当)。
| 項目 | 結果 | 日付 |
|---|---|---|
id で ID 読み出し | 📋 | — |
erase | 📋 | — |
write(512KB 充填)の実測スループット | 📋 | — |
verify | 📋 | — |
| TSOP32→DIP32 書込みアダプタの型番・価格・入手経路 | 📋 | — |
| 実装後の読み戻しベリファイ(熱で消えていないか) | 📋 | — |
| 実機(GBC)起動確認 | 📋 | — |
| ID が読めなかった実際の原因と、それが上のラダーの何番だったか | 📋 | — |
⚠ 最後の行が本書で最も価値のある情報になる。 NES 版ではラダーのステップ 0(ハーネス半挿し)が実原因だった。GB 版で違う順序になったら、ラダーを実際の失敗確率順に並べ替えること。