エミュレータ移植時に詰まったことで、(自分のためにも)メモとして残しておきたいことをいくつか。
元ソースとの差分を取ればわかりますが、入力/出力以外のロジック大半は、ほぼ元コードのままです。というか、僕自身は、MZ-80Cのハードウェアや内部動作、エミュレータの仕組みについて全然詳しくない状態で、移植だけした感じです。スイマセン。
Arduinoでも、C / C++ のソースがここまでそのまま使えるもんなんですね。
移植にあたり、作業したことはこういう感じです。
M5Stack.h をinclude するために、Mzmain.c / MZhw.c / Mzscrn.c を *.cpp に。(とはいえ中身の記述は C のままです)
z80.h / Z80Code.h は extern “C” で include。
Webサーバでのコントロール関連はいったん削除。その後、M5Stackに合わせて省機能のWebサーバ機能を新規作成。
画面描画はM5Stackにあわせて実装。テレビくんで使った TFT_eSprite が活躍しました。
File読み込みはSDファイルからの読み込みに変更。
File dataFile = SD.open(romPath, FILE_READ); while (dataFile.available()) { data = dataFile.read(); (処理) } dataFile.close();
z80.h と Arduino.h の間で、word のtypedef の違いがありました。
z80.h: typedef unsigned short word; Arduino.h: typedef unsigned int word;
z80側で使っている箇所そんなに多くなかったので、z80ソース内の word を
typedef unsigned short WORD;
に書き換えて対応してます。
メモリ確保は ps_malloc で。PSRAMから確保するように変更。4MBの広大なメモリが使えます!
mz_font = (uint8_t*)ps_malloc(ROMFONT_SIZE);
「アプリ終了時にはいったん本体リセットするでしょ」って考えのもとに、free処理はひとつもしてません!(良い子はマネしちゃダメ)
pthread 処理はそのまま使えました。
int st; st = pthread_create(&scrn_thread_id, NULL, scrn_thread, NULL); if(st != 0) { perror("update_scrn_thread"); return; } pthread_detach(scrn_thread_id);
Arduino 標準の処理、loop() には入らずに、setup() の中で自分でループをさせています。(Arduinoの作り的にはよろしくないのかな?)
本スレッドでZ80の処理を行い、画面描画、キー入力、Web処理はスレッド作成して処理してます。
エミュレータと関係ないとこでも結構はまりました。
SDからの読み込み、Serial出力/入力で止まってしまうことがありました。前後に短時間delayを入れるとなぜか安定する様子。
→ M5.begin() の中で Serial.begin() されていたのに、その後で Serial.begin() を再度呼んでしまっていた。Serial.begin() を2回やってしまうと、変な不具合が発生するみたい。Serial.begin() を呼ばないようにしたら問題解決。
M5.begin(); Serial.begin();
これはダメ。
M5.begin();
これだけでOK。
エミュレータ開始後に、SDカードにアクセスしようとすると、プログラムが落ちてしまう。
SDカードと液晶ディスプレイを同時に使うと、同じ信号線を使っているとかで不具合が起きるのかも?
→SDアクセス時(ROMファイル、テープファイル読み込み)に画面の更新を停止することで改善しました。
処理を追加していると、全体的に不安定になってしまった。SDカードアクセスしようとしただけで再起動したりする。
コンソールログを確認すると、
Task watchdog got triggered. The following tasks did not reset the watchdog in time: 03:08:42.929 -> - IDLE (CPU 0) 03:08:42.929 -> Tasks currently running: 03:08:42.929 -> CPU 0: pthread 03:08:42.929 -> CPU 1: IDLE
というのが定期的に出ていた。
調べてみたら、ループ内でdelay()が全く呼ばれない時に発生するとのこと。そんなわけないのになー、と見直してみたら
if(synctime - (syncTmp - timeTmp) > 0){ delay(synctime - (syncTmp - timeTmp)); }
処理時間(syncTmp – timeTmp)が、待ちの時間(synctime)より長い場合、delay呼ばれてなかった…。
if(synctime - (syncTmp - timeTmp) > 0){ delay(synctime - (syncTmp - timeTmp)); }else{ delay(1); }
delay(1);を入れることで動作が超安定!
Wi-Fi アクセスポイントは、簡単でした。
const IPAddress apIP(192, 168, 4, 1); int randNumber = random(99999); String adSSIDString = "M5Z700_" + String(randNumber); const char* apSSID = adSSIDString.c_str(); WiFi.mode(WIFI_MODE_STA); WiFi.disconnect(true); delay(1000); WiFi.softAPConfig(apIP, apIP, IPAddress(255, 255, 255, 0)); WiFi.softAP(apSSID); WiFi.mode(WIFI_MODE_AP); message = "[SSID:" + String(apSSID) + "] http://" + apIP.toString();
SSIDはランダム数字付にしてます。起動する前に、いったん disconnect(true) して、ちょっと待つのが安定起動のコツらしい。
softAPConfig のパラメータは、IPアドレス, ゲートウェイ, サブネットマスク。
IPアドレスとゲートウェイは同じで、サブネットマスクは IPAddress(255, 255, 255, 0)で問題ないと思います。
Wi-Fi 接続するならこう。「WiFi.begin()」よりも「WiFiMulti」使った方がよいそうです。
WiFi.disconnect(true); delay(1000); WiFi.mode(WIFI_STA); wifiMulti.addAP(ssid, pass); Serial.println("Connecting Wifi..."); if(wifiMulti.run() == WL_CONNECTED) { //接続成功 Serial.println("IP address: "); Serial.println(WiFi.localIP()); }else{ //接続エラー return false; }
こちらも、いったんdisconnect() してちょっと待つのが安定起動のポイント。SSID と PASS 指定して接続します。
Webサーバもらくらくでした。
WebServer webServer(80);
ポート指定して、Webサーバ変数を定義。
webServer.on("/selectRom", selectRom);
こんな感じで、URLごとの処理を定義できます。上の例だと、Webブラウザから「http://[サーバIPアドレス]/selectRom 」に接続時に、selectRom 関数が呼び出されるようになります。
関数の中で、Webブラウザから送られたパラメータを取得することができます。形式は、POSTでもGETでもOK。助かるー。
String romFile = webServer.arg("romFile");
値は、URL Encode されているので、必要なら URL Decode してください。
関数終了時に、WebブラウザにHTMLを返す場合は、これでいけます。
webServer.send(200, "text/html", HTMLString ));
このあたりを定義した後で、Webサーバの起動。
webServer.begin();
スレッドループの中で
webServer.handleClient();
を呼ぶと、Webへの接続が処理されます。
webServer.stop();
で、Webサーバ停止します。webServer.handleClient(); は、起動時のみ呼び出されるようにフラグ制御しています。
音は ledcWriteTone で出しました。
こちらを参考にしました。
ESP32-WROOM-32
https://ht-deko.com/arduino/esp-wroom-32.html
setup() で
ledcSetup(LEDC_CHANNEL_0, LEDC_BASE_FREQ, LEDC_TIMER_13_BIT); ledcAttachPin(SPEAKER_PIN, LEDC_CHANNEL_0);
としておいて、
ledcWriteTone(LEDC_CHANNEL_0, 1000000 / freqtmp);
で、音が出ます。
単に周波数の音を出しているだけで、音色の計算しているわけではないので、音を出していても、動作が重くなっている感じはあまりないですね。音量調整ができなくて、ちょっと(かなり)うるさいです。深夜にはキビシイ。