Appearance
Arduino Mega 2560 フラッシュライター(SST39SF040 / MX29F040)
自作 NES カートリッジ用の 5V パラレル NOR フラッシュ SST39SF040(DIP-32, 512KB。 互換品 MX29F040 も可)へ、Arduino Mega 2560 経由で .nes ROM の PRG 部を 書き込むための構成。Mega は 5V ロジックなのでレベル変換なしで GPIO 直結できる。
- ファームウェア:
firmware/mega-writer/mega-writer.ino - ホスト CLI (macOS):
tools/flashnes.py(Python 3 + pyserial)
ブレッドボード配線表
フラッシュは DIP-32(JEDEC 標準ピン配置)。ノッチ(半月の切り欠き)を上にして、 左上が 1 番ピン、反時計回りに 32 番まで。
| フラッシュ 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)だけで 完結する割り当てなので、リボンケーブルでも配線しやすい。
補足:
- 電源は Mega の 5V / GND ピンから供給する(書き込み電流は数十 mA 程度)。
- VDD–VSS 間にパスコン 0.1µF を(できるだけチップの近くに)入れると安定する。
- /CE・/OE・/WE はファームウェアが常時駆動するためプルアップは必須ではないが、 10kΩ で 5V にプルアップしておくとリセット中の誤書き込み保護になる。
セットアップ
ファームウェア書き込み(Arduino IDE でも arduino-cli でも可)
bashbrew install arduino-cli # 未導入なら arduino-cli core install arduino:avr arduino-cli compile --fqbn arduino:avr:mega firmware/mega-writer arduino-cli upload -p /dev/tty.usbmodem1101 --fqbn arduino:avr:mega firmware/mega-writerホスト側の準備
bashpip install pyserialポート名は
ls /dev/tty.usbmodem*で確認する。
使い方
bash
PORT=/dev/tty.usbmodem1101
# 1. チップ ID の確認(配線チェックを兼ねる。必ず最初に実行する)
python3 tools/flashnes.py --port $PORT id
# → ID 0xBF 0xB7 SST39SF040 (MX29F040 なら ID 0xC2 0xA4 MX29F040)
# 2. .nes から PRG 部を抽出して 消去→書き込み→ベリファイ を一括実行
python3 tools/flashnes.py --port $PORT write game.nes --ines
# 生バイナリの一部だけ書く場合
python3 tools/flashnes.py --port $PORT write prg.bin --offset 0 --length 32768
# 書き込み済み内容の照合のみ
python3 tools/flashnes.py --port $PORT verify game.nes --ines
# フラッシュ全体の吸い出し
python3 tools/flashnes.py --port $PORT dump readback.bin --length 524288writeは既定でチップ消去→書き込み→リードバックベリファイまで自動実行する (消去を飛ばす場合は--no-erase)。--inesは iNES ヘッダ 16 バイトをスキップし、PRG サイズ(ヘッダ 4 バイト目 × 16KB)だけを抽出する。トレーナー(512B)があれば自動でスキップする。- チップ消去は SST39SF040 で 1 秒未満、MX29F040 では数十秒かかることがある。
- 所要時間の目安(実測): 書き込みは約 240 バイト/秒(
digitalWriteベースの GPIO 操作がボトルネック)。512KB フル書き込みはベリファイ込みで 40 分前後 かかる。進捗表示が出ていれば正常なので途中で中断しないこと。 - 書き込み中はブレッドボード・配線・USB ケーブルに触れない(シリアル切断や 接触不良で途中失敗する)。途中失敗したチップは部分的に書き込まれた状態になる ため、リトライは必ず消去込みの
write(--no-eraseなし)で行う。
シリアルプロトコル(参考)
115200bps・行ベース。V(バージョン)、I(ID 読み出し)、E(チップ消去)、 W <addr> <len>(バイナリ書き込み、256 バイトごとに ACK、完了で OK <checksum>)、R <addr> <len>(バイナリ読み出し)。詳細は firmware/mega-writer/mega-writer.ino 冒頭のコメントを参照。
⚠ 実装上の要点(2026-08-13 の実機検証で修正済み): ファームウェアは 256 バイトのチャンクを一旦 RAM に全部受信してからフラッシュに書き込むこと。 1 バイトずつ「受信→書き込み」すると、書き込み処理(約 1ms/バイト)が受信速度 (115200bps ≒ 87µs/バイト)に追いつかず、Mega の 64 バイトのハードウェア受信 バッファが溢れてデータが黙って失われる(症状: 先頭 60〜70 バイトだけ書き込まれて ERR rx timeout)。ホスト側は各チャンクの ACK を待ってから次を送るため、 バッファリングさえすればフロー制御はプロトコルだけで成立する。
トラブルシュート: ID が読めない時の確認順
id が ID 0xFF 0xFF UNKNOWN や ID 0x00 0x00 UNKNOWN を返す場合、 以下の順で切り分ける。
- コネクタ・ハーネスの挿さり具合(まずここ): ジャンパ線を 2×18 ヘッダの コネクタブロックにまとめて Mega に挿す構成の場合、ブロックの半挿し・斜め挿し で全信号が同時に死に、配線が全部正しくても
0xFF 0xFFになる。 実機検証(2026-08-13)で ID が読めなかった実原因はこれだった。 両端を均等に、奥までまっすぐ押し込んで再実行する。ブロックの向きは 「22 側の端」と「偶数列=内側/奇数列=外側」を Mega 基板のシルクと突き合わせる。 計画上空きのはずの穴(5V・GND・D41・D53)にワイヤが挿さっていたら 1 列ズレている。 - 電源: フラッシュ pin 32 (VDD)–pin 16 (VSS) 間をテスターで実測して 約 5V あるか。ブレッドボードの電源レール分割(中央で分かれているタイプ)に注意。 プローブが穴に入らないときは、同じ縦列の空き穴に抵抗の足やジャンパ線を挿して その金属部に当てる(ブレッドボードは縦 1 列 5 穴が導通している)。
- チップの向き: ノッチ位置を再確認。逆挿しは発熱するので即座に外す。 注意: SST39SF040 には半月ノッチが無いロットがある。その場合は パッケージ端寄り・幅中央の丸いくぼみ(●)がある端が 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 も読めないか、読めても書けない)。
上記でも解決しない場合は、V コマンド(python3 -c 等でシリアルに直接送信)で ファームウェアが応答するかを確認し、応答がなければ USB ケーブル・ポート名・ ファームウェア書き込み自体を疑う。
トラブルシュート: write が ERR rx timeout で失敗する
id は成功するのに write が erase 直後の転送で ERR rx timeout になる場合、 2026-08-13 より前のファームウェア(1 バイトずつ受信しながら書き込む実装)が Mega に残っている可能性が高い。受信バッファ溢れで先頭 60〜70 バイトだけ書き込まれて 止まるのが特徴(dump で確認できる)。最新の firmware/mega-writer を 再アップロードすること(→「シリアルプロトコル」の実装上の要点を参照)。
切り分けに使えるテク:
dumpで先頭 512 バイトを吸い出し、期待データと突き合わせると 「どこまで書けたか」「何も書けていないか」が分かる。- 一度でも部分書き込みされたチップに同じデータを重ね書きすると、フラッシュは ビットを 1→0 にしか変えられないため
ERR program failになる。これは故障ではなく 正常な動作。消去込みのwriteでリトライすれば良い。
実機検証の記録
2026-08-13、SST39SF040 実チップ + Arduino Mega 2560(ELEGOO 互換機)で検証:
idで ID 読み出し → OK(0xBF 0xB7 SST39SF040。 初回はハーネス半挿しで0xFF 0xFF、挿し直しで解決)erase→ OK(数秒)write(512KB)→ 旧ファームで受信バッファ溢れを検出、チャンクバッファリング 修正後に成功。実測約 240 バイト/秒。- 書き込んだチップをカートリッジに装着して実機起動確認(issue #34 STEP 5–6)