ラズピコでN64吸出し

2023.04.22 更新

2023.04.22 ビルド環境について追記

2022.03.06 ページ公開

1.概要

 Raspberry Pi Pico(ラズピコ)を使って、N64カートリッジの吸出し機を作りました。以前にRGBA_CRTさんのサイトを参考に、FT232HでN64吸出し機を作ってみましたが、さらに安価でGPIO数も多いラズピコを使って作れたらいいなと思い、いろいろお勉強しながら作ってみました。ソフト作るのにかな~り苦戦しましたが、なんとか完成したので作製メモとして記録ておきます

2.ハードウェア

 ハードは非常にシンプルで、ラズピコのGPIO端子をN64カートリッジスロットの各端子に接続するだけです。あとはN64電源のON/OFF用トランジスタと、電源LEDを取付ければ完成です。

2.1.回路図

 N64カートリッジスロットのAD0~AD15(アドレスデータバスライン)にラズピコのGPIO7~GPIO22を接続、R/WピンとALE制御ピンにはラズピコのGPIO1~GPIO4を接続しました。これらのGPIOは連番である必要があります。

 N64電源ON/OFF用トランジスタのバイアスは、手元にあった抵抗器を利用したので適当ですが、hFEを1/3として考えても約160mAは駆動できる計算です(N64_3.3V)。

2.2.完成写真

 LEDが付いている方がN64カートリッジの前面になります。

 秋月のパワーグリッドユニバーサル基板、少し高いけどすごく便利です。(電源ラインとGNDラインがピッチ間を交差しています。)

 表面に取付けたN64カートリッジスロットをまたぐように裏面からラズピコを取付けることで省スペース化、短配線化を行なっています。

2.3.部材・費用

 材料費は約¥1,400でした。N64カートリッジスロット以外は秋月電子でそろいます。

3.ソフトウェア

 ラズピコを動かすためのソフトと、ラズピコとUSB通信するためのPC側のソフトを作製しました。


ラズピコ側ソフト

PIO(Programmable I/O)機能によりN64カートリッジデータを読取り、読取ったデータをTinyUSBによるUSBCDC通信でPC側へ転送します。
デュアルコアで動作させます。


PC側ソフト

シリアル通信ターミナルソフトです。ラズピコへの吸出し開始/中止命令の送信、データの受信・保存を行います。

3.1.ソースコード

↓クリックでダウンロードできます。

ラズピコ側ソフト -   開発環境:Visual Studio Code。 C/C++とpioasmでできています。

PC側ソフト(Windows) -   開発環境:Visual Studio 2022。 C#で.Net6.0 Windowsフォームアプリです。Windows10,11で動作確認済。


※実行ファイルは公開してません。

※このソフトウェアを使用したことにより生じたいかなる損害に関しても 一切責任は負いません。

※ラズピコ側ソフトはpico-sdk:version 1.2.0、TinyUSB:version 0.10.1を使用しています。

最新のSDK(pico-sdk:version 1.5.0、TinyUSB:version 0.15.0)でもビルドできますが、なぜか転送速度が遅くなります。2023/04/22確認

3.2.ラズピコ側ソフト解説

① main.cpp

・define、enum、グローバル変数

コメントの通りです。DumpState変数とUsbTransmitState変数を使用して、各コア間で状態遷移の通知をします。


・main関数

ボードペリフェラルとクリティカルセクションを初期化し、コア1をスタートさせています。

各コア間で共有する変数へのアクセスは、クリティカルセクションで排他制御します。

(セマフォ/ミューテックスは、各コア+割込みハンドラーからのアクセス排他制御には向いてないっぽい)


・core0_main関数

コア0では、TinyUSBによるUSBCDC通信を行なっています。具体的には、PCからの吸出し/キャンセル命令を待ち、命令を受信したら、DumpState変数を変更(start/stop)し、コア1へ通知します。また、UsbTransmitState変数をチェックし、コア1からの通知を待ちます。この変数がstartであれば、吸出したN64のバイナリデータをPCへ送信します。データ転送はリトルエンディアンで行われます。

PCからラズピコへの命令コードは以下のようにしました。


キャンセル命令 : (CNCL)

吸出し開始命令 : (DUMP,arg1,arg2,arg3,arg4)

arg1:PIOステートマシン分周器設定

