Skip to content

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 ピン
1A18D40
2A16D38
3A15D37
4A12D34
5A7D29
6A6D28
7A5D27
8A4D26
9A3D25
10A2D24
11A1D23
12A0D22
13DQ0D42
14DQ1D43
15DQ2D44
16VSSGND
17DQ3D45
18DQ4D46
19DQ5D47
20DQ6D48
21DQ7D49
22/CED50
23A10D32
24/OED51
25A11D33
26A9D31
27A8D30
28A13D35
29A14D36
30A17D39
31/WED52
32VDD5V

規則性: 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 にプルアップしておくとリセット中の誤書き込み保護になる。

セットアップ ​

  1. ファームウェア書き込み(Arduino IDE でも arduino-cli でも可)

    bash
    brew 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
  2. ホスト側の準備

    bash
    pip install pyserial
  3. ポート名は 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 524288
  • write は既定でチップ消去→書き込み→リードバックベリファイまで自動実行する (消去を飛ばす場合は --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 を返す場合、 以下の順で切り分ける。

  1. コネクタ・ハーネスの挿さり具合(まずここ): ジャンパ線を 2×18 ヘッダの コネクタブロックにまとめて Mega に挿す構成の場合、ブロックの半挿し・斜め挿し で全信号が同時に死に、配線が全部正しくても 0xFF 0xFF になる。 実機検証(2026-08-13)で ID が読めなかった実原因はこれだった。 両端を均等に、奥までまっすぐ押し込んで再実行する。ブロックの向きは 「22 側の端」と「偶数列=内側/奇数列=外側」を Mega 基板のシルクと突き合わせる。 計画上空きのはずの穴(5V・GND・D41・D53)にワイヤが挿さっていたら 1 列ズレている。
  2. 電源: フラッシュ pin 32 (VDD)–pin 16 (VSS) 間をテスターで実測して 約 5V あるか。ブレッドボードの電源レール分割(中央で分かれているタイプ)に注意。 プローブが穴に入らないときは、同じ縦列の空き穴に抵抗の足やジャンパ線を挿して その金属部に当てる(ブレッドボードは縦 1 列 5 穴が導通している)。
  3. チップの向き: ノッチ位置を再確認。逆挿しは発熱するので即座に外す。 注意: SST39SF040 には半月ノッチが無いロットがある。その場合は パッケージ端寄り・幅中央の丸いくぼみ(●)がある端が pin1/pin32 側。 端から離れた位置の丸マークは成形時の跡なので向きの判定に使わないこと。
  4. 制御 3 線: /CE(pin22)→D50、/OE(pin24)→D51、/WE(pin31)→D52 の 3 本を 最優先で確認する。ここが 1 本でも違うと ID は絶対に読めない。
    • 0xFF 0xFF → チップが応答していない(/CE か /OE の配線・電源を疑う)。
    • 0x00 0x00 → バスが駆動されていない/短絡の可能性。
  5. データバス 8 本: DQ0..DQ7(pin13,14,15,17,18,19,20,21)→ D42..D49 の 順序ずれ。ID の値が「毎回同じだが期待値と違う」ならビット順の入れ替わりを疑う (例: 0xBF がビット逆転で 0xFD に見える等)。
  6. アドレスバス: ID コマンドは $5555/$2AAA への書き込みを使うため、 A0..A14 のどれかが違っていてもコマンドが届かず 0xFF になる。 配線表と 1 本ずつ突き合わせる(特に pin 1=A18 / pin 2=A16 / pin 30=A17 / pin 31=/WE の並びは間違えやすい)。
  7. 接触不良: ブレッドボードとジャンパワイヤの品質。チップを軽く押さえながら id を再実行して値が変わるなら接触不良。
  8. チップ自体: 別の個体に差し替えて再確認。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 互換機)で検証:

  1. id で ID 読み出し → OK(0xBF 0xB7 SST39SF040。 初回はハーネス半挿しで 0xFF 0xFF、挿し直しで解決)
  2. erase → OK(数秒)
  3. write(512KB)→ 旧ファームで受信バッファ溢れを検出、チャンクバッファリング 修正後に成功。実測約 240 バイト/秒。
  4. 書き込んだチップをカートリッジに装着して実機起動確認(issue #34 STEP 5–6)