Updates for the MZ80A version and ESP32 NCM

This commit is contained in:
Philip Smart
2026-04-18 22:32:41 +01:00
parent 8ef9652935
commit 6753eec367
36 changed files with 11110 additions and 333 deletions

View File

@@ -1,4 +1,33 @@
dependencies:
espressif/esp_tinyusb:
component_hash: 6a50305bc61c7a361da8c0833642be824e92dacb0a6001719a832a4e96e471bf
dependencies:
- name: idf
require: private
version: '>=5.0'
- name: espressif/tinyusb
registry_url: https://components.espressif.com
require: public
version: '>=0.14.2'
source:
registry_url: https://components.espressif.com/
type: service
version: 1.7.6~2
espressif/tinyusb:
component_hash: 5ea9d3b6d6b0734a0a0b3491967aa0e1bece2974132294dbda5dd2839b247bfa
dependencies:
- name: idf
require: private
version: '>=5.0'
source:
registry_url: https://components.espressif.com
type: service
targets:
- esp32s2
- esp32s3
- esp32p4
- esp32h4
version: 0.19.0~2
espressif/zlib:
component_hash: 999ec50086ac1c82b8321d8f540dc9fd10f5622948b935558aa16b4b66e95d9d
dependencies:
@@ -14,8 +43,9 @@ dependencies:
type: idf
version: 5.4.0
direct_dependencies:
- espressif/esp_tinyusb
- espressif/zlib
- idf
manifest_hash: 2f0b66dbb85a50adc3a11060f7e5fbd3b793495f9be963bfb1fd35580274858d
manifest_hash: 8ed9abdcca0db125fb3f8ee86dea821c12c99daf5033378c2158023f4a6971c7
target: esp32s3
version: 2.0.0

View File

@@ -1 +1 @@
2.03
2.07

View File