arg2:永続読取りモード(0:OFF1:ON

arg3:連続してFFを受信したときに吸出しを停止する、そのバイト数。(0だと停止しない。)

arg4:連続して00を受信したときに吸出しを停止する、そのバイト数。(0だと停止しない。)


・core1_main関数

コア1では、コア0からの吸出し開始通知を待ち(DumpState変数startになったら開始)、開始通知を受けたら、PIO、DMA、DMA転送完了割込みハンドラーを開始します。そうすると、以下の流れで吸出し処理が実行されていきます。


PIOステートマシンがN64カートリッジからデータを読取る(4バイトごと)。

  ↓

②DMAによりPIOのRxFIFOからカートリッジ読取データ格納用変数へデータが転送される(4バイトごと)。

  ↓

③DMA転送完了割込みハンドラーがコア1で実行され、各種処理を行う。終了時以外は①から繰返し。


DMA転送完了割込みハンドラーは、ROMサイズ判定用のDmaHandler_CheckRomSizeと、USB転送通知用のDmaHandler_Transmitを用意しました。


・DmaHandler_CheckRomSize関数

ROMサイズ判定用のDMA転送完了割込みハンドラーです。まずはこの割込みハンドラーでROMのサイズ確認を実施します。判定方法は以下です。


判定方法A

カートリッジベースアドレス+0MiB,+4MiB,+8MiB,+12MiB,+16MiB,+20MiB,+24MiB,+32MiB,+40MiB,+48MiB,+64MiB先頭データを記録していき、同じ並びが現れたら、そのアドレスは領域外である。


判定方法B

カートリッジベースアドレス+0MiB,+4MiB,+8MiB,+12MiB,+16MiB,+20MiB,+24MiB,+32MiB,+40MiB,+48MiB,+64MiBと先頭データを確認していき、読取りブロックサイズすべてが00だったら、同じ箇所のデータを読取りブロックサイズを1/4に変更して4回に分けて読取る。その結果に違いがあったら、そのアドレスは領域外である。


※読取りブロックサイズというのは、1度のアドレス指定(ALE操作)で読取るバイト数のことです。


ROMサイズの判定が完了したら、DMA転送完了割込みハンドラーをUSB転送通知用のDmaHandler_Transmitへ切替え、カートリッジベースアドレスの先頭から読取っていくように設定します。


・DmaHandler_Transmit関数

USB転送通知用のDMA転送完了割込みハンドラーです。PIOステートマシンで読取ったデータ数がシリアル送信トリガサイズに達したら、コア0に対して、PCにデータ転送するよう通知します。終了条件に達するまでこれを繰返します。終了条件は、ターゲットROMサイズに達した場合、キャンセル命令を受信した場合、指定したFF/00連続受信数に達した場合です。

USB送信データ格納用変数については、格納したデータをコア0でUSB送信している間に、コア1でN64吸出しデータを格納していけるよう、AとBの2つを用意して切替えて使用しています。


N64DumpProtocol.pio

 PIOステートマシンのプログラムです。N64カートリッジデータの読取りを行います。


N64カートリッジの読取りプロトコルに関しては、以下のサイトが参考になります。
http://en64.shoutwiki.com/wiki/ROM


③ usb_descriptors.c

デバイスディスクリプタ、ストリングディスクリプタ

FTDI社のデバイスを使った吸出し機と比較して、ラズピコの難点がここ、USBベンダーIDが与えられてない点です。TinyUSBのサンプルプログラムでは0xcafeとなっていたのでそのままにしてありますが、他のデバイスと被ると問題なので自己責任で設定する必要があります。

3.3.PC側ソフト解説

 割愛。ラズピコに命令コード送って、受信データを保存してるだけです・・・。

4.動作確認

 私のPC環境だと800kB/s以上の速度で吸出せています。

 ラズピコのGPIOピンをロジアナで見てみると、約1.4msのUSB転送待ちが発生しているようです。どこがボトルネックになっているのか分かりませんが、USB転送処理速度が改善できれば、さらなる高速化ができそうです。

5.今後の展望


※とりあえず満足したから当分やらないと思う。疲れたし。

6.おまけ

 私が所有しているN64ソフトを吸出し、ROMサイズ、データ部のサイズ、空き領域の埋め方、領域外読取時の動作をまとめてみました。

N64ROM情報