@@ -139,21 +139,23 @@ menu "pZ80 Configuration"
config IF_WIFI_ENABLED
bool "Enable WiFi connectivity"
default false
default true
help
Allow interface to act as an Access Point to allow external connectivity. Once connected the WiFi is intended to be used for making
key mapping changes.
Note: Using WiFi on a custom board with the ESP32-S3-PICO-1 module requires FCC/RED certification.
If certification has not been obtained, disable this option and use USB NCM instead.
config IF_WIFI_SSID
string "Default SSID in Access Point Mode"
default "pZ80"
default "pZ80"
depends on IF_WIFI_ENABLED
help
The SSID broadcast whilst the sharpkey module advertises wireless connectivity.
config IF_WIFI_DEFAULT_SSID_PWD
string "Default password for initial connection to Access Point Mode"
default "pZ80pZ80"
default "pZ80pZ80"
depends on IF_WIFI_ENABLED
help
The initial password needed to connect and logon to access point.
@@ -161,7 +163,7 @@ menu "pZ80 Configuration"
config IF_WIFI_MAX_RETRIES
int "Maximum number of connection retries."
range 0 100
default 10
default 10
depends on IF_WIFI_ENABLED
help
Number of retries allowed for making a wireless connection with a client.
@@ -169,15 +171,15 @@ menu "pZ80 Configuration"
config IF_WIFI_AP_CHANNEL
int "Channel of the Access Point."
range 0 13
default 7
default 7
depends on IF_WIFI_ENABLED
help
Channel use by the Access Point, default is 7.
config IF_WIFI_SSID_HIDDEN
int "Broadcast SSID?"
range 0 1
default 0
default 0
depends on IF_WIFI_ENABLED
help
Broadcast the SSID (0) or hide it (1).
@@ -185,12 +187,41 @@ menu "pZ80 Configuration"
config IF_WIFI_MAX_CONNECTIONS
int "Maximum simultaneous connections."
range 0 20
default 5
default 5
depends on IF_WIFI_ENABLED
help
Maximum number of simultaneous open connections supported.
endmenu
menu "USB NCM Network"
config IF_USB_NCM_ENABLED
bool "Enable USB NCM network interface"
default true
help
Present a USB Ethernet (CDC-NCM) adapter on the ESP32 USB OTG port (GPIO 19/20).
The host PC sees a network adapter and can browse to the configured IP address
for status, configuration and firmware updates.
This provides the same browser-based interface as WiFi but over USB, and does not
require FCC/RED certification as no intentional RF emission is involved.
config IF_USB_NCM_IP
string "USB NCM interface IP address"
default "192.168.7.1"
depends on IF_USB_NCM_ENABLED
help
IP address of the ESP32 on the USB NCM network. The host PC will access the
configuration pages at this address (e.g. http://192.168.7.1).
config IF_USB_NCM_NETMASK
string "USB NCM interface netmask"
default "255.255.255.0"
depends on IF_USB_NCM_ENABLED
help
Netmask for the USB NCM network interface.
endmenu
choice LOGGING_OPTIONS
prompt "Logging Output Mode"
default USE_RP2350_OUTPUT

View File

@@ -38,7 +38,7 @@
/////////////////////////////////////////////////////////////////////////////////////////////////////////
#include "sdkconfig.h"
#if defined(CONFIG_IF_WIFI_ENABLED)
#if defined(CONFIG_IF_WIFI_ENABLED) || defined(CONFIG_IF_USB_NCM_ENABLED)
#include <stdio.h>
#include <stdlib.h>
@@ -60,7 +60,9 @@
#include "esp_mac.h"
#include "freertos/event_groups.h"
#include "esp_system.h"
#if defined(CONFIG_IF_WIFI_ENABLED)
#include "esp_wifi.h"
#endif
#include "esp_event.h"
#include "esp_ota_ops.h"
#include "esp_timer.h"
@@ -91,8 +93,10 @@ extern "C"
extern void CP_queueCmd(const char *cmd);
extern bool CP_sendCmd(const char *cmd, uint32_t timeoutMs);
#if defined(CONFIG_IF_WIFI_ENABLED)
// FreeRTOS event group to signal when we are connected
static EventGroupHandle_t sWifiEventGroup;
#endif
// globals (consider protecting with mutex if worried about concurrent users)
std::string gLastSelectedFloppy; // only used briefly between ?file= and processing
@@ -678,24 +682,30 @@ esp_err_t WiFi::expandVarsAndSend(httpd_req_t *req, std::string str)
pairs.push_back(keyValue);
{
static char txPwrStr[8];
snprintf(txPwrStr, sizeof(txPwrStr), "%d", wifiConfig.params.txPower);
keyValue.name = "%SK_TXPOWER%";
keyValue.value = txPwrStr;
pairs.push_back(keyValue);
static char txPwrDbmStr[16];
static char rssiStr[8];
#if defined(CONFIG_IF_WIFI_ENABLED)
snprintf(txPwrStr, sizeof(txPwrStr), "%d", wifiConfig.params.txPower);
int8_t actualPwr = 0;
esp_wifi_get_max_tx_power(&actualPwr);
snprintf(txPwrDbmStr, sizeof(txPwrDbmStr), "%.1f", actualPwr * 0.25f);
keyValue.name = "%SK_TXPOWERDBM%";
keyValue.value = txPwrDbmStr;
pairs.push_back(keyValue);
// Current RSSI (signal strength). Only meaningful in client mode when connected.
static char rssiStr[8];
wifi_ap_record_t apInfo;
if (wifiCtrl.client.connected && esp_wifi_sta_get_ap_info(&apInfo) == ESP_OK)
snprintf(rssiStr, sizeof(rssiStr), "%d", apInfo.rssi);
else
snprintf(rssiStr, sizeof(rssiStr), "N/A");
#else
snprintf(txPwrStr, sizeof(txPwrStr), "0");
snprintf(txPwrDbmStr, sizeof(txPwrDbmStr), "N/A");
snprintf(rssiStr, sizeof(rssiStr), "N/A");
#endif
keyValue.name = "%SK_TXPOWER%";
keyValue.value = txPwrStr;
pairs.push_back(keyValue);
keyValue.name = "%SK_TXPOWERDBM%";
keyValue.value = txPwrDbmStr;
pairs.push_back(keyValue);
keyValue.name = "%SK_RSSI%";
keyValue.value = rssiStr;
pairs.push_back(keyValue);
@@ -721,6 +731,42 @@ esp_err_t WiFi::expandVarsAndSend(httpd_req_t *req, std::string str)
keyValue.name = "%SK_ERRMSG%";
keyValue.value = wifiCtrl.run.errorMsg;
pairs.push_back(keyValue);
keyValue.name = "%SK_WIFINAVVISIBLE%";
#if defined(CONFIG_IF_WIFI_ENABLED)
keyValue.value = "";
#else
keyValue.value = "style=\"display:none\"";
#endif
pairs.push_back(keyValue);
keyValue.name = "%SK_USBNCMVISIBLE%";
#if !defined(CONFIG_IF_WIFI_ENABLED) && defined(CONFIG_IF_USB_NCM_ENABLED)
keyValue.value = "";
#else
keyValue.value = "style=\"display:none\"";
#endif
pairs.push_back(keyValue);
keyValue.name = "%SK_NETPANELTITLE%";
#if defined(CONFIG_IF_WIFI_ENABLED)
keyValue.value = "WiFi Configuration";
#else
keyValue.value = "Network Configuration";
#endif
pairs.push_back(keyValue);
keyValue.name = "%SK_NETPANELICON%";
#if defined(CONFIG_IF_WIFI_ENABLED)
keyValue.value = "fa-wifi";
#else
keyValue.value = "fa-usb";
#endif
pairs.push_back(keyValue);
#if defined(CONFIG_IF_USB_NCM_ENABLED)
keyValue.name = "%SK_USBNCMIP%";
keyValue.value = CONFIG_IF_USB_NCM_IP;
pairs.push_back(keyValue);
keyValue.name = "%SK_USBNCMNM%";
keyValue.value = CONFIG_IF_USB_NCM_NETMASK;
pairs.push_back(keyValue);
#endif
keyValue.name = "%SK_DEVICE%";
keyValue.value = wifiCtrl.run.hostDeviceDisplayName;
pairs.push_back(keyValue);
@@ -1482,12 +1528,14 @@ esp_err_t WiFi::defaultDataGETHandler(httpd_req_t *req)
// Lightweight JSON endpoint for AJAX polling of WiFi signal + system status.
char json[256];
int8_t txPwr = 0;
esp_wifi_get_max_tx_power(&txPwr);
wifi_ap_record_t apInfo;
int rssi = 0;
bool connected = pThis->wifiCtrl.client.connected;
#if defined(CONFIG_IF_WIFI_ENABLED)
esp_wifi_get_max_tx_power(&txPwr);
wifi_ap_record_t apInfo;
if (connected && esp_wifi_sta_get_ap_info(&apInfo) == ESP_OK)
rssi = apInfo.rssi;
#endif
// Compute uptime string
int64_t us = esp_timer_get_time();
int secs = (int) (us / 1000000);
@@ -3541,12 +3589,13 @@ esp_err_t WiFi::sendFileManagerDir(httpd_req_t *req)
// Slot 5: gap + Delete (always, one slot space from last icon)
htmlStr.append("<span style=\"display:inline-block;width:30px;\"></span>");
htmlStr.append("<span style=\"display:inline-block;width:30px;\">");
htmlStr.append("<form method=\"GET\" style=\"display:inline\" action=\"?cmd=del\">");
htmlStr.append("<form method=\"GET\" style=\"display:inline\">");
htmlStr.append("<input type=\"hidden\" name=\"cmd\" value=\"del\">");
htmlStr.append("<input type=\"hidden\" name=\"id\" value=\"").append(std::to_string(entryCnt)).append("\">");
htmlStr.append("<input type=\"hidden\" name=\"name\" value=\"").append(entry->d_name).append("\">");
htmlStr.append("<input type=\"hidden\" name=\"dir\" value=\"").append(directory).append("\">");
htmlStr.append("<button type=\"submit\" name=\"cmd\" value=\"del\" class=\"fa fa-trash wm-button-small\" "
"aria-hidden=\"true\" title=\"Delete\" onclick=\"return confirmDelete('")
htmlStr.append("<button type=\"submit\" class=\"fa fa-trash wm-button-small\" "
"aria-hidden=\"true\" title=\"Delete\" onclick=\"return confirmDelete(event, '")
.append(entry->d_name)
.append("');\"></button>");
htmlStr.append("</form>");
@@ -3606,21 +3655,33 @@ esp_err_t WiFi::defaultRebootHandler(httpd_req_t *req)
{
if (uriStr.substr(0, 5) == "esp32")
{
// Build a response message.
if (!pThis->wifiConfig.clientParams.useDHCP)
// Determine the redirect IP from the request's Host header so we return
// to the same interface (WiFi or USB NCM) the user is currently using.
char hostBuf[64] = {};
std::string redirectIP;
if (httpd_req_get_hdr_value_str(req, "Host", hostBuf, sizeof(hostBuf)) == ESP_OK)
{
resp = "<head> <meta http-equiv=\"refresh\" content=\"3; URL=http://" + std::string(pThis->wifiConfig.clientParams.ip) +
"/\" /> </head><body><font size=\"6\" face=\"verdana\" color=\"red\"/>Rebooting... </font><font size=\"5\" face=\"verdana\" "
"color=\"black\"/>Please wait.</font></body>";
redirectIP = hostBuf;
// Strip port number if present (e.g. "192.168.7.1:80" -> "192.168.7.1")
size_t colonPos = redirectIP.find(':');
if (colonPos != std::string::npos)
redirectIP = redirectIP.substr(0, colonPos);
}
else if (!pThis->wifiConfig.clientParams.useDHCP)
{
redirectIP = pThis->wifiConfig.clientParams.ip;
}
else
{
resp = "<head> <meta http-equiv=\"refresh\" content=\"3; URL=http://" + std::string(pThis->wifiCtrl.client.ip) +
"/\" /> </head><body><font size=\"6\" face=\"verdana\" color=\"red\"/>Rebooting... </font><br><font size=\"4\" face=\"verdana\" "
"color=\"black\"/><p>If this screen doesnt auto-refresh, please look in your router admin panel for the assigned IP address and enter "
"http://<router assigned ip address> into browser to continue.</p></font></body>";
redirectIP = pThis->wifiCtrl.client.ip;
}
// Build a response with a meta-refresh back to the originating IP.
resp = "<head> <meta http-equiv=\"refresh\" content=\"5; URL=http://" + redirectIP +
"/\" /> </head><body><font size=\"6\" face=\"verdana\" color=\"red\"/>Rebooting... </font><br><font size=\"4\" face=\"verdana\" "
"color=\"black\"/><p>Redirecting to http://" + redirectIP + "/ in 5 seconds. "
"If this screen doesnt auto-refresh, please reload manually.</p></font></body>";
// Send the response and wait a while, then request reboot.
result = httpd_resp_send(req, resp.c_str(), resp.size() + 1);
if (result == ESP_OK)
@@ -4286,15 +4347,14 @@ bool WiFi::startWebserver(void)
// Set to 14 to handle 12 concurrent requests with 2 slots spare.
// CONFIG_LWIP_MAX_SOCKETS must be >= max_open_sockets + system sockets (~2);
// it is set to 16 in sdkconfig.
config.max_open_sockets = 16; // LWIP_MAX_SOCKETS(20) - 3 internal - 1 spare = 16
config.max_open_sockets = 20; // LWIP_MAX_SOCKETS(24) - 3 internal - 1 spare = 20
// Keep keep-alive enabled (HTTP/1.1 default) so the browser can multiplex
// multiple resource requests over one TCP connection. Disabling it forces
// a new TCP connection per resource; with max_open_sockets=7 the browser's
// 6-8 parallel requests exhaust the pool and JS/CSS files are silently
// dropped. Socket errors are now handled by explicit close() calls so
// keep-alive is safe — responses always terminate with the zero-length chunk.
config.recv_wait_timeout = 30; // close idle/broken sockets after 30 s
config.send_wait_timeout = 30;
// multiple resource requests over one TCP connection. USB NCM at Full Speed
// (12 Mbps) is much slower than WiFi, so active transfers hold sockets longer.
// 20 sockets with a 5s idle timeout gives enough headroom for pages that load
// 12+ resources in parallel without LRU-purging active transfers.
config.recv_wait_timeout = 5;
config.send_wait_timeout = 10;
// Setup the required paths and descriptors then register them with the server.
const httpd_uri_t dataPOST = {.uri = "/data", .method = HTTP_POST, .handler = defaultDataPOSTHandler, .user_ctx = this};
@@ -4364,6 +4424,7 @@ void WiFi::stopWebserver(void)
wifiCtrl.run.server = NULL;
}
#if defined(CONFIG_IF_WIFI_ENABLED)
// Event handler for Client mode Wifi event callback.
void WiFi::wifiClientHandler(void *arg, esp_event_base_t event_base, int32_t event_id, void *event_data)
{
@@ -4488,18 +4549,21 @@ bool WiFi::setupWifiClient(void)
{
ESP_LOGI(WIFITAG, "Couldnt create event group, disabling WiFi.");
}
// Setup the network interface.
// Setup the network interface. esp_netif_init() is idempotent — safe if USB NCM called it first.
else if (esp_netif_init() != ESP_OK)
{
ESP_LOGI(WIFITAG, "Couldnt initialise netif, disabling WiFi.");
}
// Setup the event loop.
else if (esp_event_loop_create_default() != ESP_OK)
{
ESP_LOGI(WIFITAG, "Couldnt initialise event loop, disabling WiFi.");
}
// Setup the event loop. Tolerate ESP_ERR_INVALID_STATE (already created by USB NCM init).
else
{
esp_err_t loopErr = esp_event_loop_create_default();
if (loopErr != ESP_OK && loopErr != ESP_ERR_INVALID_STATE)
{
ESP_LOGI(WIFITAG, "Couldnt initialise event loop, disabling WiFi.");
}
else
{
// Setup the wifi client (station).
netConfig = esp_netif_create_default_wifi_sta();
// If fixed IP is configured, set it up.
@@ -4590,12 +4654,29 @@ bool WiFi::setupWifiClient(void)
ESP_LOGI(WIFITAG, "TX power using default: %d (%.1f dBm)", actual, actual * 0.25f);
}
// Waiting until either the connection is established (WIFI_CONNECTED_BIT) or connection failed for the maximum
// number of re-tries (WIFI_FAIL_BIT). The bits are set by wifiClientHandler() (see above)
// When USB NCM is enabled, don't block the main task waiting for WiFi —
// the webserver is already running on the USB NCM interface and needs
// the main task responsive. WiFi will connect asynchronously via the
// event handler (wifiClientHandler), which starts the webserver if needed.
// Without USB NCM, keep the original blocking behaviour to maintain
// backwards compatibility.
#if defined(CONFIG_IF_USB_NCM_ENABLED)
// Non-blocking: wait briefly then return success regardless.
// WiFi connection continues in the background via event handlers.
bits = xEventGroupWaitBits(sWifiEventGroup, WIFI_CONNECTED_BIT | WIFI_FAIL_BIT, pdFALSE, pdFALSE, pdMS_TO_TICKS(5000));
if (bits & WIFI_CONNECTED_BIT)
{
ESP_LOGI(WIFITAG, "WiFi connected.");
}
else
{
ESP_LOGI(WIFITAG, "WiFi connecting in background (USB NCM active).");
}
retVal = true; // Don't reboot — USB NCM is functional.
#else
bits = xEventGroupWaitBits(sWifiEventGroup, WIFI_CONNECTED_BIT | WIFI_FAIL_BIT, pdFALSE, pdFALSE, portMAX_DELAY);
if (bits & WIFI_CONNECTED_BIT)
{
//ESP_LOGI(WIFITAG, "Connected: SSID:%s password:%s", this->wifiConfig.clientParams.ssid, this->wifiConfig.clientParams.pwd);
retVal = true;
}
else if (bits & WIFI_FAIL_BIT)
@@ -4606,6 +4687,8 @@ bool WiFi::setupWifiClient(void)
{
ESP_LOGE(WIFITAG, "Unknown event, bits:%ld", (unsigned long) bits);
}
#endif
}
}
}
@@ -4633,17 +4716,21 @@ bool WiFi::setupWifiAP(void)
wifiConfig.ap.beacon_interval = 100;
bool retVal = false;
// Initialize the network interface.
// Initialize the network interface. esp_netif_init() is idempotent.
if (esp_netif_init() != ESP_OK)
{
ESP_LOGI(WIFITAG, "Couldnt initialise network interface, disabling WiFi.");
}
else if ((retCode = esp_event_loop_create_default()) != ESP_OK)
{
ESP_LOGI(WIFITAG, "Couldnt create default loop(%d), disabling WiFi.", retCode);
}
else
{
// Tolerate ESP_ERR_INVALID_STATE (already created by USB NCM init).
retCode = esp_event_loop_create_default();
if (retCode != ESP_OK && retCode != ESP_ERR_INVALID_STATE)
{
ESP_LOGI(WIFITAG, "Couldnt create default loop(%d), disabling WiFi.", retCode);
}
else
{
// Create the default Access Point.
wifiAP = esp_netif_create_default_wifi_ap();
// Setup the base parameters of the Access Point which may differ from ESP32 defaults.
@@ -4741,6 +4828,7 @@ bool WiFi::setupWifiAP(void)
}
}
}
}
}
// Return fail/success.
@@ -4812,6 +4900,7 @@ void WiFi::run(void)
}
}
}
#endif // CONFIG_IF_WIFI_ENABLED
// readRP2350Info — superseded by binary IPC.
// The RP2350 now sends the flash partition header proactively via the INF IPC command
@@ -4900,13 +4989,19 @@ void WiFi::init(bool defaultMode, uint16_t device, NVS *nvs, SDCard *sdcard, cJS
wifiConfig.clientParams.ip[0] = '\0';
wifiConfig.clientParams.netmask[0] = '\0';
wifiConfig.clientParams.gateway[0] = '\0';
#if defined(CONFIG_IF_WIFI_ENABLED)
strncpy(wifiConfig.apParams.ssid, CONFIG_IF_WIFI_SSID, MAX_WIFI_SSID_LEN);
strncpy(wifiConfig.apParams.pwd, CONFIG_IF_WIFI_DEFAULT_SSID_PWD, MAX_WIFI_PWD_LEN);
wifiConfig.params.wifiMode = WIFI_CONFIG_AP;
#else
wifiConfig.apParams.ssid[0] = '\0';
wifiConfig.apParams.pwd[0] = '\0';
wifiConfig.params.wifiMode = WIFI_OFF;
#endif
strncpy(wifiConfig.apParams.ip, WIFI_AP_DEFAULT_IP, MAX_WIFI_IP_LEN);
strncpy(wifiConfig.apParams.netmask, WIFI_AP_DEFAULT_NETMASK, MAX_WIFI_NETMASK_LEN);
strncpy(wifiConfig.apParams.gateway, WIFI_AP_DEFAULT_GW, MAX_WIFI_GATEWAY_LEN);
wifiConfig.params.wifiMode = WIFI_CONFIG_AP;
wifiConfig.params.txPower = 0; // 0 = use ESP-IDF default.
wifiConfig.params.txPower = 0;
if (!nvs->persistData(wifiCtrl.run.thisClass.c_str(), &this->wifiConfig, sizeof(t_wifiConfig)))
{
@@ -5002,13 +5097,19 @@ WiFi::WiFi(bool defaultMode, uint16_t device, NVS *nvs, SDCard *sdcard, cJSON *c
wifiConfig.clientParams.ip[0] = '\0';
wifiConfig.clientParams.netmask[0] = '\0';
wifiConfig.clientParams.gateway[0] = '\0';
#if defined(CONFIG_IF_WIFI_ENABLED)
strncpy(wifiConfig.apParams.ssid, CONFIG_IF_WIFI_SSID, MAX_WIFI_SSID_LEN);
strncpy(wifiConfig.apParams.pwd, CONFIG_IF_WIFI_DEFAULT_SSID_PWD, MAX_WIFI_PWD_LEN);
wifiConfig.params.wifiMode = WIFI_CONFIG_AP;
#else
wifiConfig.apParams.ssid[0] = '\0';
wifiConfig.apParams.pwd[0] = '\0';
wifiConfig.params.wifiMode = WIFI_OFF;
#endif
strncpy(wifiConfig.apParams.ip, WIFI_AP_DEFAULT_IP, MAX_WIFI_IP_LEN);
strncpy(wifiConfig.apParams.netmask, WIFI_AP_DEFAULT_NETMASK, MAX_WIFI_NETMASK_LEN);
strncpy(wifiConfig.apParams.gateway, WIFI_AP_DEFAULT_GW, MAX_WIFI_GATEWAY_LEN);
wifiConfig.params.wifiMode = WIFI_CONFIG_AP;
wifiConfig.params.txPower = 0; // 0 = use ESP-IDF default.
wifiConfig.params.txPower = 0;
if (!nvs->persistData(wifiCtrl.run.thisClass.c_str(), &this->wifiConfig, sizeof(t_wifiConfig)))
{

View File

@@ -1,17 +1,11 @@
## IDF Component Manager Manifest File
dependencies:
espressif/zlib: "^1.3.0"
espressif/esp_tinyusb:
version: "^1.3.0"
rules:
- if: "idf_version >=5.0"
- if: "target in [esp32s2, esp32s3]"
## Required IDF version
idf:
version: ">=4.1.0"
# # Put list of dependencies here
# # For components maintained by Espressif:
# component: "~1.0.0"
# # For 3rd party components:
# username/component: ">=1.0.0,<2.0.0"
# username2/component2:
# version: "~1.0.0"
# # For transient dependencies `public` flag can be set.
# # `public` flag doesn't have an effect dependencies of the `main` component.
# # All dependencies of `main` are public by default.
# public: true
version: ">=5.0"

View File

@@ -30,10 +30,12 @@
#ifndef WIFI_H
#define WIFI_H
#if defined(CONFIG_IF_WIFI_ENABLED)
#if defined(CONFIG_IF_WIFI_ENABLED) || defined(CONFIG_IF_USB_NCM_ENABLED)
#include "freertos/event_groups.h"
#include "esp_system.h"
#if defined(CONFIG_IF_WIFI_ENABLED)
#include "esp_wifi.h"
#endif
#include "esp_event.h"
#include "nvs_flash.h"
#include "lwip/err.h"
@@ -161,7 +163,9 @@ class WiFi
WiFi(bool defaultMode, uint16_t device, NVS *nvs, SDCard *sdcard, cJSON *config, const char *fsPath, t_versionList *versionList);
WiFi(void);
~WiFi(void);
#if defined(CONFIG_IF_WIFI_ENABLED)
void run(void);
#endif
void init(bool defaultMode, uint16_t device, NVS *nvs, SDCard *sdcard, cJSON *config, const char *fsPath, t_versionList *versionList);
void readRP2350Info(const std::string &inParam, FSPI &fspi);
bool doReboot(void);
@@ -169,6 +173,7 @@ class WiFi
bool setFloppyDiskFile(const std::string &param1, int diskNo);
bool setQuickDiskFile(const std::string &param1, int diskNo);
bool setRamFile(const std::string &param1, int ramfileNo);
bool startWebserver(void);
// Non Volatile Storage handle.
NVS *nvs;
@@ -341,10 +346,11 @@ class WiFi
t_wifiControl wifiCtrl;
// Prototypes.
#if defined(CONFIG_IF_WIFI_ENABLED)
bool setupWifiClient(void);
bool setupWifiAP(void);
bool stopWifi(void);
bool startWebserver(void);
#endif
void stopWebserver(void);
float getVersionNumber(const std::string &name);
esp_err_t expandAndSendFile(httpd_req_t *req, const char *basePath, const std::string &fileName);
@@ -403,8 +409,10 @@ class WiFi
static esp_err_t defaultTasksHandler(httpd_req_t *req);
static esp_err_t defaultRecoverHandler(httpd_req_t *req);
static esp_err_t defaultFileHandler(httpd_req_t *req);
#if defined(CONFIG_IF_WIFI_ENABLED)
static void wifiAPHandler(void *arg, esp_event_base_t event_base, int32_t event_id, void *event_data);
static void wifiClientHandler(void *arg, esp_event_base_t event_base, int32_t event_id, void *event_data);
#endif
};
#endif

View File

@@ -71,6 +71,15 @@
#include "SDCard.h"
#include "cJSON.h"
#include "CommandProcessor.h"
#if defined(CONFIG_IF_USB_NCM_ENABLED)
#include "tinyusb.h"
#include "tinyusb_net.h"
#include "tusb_cdc_acm.h"
#include "tusb_console.h"
#include "esp_netif.h"
#include "esp_mac.h"
#include "lwip/esp_netif_net_stack.h"
#endif
// Defined in CommandProcessor.cpp — ESP32→RP2350 reverse command dispatch.
extern void CP_queueCmd(const char *cmd); // async: fire and forget
@@ -213,7 +222,7 @@ float version(void)
// automatically; no setter call needed.
// Call this AFTER CommandProcessor::start() — SPI is live by then.
//
#if defined(CONFIG_IF_WIFI_ENABLED)
#if defined(CONFIG_IF_WIFI_ENABLED) || defined(CONFIG_IF_USB_NCM_ENABLED)
WiFi *initWiFi(NVS &nvs, SDCard &sdcard, cJSON *esp32Config, bool defaultMode, uint16_t device, WiFi::t_versionList **versionListOut)
{
@@ -300,7 +309,146 @@ void buildVersionList(WiFi::t_versionList *versionList, NVS &nvs, SDCard &sdcard
}
}
}
#endif
#endif // CONFIG_IF_WIFI_ENABLED || CONFIG_IF_USB_NCM_ENABLED
// USB NCM network interface setup.
// Creates a USB Ethernet (CDC-NCM) adapter on the ESP32-S3 USB OTG port (GPIO 19/20).
// The host PC sees a network adapter and can browse to the configured IP for configuration.
// This provides the same browser-based interface as WiFi but over USB, without requiring
// FCC/RED certification as no intentional RF emission is involved.
#if defined(CONFIG_IF_USB_NCM_ENABLED)
static esp_netif_t *s_usb_ncm_netif = NULL;
static void usb_ncm_l2_free(void *h, void *buffer)
{
free(buffer);
}
static esp_err_t usb_ncm_netif_transmit(void *h, void *buffer, size_t len)
{
if (tinyusb_net_send_sync(buffer, len, NULL, pdMS_TO_TICKS(1000)) != ESP_OK) {
ESP_LOGE(MAINTAG, "USB NCM: failed to send buffer");
}
return ESP_OK;
}
static esp_err_t usb_ncm_recv_callback(void *buffer, uint16_t len, void *ctx)
{
if (s_usb_ncm_netif) {
void *buf_copy = malloc(len);
if (!buf_copy) {
return ESP_ERR_NO_MEM;
}
memcpy(buf_copy, buffer, len);
return esp_netif_receive(s_usb_ncm_netif, buf_copy, len, NULL);
}
return ESP_OK;
}
// Phase 0: Install TinyUSB composite device (CDC-ACM + CDC-NCM), create the
// lwIP network interface, and redirect console to the CDC-ACM serial port.
//
// CRITICAL: The lwIP netif MUST be created before the host finishes USB
// enumeration and starts probing the NCM link (ARP/NDP). If the netif isn't
// ready, usb_ncm_recv_callback() drops all packets (s_usb_ncm_netif == NULL),
// the host gets no ARP replies, and marks the link as inactive.
//
// For this reason, esp_netif_init() and esp_event_loop_create_default() are
// called here (both are idempotent / tolerate double-init) so the netif can
// be created immediately after the TinyUSB NCM class is initialized.
bool setupUSBConsole(void)
{
bool cdcOk = false;
bool ncmOk = false;
// 0. Initialise the TCP/IP stack and event loop early so we can create
// the esp_netif before the host starts probing.
esp_netif_init();
esp_event_loop_create_default();
// 1. Install TinyUSB driver on the OTG peripheral (GPIO 19/20).
tinyusb_config_t tusb_cfg = {};
tusb_cfg.external_phy = false;
esp_err_t ret = tinyusb_driver_install(&tusb_cfg);
if (ret != ESP_OK) {
return false;
}
// 2. Force a clean USB disconnect/reconnect cycle. After a software reboot
// (esp_restart), the USB peripheral may not fully reset — the host sees a
// glitch rather than a proper disconnect. macOS then tries to resume the
// previous NCM session state, which fails and leaves the link "inactive".
// By explicitly pulling D+ low (tud_disconnect) and then re-enabling it
// (tud_connect), the host sees a clean device removal followed by a fresh
// enumeration, regardless of whether this was a power cycle or soft reboot.
tud_disconnect();
vTaskDelay(pdMS_TO_TICKS(1500));
tud_connect();
// 3. Initialise CDC-ACM for serial logging.
tinyusb_config_cdcacm_t acm_cfg = {};
ret = tusb_cdc_acm_init(&acm_cfg);
cdcOk = (ret == ESP_OK);
// 4. Initialise the NCM network class handler.
tinyusb_net_config_t net_config = {};
net_config.mac_addr[0] = 0x02; net_config.mac_addr[1] = 0x02;
net_config.mac_addr[2] = 0x11; net_config.mac_addr[3] = 0x22;
net_config.mac_addr[4] = 0x33; net_config.mac_addr[5] = 0x01;
net_config.on_recv_callback = usb_ncm_recv_callback;
ret = tinyusb_net_init(TINYUSB_USBDEV_0, &net_config);
ncmOk = (ret == ESP_OK);
// 5. Create the lwIP netif IMMEDIATELY so received packets are handled
// as soon as the host starts probing (ARP, NDP, DHCP).
if (ncmOk) {
esp_netif_ip_info_t ip_info = {};
ip_info.ip.addr = ipaddr_addr(CONFIG_IF_USB_NCM_IP);
ip_info.netmask.addr = ipaddr_addr(CONFIG_IF_USB_NCM_NETMASK);
ip_info.gw.addr = ip_info.ip.addr;
uint8_t lwip_mac[6] = {0x02, 0x02, 0x11, 0x22, 0x33, 0x02};
esp_netif_inherent_config_t base_cfg = {};
base_cfg.flags = (esp_netif_flags_t)(ESP_NETIF_DHCP_SERVER | ESP_NETIF_FLAG_AUTOUP);
base_cfg.ip_info = &ip_info;
base_cfg.if_key = "usbncm";
base_cfg.if_desc = "USB NCM network interface";
base_cfg.route_prio = 10;
esp_netif_driver_ifconfig_t driver_cfg = {};
driver_cfg.handle = (void *)1;
driver_cfg.transmit = usb_ncm_netif_transmit;
driver_cfg.driver_free_rx_buffer = usb_ncm_l2_free;
struct esp_netif_netstack_config lwip_netif_config = {};
lwip_netif_config.lwip.init_fn = ethernetif_init;
lwip_netif_config.lwip.input_fn = ethernetif_input;
esp_netif_config_t cfg = {};
cfg.base = &base_cfg;
cfg.driver = &driver_cfg;
cfg.stack = &lwip_netif_config;
s_usb_ncm_netif = esp_netif_new(&cfg);
if (s_usb_ncm_netif != NULL) {
esp_netif_set_mac(s_usb_ncm_netif, lwip_mac);
uint32_t lease_opt = 120; // DHCP lease time in minutes
esp_netif_dhcps_option(s_usb_ncm_netif, ESP_NETIF_OP_SET,
ESP_NETIF_IP_ADDRESS_LEASE_TIME, &lease_opt, sizeof(lease_opt));
esp_netif_action_start(s_usb_ncm_netif, 0, 0, 0);
}
}
// 6. Redirect stdout/stderr/stdin to the TinyUSB CDC-ACM port.
if (cdcOk) {
esp_tusb_init_console(TINYUSB_CDC_ACM_0);
}
return cdcOk;
}
#endif // CONFIG_IF_USB_NCM_ENABLED
// Setup phase 1 — fast path: eFUSE, NVS, SPI slave.
// Called before CommandProcessor::start() so the SPI slave is listening as
@@ -634,7 +782,9 @@ extern "C"
static FSPI fspi;
static cJSON *esp32Config;
bool rp2350ConfigChanged = false;
#if defined(CONFIG_IF_WIFI_ENABLED) || defined(CONFIG_IF_USB_NCM_ENABLED)
WiFi *wifi;
#endif
static int loopCount = 0;
static bool wifiStarted = false;
static RTC_NOINIT_ATTR int powerOn = 1;
@@ -661,15 +811,25 @@ extern "C"
IO_init(LOGGING_FRAMED);
#endif
// Install TinyUSB composite device (CDC-ACM + NCM) and redirect console
// to CDC-ACM as early as possible so all subsequent ESP_LOG output is visible.
#if defined(CONFIG_IF_USB_NCM_ENABLED)
if (setupUSBConsole()) {
ESP_LOGW(MAINTAG, "USB CDC-ACM console active — log output on TinyUSB serial port.");
}
#endif
// Phase 1: fast setup — eFUSE, NVS, SPI slave (~60 ms).
// NVS loads tzpuPicoConfig (bootMode, deviceMode) needed by initWiFi().
ESP_LOGW(MAINTAG, "Initialising hardware (phase 1: NVS + SPI).");
setupEarly(nvs, fspi);
// Create WiFi object immediately (NVS credentials, empty version list, ~10 ms).
ESP_LOGW(MAINTAG, "Initialising WiFi object.");
// Create WiFi/Web object immediately (NVS credentials, empty version list, ~10 ms).
#if defined(CONFIG_IF_WIFI_ENABLED) || defined(CONFIG_IF_USB_NCM_ENABLED)
ESP_LOGW(MAINTAG, "Initialising Web interface object.");
WiFi::t_versionList *versionList;
wifi = initWiFi(nvs, sdcard, NULL, tzpuPicoConfig.params.bootMode, tzpuPicoConfig.params.deviceMode, &versionList);
#endif
// Start CommandProcessor — SPI slave is NOW listening for RP2350 commands.
// Total time from power-on to here: ~170 ms (60 ms setup + 10 ms WiFi + 100 ms task delay).
@@ -697,8 +857,10 @@ extern "C"
//IO_setLogMode(LOGGING_FILE);
// Populate the version list in-place — WiFi holds the pointer and sees it automatically.
ESP_LOGW(MAINTAG, "Building WiFi version list.");
#if defined(CONFIG_IF_WIFI_ENABLED) || defined(CONFIG_IF_USB_NCM_ENABLED)
ESP_LOGW(MAINTAG, "Building version list.");
buildVersionList(versionList, nvs, sdcard);
#endif
// Loop waiting on callback events and action accordingly.
while (true)
@@ -737,12 +899,14 @@ extern "C"
esp_restart();
}
// Check to see if a reboot is requested by user.
// Check to see if a reboot is requested by user via web interface.
#if defined(CONFIG_IF_WIFI_ENABLED) || defined(CONFIG_IF_USB_NCM_ENABLED)
if (wifi->doReboot() == true)
{
ESP_LOGW(MAINTAG, "WiFi reboot requested..");
ESP_LOGW(MAINTAG, "Web interface reboot requested..");
esp_restart();
}
#endif
// Check to see if a reboot is requested by rp2350.
if (IO_doReboot() == true)
@@ -765,17 +929,29 @@ extern "C"
CP_queueCmd("CFG0");
}
// Start WiFi ~1 s after the main loop begins (loopCount > 100 at 10 ms/iter).
// INF exchange is disabled so there is no longer a risk of WiFi high-priority
// tasks (priority 23) interfering with SPI transactions. The short delay
// gives the SPI slave (initialized at the end of the CommandProcessor startup
// delay) a moment to settle and handle the RP2350's first sector reads before
// WiFi stack comes up.
// Start network interfaces ~1 s after the main loop begins (loopCount > 100
// at 10 ms/iter). The short delay gives the SPI slave (initialized at the end
// of the CommandProcessor startup delay) a moment to settle and handle the
// RP2350's first sector reads before the network stack comes up.
++loopCount;
if (!wifiStarted && loopCount > 100)
{
#if defined(CONFIG_IF_USB_NCM_ENABLED)
// USB NCM network + lwIP netif were already created in setupUSBConsole().
// Start the webserver now so USB NCM is immediately browsable.
// The HTTP server binds to INADDR_ANY:80 so it will also serve on
// WiFi once that connection is established.
ESP_LOGW(MAINTAG, "Starting webserver on USB NCM.");
wifi->startWebserver();
#endif
#if defined(CONFIG_IF_WIFI_ENABLED)
// Start WiFi (AP or Client mode). When USB NCM has already started
// the webserver, the WiFi event handlers detect that server != NULL
// and skip the redundant startWebserver() call.
ESP_LOGW(MAINTAG, "Starting WiFi (loopCount=%d).", loopCount);
wifi->run();
#endif
wifiStarted = true;
}

View File

@@ -588,15 +588,17 @@ CONFIG_SD_CDDETECT=21
#
# WiFi
#
CONFIG_IF_WIFI_ENABLED=y
CONFIG_IF_WIFI_SSID="tzpuPico AP"
CONFIG_IF_WIFI_DEFAULT_SSID_PWD="tzpuPico1234"
CONFIG_IF_WIFI_MAX_RETRIES=20
CONFIG_IF_WIFI_AP_CHANNEL=5
CONFIG_IF_WIFI_SSID_HIDDEN=0
CONFIG_IF_WIFI_MAX_CONNECTIONS=3
# CONFIG_IF_WIFI_ENABLED is not set
# end of WiFi
#
# USB NCM Network
#
CONFIG_IF_USB_NCM_ENABLED=y
CONFIG_IF_USB_NCM_IP="192.168.7.1"
CONFIG_IF_USB_NCM_NETMASK="255.255.255.0"
# end of USB NCM Network
CONFIG_USE_ESP32_USB_OUTPUT=y
# CONFIG_USE_RP2350_OUTPUT is not set
@@ -1219,7 +1221,7 @@ CONFIG_SPI_SLAVE_ISR_IN_IRAM=y
#
# ESP-Driver:USB Serial/JTAG Configuration
#
CONFIG_USJ_ENABLE_USB_SERIAL_JTAG=y
# CONFIG_USJ_ENABLE_USB_SERIAL_JTAG is not set
# end of ESP-Driver:USB Serial/JTAG Configuration
#
@@ -1557,13 +1559,13 @@ CONFIG_ESP_MAIN_TASK_AFFINITY=0x0
CONFIG_ESP_MINIMAL_SHARED_STACK_SIZE=4192
# CONFIG_ESP_CONSOLE_UART_DEFAULT is not set
# CONFIG_ESP_CONSOLE_USB_CDC is not set
CONFIG_ESP_CONSOLE_USB_SERIAL_JTAG=y
# CONFIG_ESP_CONSOLE_USB_SERIAL_JTAG is not set
# CONFIG_ESP_CONSOLE_UART_CUSTOM is not set
# CONFIG_ESP_CONSOLE_NONE is not set
CONFIG_ESP_CONSOLE_NONE=y
CONFIG_ESP_CONSOLE_SECONDARY_NONE=y
CONFIG_ESP_CONSOLE_USB_SERIAL_JTAG_ENABLED=y
# CONFIG_ESP_CONSOLE_SECONDARY_USB_SERIAL_JTAG is not set
CONFIG_ESP_CONSOLE_UART_NUM=-1
CONFIG_ESP_CONSOLE_ROM_SERIAL_PORT_NUM=4
CONFIG_ESP_CONSOLE_ROM_SERIAL_PORT_NUM=-1
CONFIG_ESP_INT_WDT=y
CONFIG_ESP_INT_WDT_TIMEOUT_MS=2000
CONFIG_ESP_INT_WDT_CHECK_CPU1=y
@@ -1896,7 +1898,7 @@ CONFIG_LWIP_DNS_SUPPORT_MDNS_QUERIES=y
CONFIG_LWIP_TIMERS_ONDEMAND=y
CONFIG_LWIP_ND6=y
# CONFIG_LWIP_FORCE_ROUTER_FORWARDING is not set
CONFIG_LWIP_MAX_SOCKETS=20
CONFIG_LWIP_MAX_SOCKETS=24
# CONFIG_LWIP_USE_ONLY_LWIP_SELECT is not set
# CONFIG_LWIP_SO_LINGER is not set
CONFIG_LWIP_SO_REUSE=y
@@ -2497,6 +2499,118 @@ CONFIG_WIFI_PROV_AUTOSTOP_TIMEOUT=30
CONFIG_WIFI_PROV_STA_ALL_CHANNEL_SCAN=y
# CONFIG_WIFI_PROV_STA_FAST_SCAN is not set
# end of Wi-Fi Provisioning Manager
#
# TinyUSB Stack
#
CONFIG_TINYUSB_DEBUG_LEVEL=1
CONFIG_TINYUSB_RHPORT_FS=y
#
# TinyUSB DCD
#
# CONFIG_TINYUSB_MODE_SLAVE is not set
CONFIG_TINYUSB_MODE_DMA=y
# end of TinyUSB DCD
#
# TinyUSB task configuration
#
# CONFIG_TINYUSB_NO_DEFAULT_TASK is not set
CONFIG_TINYUSB_TASK_PRIORITY=5
CONFIG_TINYUSB_TASK_STACK_SIZE=4096
# CONFIG_TINYUSB_TASK_AFFINITY_NO_AFFINITY is not set
CONFIG_TINYUSB_TASK_AFFINITY_CPU0=y
# CONFIG_TINYUSB_TASK_AFFINITY_CPU1 is not set
CONFIG_TINYUSB_TASK_AFFINITY=0x0
CONFIG_TINYUSB_INIT_IN_DEFAULT_TASK=y
# end of TinyUSB task configuration
#
# Descriptor configuration
#
#
# You can provide your custom descriptors via tinyusb_driver_install()
#
CONFIG_TINYUSB_DESC_USE_ESPRESSIF_VID=y
CONFIG_TINYUSB_DESC_USE_DEFAULT_PID=y
CONFIG_TINYUSB_DESC_BCD_DEVICE=0x0100
CONFIG_TINYUSB_DESC_MANUFACTURER_STRING="Espressif Systems"
CONFIG_TINYUSB_DESC_PRODUCT_STRING="Espressif Device"
CONFIG_TINYUSB_DESC_SERIAL_STRING="123456"
CONFIG_TINYUSB_DESC_CDC_STRING="Espressif CDC Device"
# end of Descriptor configuration
#
# Massive Storage Class (MSC)
#
# CONFIG_TINYUSB_MSC_ENABLED is not set
#
# TinyUSB FAT Format Options
#
CONFIG_TINYUSB_FAT_FORMAT_ANY=y
# CONFIG_TINYUSB_FAT_FORMAT_FAT is not set
# CONFIG_TINYUSB_FAT_FORMAT_FAT32 is not set
# CONFIG_TINYUSB_FAT_FORMAT_EXFAT is not set
# CONFIG_TINYUSB_FAT_FORMAT_SFD is not set
# end of TinyUSB FAT Format Options
# end of Massive Storage Class (MSC)
#
# Communication Device Class (CDC)
#
CONFIG_TINYUSB_CDC_ENABLED=y
CONFIG_TINYUSB_CDC_COUNT=1
CONFIG_TINYUSB_CDC_RX_BUFSIZE=512
CONFIG_TINYUSB_CDC_TX_BUFSIZE=4096
# end of Communication Device Class (CDC)
#
# Musical Instrument Digital Interface (MIDI)
#
CONFIG_TINYUSB_MIDI_COUNT=0
# end of Musical Instrument Digital Interface (MIDI)
#
# Human Interface Device Class (HID)
#
CONFIG_TINYUSB_HID_COUNT=0
# end of Human Interface Device Class (HID)
#
# Device Firmware Upgrade (DFU)
#
# CONFIG_TINYUSB_DFU_MODE_DFU is not set
# CONFIG_TINYUSB_DFU_MODE_DFU_RUNTIME is not set
CONFIG_TINYUSB_DFU_MODE_NONE=y
# end of Device Firmware Upgrade (DFU)
#
# Bluetooth Host Class (BTH)
#
# CONFIG_TINYUSB_BTH_ENABLED is not set
# end of Bluetooth Host Class (BTH)
#
# Network driver (ECM/NCM/RNDIS)
#
# CONFIG_TINYUSB_NET_MODE_ECM_RNDIS is not set
CONFIG_TINYUSB_NET_MODE_NCM=y
# CONFIG_TINYUSB_NET_MODE_NONE is not set
CONFIG_TINYUSB_NCM_OUT_NTB_BUFFS_COUNT=3
CONFIG_TINYUSB_NCM_IN_NTB_BUFFS_COUNT=3
CONFIG_TINYUSB_NCM_OUT_NTB_BUFF_MAX_SIZE=3200
CONFIG_TINYUSB_NCM_IN_NTB_BUFF_MAX_SIZE=3200
# end of Network driver (ECM/NCM/RNDIS)
#
# Vendor Specific Interface
#
CONFIG_TINYUSB_VENDOR_COUNT=0
# end of Vendor Specific Interface
# end of TinyUSB Stack
# end of Component config
# CONFIG_IDF_EXPERIMENTAL_FEATURES is not set
@@ -2756,8 +2870,8 @@ CONFIG_SYSTEM_EVENT_TASK_STACK_SIZE=2304
CONFIG_MAIN_TASK_STACK_SIZE=32768
# CONFIG_CONSOLE_UART_DEFAULT is not set
# CONFIG_CONSOLE_UART_CUSTOM is not set
# CONFIG_CONSOLE_UART_NONE is not set
# CONFIG_ESP_CONSOLE_UART_NONE is not set
CONFIG_CONSOLE_UART_NONE=y
CONFIG_ESP_CONSOLE_UART_NONE=y
CONFIG_CONSOLE_UART_NUM=-1
CONFIG_INT_WDT=y
CONFIG_INT_WDT_TIMEOUT_MS=2000

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1 +1 @@
2.03
2.27

View File

@@ -61,7 +61,7 @@
</ul>
</li>
<li>
<li %SK_WIFINAVVISIBLE%>
<a href="wifimanager.htm">
<i style="color: yellow;" class="fa fa-wifi"></i> WiFi Manager
</a>

View File

@@ -61,7 +61,7 @@
</ul>
</li>
<li>
<li %SK_WIFINAVVISIBLE%>
<a href="wifimanager.htm">
<i style="color: yellow;" class="fa fa-wifi"></i> WiFi Manager
</a>

View File

@@ -61,7 +61,7 @@
</ul>
</li>
<li>
<li %SK_WIFINAVVISIBLE%>
<a href="wifimanager.htm">
<i style="color: yellow;" class="fa fa-wifi"></i> WiFi Manager
</a>
@@ -126,54 +126,64 @@
<div class="col-lg-12">
<div class="panel panel-primary">
<div class="panel-heading">
<h3 class="panel-title"><i class="fa fa-wifi"></i> WiFi Configuration</h3>
<h3 class="panel-title"><i class="fa %SK_NETPANELICON%"></i> %SK_NETPANELTITLE%</h3>
</div>
<div class="panel-body">
<div class="table-responsive" id="client-wifi-config-area">
<!--<div id="client-wifi-config-area">-->
<div id="wifiCfg%SK_WIFIMODEAP%">
<table class="table table-borderless table-sm">
<div class="table-responsive">
<div %SK_WIFINAVVISIBLE% id="wifiCfg%SK_WIFIMODEAP%">
<table class="table table-borderless table-sm" style="width:auto;">
<tbody>
<tr style="display: compact;">
<td>SSID:</td><td><span style="color: blue;">%SK_APSSID%</span></td>
<td>TX Power:</td><td><span class="txpower-live" style="color: blue;">%SK_TXPOWERDBM% dBm</span></td>
<td style="padding-left:20px;">TX Power:</td><td><span class="txpower-live" style="color: blue;">%SK_TXPOWERDBM% dBm</span></td>
</tr>
<tr style="display: compact;">
<td>Password:</td><td><span style="color: blue;">%SK_APPWD%</span></td>
</tr>
<tr>
<td>IP (AP):</td><td><span style="color: blue;">%SK_CURRENTIP%</span></td>
<td>NETMASK:</td><td><span style="color: blue;">%SK_CURRENTNM%</span></td>
<td>GATEWAY:</td><td><span style="color: blue;">%SK_CURRENTGW%</span></td>
<td style="padding-left:20px;">NETMASK:</td><td><span style="color: blue;">%SK_CURRENTNM%</span></td>
<td style="padding-left:20px;">GATEWAY:</td><td><span style="color: blue;">%SK_CURRENTGW%</span></td>
</tr>
</tbody>
</table>
</div>
<div id="wifiCfg0%SK_WIFIMODECLIENT%">
<table class="table table-borderless table-sm">
<div %SK_WIFINAVVISIBLE% id="wifiCfg0%SK_WIFIMODECLIENT%">
<table class="table table-borderless table-sm" style="width:auto;">
<tbody>
<tr style="display: compact;">
<td>SSID:</td><td><span style="color: blue;">%SK_CLIENTSSID%</span></td>
<td>TX Power:</td><td><span class="txpower-live" style="color: blue;">%SK_TXPOWERDBM% dBm</span></td>
<td>RSSI:</td><td><span class="rssi-live" style="color: blue;">%SK_RSSI% dBm</span></td>
<td style="padding-left:20px;">TX Power:</td><td><span class="txpower-live" style="color: blue;">%SK_TXPOWERDBM% dBm</span></td>
<td style="padding-left:20px;">RSSI:</td><td><span class="rssi-live" style="color: blue;">%SK_RSSI% dBm</span></td>
</tr>
<tr style="display: compact;" id="wifiCfg1%SK_CLIENTDHCPON%">
<td>DHCP:</td><td><span style="color: blue;">Enabled</span></td>
</tr>
<tr id="wifiCfg3%SK_CLIENTDHCPON%">
<td>IP (assigned):</td><td><span style="color: blue;">%SK_CURRENTIP%</span></td>
<td>NETMASK:</td><td><span style="color: blue;">%SK_CURRENTNM%</span></td>
<td>GATEWAY:</td><td><span style="color: blue;">%SK_CURRENTGW%</span></td>
<td style="padding-left:20px;">NETMASK:</td><td><span style="color: blue;">%SK_CURRENTNM%</span></td>
<td style="padding-left:20px;">GATEWAY:</td><td><span style="color: blue;">%SK_CURRENTGW%</span></td>
</tr>
<tr id="wifiCfg3%SK_CLIENTDHCPOFF%">
<td>IP (fixed):</td><td><span style="color: blue;">%SK_CURRENTIP%</span></td>
<td>NETMASK:</td><td><span style="color: blue;">%SK_CURRENTNM%</span></td>
<td>GATEWAY:</td><td><span style="color: blue;">%SK_CURRENTGW%</span></td>
<td style="padding-left:20px;">NETMASK:</td><td><span style="color: blue;">%SK_CURRENTNM%</span></td>
<td style="padding-left:20px;">GATEWAY:</td><td><span style="color: blue;">%SK_CURRENTGW%</span></td>
</tr>
</tbody>
</table>
</div>
<div %SK_USBNCMVISIBLE%>
<table class="table table-borderless table-sm" style="width:auto;">
<tbody>
<tr>
<td>Connection:</td><td><span style="color: blue;">USB (CDC-NCM)</span></td>
<td style="padding-left:20px;">IP:</td><td><span style="color: blue;">%SK_USBNCMIP%</span></td>
<td style="padding-left:20px;">NETMASK:</td><td><span style="color: blue;">%SK_USBNCMNM%</span></td>
<td style="padding-left:20px;">GATEWAY:</td><td><span style="color: blue;">%SK_USBNCMIP%</span></td>
</tr>
</tbody>
</table>
</div>
<!-- </div>-->
</div>
</div>
</div>

View File

@@ -4,7 +4,7 @@ var lastStatus = 0;
// Uses localStorage to persist the preference across sessions.
// To re-enable: localStorage.removeItem('skipDeleteConfirm') in browser console.
var _delPendingForm = null;
function confirmDelete(filename) {
function confirmDelete(ev, filename) {
if (localStorage.getItem('skipDeleteConfirm') === 'true') {
return true;
}
@@ -25,7 +25,7 @@ function confirmDelete(filename) {
'background:#555;color:#fff;border:1px solid #444;border-radius:4px;">Cancel</button></div>';
document.body.appendChild(overlay);
// Capture the form that triggered this
_delPendingForm = event.target.closest('form');
_delPendingForm = ev.target.closest('form');
document.getElementById('delOk').onclick = function() {
if (document.getElementById('delNoAsk').checked) {
localStorage.setItem('skipDeleteConfirm', 'true');

View File

@@ -62,7 +62,7 @@
</ul>
</li>
<li>
<li %SK_WIFINAVVISIBLE%>
<a href="wifimanager.htm">
<i style="color: yellow;" class="fa fa-wifi"></i> WiFi Manager
</a>

View File

@@ -62,7 +62,7 @@
</ul>
</li>
<li>
<li %SK_WIFINAVVISIBLE%>
<a href="wifimanager.htm">
<i style="color: yellow;" class="fa fa-wifi"></i> WiFi Manager
</a>

View File

@@ -60,7 +60,7 @@
</ul>
</li>
<li>
<li %SK_WIFINAVVISIBLE%>
<a href="wifimanager.htm">
<i style="color: yellow;" class="fa fa-wifi"></i> WiFi Manager
</a>

View File

@@ -1 +1 @@
2.03
2.07

View File

@@ -60,7 +60,7 @@
</ul>
</li>
<li>
<li %SK_WIFINAVVISIBLE%>
<a href="wifimanager.htm" class="active">
<i style="color: yellow;" class="fa fa-wifi"></i> WiFi Manager
</a>

View File

@@ -14,10 +14,12 @@ set(pZ80_common_src
set(pZ80_drivers_sharp_src
${CMAKE_CURRENT_LIST_DIR}/drivers/Sharp/MZ700.c
${CMAKE_CURRENT_LIST_DIR}/drivers/Sharp/MZ80A.c
${CMAKE_CURRENT_LIST_DIR}/drivers/Sharp/RFS.c
${CMAKE_CURRENT_LIST_DIR}/drivers/Sharp/WD1773.c
${CMAKE_CURRENT_LIST_DIR}/drivers/Sharp/QDDrive.c
${CMAKE_CURRENT_LIST_DIR}/drivers/Sharp/MZ-1E05.c
${CMAKE_CURRENT_LIST_DIR}/drivers/Sharp/MZ80AFI.c
${CMAKE_CURRENT_LIST_DIR}/drivers/Sharp/MZ-1E14.c
${CMAKE_CURRENT_LIST_DIR}/drivers/Sharp/MZ-1E19.c
${CMAKE_CURRENT_LIST_DIR}/drivers/Sharp/MZ-1R12.c

View File

@@ -49,6 +49,7 @@
// Include Device drivers.
#ifdef INCLUDE_SHARP_DRIVERS
#include "drivers/Sharp/MZ700.h" // Sharp MZ700 Persona driver.
#include "drivers/Sharp/MZ80A.h" // Sharp MZ80A Persona driver.
#endif
//-----------------------------------------
@@ -84,21 +85,29 @@
static PIO pio_0 = pio0, pio_1 = pio1, pio_2 = pio2_hw;
// PIO 0 state machines.
static uint sm_addr = PIO_SM_0, offset_addr = 0;
static uint sm_data = PIO_SM_1, offset_data = 0;
// IRQ 0 - SM0 - ADDRESS LOAD
// IRQ 1 - SM1 - DATA LOAD
static uint sm_addr = PIO_SM_0, offset_addr = 0; // 4 words
static uint sm_data = PIO_SM_1, offset_data = 0; // 6 words
// PIO 1 state machines.
static uint smCycle = PIO_SM_0, offsetCycle = 0;
static uint sm_busrq = PIO_SM_1, offset_busrq = 0;
static uint __attribute__((unused)) sm_int = PIO_SM_2;
static uint __attribute__((unused)) offset_int = 0;
static uint sm_nmi = PIO_SM_3, offset_nmi = 0;
// IRQ 0 - SM0 - CYCLE START
// IRQ 5 - SM3 - REFRESH
// IRQ 6 - SM1 - BUSACK
static uint smCycle = PIO_SM_0, offsetCycle = 0; // 20 words
static uint sm_busrq = PIO_SM_1, offset_busrq = 0; // 10 words
//static uint sm_int = PIO_SM_2, offset_int = 0;
//static uint sm_refresh = PIO_SM_3, offset_refresh = 0; // 10 words
// PIO 2 state machines.
static uint sm_refresh = PIO_SM_0, offset_refresh = 0;
static uint sm_wait = PIO_SM_1, offset_wait = 0;
static uint sm_sync = PIO_SM_2, offset_sync = 0;
static uint sm_reset = PIO_SM_3, offset_reset = 0;
// IRQ 1 - SM1 - WAIT
// IRQ 2 - SM2 - SYNC
// IRQ 3 - SM3 - RESET
// IRQ 4 - SM0 - NMI
static uint sm_nmi = PIO_SM_0, offset_nmi = 0; // 3 words
static uint sm_wait = PIO_SM_1, offset_wait = 0; // 6 words
static uint sm_sync = PIO_SM_2, offset_sync = 0; // 3 words
static uint sm_reset = PIO_SM_3, offset_reset = 0; // 2 words
//-------------------------------------------------------------------------------------------------
// JSON configration helper tables.
@@ -140,8 +149,8 @@ static const size_t memoryTypeMapSize = sizeof(memoryTypeMap) / sizeof(memoryTyp
// This table allows JSON configuration to specify a function by giving a text name which is then mapped to the actual
// internal function.
static const t_FuncMap funcMap[] = {
{"DRAMREFRESH", (t_MemoryFunc) Z80CPU_refreshDRAM}, // This function is intended to perform dummy M1 Fetch cycles in order
// to refresh host DRAM during internal RAM access.
{"DRAMREFRESH", (t_MemoryFunc) Z80CPU_refreshDRAM}, // This function is intended to perform dummy M1 Fetch cycles in order
// to refresh host DRAM during internal RAM access.
};
static const size_t funcMapSize = sizeof(funcMap) / sizeof(funcMap[0]);
@@ -167,6 +176,8 @@ static const t_VirtualFuncMap virtualFuncMap[] = {
#ifdef INCLUDE_SHARP_DRIVERS
{"MZ700", (t_VirtualFunc) MZ700_Init}, // This virtual function creates a Sharp MZ700 'persona' whereby
// it sets up devices and memory structure to replicate the MZ700 logic.
{"MZ80A", (t_VirtualFunc) MZ80A_Init}, // This virtual function creates a Sharp MZ80A 'persona' whereby
// it sets up devices and memory structure to replicate the MZ80A logic.
#endif
};
static const size_t virtualFuncMapSize = sizeof(virtualFuncMap) / sizeof(virtualFuncMap[0]);
@@ -1006,6 +1017,18 @@ bool Z80CPU_configFromJSON(t_Z80CPU *cpu, int cfgAppNo)
return (false);
}
// Extract core object for runtime parameters.
cJSON *core = cJSON_GetObjectItem(z80_obj, "core");
if (cJSON_IsObject(core))
{
// "z80refresh": 1 enables Z80 DRAM refresh during virtual memory opcode fetches.
// When the Z80 runs from virtual ROM/RAM, physical DRAM doesn't get refreshed.
// Enable this when mixing virtual ROM with physical DRAM (e.g., RFS on MZ-80A).
cJSON *z80refresh = cJSON_GetObjectItem(core, "z80refresh");
cpu->refreshEnable = (cJSON_IsNumber(z80refresh) && z80refresh->valueint == 1);
debugf("Z80 Core: z80refresh(%d)\r\n", cpu->refreshEnable);
}
// Get the memory array
cJSON *memorymap = cJSON_GetObjectItem(z80_obj, "memory");
if (!cJSON_IsArray(memorymap))
@@ -2019,21 +2042,52 @@ void Z80CPU_taskMirrorInternalRAMToPhysical(t_Z80CPU *cpu, uint32_t intAddr, uin
// Method, called as an optional function to an internal memory fetch, which will activate hardware to ensure
// any motherboard hosted DRAM is refreshed.
uint8_t __func_in_RAM(Z80CPU_refreshDRAM)(bool read, uint16_t addr, uint8_t data)
// This function can be attached to any Memory region or can be globally enabled (refreshEnabled = 1) for all
// virtual memory opcode fetches.
uint8_t __func_in_RAM(Z80CPU_refreshDRAM)(void *context, bool read, uint16_t addr, uint8_t data)
{
// Locals.
static uint16_t r_reg = 0; // Cant use the Z80 refresh register as it is running at a higher speed in memory.
t_Z80CPU *cpu = (t_Z80CPU *) context;
// If a bus transaction is underway, we dont wait, virtual memory processing is much faster.
if(GET_IRQ_STATE(pio_1, 0) == 0)
return(0);
// The refresh address is a combination of the I register and the R register.
r_reg = ((uint8_t)cpu->_Z80.i << 8) | r_reg;
PUSH_FIFO(pio_0, sm_addr, (addr << 16) | 0xFFFF); // Preload Fifo with address.
PUSH_FIFO(pio_0, sm_addr, (((uint16_t) r_reg) << 16) | 0xFFFF); // Pre-load refresh address.
CLEAR_IRQ(pio_1, 0); // Enable read state machine.
CLEAR_IRQ(pio_0, 0); // Enable address load.
// No waiting, we just push 8 instructions which form T1/T2 and exit.
PUSH_INSTR32(pio_1,
smCycle,
0xfc0e, // set pins, 14 side 3 Set /M1 low
0x2013); // T1 wait 0 gpio, 35
PUSH_INSTR32(pio_1,
smCycle,
0xf80a, // set pins, 10 side 2 Set /M1 low, /MREQ low, /RD low
(0x0000 + offsetCycle + 6)); // jmp offset+6, wait state loop.
PUSH_INSTR32(pio_1,
smCycle,
0x2093, // T3 wait 1 gpio, 35
(0x0000 + offsetCycle + 12)); // jmp offset+12, execute refresh T3/T4.
//0xfc0d); // set pins, 13 side 3 Set /M1 high, /MREQ high, /RD high, /RFSH low
// PUSH_INSTR32(pio_1,
// smCycle,
// 0x2013, // T3 wait 0 gpio, 35
// (0x0000 + offsetCycle + 13)); // jmp offset+12, execute refresh T3/T4.
// 0x2013, // T3 wait 0 gpio, 35
// (0x0000 + offsetCycle + 12)); // jmp offset+12, execute refresh T3/T4.
r_reg = ++r_reg & 0x7f; // R register only 7 bits incrementing.
CLEAR_IRQ(pio_0, 0); // Enable refresh address load.
return (0);
// Only start a refresh if the state machine is waiting, exit otherwise. This is because the hardware will be going
// at a much slower rate than internal memory execution of instructions.
if (GET_IRQ_STATE(pio_2, 0))
{
PUSH_FIFO(pio_0, sm_addr, ((r_reg) << 16) | 0xFFFF); // pre-load refresh address.
CLEAR_IRQ(pio_0, 0); // Enable address load.
CLEAR_IRQ(pio_2, 0); // Enable refresh state machine.
r_reg++; // Increment refresh counter.
}
return (0);
}
@@ -2078,7 +2132,7 @@ int Z80CPU_init_pio(void)
gpio_set_slew_rate(i, GPIO_SLEW_RATE_FAST);
}
// Setup output pins.
// Setup output pins. FUNCSEL routes the output driver to the pin for a given PIO.
for (uint i = Z80_PIN_RD; i <= Z80_PIN_IORQ; i++)
{
gpio_init(i);
@@ -2216,19 +2270,19 @@ int Z80CPU_init_pio(void)
*/
// NMI - Process non-maskable NMI interrupt requests.
offset_nmi = pio_add_program(pio_1, &z80_nmi_program);
offset_nmi = pio_add_program(pio_2, &z80_nmi_program);
pio_sm_config c_nmi = z80_nmi_program_get_default_config(offset_nmi);
sm_config_set_jmp_pin(&c_nmi, Z80_PIN_NMI); // for JMP
pio_sm_set_consecutive_pindirs(pio_1, sm_nmi, Z80_PIN_NMI, 1, false);
pio_sm_set_consecutive_pindirs(pio_2, sm_nmi, Z80_PIN_NMI, 1, false);
sm_config_set_clkdiv(&c_nmi, 1.0f);
pio_sm_clear_fifos(pio_1, sm_nmi);
pio_sm_restart(pio_1, sm_nmi);
initResult = pio_sm_init(pio_1, sm_nmi, offset_nmi, &c_nmi);
pio_sm_clear_fifos(pio_2, sm_nmi);
pio_sm_restart(pio_2, sm_nmi);
initResult = pio_sm_init(pio_2, sm_nmi, offset_nmi, &c_nmi);
if (initResult != 0)
{
return (initResult);
}
pio_sm_set_enabled(pio_1, sm_nmi, true);
pio_sm_set_enabled(pio_2, sm_nmi, true);
// RESET - Process host reset requests.
offset_reset = pio_add_program(pio_2, &z80_reset_program);
@@ -2270,28 +2324,28 @@ int Z80CPU_init_pio(void)
pio_sm_set_enabled(pio_1, smCycle, true);
// Refresh State Machine. Performs Z80 Refresh cycles.
offset_refresh = pio_add_program(pio_2, &z80_refresh_program);
pio_sm_config c_refresh = z80_refresh_program_get_default_config(offset_refresh);
sm_config_set_in_pins(&c_refresh, Z80_PIN_DATA_0);
sm_config_set_jmp_pin(&c_refresh, Z80_PIN_WAIT); // for JMP
sm_config_set_set_pins(&c_refresh, Z80_PIN_M1, 4); // /M1, /RFSH, /MREQ, /IORQ
sm_config_set_sideset_pins(&c_refresh, Z80_PIN_RD); // /RD, /WR
sm_config_set_out_shift(&c_refresh, true, true, 32); // Shift right, auto pull 32 bits.
sm_config_set_in_shift(&c_refresh, false, true, 8); // Shift left, auto push 8 bits.
pio_sm_set_consecutive_pindirs(pio_2, sm_refresh, Z80_PIN_RD, 2, true);
pio_sm_set_consecutive_pindirs(pio_2, sm_refresh, Z80_PIN_M1, 4, true);
pio_sm_set_consecutive_pindirs(pio_2, sm_refresh, Z80_PIN_DATA_0, 8, false);
pio_sm_set_consecutive_pindirs(pio_2, sm_refresh, Z80_PIN_WAIT, 1, false);
pio_sm_set_pins_with_mask(pio_2, sm_refresh, 0b11111111000000000000000000000000, 0b11111111000000000000000000000000);
sm_config_set_clkdiv(&c_refresh, 1.0f);
pio_sm_clear_fifos(pio_2, sm_refresh);
pio_sm_restart(pio_2, sm_refresh);
initResult = pio_sm_init(pio_2, sm_refresh, offset_refresh, &c_refresh);
if (initResult != 0)
{
return (initResult);
}
pio_sm_set_enabled(pio_2, sm_refresh, true);
// offset_refresh = pio_add_program(pio_1, &z80_refresh_program);
// pio_sm_config c_refresh = z80_refresh_program_get_default_config(offset_refresh);
// sm_config_set_in_pins(&c_refresh, Z80_PIN_DATA_0);
// sm_config_set_jmp_pin(&c_refresh, Z80_PIN_WAIT); // for JMP
// sm_config_set_set_pins(&c_refresh, Z80_PIN_M1, 4); // /M1, /RFSH, /MREQ, /IORQ
// sm_config_set_sideset_pins(&c_refresh, Z80_PIN_RD); // /RD, /WR
// sm_config_set_out_shift(&c_refresh, true, true, 32); // Shift right, auto pull 32 bits.
// sm_config_set_in_shift(&c_refresh, false, true, 8); // Shift left, auto push 8 bits.
// pio_sm_set_consecutive_pindirs(pio_1, sm_refresh, Z80_PIN_RD, 2, true);
// pio_sm_set_consecutive_pindirs(pio_1, sm_refresh, Z80_PIN_M1, 4, true);
// pio_sm_set_consecutive_pindirs(pio_1, sm_refresh, Z80_PIN_DATA_0, 8, false);
// pio_sm_set_consecutive_pindirs(pio_1, sm_refresh, Z80_PIN_WAIT, 1, false);
// pio_sm_set_pins_with_mask(pio_1, sm_refresh, 0b11111111000000000000000000000000, 0b11111111000000000000000000000000);
// sm_config_set_clkdiv(&c_refresh, 1.0f);
// pio_sm_clear_fifos(pio_1, sm_refresh);
// pio_sm_restart(pio_1, sm_refresh);
// initResult = pio_sm_init(pio_1, sm_refresh, offset_refresh, &c_refresh);
// if (initResult != 0)
// {
// return (initResult);
// }
// pio_sm_set_enabled(pio_1, sm_refresh, true);
// Wait state generator sm.
offset_wait = pio_add_program(pio_2, &z80_wait_program);
@@ -2385,6 +2439,7 @@ t_Z80CPU *Z80CPU_init(void)
cpu._z80PSRAM = NULL;
cpu._drivers.driver = NULL;
cpu.halt = false;
cpu.refreshEnable = false;
cpu.hold = false;
cpu.forceReset = false;
@@ -2921,6 +2976,15 @@ uint8_t __func_in_RAM(Z80CPU_fetchOpcode)(void *context, uint16_t address)
Z80CPU_syncPhysical(cpu);
}
data = Z80CPU_readMem(cpu, address, membankptr, cpu->_memAttr[bank][block].waitStates);
// DRAM refresh: when fetching opcodes from virtual memory, the physical M1 cycle
// (which includes a RFSH bus cycle) is not generated. Physical DRAM on the host
// motherboard will decay without a refresh. Trigger a full M1 cycle but ignore
// the read opcode.
if(cpu->refreshEnable)
{
Z80CPU_refreshDRAM(context, true, address, data);
}
}
// BUSRQ/BUSACK State, INT, NMI? Inline single test for speed expanding if any IRQ is set.
@@ -3192,42 +3256,44 @@ uint8_t __func_in_RAM(Z80CPU_fetchPhysicalMem)(t_Z80CPU *cpu, uint16_t addr)
PUSH_INSTR32(pio_1,
smCycle,
0xfc0e, // set pins, 14 side 3 Set /M1 low
0x2013); // T1 wait 0 gpio, 35
0xfc0e, // set pins, 14 side 3 Set /M1 low
0x2013); // T1 wait 0 gpio, 35
PUSH_INSTR32(pio_1,
smCycle,
0xf80a, // set pins, 10 side 2 Set /M1 low, /MREQ low, /RD low
0xf80a, // set pins, 10 side 2 Set /M1 low, /MREQ low, /RD low
(0x0000 + offsetCycle + 6)); // jmp offset+6, wait state loop.
PUSH_INSTR32(pio_1,
smCycle,
0x2093, // T3 wait 1 gpio, 35
0x4008); // in pins, 8
0x2093, // T3 wait 1 gpio, 35
0x4008); // in pins, 8
// Prepare Refresh cycle.
PUSH_INSTR32(pio_1,
smCycle,
0xfc0d, // set pins, 13 side 3 Set /M1 high, /MREQ high, /RD high, /RFSH low
0x2013); // T3 wait 0 gpio, 35
0xfc0d, // set pins, 13 side 3 Set /M1 high, /MREQ high, /RD high, /RFSH low
(0x0000 + offsetCycle + 12)); // jmp offset+13, execute refresh T3/T4.
// 0x2013); // T3 wait 0 gpio, 35
// Retrieve opcode during lull period.
WAIT_RX_FIFO_HAS_DATA(pio_1, smCycle);
CLEAR_IRQ(pio_0, 0); // Enable refresh address load.
data = POP_FIFO(pio_1, smCycle);
// Enter refresh cycle.
PUSH_INSTR32(pio_1,
smCycle,
0xfc09, // set pins, 9 side 3 Set /M1 high, /MREQ low, /RD high, /RFSH low
0x2093); // T4 wait 1 gpio, 35
PUSH_INSTR32(pio_1,
smCycle,
0x2013, // T4 wait 0 gpio, 35
0xfd0d); // set pins, 15 side 3 Set /M1 high, /MREQ high, /RD high, /RFSH low delay 1
PUSH_INSTR32(pio_1,
smCycle,
0xfc0f, // set pins, 15 side 3 Set /M1 high, /MREQ high, /RD high, /RFSH high
(0x0000 + offsetCycle)); // jmp start
// REFRESH Cycle during T3/T4 is performed withn the PIO cycle_program program.
// // Enter refresh cycle.
// PUSH_INSTR32(pio_1,
// smCycle,
// 0xfc09, // set pins, 9 side 3 Set /M1 high, /MREQ low, /RD high, /RFSH low
// 0x2093); // T4 wait 1 gpio, 35
// PUSH_INSTR32(pio_1,
// smCycle,
// 0x2013, // T4 wait 0 gpio, 35
// 0xfd0d); // set pins, 15 side 3 Set /M1 high, /MREQ high, /RD high, /RFSH low delay 1
// PUSH_INSTR32(pio_1,
// smCycle,
// 0xfc0f, // set pins, 15 side 3 Set /M1 high, /MREQ high, /RD high, /RFSH high
// (0x0000 + offsetCycle)); // jmp start
#ifdef DEBUG_PIO
debugf("Fetch %04x -> %02x\r\n", addr, (uint8_t) data);

View File

@@ -0,0 +1,839 @@
/////////////////////////////////////////////////////////////////////////////////////////////////////////
//
// Name: MZ80A.c
// Created: Apr 2026
// Author(s): Philip Smart
// Description: Z80 CPU DRIVER - Sharp MZ-80A
// This file contains setup and drivers to mimic a Sharp MZ-80A machine internally to
// increase speed through use of internal RAM and provide virtual expansion drivers.
// Based on the MZ700 driver but adapted for the MZ-80A memory map and peripherals.
// Credits:
// Copyright: (c) 2019-2026 Philip Smart <philip.smart@net2net.org>
//
// History: Apr 2026 v1.0 - Initial write based on MZ700 driver.
//
// Notes: See Makefile to enable/disable conditional components
//
/////////////////////////////////////////////////////////////////////////////////////////////////////////
// This source file is free software: you can redistribute it and#or modify
// it under the terms of the GNU General Public License as published
// by the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This source file is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
/////////////////////////////////////////////////////////////////////////////////////////////////////////
#include "Z80CPU.h"
#define Z80_STATIC
#include "Z80.h"
#include <string.h>
#include <stdlib.h>
#include <pico/printf.h>
#include <pico/stdlib.h>
#include "pico/util/queue.h"
#include "pico/multicore.h"
#include "intercore.h"
#include "debug.h"
#include "drivers/Sharp/MZ.h"
#include "drivers/Sharp/RFS.h"
#include "drivers/Sharp/WD1773.h"
#include "drivers/Sharp/QDDrive.h"
#include "drivers/Sharp/MZ80AFI.h"
#include "drivers/Sharp/MZ-1E14.h"
#include "drivers/Sharp/MZ-1E19.h"
#include "drivers/Sharp/MZ-1R12.h"
#include "drivers/Sharp/MZ-1R18.h"
#include "drivers/Sharp/MZ80A.h"
// Map to contain interface name against method to initialise it.
static t_InterfaceFuncMap interfaceFuncMap[] = {
{"RFS", false, RFS_Init, RFS_Reset, RFS_PollCB, RFS_TaskProcessor},
{"MZ80AFI", false, MZ80AFI_Init, MZ80AFI_Reset, MZ80AFI_PollCB, MZ80AFI_TaskProcessor},
{"MZ-1E14", false, MZ1E14_Init, MZ1E14_Reset, MZ1E14_PollCB, MZ1E14_TaskProcessor},
{"MZ-1E19", false, MZ1E19_Init, MZ1E19_Reset, MZ1E19_PollCB, MZ1E19_TaskProcessor},
{"MZ-1R12", false, MZ1R12_Init, MZ1R12_Reset, MZ1R12_PollCB, MZ1R12_TaskProcessor},
{"MZ-1R18", false, MZ1R18_Init, MZ1R18_Reset, MZ1R18_PollCB, MZ1R18_TaskProcessor},
};
static const size_t interfaceFuncMapSize = sizeof(interfaceFuncMap) / sizeof(interfaceFuncMap[0]);
// -----------------------------------------------------------------------
// Comprehensive Intel 8253 PIT (Programmable Interval Timer) emulation.
// The MZ-80A uses an 8253 PIT at memory-mapped addresses 0xE004-0xE007
// clocked at ~895 kHz.
// Counter 0 = 0xE004, Counter 1 = 0xE005, Counter 2 = 0xE006,
// Control = 0xE007.
//
// Supports all 6 counter modes:
// Mode 0: Interrupt on Terminal Count
// Mode 1: Hardware Retriggerable One-Shot
// Mode 2: Rate Generator
// Mode 3: Square Wave Generator
// Mode 4: Software Triggered Strobe
// Mode 5: Hardware Triggered Strobe
//
// Supports: BCD/binary counting, counter latch, LSB/MSB/LSB-then-MSB
// read/load modes, and proper output pin state tracking.
// -----------------------------------------------------------------------
#define PIT_NUM_COUNTERS 3
#define PIT_CLOCK_HZ 895000 // ~895 kHz clock input on MZ-80A
typedef struct
{
uint16_t count; // Current counter value (CE = Counting Element).
uint16_t reload; // Reload value (CR = Count Register, written by CPU).
uint16_t latchVal; // Latched counter value for reading.
bool latched; // True if counter value has been latched (counter latch cmd).
uint8_t mode; // Counter mode (0-5).
uint8_t rlMode; // Read/Load mode: 1=LSB only, 2=MSB only, 3=LSB then MSB.
bool bcd; // True = BCD counting, false = binary.
bool readLsbNext; // For RL mode 3 reads: true = next read byte is LSB.
bool writeLsbNext; // For RL mode 3 writes: true = next write byte is LSB.
bool gate; // Gate input state (active high for modes 0,2,3,4; edge for 1,5).
bool output; // Output pin state.
bool counting; // Counter has been loaded and is actively counting.
bool nullCount; // True while new count written but not yet loaded into CE.
bool reloadPending; // For modes 2/3: reload pending on next terminal count.
absolute_time_t lastUpdate; // Last time counter was updated.
} t_PITCounter;
static t_PITCounter pitCounter[PIT_NUM_COUNTERS];
static bool pitInitialized = false;
// Decrement a 16-bit value by 1 in BCD mode (4-digit BCD: 0000-9999).
static uint16_t pitBcdDecrement(uint16_t val)
{
uint16_t d0 = (val) & 0xF;
uint16_t d1 = (val >> 4) & 0xF;
uint16_t d2 = (val >> 8) & 0xF;
uint16_t d3 = (val >> 12) & 0xF;
uint16_t bin = d0 + d1 * 10 + d2 * 100 + d3 * 1000;
if (bin == 0)
bin = 10000;
bin--;
d0 = bin % 10;
d1 = (bin / 10) % 10;
d2 = (bin / 100) % 10;
d3 = (bin / 1000) % 10;
return (uint16_t)(d0 | (d1 << 4) | (d2 << 8) | (d3 << 12));
}
// Get the effective count value for terminal count detection.
static uint32_t pitEffectiveCount(t_PITCounter *ctr)
{
if (ctr->reload == 0)
return ctr->bcd ? 10000 : 0x10000;
return ctr->reload;
}
// Update a PIT counter based on elapsed wall-clock time.
static void pitUpdateCounter(int ch)
{
t_PITCounter *ctr = &pitCounter[ch];
if (!ctr->counting)
return;
if (!ctr->gate && (ctr->mode == 0 || ctr->mode == 2 || ctr->mode == 3 || ctr->mode == 4))
return;
absolute_time_t now = get_absolute_time();
int64_t elapsedUs = absolute_time_diff_us(ctr->lastUpdate, now);
if (elapsedUs <= 0)
return;
uint32_t ticks = (uint32_t)((elapsedUs * (int64_t)PIT_CLOCK_HZ) / 1000000);
if (ticks == 0)
return;
ctr->lastUpdate = now;
if (ctr->bcd)
{
if (ticks > 20000)
ticks = 20000;
for (uint32_t t = 0; t < ticks; t++)
{
ctr->count = pitBcdDecrement(ctr->count);
if (ctr->count == 0)
{
switch (ctr->mode)
{
case 0:
ctr->output = true;
break;
case 1:
ctr->output = true;
ctr->counting = false;
return;
case 2:
ctr->count = ctr->reload ? ctr->reload : 0x9999;
ctr->output = true;
break;
case 3:
ctr->count = ctr->reload ? ctr->reload : 0x9999;
ctr->output = !ctr->output;
break;
case 4:
ctr->output = false;
ctr->counting = false;
return;
case 5:
ctr->output = false;
ctr->counting = false;
return;
}
}
}
return;
}
// Binary counting.
uint32_t c = ctr->count;
switch (ctr->mode)
{
case 0:
if (ticks >= c)
{
ctr->output = true;
uint32_t rem = ticks - c;
ctr->count = (uint16_t)(0x10000 - (rem % 0x10000));
if (ctr->count == 0x10000)
ctr->count = 0;
}
else
{
ctr->count = (uint16_t)(c - ticks);
}
break;
case 1:
if (ticks >= c)
{
ctr->output = true;
ctr->count = 0;
ctr->counting = false;
}
else
{
ctr->count = (uint16_t)(c - ticks);
}
break;
case 2:
{
uint32_t rl = pitEffectiveCount(ctr);
if (ticks >= c)
{
uint32_t rem = ticks - c;
ctr->count = (uint16_t)(rl - (rem % rl));
if (ctr->count == 0)
ctr->count = (uint16_t)rl;
ctr->output = true;
}
else
{
ctr->count = (uint16_t)(c - ticks);
ctr->output = (ctr->count != 1);
}
}
break;
case 3:
{
uint32_t rl = pitEffectiveCount(ctr);
uint32_t halfPeriod = rl / 2;
if (halfPeriod == 0)
halfPeriod = 1;
uint32_t totalTicks;
if (ticks >= c)
totalTicks = ticks - c;
else
{
ctr->count = (uint16_t)(c - ticks);
break;
}
uint32_t toggles = 1 + (totalTicks / halfPeriod);
if (toggles & 1)
ctr->output = !ctr->output;
uint32_t rem = totalTicks % halfPeriod;
ctr->count = (uint16_t)(halfPeriod - rem);
if (ctr->count == 0)
ctr->count = (uint16_t)halfPeriod;
}
break;
case 4:
if (ticks >= c)
{
ctr->output = true;
ctr->count = 0;
ctr->counting = false;
}
else
{
ctr->count = (uint16_t)(c - ticks);
ctr->output = true;
}
break;
case 5:
if (ticks >= c)
{
ctr->output = true;
ctr->count = 0;
ctr->counting = false;
}
else
{
ctr->count = (uint16_t)(c - ticks);
ctr->output = true;
}
break;
}
}
// I/O handler for the 8253 PIT (memory-mapped at 0xE004-0xE007 on MZ-80A).
uint8_t MZ80A_IO_PIT(t_Z80CPU *cpu, bool read, uint16_t addr, uint8_t data)
{
uint8_t port = addr & 0x03;
if (!pitInitialized)
{
for (int i = 0; i < PIT_NUM_COUNTERS; i++)
{
memset(&pitCounter[i], 0, sizeof(t_PITCounter));
pitCounter[i].gate = true;
pitCounter[i].output = true;
pitCounter[i].readLsbNext = true;
pitCounter[i].writeLsbNext = true;
pitCounter[i].rlMode = 3;
pitCounter[i].lastUpdate = get_absolute_time();
}
pitInitialized = true;
}
// Control register (port 3, write only).
if (port == 3)
{
if (!read)
{
uint8_t ch = (data >> 6) & 0x03;
uint8_t rl = (data >> 4) & 0x03;
if (ch == 3)
return 0xFF;
if (rl == 0)
{
if (!pitCounter[ch].latched)
{
pitUpdateCounter(ch);
pitCounter[ch].latchVal = pitCounter[ch].count;
pitCounter[ch].latched = true;
pitCounter[ch].readLsbNext = true;
}
}
else
{
pitCounter[ch].rlMode = rl;
pitCounter[ch].mode = (data >> 1) & 0x07;
pitCounter[ch].bcd = (data & 0x01) != 0;
pitCounter[ch].readLsbNext = true;
pitCounter[ch].writeLsbNext = true;
pitCounter[ch].latched = false;
pitCounter[ch].nullCount = true;
pitCounter[ch].counting = false;
switch (pitCounter[ch].mode)
{
case 0:
pitCounter[ch].output = false;
break;
case 1:
pitCounter[ch].output = true;
break;
case 2:
case 3:
pitCounter[ch].output = true;
break;
case 4:
case 5:
pitCounter[ch].output = true;
break;
}
}
}
return 0xFF;
}
// Counter data ports (0-2).
if (port >= PIT_NUM_COUNTERS)
return 0xFF;
t_PITCounter *ctr = &pitCounter[port];
if (read)
{
uint16_t val;
if (ctr->latched)
val = ctr->latchVal;
else
{
pitUpdateCounter(port);
val = ctr->count;
}
uint8_t result;
switch (ctr->rlMode)
{
case 1:
result = (uint8_t)(val & 0xFF);
ctr->latched = false;
break;
case 2:
result = (uint8_t)((val >> 8) & 0xFF);
ctr->latched = false;
break;
case 3:
if (ctr->readLsbNext)
{
ctr->readLsbNext = false;
result = (uint8_t)(val & 0xFF);
}
else
{
ctr->readLsbNext = true;
result = (uint8_t)((val >> 8) & 0xFF);
ctr->latched = false;
}
break;
default:
result = (uint8_t)(val & 0xFF);
ctr->latched = false;
break;
}
return result;
}
else
{
switch (ctr->rlMode)
{
case 1:
ctr->reload = (ctr->reload & 0xFF00) | data;
ctr->count = ctr->reload;
ctr->counting = true;
ctr->nullCount = false;
ctr->lastUpdate = get_absolute_time();
break;
case 2:
ctr->reload = (ctr->reload & 0x00FF) | ((uint16_t)data << 8);
ctr->count = ctr->reload;
ctr->counting = true;
ctr->nullCount = false;
ctr->lastUpdate = get_absolute_time();
break;
case 3:
if (ctr->writeLsbNext)
{
ctr->reload = (ctr->reload & 0xFF00) | data;
ctr->writeLsbNext = false;
}
else
{
ctr->reload = (ctr->reload & 0x00FF) | ((uint16_t)data << 8);
ctr->count = ctr->reload;
ctr->counting = true;
ctr->nullCount = false;
ctr->lastUpdate = get_absolute_time();
ctr->writeLsbNext = true;
}
break;
}
return 0;
}
}
// Method to write out a file data buffer into the internal emulation RAM as directed by the configuration array.
void MZ80A_readFileData(void *ctx, void *cfg, int filepos, char *buf, int len)
{
// Locals.
t_Z80CPU *cpu = (t_Z80CPU *) ctx;
t_drvROMConfig *config = (t_drvROMConfig *) cfg;
if (cpu == NULL || config == NULL)
{
return;
}
for (uint32_t fileIdx = 0; fileIdx < len; fileIdx++)
{
for (int loadIdx = 0; loadIdx < config->romAddrCount; loadIdx++)
{
int baseWindowAddr = (filepos - config->romAddr[loadIdx].position) + fileIdx;
if (baseWindowAddr >= 0 && baseWindowAddr < config->romAddr[loadIdx].size)
{
uint32_t targetAddr =
(uint32_t) (config->romAddr[loadIdx].bank * MEMORY_PAGE_SIZE) + (uint32_t) config->romAddr[loadIdx].addr + (uint32_t) baseWindowAddr;
cpu->_z80PSRAM->RAM[targetAddr] = buf[fileIdx];
}
}
}
return;
}
// Method to write out a stored ROM into the internal emulation RAM as directed by the configuration array.
void MZ80A_readROMData(void *ctx, void *cfg, char *buf, int len)
{
// Locals.
t_Z80CPU *cpu = (t_Z80CPU *) ctx;
t_drvROMConfig *config = (t_drvROMConfig *) cfg;
if (cpu == NULL || config == NULL)
{
return;
}
for (int loadIdx = 0; loadIdx < config->romAddrCount; loadIdx++)
{
uint32_t srcAddr = config->romAddr[loadIdx].position;
uint32_t dstAddr = (uint32_t) (config->romAddr[loadIdx].bank * MEMORY_PAGE_SIZE) + (uint32_t) config->romAddr[loadIdx].addr;
uint32_t size = (config->romAddr[loadIdx].size > len ? len : config->romAddr[loadIdx].size);
size = (size > MEMORY_SIZE ? MEMORY_SIZE : size);
if (config->romAddr[loadIdx].position > len)
continue;
for (uint32_t idx = 0; idx < size; idx++)
{
cpu->_z80PSRAM->RAM[dstAddr + idx] = buf[srcAddr + idx];
}
if (config->romAddr[loadIdx].bank == 0)
{
for (int idx = config->romAddr[loadIdx].addr / MEMORY_BLOCK_SIZE;
idx < (config->romAddr[loadIdx].addr + config->romAddr[loadIdx].size) / MEMORY_BLOCK_SIZE;
idx++)
{
cpu->_membankPtr[idx] = (MEMBANK_TYPE_ROM << 24) | (idx * MEMORY_BLOCK_SIZE);
cpu->_memAttr[MZ80A_MEMBANK_0][idx].waitStates = config->romAddr[loadIdx].wait;
cpu->_memAttr[MZ80A_MEMBANK_0][idx].tCycSync = config->romAddr[loadIdx].sync == 0 ? false : true;
}
}
debugf("ROM_LOAD: file=%s bank=%d addr=%04x size=%d first=%02x\r\n",
config->romFile, config->romAddr[loadIdx].bank,
config->romAddr[loadIdx].addr, size, buf[srcAddr]);
}
return;
}
// Debug handler, any byte output to this port will be sent to the debugf channel.
uint8_t MZ80A_IO_Debug(t_Z80CPU *cpu, bool read, uint16_t addr, uint8_t data)
{
uint8_t port = (uint8_t) addr & 0xff;
(void)port;
if (read)
return (0xFF);
debug_putchar(data);
return (0);
}
// Reset handler.
// Reset pointers and control variables to initial state.
// Reset all interface cards if installed.
uint8_t MZ80A_Reset(t_Z80CPU *cpu)
{
// Locals.
uint8_t result = 0;
debugf("MZ80A_Reset\r\n");
// Reset PIT emulation state so it re-initializes on next access.
pitInitialized = false;
// Clear Z80 RAM work areas that may hold stale data from the previous session.
// RFS startup variables (0x1000-0x1037) and MZF header area (0x10F0-0x1167).
for (uint16_t addr = 0x1000; addr < 0x1168; addr++)
{
uint8_t block = addr / MEMORY_BLOCK_SIZE;
uint32_t membankptr = cpu->_membankPtr[block];
uint8_t memType = membankptr >> 24;
if (memType == MEMBANK_TYPE_RAM || memType == MEMBANK_TYPE_ROM)
{
uint32_t RAMaddr = membankptr & 0x7fffff;
uint16_t blockaddr = addr & (MEMORY_BLOCK_SIZE - 1);
cpu->_z80PSRAM->RAM[RAMaddr + blockaddr] = 0;
}
}
// Reprogram 8253 channel 2 to prevent rapid interrupt flooding after reset.
// Control word 0xB0 = channel 2, RW both bytes, mode 0 (interrupt on terminal count).
// Counter 0xA8C0 = 43200 = standard 12-hour RTC count used by the monitor.
Z80CPU_writePhysicalMem(cpu, 0xE007, 0xB0); // 8253 control: ch2, mode 0, RW both
Z80CPU_writePhysicalMem(cpu, 0xE006, 0xC0); // Counter LSB (0xA8C0 & 0xFF)
Z80CPU_writePhysicalMem(cpu, 0xE006, 0xA8); // Counter MSB (0xA8C0 >> 8)
// Mask PC2 as belt-and-suspenders — QMODE will re-enable it.
Z80CPU_writePhysicalMem(cpu, 0xE003, 0x04);
// Call interface reset handlers if board installed.
for (size_t i = 0; i < interfaceFuncMapSize; i++)
{
debugf("IF_RST[%d]: %s active=%d\r\n", i, interfaceFuncMap[i].interfaceFuncName, interfaceFuncMap[i].active);
if (interfaceFuncMap[i].active)
{
result |= interfaceFuncMap[i].resetFuncPtr(cpu);
}
}
return (result);
}
// Poll Callback handler.
uint8_t MZ80A_PollCB(t_Z80CPU *cpu)
{
// Locals.
uint8_t result = 0;
for (size_t i = 0; i < interfaceFuncMapSize; i++)
{
if (interfaceFuncMap[i].active)
{
result |= interfaceFuncMap[i].pollFuncPtr(cpu);
}
}
return (result);
}
// Task processing handler.
uint8_t MZ80A_TaskProcessor(t_Z80CPU *cpu, enum Z80CPU_TASK_NAME task, char *param)
{
// Locals.
uint8_t result = 0;
for (size_t i = 0; i < interfaceFuncMapSize; i++)
{
if (interfaceFuncMap[i].active)
{
result |= interfaceFuncMap[i].taskFuncPtr(cpu, task, param);
}
}
return (result);
}
// Driver to configure the emulation as an MZ-80A device to enable execution of code from hardware or
// within internal memory only going hardware such as the video, ports etc.
// Parameters: cpu = primary structure containing Z80 CPU configuration and Z80 state.
// appConfig = application specific configuration area within the FlashRAM.
// config = Structure containing driver configuration information
// ifName = When set and config = NULL, validate the interface name against known supported interfaces.
uint8_t MZ80A_Init(t_Z80CPU *cpu, t_FlashAppConfigHeader *appConfig, t_drvConfig *config, const char *ifName)
{
// Locals.
uint8_t result = 0;
char *fileBuffer = NULL;
(void)fileBuffer;
// Call to check validity of interface for this driver.
if (ifName != NULL && config == NULL)
{
for (size_t i = 0; i < interfaceFuncMapSize; i++)
{
const char *constant = interfaceFuncMap[i].interfaceFuncName;
size_t constantLen = strlen(constant);
if (strncasecmp(ifName, constant, constantLen) == 0 && strlen(ifName) == constantLen)
{
result = 1;
}
}
}
else
{
// Call to configure the driver.
if (ifName == NULL && config != NULL)
{
bool isPhysical = config->isPhysical;
// Memory map of the MZ-80A.
// 0000 - 0FFF = Monitor ROM (SA-1510) or RAM (via MEMSW at E00C/E010)
// 1000 - BFFF = RAM
// C000 - CFFF = RAM (swap target for MEMSW 0000:0FFF <-> C000:CFFF)
// D000 - D7FF = VRAM (2K, 40x25=1000 chars standard, 80x25=2000 with Kuma 80 mod)
// D800 - DFFF = Unused (open bus)
// E000 - E003 = 8255 PPI
// E004 - E007 = 8253 PIT
// E008 - E00B = LS367
// E00C = MEMSW (swap ROM 0000:0FFF to C000:CFFF, RAM to 0000:0FFF)
// E010 = MEMSWR (reverse the swap)
// E800 - EFFF = User ROM socket / RFS
// F000 - FFFF = Floppy Disk ROM (MZ80AFI)
for (int idx = 0; idx < MEMORY_PAGE_BLOCKS; idx++)
{
uint32_t memType = MEMBANK_TYPE_PHYSICAL;
uint32_t memPtr = 0x00000000;
uint8_t waitStates = 0;
bool tCycSync = false;
switch (idx)
{
// 0x0000:0x0FFF = Monitor ROM.
case 0 ... 7:
if (!isPhysical)
{
memType = MEMBANK_TYPE_ROM;
waitStates = 1;
tCycSync = true;
}
break;
// 0x1000:0xCFFF = RAM (includes 0xC000:0xCFFF MEMSW swap target)
case 8 ... 103:
if (!isPhysical)
{
memType = MEMBANK_TYPE_RAM;
waitStates = 1;
tCycSync = true;
}
break;
// 0xD000:0xD7FF = VRAM (2K character RAM, A0-A9)
// Must remain physical as it controls underlying video hardware.
case 104 ... 107:
memType = MEMBANK_TYPE_PHYSICAL_VRAM;
break;
// 0xD800:0xDFFF = Unused (open bus on MZ-80A, no colour RAM)
case 108 ... 111:
break;
// 0xE000:0xE7FF = Physical IO (8255, 8253, LS367, MEMSW/MEMSWR)
// Must remain physical as it controls underlying hardware.
case 112 ... 115:
memType = MEMBANK_TYPE_PHYSICAL_HW;
break;
// 0xE800:0xEFFF = User ROM / RFS
case 116 ... 119:
if (!isPhysical)
{
memType = MEMBANK_TYPE_ROM;
waitStates = 1;
tCycSync = true;
}
break;
// 0xF000:0xFFFF = Floppy ROM (MZ80AFI)
case 120 ... 127:
if (!isPhysical)
{
waitStates = 1;
tCycSync = true;
memType = MEMBANK_TYPE_ROM;
}
break;
}
cpu->_membankPtr[idx] = (memType << 24) | (MZ80A_MEMBANK_0 << 16) | (idx * MEMORY_BLOCK_SIZE);
cpu->_memAttr[MZ80A_MEMBANK_0][idx].waitStates = waitStates;
cpu->_memAttr[MZ80A_MEMBANK_0][idx].tCycSync = tCycSync;
cpu->_z80PSRAM->memPtr[idx] = memPtr;
}
for (int idx = 0; idx < MEMORY_PAGE_SIZE; idx++)
{
cpu->_z80PSRAM->memioPtr[idx] = (t_MemoryFunc) NULL;
}
for (int idx = 0; idx < IO_PAGE_SIZE; idx++)
{
t_MemoryFunc ioPtr = (t_MemoryFunc) NULL;
// Debug output handler.
if ((idx & 0xff) == 0x50) ioPtr = (t_MemoryFunc) MZ80A_IO_Debug;
cpu->_z80PSRAM->ioPtr[idx] = ioPtr;
}
// If not physical, we need to copy the ROMS from the mainboard into internal RAM.
if (!isPhysical)
{
// Copy monitor ROM to Bank 0.
Z80CPU_taskMirrorPhysicalToInternalRAM(cpu, (MZ80A_MEMBANK_0 << 16) | 0x0000, 0x0000, 0x1000);
// Copy User ROM to Bank 0.
Z80CPU_taskMirrorPhysicalToInternalRAM(cpu, (MZ80A_MEMBANK_0 << 16) | 0xE800, 0xE800, 0x800);
// Copy Floppy ROM to Bank 0.
Z80CPU_taskMirrorPhysicalToInternalRAM(cpu, (MZ80A_MEMBANK_0 << 16) | 0xF000, 0xF000, 0x1000);
}
// Install reset handler.
config->resetPtr = MZ80A_Reset;
// Install polling loop handler.
config->pollPtr = MZ80A_PollCB;
// Install task processing handler.
config->taskPtr = MZ80A_TaskProcessor;
// Go through each interface and perform necessary configuration.
for (int ifIdx = 0; ifIdx < config->ifCount; ifIdx++)
{
isPhysical = config->ifConfig[ifIdx].isPhysical;
// Load up any ROM data as specified in the LoadAddr mapping for this interface/ROM.
for (int romIdx = 0; romIdx < config->ifConfig[ifIdx].romCount; romIdx++)
{
if (Z80CPU_ReadROM(appConfig,
config->ifConfig[ifIdx].romConfig[romIdx].romFile,
cpu,
&config->ifConfig[ifIdx].romConfig[romIdx],
MZ80A_readROMData,
NULL,
0,
0) == 0)
{
continue;
}
}
// Lookup the interface and execute initialisation routines.
for (size_t i = 0; i < interfaceFuncMapSize; i++)
{
const char *constant = interfaceFuncMap[i].interfaceFuncName;
size_t constantLen = strlen(constant);
if (strncasecmp(config->ifConfig[ifIdx].name, constant, constantLen) == 0 && strlen(config->ifConfig[ifIdx].name) == constantLen)
{
if (interfaceFuncMap[i].initFuncPtr(cpu, (t_drvIFConfig *) &config->ifConfig[ifIdx]) != 0)
{
interfaceFuncMap[i].active = true;
}
}
}
}
// When the driver is PHYSICAL, interface ROM loading (MZ80A_readROMData) may have
// changed F000-FFFF from PHYSICAL to ROM type. Restore to PHYSICAL so the hardware
// ROM is used — virtual ROM + physical FDC hardware doesn't work because the virtual
// WD1773 DRQ can't toggle the real hardware's A10 line.
if (config->isPhysical)
{
for (int idx = 0xF000 / MEMORY_BLOCK_SIZE; idx < 0x10000 / MEMORY_BLOCK_SIZE; idx++)
{
cpu->_membankPtr[idx] = (MEMBANK_TYPE_PHYSICAL << 24) | (MZ80A_MEMBANK_0 << 16) | (idx * MEMORY_BLOCK_SIZE);
}
}
}
}
return (result);
}
//---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

View File

@@ -0,0 +1,292 @@
/////////////////////////////////////////////////////////////////////////////////////////////////////////
//
// Name: MZ80AFI.c
// Created: Apr 2026
// Author(s): Philip Smart
// Description: Z80 CPU DRIVER - Sharp MZ-80A - MZ80AFI Floppy Disk Controller
// This file contains setup and driver to mimic the MZ-80A Floppy Disk Controller.
// Functionally similar to the MZ-1E05 but uses MZ_80A disk format parameters.
// Credits:
// Copyright: (c) 2019-2026 Philip Smart <philip.smart@net2net.org>
//
// History: Apr 2026 v1.0 - Initial write based on MZ-1E05.
//
// Notes: See Makefile to enable/disable conditional components
//
/////////////////////////////////////////////////////////////////////////////////////////////////////////
// This source file is free software: you can redistribute it and#or modify
// it under the terms of the GNU General Public License as published
// by the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This source file is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
/////////////////////////////////////////////////////////////////////////////////////////////////////////
#include "Z80CPU.h"
#define Z80_STATIC
#include "Z80.h"
#include <string.h>
#include <stdlib.h>
#include <pico/printf.h>
#include <pico/stdlib.h>
#include "pico/util/queue.h"
#include "pico/multicore.h"
#include "intercore.h"
#include "drivers/Sharp/MZ.h"
#include "drivers/Sharp/WD1773.h"
#include "drivers/Sharp/MZ80AFI.h"
t_MZ80AFI *mz80afiCtrl; // Control structure for the MZ80AFI Floppy Disk Interface.
// -----------------------------------------------------------------------------------------------
// Interface: Sharp MZ-80A Floppy Disk Interface Board (MZ80AFI).
// Description: A Floppy Drive interface including driver ROM to allow use of Floppy Disk Drives
// on the MZ-80A. Uses the same WD1773 FDC as the MZ-1E05 but initialised for
// MZ-80A disk format.
// -----------------------------------------------------------------------------------------------
// Method to initialise the CPU state to support an MZ80AFI Floppy Disk Interface.
uint8_t MZ80AFI_Init(t_Z80CPU *cpu, t_drvIFConfig *config)
{
// Locals.
uint8_t result = 0;
// Interface logic only for virtual device. Physical device doesnt need configuration.
if (config->isPhysical)
return (0);
// First instance, allocate memory.
if (mz80afiCtrl == NULL)
{
mz80afiCtrl = (t_MZ80AFI *) calloc(1, sizeof(t_MZ80AFI));
if (!mz80afiCtrl)
{
return (0);
}
}
else
{
// Only one instance of this board allowed.
return (0);
}
// Get raw disk image size.
int diskSize = wd1773_getDiskSize(&mz80afiCtrl->fdc, MZ_80A);
if (diskSize > 0)
{
// Allocate space for the disk images. Add overhead for Extended CPC DSK format
// which includes a 256-byte disk header and a 256-byte Track-Info block per track.
int extDskOverhead = 256 + (mz80afiCtrl->fdc.cylinders * mz80afiCtrl->fdc.heads * 256);
int allocSize = diskSize + extDskOverhead;
mz80afiCtrl->disk = (uint8_t *) calloc(MAX_MZ80AFI_DISK_DRIVES, allocSize);
}
if (mz80afiCtrl->disk != NULL)
{
// Go through the config parameters to obtain any predefined FDC disk file names.
for (int idx = 0; idx < MAX_MZ80AFI_DISK_DRIVES; idx++)
{
if (idx < config->ifParamCount)
{
mz80afiCtrl->diskName[idx] = strdup(config->ifParam->file);
mz80afiCtrl->diskctl = MZ80AFI_FDC_FILE_NOT_LOADED;
}
else
{
mz80afiCtrl->diskName[idx] = NULL;
mz80afiCtrl->diskctl = MZ80AFI_FDC_NO_FILE;
}
}
int extDskOverhead = 256 + (mz80afiCtrl->fdc.cylinders * mz80afiCtrl->fdc.heads * 256);
int allocSize = diskSize + extDskOverhead;
if (wd1773_init(&mz80afiCtrl->fdc, &cpu->requestQueue, &cpu->responseQueue, mz80afiCtrl->diskName[0], mz80afiCtrl->disk, allocSize, MZ_80A))
{
result = 1;
// Install I/O handlers.
// WD1773 0xD8 Status
// 0xD9 Track
// 0xDA Sector
// 0xDB Data
// Drive Sel 0xDC D2 = 1 (enable), D1:D0 Drive 0-3, D7 High = Motor On
// Side Sel 0xDD D0 = Side
// MFM Mode 0xDE D0 = Mode, 0 = DDEN Enable
for (int idx = 0; idx < IO_PAGE_SIZE; idx++)
{
if ((idx & 0xff) >= 0xd8 && (idx & 0xff) < 0xdc)
cpu->_z80PSRAM->ioPtr[idx] = (t_MemoryFunc) MZ80AFI_IO_WD1773;
if ((idx & 0xff) == 0xdc)
cpu->_z80PSRAM->ioPtr[idx] = (t_MemoryFunc) MZ80AFI_IO_DriveSel;
if ((idx & 0xff) == 0xdd)
cpu->_z80PSRAM->ioPtr[idx] = (t_MemoryFunc) MZ80AFI_IO_SideSel;
if ((idx & 0xff) == 0xde)
cpu->_z80PSRAM->ioPtr[idx] = (t_MemoryFunc) MZ80AFI_IO_DDENSel;
}
// Install memory-mapped I/O handlers for MZ-80A FDC hardware acceleration.
// The MZ-80A FDC ORs the WD1773 DRQ signal with address line A10 for ROM
// fetches. The FDC driver places JP (IX) at F3FE and JP (IY) at F7FE.
// When DRQ is low, Z80 fetches from F3FE (spin loop). When DRQ goes high,
// A10 is forced high so F3FE fetch returns F7FE data (exit to INI handler).
cpu->_z80PSRAM->memioPtr[0xF3FE] = (t_MemoryFunc) MZ80AFI_IO_A10Toggle;
cpu->_z80PSRAM->memioPtr[0xF3FF] = (t_MemoryFunc) MZ80AFI_IO_A10Toggle;
}
}
// Any errors allocating the disk images then we cant activate the interface, free memory and exit.
if (!result)
{
if (mz80afiCtrl->disk != NULL)
free(mz80afiCtrl->disk);
if (mz80afiCtrl != NULL)
free(mz80afiCtrl);
mz80afiCtrl = NULL;
}
return (result);
}
// Reset handler for the MZ80AFI Floppy Disk Interface.
uint8_t MZ80AFI_Reset(t_Z80CPU *cpu)
{
// Locals.
return (0);
}
// Poll handler for the MZ80AFI Floppy Disk Interface.
uint8_t MZ80AFI_PollCB(t_Z80CPU *cpu)
{
// Locals.
return (0);
}
// Task processor, called by external events to influence/update the driver.
uint8_t MZ80AFI_TaskProcessor(t_Z80CPU *cpu, enum Z80CPU_TASK_NAME task, char *param)
{
// Locals.
uint8_t result = 0;
// Process according to requested task.
switch (task)
{
case FLOPPY_DISK_CHANGE:
int diskNo = atoi(strtok(param, ","));
char *fileName = strtok(NULL, ",");
if (fileName != NULL)
{
wd1773_changeDisk(&mz80afiCtrl->fdc, fileName, diskNo);
}
break;
case QUICK_DISK_CHANGE:
break;
default:
result = 1;
break;
}
return (result);
}
// MZ-80A FDC hardware acceleration: A10 toggle via DRQ.
// On the real MZ-80A, the FDC interface ORs the WD1773 DRQ signal with address line A10
// during ROM fetches. This allows a tight spin loop at F3FE (JP (IX)) to automatically
// exit to F7FE (JP (IY)) when data is ready, without polling the status register.
uint8_t MZ80AFI_IO_A10Toggle(t_Z80CPU *cpu, bool read, uint16_t addr, uint8_t data)
{
if (!read)
return data;
// When DRQ is active, force A10 high — F3FE reads as F7FE, F3FF reads as F7FF.
uint16_t effectiveAddr = addr;
if (mz80afiCtrl != NULL && mz80afiCtrl->fdc.drq)
{
effectiveAddr = addr | 0x0400;
}
return cpu->_z80PSRAM->RAM[effectiveAddr];
}
// The WD1773 emulation already handles MZ-80A bus inversion internally:
// wd1773_write() does val=~val, wd1773_read() does return ~val.
// No additional inversion needed in the handler.
uint8_t MZ80AFI_IO_WD1773(t_Z80CPU *cpu, bool read, uint16_t addr, uint8_t data)
{
// Locals.
uint8_t result = 0;
uint8_t reg = addr & 0x03;
if (read)
{
result = wd1773_read(&mz80afiCtrl->fdc, reg);
// Log first 5 status reads to trace ready/busy polling without flooding.
static int statusReadCount = 0;
if (reg == 0 && statusReadCount < 5)
{
int64_t elapsed = absolute_time_diff_us(mz80afiCtrl->fdc.motorStartTime, get_absolute_time());
debugf("FDC R[0]=%02X mot=%d loaded=%d lpend=%d rpend=%d spinUp=%llu elapsed=%lld\r\n",
result, mz80afiCtrl->fdc.motorOn, mz80afiCtrl->fdc.diskLoaded,
mz80afiCtrl->fdc.opState.loadPending, mz80afiCtrl->fdc.opState.readPending,
mz80afiCtrl->fdc.spinUpUs, elapsed);
statusReadCount++;
}
}
else
{
// Log command and register writes (not data transfers).
if (reg <= 2)
debugf("FDC W[%d] raw=%02X trk=%d hd=%d sec=%d\r\n",
reg, data, mz80afiCtrl->fdc.trackReg, mz80afiCtrl->fdc.currentHead,
mz80afiCtrl->fdc.sectorReg);
wd1773_write(&mz80afiCtrl->fdc, reg, data);
}
return (result);
}
uint8_t MZ80AFI_IO_DriveSel(t_Z80CPU *cpu, bool read, uint16_t addr, uint8_t data)
{
// Locals.
uint8_t result = 0;
// Check if motor already spinning, if it isnt, preset the spin up time.
bool oldMotor = mz80afiCtrl->fdc.motorOn;
mz80afiCtrl->fdc.motorOn = (data & 0x80) != 0;
if (mz80afiCtrl->fdc.motorOn && !oldMotor)
{
mz80afiCtrl->fdc.motorStartTime = get_absolute_time();
}
debugf("FDC DC=%02X mot=%d loaded=%d\r\n", data, mz80afiCtrl->fdc.motorOn, mz80afiCtrl->fdc.diskLoaded);
return (result);
}
uint8_t MZ80AFI_IO_SideSel(t_Z80CPU *cpu, bool read, uint16_t addr, uint8_t data)
{
// Locals.
uint8_t result = 0;
// Set head, 0 or 1.
wd1773_setHead(&mz80afiCtrl->fdc, data & 0x01);
return (result);
}
// DDEN select (0 = MFM, 1 = FM)
uint8_t MZ80AFI_IO_DDENSel(t_Z80CPU *cpu, bool read, uint16_t addr, uint8_t data)
{
// Locals.
uint8_t result = 0;
// Setup density, 0 = MFM, 1 = FM
wd1773_setDensity(&mz80afiCtrl->fdc, data & 0x01);
return (result);
}

View File

@@ -101,13 +101,13 @@ uint8_t RFS_Init(t_Z80CPU *cpu, t_drvIFConfig *config)
// Install Memory I/O handlers.
cpu->_z80PSRAM->memioPtr[BNKCTRLRST] = (t_MemoryFunc) RFS_IO_BNKCTRLRST;
cpu->_z80PSRAM->memioPtr[BNKCTRLDIS] = (t_MemoryFunc) RFS_IO_BNKCTRLDIS;
cpu->_z80PSRAM->memioPtr[HWSPIDATA] = (t_MemoryFunc) RFS_IO_HWSPIDATA;
cpu->_z80PSRAM->memioPtr[HWSPIDATA] = (t_MemoryFunc) RFS_IO_HWSPIDATA;
cpu->_z80PSRAM->memioPtr[HWSPISTART] = (t_MemoryFunc) RFS_IO_HWSPISTART;
cpu->_z80PSRAM->memioPtr[BNKSELMROM] = (t_MemoryFunc) RFS_IO_BNKSELMROM;
cpu->_z80PSRAM->memioPtr[BNKSELUSER] = (t_MemoryFunc) RFS_IO_BNKSELUSER;
cpu->_z80PSRAM->memioPtr[BNKCTRL] = (t_MemoryFunc) RFS_IO_BNKCTRL;
cpu->_z80PSRAM->memioPtr[MEMSW] = (t_MemoryFunc) RFS_IO_MEMSW;
cpu->_z80PSRAM->memioPtr[MEMSWR] = (t_MemoryFunc) RFS_IO_MEMSWR;
cpu->_z80PSRAM->memioPtr[BNKCTRL] = (t_MemoryFunc) RFS_IO_BNKCTRL;
cpu->_z80PSRAM->memioPtr[MEMSW] = (t_MemoryFunc) RFS_IO_MEMSW;
cpu->_z80PSRAM->memioPtr[MEMSWR] = (t_MemoryFunc) RFS_IO_MEMSWR;
// Invoke the reset handler as it sets the initial state of the RFS board.
RFS_Reset(cpu);
@@ -116,7 +116,7 @@ uint8_t RFS_Init(t_Z80CPU *cpu, t_drvIFConfig *config)
return (result);
}
// Reset handler for the RFS S-RAM Board.
// Reset handler for the virtual RFS ROMDISK board.
uint8_t RFS_Reset(t_Z80CPU *cpu)
{
// Locals.
@@ -486,8 +486,10 @@ uint8_t RFS_IO_BNKSELUSER(t_Z80CPU *cpu, bool read, uint16_t addr, uint8_t data)
// debugf("UROM: bank2=%02x uromAddr=%05lx\r\n", rfsCtrl->regBank2, rfsCtrl->uromAddr);
// Update memory map to reflect register change.
// UROM window is 2K (E800-EFFF), not 4K like MROM — only update 4 blocks, not 8.
// Using MROMSIZE (4K) here overwrites F000-F7FF which is the Floppy ROM area.
uint8_t offset = 0xE800 / MEMORY_BLOCK_SIZE;
for (int idx = 0; idx < (MROMSIZE / MEMORY_BLOCK_SIZE); idx++)
for (int idx = 0; idx < (0x800 / MEMORY_BLOCK_SIZE); idx++)
{
// Memory is both ROM and hardware, the registers share the same address space.
// A bank is 64K in size, so bankAddr needs to point to the correct location.
@@ -496,11 +498,6 @@ uint8_t RFS_IO_BNKSELUSER(t_Z80CPU *cpu, bool read, uint16_t addr, uint8_t data)
cpu->_membankPtr[idx + offset] = (MEMBANK_TYPE_ROM << 24) | (bank << 16) | (bankAddr);
cpu->_memAttr[bank][idx + offset].waitStates = 1;
cpu->_memAttr[bank][idx + offset].tCycSync = true;
//if(idx == 0)
//{
//debugf("USER: bank:%02x, bankAddr:%04x, ptr:%08x, offset=%02x, uromAddr:%04x, regbank2:%02x, regctrl:%02x\r\n", bank, bankAddr, cpu->_membankPtr[idx+offset], offset, rfsCtrl->uromAddr, rfsCtrl->regBank2, rfsCtrl->regCtrl, cpu->_membankPtr[idx+offset]);
//sleep_ms(1);
//}
}
}
}
@@ -546,8 +543,9 @@ uint8_t RFS_IO_BNKCTRL(t_Z80CPU *cpu, bool read, uint16_t addr, uint8_t data)
rfsCtrl->uromAddr = (uint32_t) (((((rfsCtrl->regCtrl & 0xC0) << 2) | rfsCtrl->regBank2) << 11));
// Update memory map to reflect register change.
// UROM window is 2K (E800-EFFF), not 4K like MROM.
uint8_t offset = 0xE800 / MEMORY_BLOCK_SIZE;
for (int idx = 0; idx < (MROMSIZE / MEMORY_BLOCK_SIZE); idx++)
for (int idx = 0; idx < (0x800 / MEMORY_BLOCK_SIZE); idx++)
{
// Memory is both ROM and hardware, the registers share the same address space.
uint8_t bank = (uint8_t) (rfsCtrl->uromAddr / MEMORY_PAGE_SIZE) + USER_ROM_I_BANK;
@@ -555,11 +553,6 @@ uint8_t RFS_IO_BNKCTRL(t_Z80CPU *cpu, bool read, uint16_t addr, uint8_t data)
cpu->_membankPtr[idx + offset] = (MEMBANK_TYPE_ROM << 24) | (bank << 16) | (bankAddr);
cpu->_memAttr[bank][idx + offset].waitStates = 1;
cpu->_memAttr[bank][idx + offset].tCycSync = true;
//if(idx == 0)
//{
//debugf("CTRL: data:%02x, bank:%02x, bankAddr:%04x, ptr:%08x, offset=%02x\r\n", data, bank, bankAddr, cpu->_membankPtr[idx+offset], offset);
//sleep_ms(1);
//}
}
}
}

View File

@@ -63,6 +63,7 @@ static uint16_t wd1773_crc16Ccitt(const uint8_t *data, size_t len)
bool wd1773_setDiskSize(t_WD1773 *wd, int machineType)
{
wd->machineType = machineType;
wd->spinUpUs = SPIN_UP_US; // Default spin-up time; overridden per-machine below if needed.
switch (machineType)
{
case MZ_700:
@@ -75,6 +76,13 @@ bool wd1773_setDiskSize(t_WD1773 *wd, int machineType)
wd->densityMfm = true;
break;
case MZ_80A:
wd->cylinders = 35;
wd->heads = 2;
wd->sectorsPerTrack = 17;
wd->sectorSize = 256;
wd->densityMfm = true;
wd->spinUpUs = 1000; // ~1ms — MZ-80A RFS runs at ARM speed (no tCycSync), Z80 delay loops are ~120x faster.
break;
case MZ_80B:
case MZ_2000:
case MZ_2200:
@@ -342,7 +350,7 @@ bool wd1773_init(t_WD1773 *wd, queue_t *req, queue_t *resp, const char *filename
t_CoreMsg msg = {.type = MSG_LOAD_FLOPPYDISK, .context = wd};
snprintf(msg.fileOp.filename, MAX_IC_FILENAME_LEN, "%s", filename);
msg.fileOp.buffer = img;
msg.fileOp.size = wd->diskSize;
msg.fileOp.size = bufSize; // Use full buffer size to allow Extended CPC DSK files (larger than raw disk size due to Track-Info headers).
msg.fileOp.diskNo = 0;
queue_add_blocking(req, &msg);
}
@@ -439,7 +447,7 @@ void wd1773_clearStatusFlags(t_WD1773 *wd)
uint8_t wd1773_getStatus(t_WD1773 *wd)
{
wd1773_processResponses(wd);
uint8_t st = wd->statusFlags;
uint8_t st = wd->statusFlags & ~WD1773_STATUS_NOT_READY; // Mask out stale NOT_READY — computed live below.
if (wd->busy)
st |= WD1773_STATUS_BUSY;
@@ -448,7 +456,7 @@ uint8_t wd1773_getStatus(t_WD1773 *wd)
if (wd->drq)
st |= WD1773_STATUS_DRQ;
bool ready = wd->diskLoaded && wd->motorOn && (absolute_time_diff_us(wd->motorStartTime, get_absolute_time()) >= SPIN_UP_US);
bool ready = wd->diskLoaded && wd->motorOn && (absolute_time_diff_us(wd->motorStartTime, get_absolute_time()) >= wd->spinUpUs);
if (!ready || wd->opState.loadPending || wd->opState.readPending)
st |= WD1773_STATUS_NOT_READY;
@@ -612,7 +620,7 @@ void wd1773_write(t_WD1773 *wd, uint8_t offset, uint8_t val)
*/
void wd1773_executeCommand(t_WD1773 *wd)
{
if (!wd->diskLoaded || !wd->motorOn || absolute_time_diff_us(wd->motorStartTime, get_absolute_time()) < SPIN_UP_US)
if (!wd->diskLoaded || !wd->motorOn || absolute_time_diff_us(wd->motorStartTime, get_absolute_time()) < wd->spinUpUs)
{
wd1773_setStatusFlag(wd, WD1773_STATUS_NOT_READY);
wd->busy = false;

View File

@@ -162,25 +162,21 @@
} \
}
*/
#define SERVICE_REQUESTS() if ((pio_1->irq & ((1u << 6) | (1u << 4))) != 0) \
#define SERVICE_REQUESTS() if ((pio_1->irq & (1u << 6)) != 0) \
{ \
WAIT_TX_FIFO_EMPTY(pio_1, smCycle); \
if ((pio_1->irq & (1u << 6)) != 0) \
{ \
PUSH_FIFO(pio_0, sm_data, 0x000000); \
CLEAR_IRQ(pio_0, 1); \
PUSH_FIFO(pio_0, sm_addr, 0x00000000); \
CLEAR_IRQ(pio_0, 0); \
PUSH_FIFO(pio_1, sm_busrq, 0xFFFFFB1C); \
WAIT_IRQ_CLEAR(pio_1, 6); \
} \
\
if ((pio_1->irq & (1u << 4)) != 0) \
{ \
debugf("NMI\r\n"); \
z80_nmi(&cpu->_Z80); \
CLEAR_IRQ(pio_0, 0); \
} \
PUSH_FIFO(pio_0, sm_data, 0x000000); \
CLEAR_IRQ(pio_0, 1); \
PUSH_FIFO(pio_0, sm_addr, 0x00000000); \
CLEAR_IRQ(pio_0, 0); \
PUSH_FIFO(pio_1, sm_busrq, 0xFFFFFB1C); \
WAIT_IRQ_CLEAR(pio_1, 6); \
} \
if ((pio_2->irq & (1u << 4)) != 0) \
{ \
debugf("NMI\r\n"); \
z80_nmi(&cpu->_Z80); \
CLEAR_IRQ(pio_2, 4); \
}
//cpu->interrupt = true;
@@ -406,6 +402,7 @@ struct Z80CPU
queue_t requestQueue; // Inter core command request queue. Used to dispatch commands from the Z80 to core0 for processing.
queue_t responseQueue; // Inter core command wresponse queue. Used to dispatch responses to commands received from the Z80 on core0.
bool halt; // Halt state, true = active.
bool refreshEnable; // Enable DRAM refresh during virtual memory fetches. Set via JSON config "refresh" parameter.
volatile bool hold; // Hold CPU processing, used by Core 0 when access to physical is needed.
volatile bool holdAck; // Hold confirmation flag.
volatile bool forceReset; // Force a CPU reset asynchronously.
@@ -454,7 +451,7 @@ t_VirtualFunc Z80CPU_getVirtualFunc(const char *funcName);
// Private prototypes for internal memory access functions or dedicated address range functions.
// Used and mapped within the funcMap[] array.
uint8_t Z80CPU_refreshDRAM(bool read, uint16_t addr, uint8_t data);
uint8_t Z80CPU_refreshDRAM(void *context, bool read, uint16_t addr, uint8_t data);
// Private prototypes for memory setup tasks.
// Used and mapped within the taskMap[] array.

View File

@@ -30,6 +30,8 @@
#ifndef MZ_H
#define MZ_H
#include "Z80CPU.h"
// Constants.
#define MZ700_MEMBANK_0 0 // Primary RAM bank.
#define MZ700_MEMBANK_1 1 // RAM bank to use for paging in RAM.
@@ -45,4 +47,22 @@
#define MZ_2200 6
#define MZ_2500 7
// Function pointer types for virtual driver feature processing.
// Shared across all Sharp MZ persona drivers (MZ700, MZ80A, etc.).
typedef uint8_t (*t_InitFunc)(t_Z80CPU *cpu, t_drvIFConfig *config);
typedef uint8_t (*t_ResetFunc)(t_Z80CPU *cpu);
typedef uint8_t (*t_PollFunc)(t_Z80CPU *cpu);
typedef uint8_t (*t_TaskFunc)(t_Z80CPU *cpu, enum Z80CPU_TASK_NAME task, char *param);
// Struct for interface name to function pointer mapping.
typedef struct
{
const char *interfaceFuncName; // Virtual function name from JSON
bool active; // Interface active?
t_InitFunc initFuncPtr; // Corresponding Init function pointer
t_ResetFunc resetFuncPtr; // Corresponding Reset function pointer
t_PollFunc pollFuncPtr; // Corresponding Polling function pointer
t_TaskFunc taskFuncPtr; // Corresponding Task function pointer
} t_InterfaceFuncMap;
#endif // MZ_H

View File

@@ -37,25 +37,6 @@
// Constants.
// Register control bits.
// Function pointer type for virtual driver feature processing.
typedef uint8_t (*t_InitFunc)(t_Z80CPU *cpu, t_drvIFConfig *config);
typedef uint8_t (*t_ResetFunc)(t_Z80CPU *cpu);
typedef uint8_t (*t_PollFunc)(t_Z80CPU *cpu);
typedef uint8_t (*t_TaskFunc)(t_Z80CPU *cpu, enum Z80CPU_TASK_NAME task, char *param);
// Struct for interface name to function pointer mapping.
typedef struct
{
const char *interfaceFuncName; // Virtual function name from JSON
bool active; // Interface active?
t_InitFunc initFuncPtr; // Corresponding Init function pointer
t_ResetFunc resetFuncPtr; // Corresponding Reset function pointer
t_PollFunc pollFuncPtr; // Corresponding Polling function pointer
t_TaskFunc taskFuncPtr; // Corresponding Task function pointer
} t_InterfaceFuncMap;
// Saved memioPtr entry for DRAM/MMIO switching.
// On real hardware, virtual devices (RFS etc.) at 0xE800-0xEFFF are not on the bus when DRAM is
// selected. We save and NULL their memioPtr entries on E1, restore on E3/E4.

View File

@@ -0,0 +1,50 @@
/////////////////////////////////////////////////////////////////////////////////////////////////////////
//
// Name: MZ80A.h
// Created: Apr 2026
// Author(s): Philip Smart
// Description: Z80 CPU DRIVER - Sharp MZ80A
// This file contains setup and drivers to mimic a Sharp MZ-80A machine internally to
// increase speed through use of internal RAM and provide virtual expansion drivers.
// Credits:
// Copyright: (c) 2019-2026 Philip Smart <philip.smart@net2net.org>
//
// History: Apr 2026 v1.0 - Initial write based on MZ700 driver.
//
// Notes: See Makefile to enable/disable conditional components
//
/////////////////////////////////////////////////////////////////////////////////////////////////////////
// This source file is free software: you can redistribute it and#or modify
// it under the terms of the GNU General Public License as published
// by the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This source file is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
/////////////////////////////////////////////////////////////////////////////////////////////////////////
#ifndef MZ80A_H
#define MZ80A_H
#include "drivers/Sharp/MZ.h"
#include "drivers/Sharp/WD1773.h"
// Constants.
#define MZ80A_MEMBANK_0 0 // Primary RAM bank.
#define MZ80A_MEMBANK_1 1 // RAM bank to use for paging in RAM.
// Private prototypes.
void MZ80A_readFileData(void *ctx, void *cfg, int filepos, char *buf, int len);
void MZ80A_readROMData(void *ctx, void *cfg, char *buf, int len);
uint8_t MZ80A_IO_Debug(t_Z80CPU *cpu, bool read, uint16_t addr, uint8_t data);
uint8_t MZ80A_IO_PIT(t_Z80CPU *cpu, bool read, uint16_t addr, uint8_t data);
uint8_t MZ80A_PollCB(t_Z80CPU *cpu);
uint8_t MZ80A_TaskProcessor(t_Z80CPU *cpu, enum Z80CPU_TASK_NAME task, char *param);
uint8_t MZ80A_Init(t_Z80CPU *cpu, t_FlashAppConfigHeader *appConfig, t_drvConfig *config, const char *ifName);
#endif // MZ80A_H

View File

@@ -0,0 +1,65 @@
/////////////////////////////////////////////////////////////////////////////////////////////////////////
//
// Name: MZ80AFI.h
// Created: Apr 2026
// Author(s): Philip Smart
// Description: Z80 CPU DRIVER - MZ80AFI Floppy Disk Controller
// This file contains setup and driver to mimic an MZ-80A Floppy Disk Controller.
// The MZ80AFI is functionally similar to the MZ-1E05 but intended for the MZ-80A.
// Credits:
// Copyright: (c) 2019-2026 Philip Smart <philip.smart@net2net.org>
//
// History: Apr 2026 v1.0 - Initial write based on MZ-1E05.
//
// Notes: See Makefile to enable/disable conditional components
//
/////////////////////////////////////////////////////////////////////////////////////////////////////////
// This source file is free software: you can redistribute it and#or modify
// it under the terms of the GNU General Public License as published
// by the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This source file is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
/////////////////////////////////////////////////////////////////////////////////////////////////////////
#ifndef MZ80AFI_H
#define MZ80AFI_H
#include "drivers/Sharp/WD1773.h"
// Constants.
#define MAX_MZ80AFI_DISK_DRIVES 2 // Maximum number of Disk Drives attached to the MZ80AFI board.
enum t_MZ80AFIDiskCtrl
{
MZ80AFI_FDC_NO_FILE = 0, // No file defined as disk image.
MZ80AFI_FDC_FILE_NOT_LOADED = 1, // File has not been loaded into RAM.
};
// Struct for the MZ80AFI Floppy Disk Interface with ROM.
typedef struct
{
t_WD1773 fdc; // FDC Controller instance.
uint8_t *disk; // Disk drive images in RAM.
char *diskName[MAX_MZ80AFI_DISK_DRIVES]; // Filename of the disk on the ESP32 SD card.
enum t_MZ80AFIDiskCtrl diskctl; // Disk control.
} t_MZ80AFI;
// Public prototypes.
uint8_t MZ80AFI_Init(t_Z80CPU *cpu, t_drvIFConfig *config);
uint8_t MZ80AFI_Reset(t_Z80CPU *cpu);
uint8_t MZ80AFI_PollCB(t_Z80CPU *cpu);
uint8_t MZ80AFI_TaskProcessor(t_Z80CPU *cpu, enum Z80CPU_TASK_NAME task, char *param);
uint8_t MZ80AFI_IO_A10Toggle(t_Z80CPU *cpu, bool read, uint16_t addr, uint8_t data);
uint8_t MZ80AFI_IO_WD1773(t_Z80CPU *cpu, bool read, uint16_t addr, uint8_t data);
uint8_t MZ80AFI_IO_DriveSel(t_Z80CPU *cpu, bool read, uint16_t addr, uint8_t data);
uint8_t MZ80AFI_IO_SideSel(t_Z80CPU *cpu, bool read, uint16_t addr, uint8_t data);
uint8_t MZ80AFI_IO_DDENSel(t_Z80CPU *cpu, bool read, uint16_t addr, uint8_t data);
#endif // MZ80AFI_H

View File

@@ -72,7 +72,7 @@
#define MAX_DISK_SIZE (80 * 2 * 8 * 512) // MZ-2500 max
#define ROTATION_US 200000 // 300 RPM = 200ms
#define PULSE_WIDTH_US 2000 // Index pulse duration
#define SPIN_UP_US 500000ULL // Motor spin-up time
#define SPIN_UP_US 500000ULL // Motor spin-up time (default, real-speed Z80 with tCycSync)
/* Internal command classification */
#define TYPE_I 1 // Movement
@@ -159,6 +159,7 @@ typedef struct
absolute_time_t lastRotation; // For index pulse emulation
bool motorOn; // Motor status
absolute_time_t motorStartTime; // Spin-up start time
uint64_t spinUpUs; // Motor spin-up time (machine-specific)
// --- Format Support ---
bool diskLoaded; // Image successfully loaded

View File

@@ -1 +1 @@
2.009
2.083

View File

@@ -75,6 +75,7 @@
.define PUBLIC Z80_PIN_INT 33
.define PUBLIC Z80_PIN_WAIT 34
.define PUBLIC Z80_PIN_CLK 35
.define PUBLIC Z80_PIN_CLK_S Z80_PIN_CLK - Z80_PIN_DATA_0
.define PUBLIC Z80_PIN_NMI 36
.define PUBLIC Z80_PIN_RESET 38
.define PUBLIC Z80_PIN_RD_OFS Z80_PIN_RD - GPIO_UPPER_START
@@ -187,6 +188,7 @@ nmihigh:
; mechanism.
.program z80_cycle
.side_set 2 opt
.wrap_target
public start_cycle:
wait 0 irq 6 ; Pause if BUSACK active.
irq set 0
@@ -204,35 +206,54 @@ cycle_wait_eval:
cycle_nowait:
out exec, 16
jmp cycle_nowait ; Loop until jmp start executed.
; A refresh state, performs a fetch but doesnt read the data and then goes onto refresh state. This SM is used
; when the RP2350 is executing Z80 code internally and ensures DRAM is refreshed.
.program z80_refresh
.side_set 2 opt
.wrap_target
public start_refresh:
irq set 0 ; Indicate SM is at start.
wait 1 gpio Z80_PIN_CLK ; T1: GPIO35 high
wait 0 irq 0 ; Wait to Output PC (Z80_PIN_ADDR_0-15), it is also the refresh address.
set pins, 0b1110 side 0b11 ; /IORQ, /MREQ, /RFSH, /M1, Side: /WR /RD -> Set /M1 low
wait 0 gpio Z80_PIN_CLK ; T1: GPIO35 low
set pins, 0b1010 side 0b11 ; /IORQ, /MREQ, /RFSH, /M1, Side: /WR /RD -> Set /M1 low, /MREQ low, /RD high
waitrefresh:
wait 1 gpio Z80_PIN_CLK ; T2: GPIO35 high
wait 0 gpio Z80_PIN_CLK ; T2: GPIO35 low
jmp pin, nowaitrefresh ; WAIT is high, so dont add wait states.
jmp waitrefresh ; Add wait state TW.
nowaitrefresh:
wait 1 gpio Z80_PIN_CLK ; T3: GPIO35 high
cycle_refresh:
; wait 1 gpio Z80_PIN_CLK ; T3: GPIO35 high
set pins, 0b1101 side 0b11 ; /IORQ, /MREQ, /RFSH, /M1, Side: /WR /RD -> Set /M1 high, /MREQ high, /RD high, /RFSH low
wait 0 gpio Z80_PIN_CLK ; T3: GPIO35 low
set pins, 0b1001 side 0b11 ; /IORQ, /MREQ, /RFSH, /M1, Side: /WR /RD -> Set /M1 high, /MREQ low, /RD high, /RFSH low
wait 1 gpio Z80_PIN_CLK ; T4: GPIO35 high
wait 0 gpio Z80_PIN_CLK ; T4: GPIO35 low
set pins, 0b1101 side 0b11 [1]; /IORQ, /MREQ, /RFSH, /M1, Side: /WR /RD -> Set /M1 high, /MREQ high, /RD high, /RFSH low
set pins, 0b1111 side 0b11 ; /IORQ, /MREQ, /RFSH, /M1, Side: /WR /RD -> Set /M1 high, /MREQ high, /RD high, /RFSH high
.wrap
; A refresh state, performs a fetch but doesnt read the data and then goes onto refresh state. This SM is used
; when the RP2350 is executing Z80 code internally and ensures DRAM is refreshed.
;.program z80_refresh
;.side_set 2 opt
;.wrap_target
;public start_refresh:
; irq set 5
; wait 0 irq 5 ; Wait until Output of PC (Z80_PIN_ADDR_0-15) has been performed.
; wait 1 gpio Z80_PIN_CLK ; T3: GPIO35 high
; set pins, 0b1101 side 0b11 ; /IORQ, /MREQ, /RFSH, /M1, Side: /WR /RD -> Set /M1 high, /MREQ high, /RD high, /RFSH low
; wait 0 gpio Z80_PIN_CLK ; T3: GPIO35 low
; set pins, 0b1001 side 0b11 ; /IORQ, /MREQ, /RFSH, /M1, Side: /WR /RD -> Set /M1 high, /MREQ low, /RD high, /RFSH low
; wait 1 gpio Z80_PIN_CLK ; T4: GPIO35 high
; wait 0 gpio Z80_PIN_CLK ; T4: GPIO35 low
; set pins, 0b1101 side 0b11 [3]; /IORQ, /MREQ, /RFSH, /M1, Side: /WR /RD -> Set /M1 high, /MREQ high, /RD high, /RFSH low
; set pins, 0b1111 side 0b11 ; /IORQ, /MREQ, /RFSH, /M1, Side: /WR /RD -> Set /M1 high, /MREQ high, /RD high, /RFSH high
;
; set pins, 0b1110 side 0b11 ; /IORQ, /MREQ, /RFSH, /M1, Side: /WR /RD -> Set /M1 low
; wait 0 gpio Z80_PIN_CLK ; T1: GPIO35 low
; set pins, 0b1010 side 0b10 ; /IORQ, /MREQ, /RFSH, /M1, Side: /WR /RD -> Set /M1 low, /MREQ low, /RD low
;waitrefresh:
; wait 1 gpio Z80_PIN_CLK ; T2: GPIO35 high
; wait 0 gpio Z80_PIN_CLK ; T2: GPIO35 low
;
; jmp pin, nowaitrefresh ; WAIT is high, so dont add wait states.
; jmp waitrefresh ; Add wait state TW.
;nowaitrefresh:
; wait 1 gpio Z80_PIN_CLK ; T3: GPIO35 high
; set pins, 0b1101 side 0b11 ; /IORQ, /MREQ, /RFSH, /M1, Side: /WR /RD -> Set /M1 high, /MREQ high, /RD high, /RFSH low
; wait 0 gpio Z80_PIN_CLK ; T3: GPIO35 low
; set pins, 0b1001 side 0b11 ; /IORQ, /MREQ, /RFSH, /M1, Side: /WR /RD -> Set /M1 high, /MREQ low, /RD high, /RFSH low
; wait 1 gpio Z80_PIN_CLK ; T4: GPIO35 high
; wait 0 gpio Z80_PIN_CLK ; T4: GPIO35 low
; set pins, 0b1101 side 0b11 [1]; /IORQ, /MREQ, /RFSH, /M1, Side: /WR /RD -> Set /M1 high, /MREQ high, /RD high, /RFSH low
; set pins, 0b1111 side 0b11 ; /IORQ, /MREQ, /RFSH, /M1, Side: /WR /RD -> Set /M1 high, /MREQ high, /RD high, /RFSH high
;.wrap
; State machine to generate a requested number of wait states.
; The state machine is used for pacing internal ARM processing.
.program z80_wait
@@ -270,6 +291,8 @@ resethigh:
;-----------------------------------------------------------------------------------------------------------------
; These are the basic pio programs for z80 cycle recreation. They have been embedded within the C code of the
; Z80CPU.c module. This is primarily due to limited PIO program space.
; NB. Z80_PIN_CLK_S is used as the PIO is known. Normally if you write a program using wait x gpio Z80_PIN_CLK
; the the initialisation routines would adjust the pin to account for the base offset.
;-----------------------------------------------------------------------------------------------------------------
.program z80_fetch
@@ -277,32 +300,32 @@ resethigh:
.wrap_target
public start_fetch:
irq set 1
wait 1 gpio Z80_PIN_CLK ; T1: GPIO35 high
wait 1 gpio Z80_PIN_CLK_S ; T1: GPIO35 high
wait 0 irq 1 ; Wait to Output PC (Z80_PIN_ADDR_0-15)
;out pins, 16 ; Output PC (Z80_PIN_ADDR_0-15)
set pins, 0b1110 side 0b11 ; /IORQ, /MREQ, /RFSH, /M1, Side: /WR /RD -> Set /M1 low
wait 0 gpio Z80_PIN_CLK ; T1: GPIO35 low
wait 0 gpio Z80_PIN_CLK_S ; T1: GPIO35 low
set pins, 0b1010 side 0b10 ; /IORQ, /MREQ, /RFSH, /M1, Side: /WR /RD -> Set /M1 low, /MREQ low, /RD low
waitfetch:
wait 1 gpio Z80_PIN_CLK ; T2: GPIO35 high
wait 0 gpio Z80_PIN_CLK ; T2: GPIO35 low
wait 1 gpio Z80_PIN_CLK_S ; T2: GPIO35 high
wait 0 gpio Z80_PIN_CLK_S ; T2: GPIO35 low
jmp pin, nowaitfetch ; WAIT is high, so dont add wait states.
jmp waitfetch ; Add wait state TW.
nowaitfetch:
wait 1 gpio Z80_PIN_CLK ; T3: GPIO35 high
wait 1 gpio Z80_PIN_CLK_S ; T3: GPIO35 high
in pins, 8 ; Read opcode (Z80_PIN_DATA_0-7)
irq set 1
wait 0 irq 1 ; Wait to Output RFSH Address (Z80_PIN_ADDR_0-15)
;out pins, 16 ; Output RFSH Address (Z80_PIN_ADDR_0-15)
set pins, 0b1101 side 0b11 ; /IORQ, /MREQ, /RFSH, /M1, Side: /WR /RD -> Set /M1 high, /MREQ high, /RD high, /RFSH low
wait 0 gpio Z80_PIN_CLK ; T3: GPIO35 low
wait 0 gpio Z80_PIN_CLK_S ; T3: GPIO35 low
set pins, 0b1001 side 0b11 ; /IORQ, /MREQ, /RFSH, /M1, Side: /WR /RD -> Set /M1 high, /MREQ low, /RD high, /RFSH low
wait 1 gpio Z80_PIN_CLK ; T4: GPIO35 high
wait 0 gpio Z80_PIN_CLK ; T4: GPIO35 low
wait 1 gpio Z80_PIN_CLK_S ; T4: GPIO35 high
wait 0 gpio Z80_PIN_CLK_S ; T4: GPIO35 low
set pins, 0b1101 side 0b11 [1]; /IORQ, /MREQ, /RFSH, /M1, Side: /WR /RD -> Set /M1 high, /MREQ high, /RD high, /RFSH low
set pins, 0b1111 side 0b11 ; /IORQ, /MREQ, /RFSH, /M1, Side: /WR /RD -> Set /M1 high, /MREQ high, /RD high, /RFSH high
.wrap
@@ -312,22 +335,22 @@ nowaitfetch:
.wrap_target
public start_mem_read:
;irq set 2
;wait 1 gpio Z80_PIN_CLK ; T1: GPIO35 high
;wait 1 gpio Z80_PIN_CLK_S ; T1: GPIO35 high
;wait 0 irq 2 ; Wait till control starts the sequence.
;out pins, 16 ; Output address (Z80_PIN_ADDR_0-15)
wait 0 gpio Z80_PIN_CLK ; EXEC T1: GPIO35 low
wait 0 gpio Z80_PIN_CLK_S ; EXEC T1: GPIO35 low
set pins, 0b1011 side 0b10 ; EXEC /IORQ, /MREQ, /RFSH, /M1, Side: /WR /RD -> Set /MREQ low, /RD low
waitread:
wait 1 gpio Z80_PIN_CLK ; EXEC T2: GPIO35 high
wait 0 gpio Z80_PIN_CLK ; EXEC T2: GPIO35 low
wait 1 gpio Z80_PIN_CLK_S ; EXEC T2: GPIO35 high
wait 0 gpio Z80_PIN_CLK_S ; EXEC T2: GPIO35 low
jmp pin, nowaitread ; WAIT is high, so dont add wait states.
jmp waitread ; Add wait state TW.
nowaitread:
wait 1 gpio Z80_PIN_CLK ; EXEC2 T3: GPIO35 high
wait 0 gpio Z80_PIN_CLK ; EXEC2 T3: GPIO35 low
wait 1 gpio Z80_PIN_CLK_S ; EXEC2 T3: GPIO35 high
wait 0 gpio Z80_PIN_CLK_S ; EXEC2 T3: GPIO35 low
in pins, 8 ; EXEC2 Read data (Z80_PIN_DATA_0-7)
set pins, 0b1111 side 0b11 ; EXEC2 /MREQ, /IORQ, /RFSH, /M1, Side: /RD -> Set /MREQ high, /RD high
.wrap
@@ -337,25 +360,25 @@ nowaitread:
.wrap_target
public start_mem_write:
irq set 0
wait 1 gpio Z80_PIN_CLK ; T1: GPIO35 high
wait 1 gpio Z80_PIN_CLK_S ; T1: GPIO35 high
wait 0 irq 0 ; Wait till control starts the sequence.
;out pins, 16 ; Output address (Z80_PIN_ADDR_0-15)
wait 0 gpio Z80_PIN_CLK ; T1: GPIO35 low
wait 0 gpio Z80_PIN_CLK_S ; T1: GPIO35 low
set pins, 0b1011 side 0b11 ; /IORQ, /MREQ, /RFSH, /M1, Side: /WR /RD -> Set /MREQ low, /WR high
;out pins, 8 ; Output data (Z80_PIN_DATA_0-7)
waitwrite:
wait 1 gpio Z80_PIN_CLK ; T2: GPIO35 high
wait 0 gpio Z80_PIN_CLK ; T2: GPIO35 low
wait 1 gpio Z80_PIN_CLK_S ; T2: GPIO35 high
wait 0 gpio Z80_PIN_CLK_S ; T2: GPIO35 low
set pins, 0b1011 side 0b01 ; /IORQ, /MREQ, /RFSH, /M1, Side: /WR /RD -> Set /MREQ low, /WR low
jmp pin, nowaitwrite ; WAIT is high, so dont add wait states.
jmp waitwrite ; Add wait state TW.
nowaitwrite:
wait 1 gpio Z80_PIN_CLK ; T3: GPIO35 high
wait 0 gpio Z80_PIN_CLK ; T3: GPIO35 low
wait 1 gpio Z80_PIN_CLK_S ; T3: GPIO35 high
wait 0 gpio Z80_PIN_CLK_S ; T3: GPIO35 low
set pins, 0b1111 side 0b11 ; /IORQ, /MREQ, /RFSH, /M1, Side: /WR /RD -> Set /MREQ high, /WR high
.wrap
@@ -364,25 +387,25 @@ nowaitwrite:
.wrap_target
public start:
irq set 0
wait 1 gpio Z80_PIN_CLK ; T1: GPIO35 high
wait 1 gpio Z80_PIN_CLK_S ; T1: GPIO35 high
wait 0 irq 0 ; Wait till control starts the sequence.
;out pins, 16 ; Output port address (Z80_PIN_ADDR_0-15)
wait 0 gpio Z80_PIN_CLK ; T1: GPIO35 low
wait 0 gpio Z80_PIN_CLK_S ; T1: GPIO35 low
wait 1 gpio Z80_PIN_CLK ; EXEC T2: GPIO35 high
wait 1 gpio Z80_PIN_CLK_S ; EXEC T2: GPIO35 high
set pins, 0b0111 side 0b10 ; EXEC /IORQ, /MREQ, /RFSH, /M1, Side: /WR /RD -> Set /IORQ low, /RD low
wait 0 gpio Z80_PIN_CLK ; T2: GPIO35 low
wait 0 gpio Z80_PIN_CLK_S ; T2: GPIO35 low
waitreadio:
wait 1 gpio Z80_PIN_CLK ; TW: GPIO35 high
wait 0 gpio Z80_PIN_CLK ; TW: GPIO35 low
wait 1 gpio Z80_PIN_CLK_S ; TW: GPIO35 high
wait 0 gpio Z80_PIN_CLK_S ; TW: GPIO35 low
jmp pin, nowaitreadio ; WAIT is high, so dont add wait states.
jmp waitreadio ; Add wait state TW.
nowaitreadio:
wait 1 gpio Z80_PIN_CLK ; T3: GPIO35 high
wait 0 gpio Z80_PIN_CLK ; T3: GPIO35 low
wait 1 gpio Z80_PIN_CLK_S ; T3: GPIO35 high
wait 0 gpio Z80_PIN_CLK_S ; T3: GPIO35 low
in pins, 8 ; Read data (Z80_PIN_DATA_0-7)
set pins, 0b1111 side 0b11 ; /MREQ, /IORQ, /RFSH, /M1, Side: /RD -> Set /IORQ high, /RD high
.wrap
@@ -392,24 +415,24 @@ nowaitreadio:
.wrap_target
public start:
irq set 0
wait 1 gpio Z80_PIN_CLK ; T1: GPIO35 high
wait 1 gpio Z80_PIN_CLK_S ; T1: GPIO35 high
wait 0 irq 0 ; Wait till control starts the sequence.
;out pins, 16 ; Output address (Z80_PIN_ADDR_0-15)
wait 0 gpio Z80_PIN_CLK ; T1: GPIO35 low
wait 0 gpio Z80_PIN_CLK_S ; T1: GPIO35 low
;out pins, 8 ; Output data (Z80_PIN_DATA_0-7)
wait 1 gpio Z80_PIN_CLK ; T2: GPIO35 high
wait 1 gpio Z80_PIN_CLK_S ; T2: GPIO35 high
set pins, 0b0111 side 0b01 ; /IORQ, /MREQ, /RFSH, /M1, Side: /WR /RD -> Set /IORQ low, /WR low
wait 0 gpio Z80_PIN_CLK ; T2: GPIO35 low
wait 0 gpio Z80_PIN_CLK_S ; T2: GPIO35 low
waitwriteio:
wait 1 gpio Z80_PIN_CLK ; TW: GPIO35 high
wait 0 gpio Z80_PIN_CLK ; TW: GPIO35 low
wait 1 gpio Z80_PIN_CLK_S ; TW: GPIO35 high
wait 0 gpio Z80_PIN_CLK_S ; TW: GPIO35 low
jmp pin, nowaitwriteio ; WAIT is high, so dont add wait states.
jmp waitwriteio ; Add wait state TW.
nowaitwriteio:
wait 1 gpio Z80_PIN_CLK ; T3: GPIO35 high
wait 0 gpio Z80_PIN_CLK ; T3: GPIO35 low
wait 1 gpio Z80_PIN_CLK_S ; T3: GPIO35 high
wait 0 gpio Z80_PIN_CLK_S ; T3: GPIO35 low
set pins, 0b1111 side 0b11 ; /IORQ, /MREQ, /RFSH, /M1, Side: /WR /RD -> Set /IORQ high, /WR high
.wrap
@@ -429,39 +452,39 @@ public start_clk_sync:
.wrap_target
public start:
irq set 1
wait 1 gpio Z80_PIN_CLK ; T1: GPIO35 high
wait 1 gpio Z80_PIN_CLK_S ; T1: GPIO35 high
wait 0 irq 1 ; Wait to Output PC (Z80_PIN_ADDR_0-15)
;out pins, 16 ; Output PC (Z80_PIN_ADDR_0-15)
set pins, 0b1110 side 0b11 ; /IORQ, /MREQ, /RFSH, /M1, Side: /WR /RD -> Set /M1 low
wait 0 gpio Z80_PIN_CLK ; T1: GPIO35 low
wait 0 gpio Z80_PIN_CLK_S ; T1: GPIO35 low
wait 1 gpio Z80_PIN_CLK ; T2: GPIO35 high
wait 0 gpio Z80_PIN_CLK ; T2: GPIO35 low
wait 1 gpio Z80_PIN_CLK_S ; T2: GPIO35 high
wait 0 gpio Z80_PIN_CLK_S ; T2: GPIO35 low
wait 1 gpio Z80_PIN_CLK ; TW1: GPIO35 high
wait 0 gpio Z80_PIN_CLK ; TW1: GPIO35 low
wait 1 gpio Z80_PIN_CLK_S ; TW1: GPIO35 high
wait 0 gpio Z80_PIN_CLK_S ; TW1: GPIO35 low
set pins, 0b0110 side 0b11 ; /IORQ, /MREQ, /RFSH, /M1, Side: /WR /RD -> Set /M1 low, /IORQ low
waitinta:
wait 1 gpio Z80_PIN_CLK ; TW2: GPIO35 high
wait 0 gpio Z80_PIN_CLK ; TW2: GPIO35 low
wait 1 gpio Z80_PIN_CLK_S ; TW2: GPIO35 high
wait 0 gpio Z80_PIN_CLK_S ; TW2: GPIO35 low
jmp pin, nowaitinta ; WAIT is high, so dont add wait states.
jmp waitinta ; Add wait state TW.
nowaitinta:
in pins, 8 ; Read vector (Z80_PIN_DATA_0-7)
wait 1 gpio Z80_PIN_CLK ; T3: GPIO35 high
wait 1 gpio Z80_PIN_CLK_S ; T3: GPIO35 high
irq set 1
wait 0 irq 1 ; Wait to Output RFSH Address (Z80_PIN_ADDR_0-15)
;out pins, 16 ; Output RFSH Address (Z80_PIN_ADDR_0-15)
set pins, 0b1101 side 0b11 ; /IORQ, /MREQ, /RFSH, /M1, Side: /WR /RD -> Set /M1 high, /IORQ high, /RD high, /RFSH low
wait 0 gpio Z80_PIN_CLK ; T3: GPIO35 low
wait 0 gpio Z80_PIN_CLK_S ; T3: GPIO35 low
set pins, 0b1001 side 0b11 ; /IORQ, /MREQ, /RFSH, /M1, Side: /WR /RD -> Set /M1 high, /MREQ low, /RD high, /RFSH low
wait 1 gpio Z80_PIN_CLK ; T4: GPIO35 high
wait 0 gpio Z80_PIN_CLK ; T4: GPIO35 low
wait 1 gpio Z80_PIN_CLK_S ; T4: GPIO35 high
wait 0 gpio Z80_PIN_CLK_S ; T4: GPIO35 low
set pins, 0b1101 side 0b11 ; /IORQ, /MREQ, /RFSH, /M1, Side: /WR /RD -> Set /M1 high, /MREQ high, /RD high, /RFSH low
wait 0 irq 6 ; Halt if BUSRQ is asserted.
.wrap

View File

@@ -1 +1 @@
2.011
2.079