Sharp MZ80A updates and performance tuning

This commit is contained in:
Philip Smart
2026-04-30 08:58:26 +01:00
parent 6753eec367
commit 32ac09c92e
52 changed files with 2408 additions and 645 deletions

View File

@@ -1 +1 @@
2.07
2.18

View File

@@ -814,19 +814,48 @@ bool SDCard::storeRP2350Info(const t_IpcFrameHdr &frame, FSPI &fspi)
{
memcpy(rp2350Header, infoBuf, sizeof(t_FlashPartitionHeader));
// Extended payload includes cpufreq/psramfreq/voltage/flashSize/psramSize after the header.
if (payloadSize >= sizeof(t_FlashInfoPayload))
// Extended payload fields — extract via WiFi's wifiCtrl.run fields directly.
// The rp2350Header pointer points to wifiCtrl.run.rp2350FlashHeader, and the
// fields after it (rp2350CpuFreq, rp2350PsramFreq, etc.) are at known offsets.
// Backwards-compatible: only extract fields present in the payload.
if (payloadSize > sizeof(t_FlashPartitionHeader))
{
t_FlashInfoPayload *info = (t_FlashInfoPayload *) infoBuf;
// Store in the fields immediately after rp2350FlashHeader in wifiCtrl.run.
// Layout must match: int32_t cpufreq, psramfreq, voltage; uint32_t flashSize, psramSize;
int32_t *extra = (int32_t *) (rp2350Header + 1);
extra[0] = info->cpufreq;
extra[1] = info->psramfreq;
extra[2] = info->voltage;
uint32_t *extra32 = (uint32_t *) &extra[3];
extra32[0] = info->flashSize;
extra32[1] = info->psramSize;
// Core config fields (cpufreq..psramSize) — present in all extended payloads.
const size_t coreExtSize = offsetof(t_FlashInfoPayload, hostClkHz);
if (payloadSize >= coreExtSize)
{
int32_t *extra = (int32_t *) (rp2350Header + 1);
extra[0] = info->cpufreq;
extra[1] = info->psramfreq;
extra[2] = info->voltage;
uint32_t *extra32 = (uint32_t *) &extra[3];
extra32[0] = info->flashSize;
extra32[1] = info->psramSize;
}
// Host clock + emulation speed — added after psramSize.
if (payloadSize >= offsetof(t_FlashInfoPayload, driverSummary))
{
int32_t *extra = (int32_t *) (rp2350Header + 1);
uint32_t *extra32 = (uint32_t *) &extra[3];
extra32[2] = info->hostClkHz; // rp2350HostClkHz
extra32[3] = info->emulSpeedHz; // rp2350EmulSpeedHz
}
// Driver summary — last field, variable-length content.
uint32_t *extra32base = (uint32_t *) ((int32_t *)(rp2350Header + 1) + 3);
char *drvSummary = (char *) &extra32base[4]; // After flashSize, psramSize, hostClkHz, emulSpeedHz
if (payloadSize >= sizeof(t_FlashInfoPayload))
{
memcpy(drvSummary, info->driverSummary, FLASHHDR_DRIVER_SUMMARY_SIZE);
drvSummary[FLASHHDR_DRIVER_SUMMARY_SIZE - 1] = '\0';
}
else
{
drvSummary[0] = '\0';
}
}
}
free(infoBuf);

View File

@@ -476,39 +476,41 @@ bool WiFi::stringReplace(std::string &str, const std::string &from, const std::s
//
bool WiFi::setFloppyDiskFile(const std::string &param1, int diskNo)
{
// Check for out of bounds values.
if (diskNo > WIFI_MAX_FLOPPY_DISK_IMAGES)
return (false);
if (diskNo < 0 || diskNo >= WIFI_MAX_FLOPPY_DISK_IMAGES)
return false;
// Save the floppy disk image name for later use.
this->wifiCtrl.run.floppyDiskImage[diskNo] = param1;
return (true);
if (this->wifiCtrl.run.floppyDiskImage[diskNo] != param1)
{
this->wifiCtrl.run.floppyDiskImage[diskNo] = param1;
ESP_LOGI(WIFITAG, "Floppy %d: %s", diskNo, param1.c_str());
}
return true;
}
// Method to change the name of the quick disk image indexed by 'diskNo'.
//
bool WiFi::setQuickDiskFile(const std::string &param1, int diskNo)
{
// Check for out of bounds values.
if (diskNo > WIFI_MAX_QUICK_DISK_IMAGES)
return (false);
if (diskNo < 0 || diskNo >= WIFI_MAX_QUICK_DISK_IMAGES)
return false;
// Save the floppy disk image name for later use.
this->wifiCtrl.run.quickDiskImage[diskNo] = param1;
return (true);
if (this->wifiCtrl.run.quickDiskImage[diskNo] != param1)
{
this->wifiCtrl.run.quickDiskImage[diskNo] = param1;
ESP_LOGI(WIFITAG, "Quick Disk %d: %s", diskNo, param1.c_str());
}
return true;
}
// Method to change the name of the ramfile backup image indexed by 'ramfileNo'.
//
bool WiFi::setRamFile(const std::string &param1, int ramfileNo)
{
// Check for out of bounds values.
if (ramfileNo > WIFI_MAX_RAMFILE_IMAGES)
return (false);
if (ramfileNo < 0 || ramfileNo >= WIFI_MAX_RAMFILE_IMAGES)
return false;
// Save the floppy disk image name for later use.
this->wifiCtrl.run.ramFileImage[ramfileNo] = param1;
return (true);
if (this->wifiCtrl.run.ramFileImage[ramfileNo] != param1)
{
this->wifiCtrl.run.ramFileImage[ramfileNo] = param1;
ESP_LOGI(WIFITAG, "RAMFILE %d: %s", ramfileNo, param1.c_str());
}
return true;
}
// Method to unpack a gzipped and/or tar file. The extension of the srcFile determines the action taken.
@@ -739,14 +741,16 @@ esp_err_t WiFi::expandVarsAndSend(httpd_req_t *req, std::string str)
#endif
pairs.push_back(keyValue);
keyValue.name = "%SK_USBNCMVISIBLE%";
#if !defined(CONFIG_IF_WIFI_ENABLED) && defined(CONFIG_IF_USB_NCM_ENABLED)
#if 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)
#if defined(CONFIG_IF_WIFI_ENABLED) && defined(CONFIG_IF_USB_NCM_ENABLED)
keyValue.value = "Network Configuration";
#elif defined(CONFIG_IF_WIFI_ENABLED)
keyValue.value = "WiFi Configuration";
#else
keyValue.value = "Network Configuration";
@@ -910,6 +914,23 @@ esp_err_t WiFi::expandVarsAndSend(httpd_req_t *req, std::string str)
keyValue.value = (ai && ai->description[0]) ? std::string(ai->description) : "N/A";
pairs.push_back(keyValue);
keyValue.name = "%SK_FWAUTHOR%";
keyValue.value = (ai && ai->author[0]) ? std::string(ai->author) : "N/A";
pairs.push_back(keyValue);
keyValue.name = "%SK_FWLICENSE%";
keyValue.value = (ai && ai->license[0]) ? std::string(ai->license) : "N/A";
pairs.push_back(keyValue);
keyValue.name = "%SK_FWCOPYRIGHT%";
keyValue.value = (ai && ai->copyright[0]) ? std::string(ai->copyright) : "N/A";
pairs.push_back(keyValue);
// Active drivers — populated by RP2350 via INF IPC command.
keyValue.name = "%SK_ACTIVEDRIVERS%";
keyValue.value = (wifiCtrl.run.rp2350DriverSummary[0]) ? std::string(wifiCtrl.run.rp2350DriverSummary) : "N/A";
pairs.push_back(keyValue);
keyValue.name = "%SK_CPUCLOCK%";
keyValue.value = (wifiCtrl.run.rp2350CpuFreq > 0) ?
std::to_string(wifiCtrl.run.rp2350CpuFreq / 1000000) + " MHz" : "N/A";
@@ -1072,6 +1093,12 @@ esp_err_t WiFi::versionedRename(const std::string &src, const std::string &dst,
}
// dst is now free — rename src into place.
// If src == dst this is a backup-only operation (the dst was already versioned
// above), so skip the rename — there's nothing to move.
if (src == dst)
{
return ESP_OK;
}
if (rename(src.c_str(), dst.c_str()) != 0)
{
errMsg = "Failed to rename " + src + " -> " + dst;
@@ -1109,6 +1136,9 @@ esp_err_t WiFi::expandAndSendFile(httpd_req_t *req, const char *basePath, const
inFile.close();
ESP_LOGI(WIFITAG, "expandAndSendFile: %s loaded (%d bytes)", fileName.c_str(), (int)contents.length());
// Close connection after response — see defaultFileHandler for rationale.
httpd_resp_set_hdr(req, "Connection", "close");
if (contents.find("%SK_") == std::string::npos)
{
// No template variables — send entire file as a single HTTP chunk.
@@ -1292,6 +1322,15 @@ esp_err_t WiFi::defaultFileHandler(httpd_req_t *req)
std::string disposition = "";
esp_err_t result = ESP_OK;
// Per-request local variables — the shared wifiCtrl.session struct must NOT be
// used here because multiple requests are handled concurrently by esp_http_server.
// Using the shared session caused a race condition where parallel requests
// overwrote each other's filePath/fileName/gzip fields, leaving some responses
// (typically jquery.min.js) stuck forever in "pending" state.
std::string localFilePath;
std::string localFileName;
bool localGzip = false;
// Retrieve pointer to object in order to access data.
WiFi *pThis = (WiFi *) req->user_ctx;
@@ -1312,18 +1351,17 @@ esp_err_t WiFi::defaultFileHandler(httpd_req_t *req)
buf.reset(new char[bufLen]);
if (buf != nullptr && httpd_req_get_hdr_value_str(req, "Accept-Encoding", buf.get(), bufLen) == ESP_OK)
{
pThis->wifiCtrl.session.gzip = (strstr(buf.get(), "gzip") != NULL);
pThis->wifiCtrl.session.deflate = (strstr(buf.get(), "deflate") != NULL);
localGzip = (strstr(buf.get(), "gzip") != NULL);
}
}
// Look for a filename in the URI and construct the file path returning both.
result = pThis->getPathFromURI(pThis->wifiCtrl.session.filePath, pThis->wifiCtrl.session.fileName, pThis->wifiCtrl.run.basePath, req->uri);
result = pThis->getPathFromURI(localFilePath, localFileName, pThis->wifiCtrl.run.basePath, req->uri);
if (result == ESP_FAIL)
{
if (strlen(req->uri) == 1 && req->uri[0] == '/')
{
pThis->wifiCtrl.session.fileName = "/";
localFileName = "/";
result = ESP_OK;
}
else
@@ -1336,20 +1374,20 @@ esp_err_t WiFi::defaultFileHandler(httpd_req_t *req)
if (result == ESP_OK)
{
// See if the provided name matches a static handler, such as root.
if (pThis->wifiCtrl.session.fileName == "/" || pThis->wifiCtrl.session.fileName == "index.html" || pThis->wifiCtrl.session.fileName == "index.htm")
if (localFileName == "/" || localFileName == "index.html" || localFileName == "index.htm")
{
result = pThis->expandAndSendFile(req, pThis->wifiCtrl.run.basePath, "index.htm");
}
else
{
// Get details of the file.
if (stat(pThis->wifiCtrl.session.filePath.c_str(), &fileStat) == -1)
if (stat(localFilePath.c_str(), &fileStat) == -1)
{
if (pThis->wifiCtrl.session.gzip)
if (localGzip)
{
gzipFile = pThis->wifiCtrl.session.filePath + ".gz";
gzipFile = localFilePath + ".gz";
}
if (pThis->wifiCtrl.session.gzip && stat(gzipFile.c_str(), &fileStat) == -1)
if (localGzip && stat(gzipFile.c_str(), &fileStat) == -1)
{
result = ESP_FAIL;
httpd_resp_send_err(req, HTTPD_404_NOT_FOUND, "File does not exist");
@@ -1367,22 +1405,22 @@ esp_err_t WiFi::defaultFileHandler(httpd_req_t *req)
}
else
{
ESP_LOGI(WIFITAG, "Opened file : %s ", pThis->wifiCtrl.session.filePath.c_str());
ESP_LOGI(WIFITAG, "Opened file : %s ", localFilePath.c_str());
}
if (result == ESP_OK)
{
// If the file is HTML, JS or CSS then process externally.
if ((pThis->isFileExt(pThis->wifiCtrl.session.fileName, ".html") || pThis->isFileExt(pThis->wifiCtrl.session.fileName, ".htm") ||
(pThis->isFileExt(pThis->wifiCtrl.session.fileName, ".js") && !pThis->isFileExt(pThis->wifiCtrl.session.fileName, ".min.js")) ||
(pThis->isFileExt(pThis->wifiCtrl.session.fileName, ".css") && !pThis->isFileExt(pThis->wifiCtrl.session.fileName, ".min.css"))) &&
if ((pThis->isFileExt(localFileName, ".html") || pThis->isFileExt(localFileName, ".htm") ||
(pThis->isFileExt(localFileName, ".js") && !pThis->isFileExt(localFileName, ".min.js")) ||
(pThis->isFileExt(localFileName, ".css") && !pThis->isFileExt(localFileName, ".min.css"))) &&
gzipFile.empty())
{
result = pThis->expandAndSendFile(req, pThis->wifiCtrl.run.basePath, pThis->wifiCtrl.session.fileName);
result = pThis->expandAndSendFile(req, pThis->wifiCtrl.run.basePath, localFileName);
}
else
{
fd = fopen(gzipFile.empty() ? pThis->wifiCtrl.session.filePath.c_str() : gzipFile.c_str(), "r");
fd = fopen(gzipFile.empty() ? localFilePath.c_str() : gzipFile.c_str(), "r");
if (!fd)
{
result = ESP_FAIL;
@@ -1393,9 +1431,17 @@ esp_err_t WiFi::defaultFileHandler(httpd_req_t *req)
ESP_LOGI(WIFITAG,
"Sending %sfile : %s (%ld bytes)...",
gzipFile.empty() ? "" : "gzip ",
pThis->wifiCtrl.session.fileName.c_str(),
localFileName.c_str(),
fileStat.st_size);
result = pThis->setContentTypeFromFileType(req, pThis->wifiCtrl.session.fileName);
// Close connection after each file response. The server is
// single-threaded — while sending a large file (e.g. 98KB font),
// all other requests are blocked. With keep-alive, the browser
// queues subsequent requests on existing connections that may be
// stalled behind a large transfer, leaving them "pending" forever.
// With Connection:close, the browser opens fresh connections for
// each resource. With 20 sockets available this works well.
httpd_resp_set_hdr(req, "Connection", "close");
result = pThis->setContentTypeFromFileType(req, localFileName);
if (result == ESP_OK)
{
std::unique_ptr<char[]> chunk(new char[MAX_CHUNK_SIZE]);
@@ -1525,36 +1571,112 @@ esp_err_t WiFi::defaultDataGETHandler(httpd_req_t *req)
// Match URI and execute required data retrieval and return.
if (uriStr == "wifistatus")
{
// Lightweight JSON endpoint for AJAX polling of WiFi signal + system status.
char json[256];
// JSON endpoint for AJAX polling of WiFi + system + RP2350 status.
// All fields that can change at runtime are included so the dashboard
// updates live without requiring a page refresh.
int8_t txPwr = 0;
int rssi = 0;
bool connected = pThis->wifiCtrl.client.connected;
const char *ip = (pThis->wifiCtrl.run.wifiMode == WIFI_CONFIG_AP)
? pThis->wifiCtrl.ap.ip : pThis->wifiCtrl.client.ip;
const char *nm = (pThis->wifiCtrl.run.wifiMode == WIFI_CONFIG_AP)
? pThis->wifiCtrl.ap.netmask : pThis->wifiCtrl.client.netmask;
const char *gw = (pThis->wifiCtrl.run.wifiMode == WIFI_CONFIG_AP)
? pThis->wifiCtrl.ap.gateway : pThis->wifiCtrl.client.gateway;
#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
// Compute uptime string.
int64_t us = esp_timer_get_time();
int secs = (int) (us / 1000000);
int days = secs / 86400;
secs %= 86400;
int hrs = secs / 3600;
secs %= 3600;
int mins = secs / 60;
secs %= 60;
int days = secs / 86400; secs %= 86400;
int hrs = secs / 3600; secs %= 3600;
int mins = secs / 60; secs %= 60;
char uptimeBuf[32];
if (days > 0)
snprintf(uptimeBuf, sizeof(uptimeBuf), "%dd %02d:%02d:%02d", days, hrs, mins, secs);
else
snprintf(uptimeBuf, sizeof(uptimeBuf), "%02d:%02d:%02d", hrs, mins, secs);
snprintf(json, sizeof(json),
"{\"rssi\":%d,\"txPower\":%.1f,\"connected\":%s,\"uptime\":\"%s\"}",
rssi, txPwr * 0.25f, connected ? "true" : "false", uptimeBuf);
// RP2350 partition header data (populated by INF IPC from RP2350).
uint8_t activeApp = pThis->wifiCtrl.run.rp2350FlashHeader.activeApp;
bool haveInfo = (pThis->wifiCtrl.run.rp2350FlashHeader.configured == FLASH_APP_CONFIGURED_FLAG &&
activeApp >= 1 && activeApp < FLASH_APP_MAX_INSTANCES);
t_FlashPartitionInstance *ai = haveInfo ? &pThis->wifiCtrl.run.rp2350FlashHeader.config[activeApp] : NULL;
const char *fwVer = (ai && ai->version[0]) ? ai->version : "";
const char *fwDate = (ai && ai->versionDate[0]) ? ai->versionDate : "";
const char *persona = (ai && ai->description[0]) ? ai->description : "";
const char *drvSummary = pThis->wifiCtrl.run.rp2350DriverSummary[0] ? pThis->wifiCtrl.run.rp2350DriverSummary : "";
int cpuMHz = pThis->wifiCtrl.run.rp2350CpuFreq / 1000000;
int psramMHz = pThis->wifiCtrl.run.rp2350PsramFreq / 1000000;
int flashMB = pThis->wifiCtrl.run.rp2350FlashSize / (1024 * 1024);
int psramMB = pThis->wifiCtrl.run.rp2350PsramSize / (1024 * 1024);
uint32_t hostClkHz = pThis->wifiCtrl.run.rp2350HostClkHz;
uint32_t emulSpeedHz = pThis->wifiCtrl.run.rp2350EmulSpeedHz;
// Build RP2350 partition table HTML for live update.
std::ostringstream rpParts;
for (int idx = 0; idx < FLASH_APP_MAX_INSTANCES; idx++)
{
const t_FlashPartitionInstance *pi = &pThis->wifiCtrl.run.rp2350FlashHeader.config[idx];
std::string active = (idx == activeApp && haveInfo) ? "Yes" : "No";
rpParts << "<tr>"
<< "<td>" << idx << "</td>"
<< "<td>" << to_str(pi->addr, 0, 16) << "</td>"
<< "<td>" << to_str(pi->size, 0, 16) << "</td>"
<< "<td>" << to_str(pi->chksum, 0, 16) << "</td>"
<< "<td>" << active << "</td>"
<< "<td>" << pi->license << "</td>"
<< "<td>" << pi->author << "</td>"
<< "<td>" << pi->description << "</td>"
<< "<td>" << pi->version << "</td>"
<< "<td>" << pi->versionDate << "</td>"
<< "<td>" << pi->copyright << "</td>"
<< "</tr>";
}
// Escape the HTML for safe embedding in JSON string value.
std::string rpPartsHtml = rpParts.str();
std::string rpPartsEsc;
rpPartsEsc.reserve(rpPartsHtml.size() + 64);
for (char c : rpPartsHtml)
{
if (c == '"') rpPartsEsc += "\\\"";
else if (c == '\\') rpPartsEsc += "\\\\";
else if (c == '\n') rpPartsEsc += "\\n";
else rpPartsEsc += c;
}
// Build JSON response using std::string (partition HTML can be large).
std::ostringstream json;
json << "{\"rssi\":" << rssi
<< ",\"txPower\":" << (txPwr * 0.25f)
<< ",\"connected\":" << (connected ? "true" : "false")
<< ",\"uptime\":\"" << uptimeBuf << "\""
<< ",\"ip\":\"" << (ip[0] ? ip : "") << "\""
<< ",\"netmask\":\"" << (nm[0] ? nm : "") << "\""
<< ",\"gateway\":\"" << (gw[0] ? gw : "") << "\""
<< ",\"fwVersion\":\"" << fwVer << "\""
<< ",\"fwDate\":\"" << fwDate << "\""
<< ",\"activePartition\":" << (haveInfo ? std::to_string(activeApp) : "null")
<< ",\"persona\":\"" << persona << "\""
<< ",\"drivers\":\"" << drvSummary << "\""
<< ",\"rp2350Clock\":" << (cpuMHz > 0 ? std::to_string(cpuMHz) : "null")
<< ",\"psramClock\":" << (psramMHz > 0 ? std::to_string(psramMHz) : "null")
<< ",\"rp2350Flash\":" << (flashMB > 0 ? std::to_string(flashMB) : "null")
<< ",\"rp2350Psram\":" << (psramMB > 0 ? std::to_string(psramMB) : "null")
<< ",\"hostClkHz\":" << (hostClkHz > 0 ? std::to_string(hostClkHz) : "null")
<< ",\"emulSpeedHz\":" << (emulSpeedHz > 0 ? std::to_string(emulSpeedHz) : "null")
<< ",\"rpPartitions\":\"" << rpPartsEsc << "\""
<< ",\"floppy1\":\"" << (pThis->wifiCtrl.run.floppyDiskImage[0].empty() ? "none" : pThis->wifiCtrl.run.floppyDiskImage[0]) << "\""
<< ",\"floppy2\":\"" << (pThis->wifiCtrl.run.floppyDiskImage[1].empty() ? "none" : pThis->wifiCtrl.run.floppyDiskImage[1]) << "\""
<< ",\"qdisk\":\"" << (pThis->wifiCtrl.run.quickDiskImage[0].empty() ? "none" : pThis->wifiCtrl.run.quickDiskImage[0]) << "\""
<< "}";
httpd_resp_set_type(req, "application/json");
httpd_resp_sendstr(req, json);
httpd_resp_sendstr(req, json.str().c_str());
return ESP_OK;
}
else if (uriStr.substr(0, 6) == "rename")
@@ -3475,6 +3597,12 @@ esp_err_t WiFi::sendFileManagerDir(httpd_req_t *req)
htmlStr.append("<button id=\"mkdir\" class=\"fa fa-folder-o wm-button-small\" aria-hidden=\"true\" title=\"Create Directory\" type=\"button\" "
"onclick=\"mkdir()\"></button>");
htmlStr.append("</td></tr>\n");
htmlStr.append("<tr id=\"uploadProgressRow\" style=\"display:none;\">"
"<td colspan=\"4\">"
"<div style=\"background:#444;border-radius:4px;height:22px;width:100%;position:relative;\">"
"<div id=\"uploadProgressBar\" style=\"background:#5cb85c;height:100%;border-radius:4px;width:0%;transition:width 0.2s;\"></div>"
"<span id=\"uploadProgressText\" style=\"position:absolute;top:0;left:0;width:100%;text-align:center;line-height:22px;color:#fff;font-size:12px;\"></span>"
"</div></td></tr>\n");
}
else
{
@@ -3493,6 +3621,12 @@ esp_err_t WiFi::sendFileManagerDir(httpd_req_t *req)
htmlStr.append("<button id=\"mkdir\" class=\"fa fa-folder-o wm-button-small\" aria-hidden=\"true\" title=\"Create Directory\" type=\"button\" "
"onclick=\"mkdir()\"></button>");
htmlStr.append("</td></tr>\n");
htmlStr.append("<tr id=\"uploadProgressRow\" style=\"display:none;\">"
"<td colspan=\"4\">"
"<div style=\"background:#444;border-radius:4px;height:22px;width:100%;position:relative;\">"
"<div id=\"uploadProgressBar\" style=\"background:#5cb85c;height:100%;border-radius:4px;width:0%;transition:width 0.2s;\"></div>"
"<span id=\"uploadProgressText\" style=\"position:absolute;top:0;left:0;width:100%;text-align:center;line-height:22px;color:#fff;font-size:12px;\"></span>"
"</div></td></tr>\n");
}
int entryCnt = 1;
@@ -4351,10 +4485,12 @@ bool WiFi::startWebserver(void)
// Keep keep-alive enabled (HTTP/1.1 default) so the browser can multiplex
// 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;
// Short recv_wait_timeout aggressively recycles idle keep-alive connections.
// With 20 sockets available, the browser can always open fresh connections.
// Longer timeouts (5-15s) cause stale keep-alive connections from previous
// page loads to hold sockets, triggering LRU purge of active transfers.
config.recv_wait_timeout = 1;
config.send_wait_timeout = 15;
// 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};
@@ -5046,6 +5182,14 @@ WiFi::WiFi(bool defaultMode, uint16_t device, NVS *nvs, SDCard *sdcard, cJSON *c
strncpy(wifiCtrl.run.fsPath, fsPath, FILE_PATH_MAX);
wifiCtrl.run.versionList = versionList;
memset(&wifiCtrl.run.rp2350FlashHeader, 0, sizeof(t_FlashPartitionHeader));
wifiCtrl.run.rp2350CpuFreq = 0;
wifiCtrl.run.rp2350PsramFreq = 0;
wifiCtrl.run.rp2350Voltage = 0;
wifiCtrl.run.rp2350FlashSize = 0;
wifiCtrl.run.rp2350PsramSize = 0;
wifiCtrl.run.rp2350HostClkHz = 0;
wifiCtrl.run.rp2350EmulSpeedHz = 0;
wifiCtrl.run.rp2350DriverSummary[0] = '\0';
// Wire SDCard's rp2350Header pointer directly to our rp2350FlashHeader so
// SDCard::storeRP2350Info() can populate it when the INF IPC command arrives.
if (sdcard)
@@ -5128,6 +5272,15 @@ WiFi::WiFi(bool defaultMode, uint16_t device, NVS *nvs, SDCard *sdcard, cJSON *c
wifiConfig.params.txPower = 0;
}
// Sync the runtime wifiMode from the persisted config so that template
// expansion shows the correct WiFi mode (AP vs Client) even before
// wifi->run() is called. Without this, pages loaded via USB NCM before
// WiFi connects would show AP config instead of Client config.
if (wifiConfig.params.wifiMode == WIFI_CONFIG_CLIENT || wifiConfig.params.wifiMode == WIFI_CONFIG_AP)
{
wifiCtrl.run.wifiMode = wifiConfig.params.wifiMode;
}
// Process JSON and override any setting specified in config.
// Check we have a valid handle.
if (config == NULL)

View File

@@ -155,8 +155,10 @@ class CommandProcessor : public WiFi, FSPI, SDCard
// WiFi tracking for floppy/QD:
if (frame.command == IPCF_CMD_RFD)
wifi.setFloppyDiskFile(std::string(frame.filename), frame.diskNo);
else if (frame.command == IPCF_CMD_RQD || frame.command == IPCF_CMD_RRF)
else if (frame.command == IPCF_CMD_RQD)
wifi.setQuickDiskFile(std::string(frame.filename), frame.diskNo);
else if (frame.command == IPCF_CMD_RRF)
wifi.setRamFile(std::string(frame.filename), frame.diskNo);
sdcard.readFileViaSPI(frame, fspi);
}

View File

@@ -324,6 +324,9 @@ class WiFi
int32_t rp2350Voltage; // Core voltage (from INF)
uint32_t rp2350FlashSize; // RP2350 Flash size in bytes (from INF)
uint32_t rp2350PsramSize; // RP2350 PSRAM size in bytes (from INF)
uint32_t rp2350HostClkHz; // Measured host clock frequency in Hz (from INF)
uint32_t rp2350EmulSpeedHz; // Effective Z80 emulation speed in Hz (from INF)
char rp2350DriverSummary[FLASHHDR_DRIVER_SUMMARY_SIZE]; // Active drivers (from INF)
// Active Floppy Disk images.
std::string floppyDiskImage[WIFI_MAX_FLOPPY_DISK_IMAGES];

View File

@@ -152,6 +152,9 @@ typedef struct
} t_FlashPartitionHeader;
// Extended INF payload — partition header + runtime core config.
// Maximum size of the active driver summary string sent via INF IPC.
#define FLASHHDR_DRIVER_SUMMARY_SIZE 128
// Sent by RP2350 ESP_sendVersionInfo, received by ESP32 storeRP2350Info.
typedef struct
{
@@ -161,6 +164,9 @@ typedef struct
int32_t voltage; // Core voltage setting (VREG enum)
uint32_t flashSize; // RP2350 Flash size in bytes
uint32_t psramSize; // RP2350 PSRAM size in bytes
uint32_t hostClkHz; // Measured host clock frequency in Hz
uint32_t emulSpeedHz; // Effective Z80 emulation speed in Hz
char driverSummary[FLASHHDR_DRIVER_SUMMARY_SIZE]; // Active drivers and interfaces
} t_FlashInfoPayload;
// Structure to describe a single file stored in ROM. Indexed by its filename (matching a filename appearing in JSON including path)

View File

@@ -319,6 +319,7 @@ void buildVersionList(WiFi::t_versionList *versionList, NVS &nvs, SDCard &sdcard
#if defined(CONFIG_IF_USB_NCM_ENABLED)
static esp_netif_t *s_usb_ncm_netif = NULL;
static volatile bool g_usbReady = false; // Set by USB task when setup completes.
static void usb_ncm_l2_free(void *h, void *buffer)
{
@@ -357,13 +358,16 @@ static esp_err_t usb_ncm_recv_callback(void *buffer, uint16_t len, void *ctx)
// 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)
// USB setup task — runs asynchronously so the main task can start the
// CommandProcessor (SPI slave) without waiting for USB delays. The 2-second
// disconnect/reconnect cycle that macOS needs would otherwise block SPI
// slave init, causing the RP2350's first sector reads to fail.
void setupUSBTask(void *pvParameters)
{
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.
// 0. Initialise the TCP/IP stack and event loop (idempotent / tolerates double-init).
esp_netif_init();
esp_event_loop_create_default();
@@ -372,19 +376,18 @@ bool setupUSBConsole(void)
tusb_cfg.external_phy = false;
esp_err_t ret = tinyusb_driver_install(&tusb_cfg);
if (ret != ESP_OK) {
return false;
ESP_LOGE(MAINTAG, "USB: TinyUSB driver install failed");
g_usbReady = true; // Signal ready (even on failure) so main loop doesn't wait forever.
vTaskDelete(NULL);
return;
}
// 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.
// 2. Force a clean USB disconnect/reconnect cycle. These delays run in this
// background task so the main task is free to start the CommandProcessor.
tud_disconnect();
vTaskDelay(pdMS_TO_TICKS(1500));
tud_connect();
vTaskDelay(pdMS_TO_TICKS(500));
// 3. Initialise CDC-ACM for serial logging.
tinyusb_config_cdcacm_t acm_cfg = {};
@@ -400,8 +403,7 @@ bool setupUSBConsole(void)
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).
// 5. Create the lwIP netif so received packets are handled immediately.
if (ncmOk) {
esp_netif_ip_info_t ip_info = {};
ip_info.ip.addr = ipaddr_addr(CONFIG_IF_USB_NCM_IP);
@@ -434,19 +436,21 @@ bool setupUSBConsole(void)
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
uint32_t lease_opt = 120;
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.
// 6. Redirect console to TinyUSB CDC-ACM.
if (cdcOk) {
esp_tusb_init_console(TINYUSB_CDC_ACM_0);
}
return cdcOk;
ESP_LOGI(MAINTAG, "USB setup complete (CDC:%s NCM:%s)", cdcOk ? "OK" : "FAIL", ncmOk ? "OK" : "FAIL");
g_usbReady = true;
vTaskDelete(NULL);
}
#endif // CONFIG_IF_USB_NCM_ENABLED
@@ -811,12 +815,11 @@ 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.
// Launch USB setup (TinyUSB + CDC + NCM + netif) as a background task.
// The 2-second disconnect/reconnect cycle runs asynchronously so the main
// task can proceed immediately to start the CommandProcessor (SPI slave).
#if defined(CONFIG_IF_USB_NCM_ENABLED)
if (setupUSBConsole()) {
ESP_LOGW(MAINTAG, "USB CDC-ACM console active — log output on TinyUSB serial port.");
}
xTaskCreate(setupUSBTask, "usbSetup", 8192, NULL, 5, NULL);
#endif
// Phase 1: fast setup — eFUSE, NVS, SPI slave (~60 ms).
@@ -937,10 +940,17 @@ extern "C"
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.
// Wait for the async USB setup task to complete before starting
// the webserver. This is non-blocking in the sense that the SPI
// slave and SD card are already running while we wait here.
if (!g_usbReady)
{
// USB task still running — skip this iteration, try next loop.
++loopCount;
vTaskDelay(10);
continue;
}
// USB NCM network + lwIP netif are now ready.
ESP_LOGW(MAINTAG, "Starting webserver on USB NCM.");
wifi->startWebserver();
#endif

View File

@@ -588,7 +588,13 @@ CONFIG_SD_CDDETECT=21
#
# WiFi
#
# CONFIG_IF_WIFI_ENABLED is not set
CONFIG_IF_WIFI_ENABLED=y
CONFIG_IF_WIFI_SSID="pZ80"
CONFIG_IF_WIFI_DEFAULT_SSID_PWD="pZ80pZ80"
CONFIG_IF_WIFI_MAX_RETRIES=10
CONFIG_IF_WIFI_AP_CHANNEL=7
CONFIG_IF_WIFI_SSID_HIDDEN=0
CONFIG_IF_WIFI_MAX_CONNECTIONS=5
# end of WiFi
#
@@ -1951,8 +1957,8 @@ CONFIG_LWIP_LOOPBACK_MAX_PBUFS=8
#
# TCP
#
CONFIG_LWIP_MAX_ACTIVE_TCP=16
CONFIG_LWIP_MAX_LISTENING_TCP=16
CONFIG_LWIP_MAX_ACTIVE_TCP=24
CONFIG_LWIP_MAX_LISTENING_TCP=24
CONFIG_LWIP_TCP_HIGH_SPEED_RETRANSMISSION=y
CONFIG_LWIP_TCP_MAXRTX=12
CONFIG_LWIP_TCP_SYNMAXRTX=12

View File

@@ -1956,8 +1956,8 @@ CONFIG_LWIP_LOOPBACK_MAX_PBUFS=8
#
# TCP
#
CONFIG_LWIP_MAX_ACTIVE_TCP=16
CONFIG_LWIP_MAX_LISTENING_TCP=16
CONFIG_LWIP_MAX_ACTIVE_TCP=24
CONFIG_LWIP_MAX_LISTENING_TCP=24
CONFIG_LWIP_TCP_HIGH_SPEED_RETRANSMISSION=y
CONFIG_LWIP_TCP_MAXRTX=12
CONFIG_LWIP_TCP_SYNMAXRTX=12

View File

@@ -1962,8 +1962,8 @@ CONFIG_LWIP_LOOPBACK_MAX_PBUFS=8
#
# TCP
#
CONFIG_LWIP_MAX_ACTIVE_TCP=16
CONFIG_LWIP_MAX_LISTENING_TCP=16
CONFIG_LWIP_MAX_ACTIVE_TCP=24
CONFIG_LWIP_MAX_LISTENING_TCP=24
CONFIG_LWIP_TCP_HIGH_SPEED_RETRANSMISSION=y
CONFIG_LWIP_TCP_MAXRTX=12
CONFIG_LWIP_TCP_SYNMAXRTX=12

View File

@@ -1960,8 +1960,8 @@ CONFIG_LWIP_LOOPBACK_MAX_PBUFS=8
#
# TCP
#
CONFIG_LWIP_MAX_ACTIVE_TCP=16
CONFIG_LWIP_MAX_LISTENING_TCP=16
CONFIG_LWIP_MAX_ACTIVE_TCP=24
CONFIG_LWIP_MAX_LISTENING_TCP=24
CONFIG_LWIP_TCP_HIGH_SPEED_RETRANSMISSION=y
CONFIG_LWIP_TCP_MAXRTX=12
CONFIG_LWIP_TCP_SYNMAXRTX=12

View File

@@ -1 +1 @@
2.0
2.01

View File

@@ -1 +1 @@
2.27
2.46

View File

@@ -95,9 +95,9 @@
<ul class="dropdown-menu">
<li style="margin-left: 1em;"><b>Action Menu</b></li>
<li class="divider"></li>
<li><a href="tasks/changefloppy?diskno=0"><i class="fa fa-floppy-o"></i><span style="padding-left: 10px;">Change Floppy Disk 1 (%SK_FLOPPY1%)</a></li>
<li><a href="tasks/changefloppy?diskno=1"><i class="fa fa-floppy-o"></i><span style="padding-left: 10px;">Change Floppy Disk 2 (%SK_FLOPPY2%)</a></li>
<li><a href="tasks/changeqd"><i class="fa fa-file"></i><span style="padding-left: 10px;">Change QD Disk (%SK_QDDISK%)</a></li>
<li><a href="tasks/changefloppy?diskno=0"><i class="fa fa-floppy-o"></i><span style="padding-left: 10px;">Change Floppy Disk 1 (%SK_FLOPPY1%)</span></a></li>
<li><a href="tasks/changefloppy?diskno=1"><i class="fa fa-floppy-o"></i><span style="padding-left: 10px;">Change Floppy Disk 2 (%SK_FLOPPY2%)</span></a></li>
<li><a href="tasks/changeqd"><i class="fa fa-file"></i><span style="padding-left: 10px;">Change QD Disk (%SK_QDDISK%)</span></a></li>
<hr style="margin-top: 10px; margin-bottom: 10px;">
<li><a href="#" onclick="reloadConfig(); return false;"><i class="fa fa-repeat"></i><span style="padding-left: 10px;">Reload RP2350 Config</a></li>
</ul>
@@ -117,7 +117,7 @@
</ol>
<div class="alert alert-success alert-dismissable justify">
<button type="button" class="close" data-dismiss="alert" aria-hidden="true">&times;</button>
<p>This is the JSON configuration editor to define the %SK_PROCESSOR% and ESP32 configuration. Please read the documentation if you need help.</p>
<p>This is the JSON configuration editor to define the %SK_PROCESSOR% and ESP32 configuration.</p>
</div>
</div>
</div><!-- /.row -->

View File

@@ -95,9 +95,9 @@
<ul class="dropdown-menu">
<li style="margin-left: 1em;"><b>Action Menu</b></li>
<li class="divider"></li>
<li><a href="tasks/changefloppy?diskno=0"><i class="fa fa-floppy-o"></i><span style="padding-left: 10px;">Change Floppy Disk 1 (%SK_FLOPPY1%)</a></li>
<li><a href="tasks/changefloppy?diskno=1"><i class="fa fa-floppy-o"></i><span style="padding-left: 10px;">Change Floppy Disk 2 (%SK_FLOPPY2%)</a></li>
<li><a href="tasks/changeqd"><i class="fa fa-file"></i><span style="padding-left: 10px;">Change QD Disk (%SK_QDDISK%)</a></li>
<li><a href="tasks/changefloppy?diskno=0"><i class="fa fa-floppy-o"></i><span style="padding-left: 10px;">Change Floppy Disk 1 (%SK_FLOPPY1%)</span></a></li>
<li><a href="tasks/changefloppy?diskno=1"><i class="fa fa-floppy-o"></i><span style="padding-left: 10px;">Change Floppy Disk 2 (%SK_FLOPPY2%)</span></a></li>
<li><a href="tasks/changeqd"><i class="fa fa-file"></i><span style="padding-left: 10px;">Change QD Disk (%SK_QDDISK%)</span></a></li>
<hr style="margin-top: 10px; margin-bottom: 10px;">
<li><a href="#" onclick="reloadConfig(); return false;"><i class="fa fa-repeat"></i><span style="padding-left: 10px;">Reload RP2350 Config</a></li>
</ul>

View File

@@ -95,9 +95,9 @@
<ul class="dropdown-menu">
<li style="margin-left: 1em;"><b>Action Menu</b></li>
<li class="divider"></li>
<li><a href="tasks/changefloppy?diskno=0"><i class="fa fa-floppy-o"></i><span style="padding-left: 10px;">Change Floppy Disk 1 (%SK_FLOPPY1%)</a></li>
<li><a href="tasks/changefloppy?diskno=1"><i class="fa fa-floppy-o"></i><span style="padding-left: 10px;">Change Floppy Disk 2 (%SK_FLOPPY2%)</a></li>
<li><a href="tasks/changeqd"><i class="fa fa-file"></i><span style="padding-left: 10px;">Change QD Disk (%SK_QDDISK%)</a></li>
<li><a href="tasks/changefloppy?diskno=0"><i class="fa fa-floppy-o"></i><span style="padding-left: 10px;">Change Floppy Disk 1 (<span class="rp-floppy1">%SK_FLOPPY1%</span>)</span></a></li>
<li><a href="tasks/changefloppy?diskno=1"><i class="fa fa-floppy-o"></i><span style="padding-left: 10px;">Change Floppy Disk 2 (<span class="rp-floppy2">%SK_FLOPPY2%</span>)</span></a></li>
<li><a href="tasks/changeqd"><i class="fa fa-file"></i><span style="padding-left: 10px;">Change QD Disk (<span class="rp-qdisk">%SK_QDDISK%</span>)</span></a></li>
<hr style="margin-top: 10px; margin-bottom: 10px;">
<li><a href="#" onclick="reloadConfig(); return false;"><i class="fa fa-repeat"></i><span style="padding-left: 10px;">Reload RP2350 Config</a></li>
</ul>
@@ -115,10 +115,12 @@
<ol class="breadcrumb">
<li class="active"><i class="fa fa-dashboard"></i> Status</li>
</ol>
<!--
<div class="alert alert-success alert-dismissable justify">
<button type="button" class="close" data-dismiss="alert" aria-hidden="true">&times;</button>
<p>This built-in web app allows configuration of the host, emulated processor, virtual interfaces, <u>O</u>ver <u>T</u>he <u>A</u>ir firmware updates and more.<br>Please read the documentation if you need help.</p>
<p>This built-in web app allows configuration of the host, emulated processor, virtual interfaces, <u>O</u>ver <u>T</u>he <u>A</u>ir firmware updates and more.</p>
</div>
-->
</div>
</div><!-- /.row -->
@@ -141,9 +143,9 @@
<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 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>
<td>IP (AP):</td><td><span class="wifi-ip-live" style="color: blue;">%SK_CURRENTIP%</span></td>
<td style="padding-left:20px;">NETMASK:</td><td><span class="wifi-nm-live" style="color: blue;">%SK_CURRENTNM%</span></td>
<td style="padding-left:20px;">GATEWAY:</td><td><span class="wifi-gw-live" style="color: blue;">%SK_CURRENTGW%</span></td>
</tr>
</tbody>
</table>
@@ -160,14 +162,14 @@
<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 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>
<td>IP (assigned):</td><td><span class="wifi-ip-live" style="color: blue;">%SK_CURRENTIP%</span></td>
<td style="padding-left:20px;">NETMASK:</td><td><span class="wifi-nm-live" style="color: blue;">%SK_CURRENTNM%</span></td>
<td style="padding-left:20px;">GATEWAY:</td><td><span class="wifi-gw-live" 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 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>
<td>IP (fixed):</td><td><span class="wifi-ip-live" style="color: blue;">%SK_CURRENTIP%</span></td>
<td style="padding-left:20px;">NETMASK:</td><td><span class="wifi-nm-live" style="color: blue;">%SK_CURRENTNM%</span></td>
<td style="padding-left:20px;">GATEWAY:</td><td><span class="wifi-gw-live" style="color: blue;">%SK_CURRENTGW%</span></td>
</tr>
</tbody>
</table>
@@ -200,27 +202,23 @@
<table class="table table-borderless table-sm" style="margin-bottom:0; width:auto;">
<tbody>
<tr>
<td style="width:140px; padding-right:4px;">Firmware Version:</td><td style="width:200px;"><span style="color:#5bf;">%SK_FWVERSION%</span></td>
<td style="width:140px; padding-right:4px;">Firmware Version:</td><td style="width:200px;"><span class="rp-fwversion" style="color:#5bf;">%SK_FWVERSION%</span></td>
<td style="width:140px; padding-right:4px;">ESP32 Version:</td><td style="width:200px;"><span style="color:#5bf;">%SK_ESPVERSION%</span></td>
</tr>
<tr>
<td>Active Partition:</td><td><span style="color:#5bf;">%SK_ACTIVEPARTITION%</span></td>
<td>Active Persona:</td><td><span style="color:#5bf;">%SK_PERSONA%</span></td>
</tr>
<tr>
<td>RP2350 Clock:</td><td><span style="color:#5bf;">%SK_CPUCLOCK%</span></td>
<td>RP2350 Clock:</td><td><span class="rp-clock" style="color:#5bf;">%SK_CPUCLOCK%</span></td>
<td>ESP32 Clock:</td><td><span style="color:#5bf;">%SK_ESP32CLOCK%</span></td>
</tr>
<tr>
<td>RP2350 Flash:</td><td><span style="color:#5bf;">%SK_RP2350FLASH%</span></td>
<td>RP2350 Flash:</td><td><span class="rp-flash" style="color:#5bf;">%SK_RP2350FLASH%</span></td>
<td>ESP32 Flash:</td><td><span style="color:#5bf;">%SK_ESP32FLASH%</span></td>
</tr>
<tr>
<td>RP2350 PSRAM:</td><td><span style="color:#5bf;">%SK_RP2350PSRAM%</span></td>
<td>RP2350 PSRAM:</td><td><span class="rp-psram" style="color:#5bf;">%SK_RP2350PSRAM%</span></td>
<td>ESP32 PSRAM:</td><td><span style="color:#5bf;">%SK_ESP32PSRAM%</span></td>
</tr>
<tr>
<td>PSRAM Clock:</td><td><span style="color:#5bf;">%SK_PSRAMCLOCK%</span></td>
<td>PSRAM Clock:</td><td><span class="rp-psramclk" style="color:#5bf;">%SK_PSRAMCLOCK%</span></td>
<td>SD Card:</td><td><span style="color:#5bf;">%SK_SDCARD%</span></td>
</tr>
<tr>
@@ -234,6 +232,41 @@
</div>
</div><!-- /.row -->
<div class="row">
<div class="col-lg-12">
<div class="panel panel-primary">
<div class="panel-heading">
<h3 class="panel-title"><i class="fa fa-microchip"></i> Active Personality</h3>
</div>
<div class="panel-body">
<table class="table table-borderless table-sm" style="margin-bottom:0; width:auto;">
<tbody>
<tr>
<td style="width:140px; padding-right:4px;">Personality:</td><td style="width:200px;"><span class="rp-persona" style="color:#5bf;">%SK_PERSONA%</span></td>
<td style="padding-left:20px; width:140px;">Processor:</td><td style="width:200px;"><span style="color:#5bf;">%SK_PROCESSOR%</span></td>
</tr>
<tr>
<td>Active Partition:</td><td><span class="rp-partition" style="color:#5bf;">%SK_ACTIVEPARTITION%</span></td>
<td style="padding-left:20px;">Host Clock:</td><td><span class="rp-hostclk" style="color:#5bf;">N/A</span></td>
</tr>
<tr>
<td>Emulation Speed:</td><td><span class="rp-emulspeed" style="color:#5bf;">N/A</span></td>
<td style="padding-left:20px;">Drivers:</td><td><span class="rp-drivers" style="color:#5bf;">%SK_ACTIVEDRIVERS%</span></td>
</tr>
<tr>
<td>Floppy 1:</td><td><span class="rp-floppy1" style="color:#5bf;">%SK_FLOPPY1%</span></td>
<td style="padding-left:20px;">Floppy 2:</td><td><span class="rp-floppy2" style="color:#5bf;">%SK_FLOPPY2%</span></td>
</tr>
<tr>
<td>Quick Disk:</td><td><span class="rp-qdisk" style="color:#5bf;">%SK_QDDISK%</span></td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div><!-- /.row -->
<div class="row">
<div class="col-lg-12">
<div class="panel panel-primary">
@@ -281,7 +314,7 @@
<th><b>Copyright</b></th>
</tr>
</thead>
<tbody>
<tbody id="rp-partitions-body">
%SK_RP_PARTITIONS%
</tbody>
</table>
@@ -309,7 +342,7 @@
<script src="js/actions.js"></script>
<script src="js/common.js"></script>
<script>
// Live WiFi signal strength polling (updates every 3 seconds).
// Live WiFi/network status polling (updates every 3 seconds).
(function pollWifiStatus() {
$.getJSON("/data/wifistatus", function(data) {
$(".rssi-live").text(data.rssi + " dBm");
@@ -321,10 +354,46 @@
} else {
$(".rssi-live").text("N/A").css("color", "gray");
}
// Update uptime if present in response
// Update IP/Netmask/Gateway dynamically (WiFi may connect after page load).
if (data.ip) {
$(".wifi-ip-live").text(data.ip || "");
$(".wifi-nm-live").text(data.netmask || "");
$(".wifi-gw-live").text(data.gateway || "");
}
// Update uptime.
if (data.uptime) {
$("#sys-uptime").text(data.uptime);
}
// Update RP2350 fields (populated by INF IPC, may arrive after page load).
var na = "N/A";
$(".rp-fwversion").text(data.fwVersion ? data.fwVersion + " (" + data.fwDate + ")" : na);
$(".rp-clock").text(data.rp2350Clock !== null ? data.rp2350Clock + " MHz" : na);
$(".rp-psramclk").text(data.psramClock !== null ? data.psramClock + " MHz" : na);
$(".rp-flash").text(data.rp2350Flash !== null ? data.rp2350Flash + " MB" : na);
$(".rp-psram").text(data.rp2350Psram !== null ? data.rp2350Psram + " MB" : na);
$(".rp-persona").text(data.persona || na);
$(".rp-partition").text(data.activePartition !== null ? data.activePartition : na);
$(".rp-drivers").text(data.drivers || na);
if (data.hostClkHz !== null && data.hostClkHz > 0) {
var mhz = (data.hostClkHz / 1000000).toFixed(3);
$(".rp-hostclk").text(mhz + " MHz");
} else {
$(".rp-hostclk").text(na);
}
if (data.emulSpeedHz !== null && data.emulSpeedHz > 0) {
var emhz = (data.emulSpeedHz / 1000000).toFixed(3);
$(".rp-emulspeed").text(emhz + " MHz");
} else {
$(".rp-emulspeed").text(na);
}
// Update RP2350 partition table.
if (data.rpPartitions) {
$("#rp-partitions-body").html(data.rpPartitions);
}
// Update disk image names.
if (data.floppy1) $(".rp-floppy1").text(data.floppy1);
if (data.floppy2) $(".rp-floppy2").text(data.floppy2);
if (data.qdisk) $(".rp-qdisk").text(data.qdisk);
}).always(function() {
setTimeout(pollWifiStatus, 3000);
});

View File

@@ -101,53 +101,74 @@ function uploadFile() {
} else if (filePath.indexOf(' ') >= 0)
{
alert("File path on server cannot have spaces!");
} else if (filePath[filePath.length-1] == '/')
} else if (filePath[filePath.length-1] == '/')
{
alert("File name not specified after path!");
} else if (fileInput[0].size > MAX_FILE_SIZE)
} else if (fileInput[0].size > MAX_FILE_SIZE)
{
alert("File size must be less than "+MAX_FILE_SIZE_STR+"!");
} else
} else
{
document.getElementById("newfile").disabled = true;
document.getElementById("filepath").disabled = true;
document.getElementById("upload").disabled = true;
//document.getElementById("download").disabled = true;
document.getElementById("mkdir").disabled = true;
// Show progress bar.
var progressRow = document.getElementById("uploadProgressRow");
var progressBar = document.getElementById("uploadProgressBar");
var progressText = document.getElementById("uploadProgressText");
if (progressRow) progressRow.style.display = "";
if (progressBar) { progressBar.style.width = "0%"; progressBar.style.background = "#5cb85c"; }
if (progressText) progressText.textContent = "Uploading... 0%";
var file = fileInput[0];
var xhttp;
if(window.XMLHttpRequest)
var fileSize = file.size;
var xhttp = new XMLHttpRequest();
// Track upload progress.
xhttp.upload.addEventListener("progress", function(e) {
if (e.lengthComputable && progressBar && progressText) {
var pct = Math.round((e.loaded / e.total) * 100);
progressBar.style.width = pct + "%";
var loaded = (e.loaded / 1024).toFixed(0);
var total = (e.total / 1024).toFixed(0);
if (e.total > 1048576) {
loaded = (e.loaded / 1048576).toFixed(1);
total = (e.total / 1048576).toFixed(1);
progressText.textContent = "Uploading... " + pct + "% (" + loaded + " / " + total + " MB)";
} else {
progressText.textContent = "Uploading... " + pct + "% (" + loaded + " / " + total + " KB)";
}
}
});
xhttp.onreadystatechange = function()
{
xhttp = new XMLHttpRequest();
} else
{
xhttp = new ActiveXObject("Microsoft.XMLHTTP");
}
xhttp.onreadystatechange = function()
{
if (xhttp.readyState == 4)
if (xhttp.readyState == 4)
{
if (xhttp.status == 200)
{
document.open();
document.write(xhttp.responseText);
document.close();
alert("File upload successful!");
location.reload()
} else if (xhttp.status == 0)
{
if (xhttp.status == 200)
{
if (progressBar) progressBar.style.width = "100%";
if (progressText) progressText.textContent = "Upload complete!";
setTimeout(function() { location.reload(); }, 1000);
} else if (xhttp.status == 0)
{
if (progressBar) { progressBar.style.width = "100%"; progressBar.style.background = "#d9534f"; }
if (progressText) progressText.textContent = "Upload failed — connection lost";
alert("Upload failed, server closed the connection abruptly!");
location.reload()
} else
{
location.reload();
} else
{
if (progressBar) { progressBar.style.width = "100%"; progressBar.style.background = "#d9534f"; }
if (progressText) progressText.textContent = "Upload failed — error " + xhttp.status;
alert(xhttp.status + " Error!\n" + xhttp.responseText);
location.reload()
location.reload();
}
}
};
xhttp.open("POST", upload_path+window.location.search, true);
xhttp.send(fileInput[0]);
xhttp.send(file);
}
}

View File

@@ -96,9 +96,9 @@
<ul class="dropdown-menu">
<li style="margin-left: 1em;"><b>Action Menu</b></li>
<li class="divider"></li>
<li><a href="tasks/changefloppy?diskno=0"><i class="fa fa-floppy-o"></i><span style="padding-left: 10px;">Change Floppy Disk 1 (%SK_FLOPPY1%)</a></li>
<li><a href="tasks/changefloppy?diskno=1"><i class="fa fa-floppy-o"></i><span style="padding-left: 10px;">Change Floppy Disk 2 (%SK_FLOPPY2%)</a></li>
<li><a href="tasks/changeqd"><i class="fa fa-file"></i><span style="padding-left: 10px;">Change QD Disk (%SK_QDDISK%)</a></li>
<li><a href="tasks/changefloppy?diskno=0"><i class="fa fa-floppy-o"></i><span style="padding-left: 10px;">Change Floppy Disk 1 (%SK_FLOPPY1%)</span></a></li>
<li><a href="tasks/changefloppy?diskno=1"><i class="fa fa-floppy-o"></i><span style="padding-left: 10px;">Change Floppy Disk 2 (%SK_FLOPPY2%)</span></a></li>
<li><a href="tasks/changeqd"><i class="fa fa-file"></i><span style="padding-left: 10px;">Change QD Disk (%SK_QDDISK%)</span></a></li>
<hr style="margin-top: 10px; margin-bottom: 10px;">
<li><a href="#" onclick="reloadConfig(); return false;"><i class="fa fa-repeat"></i><span style="padding-left: 10px;">Reload RP2350 Config</a></li>
</ul>
@@ -118,7 +118,7 @@
</ol>
<div class="alert alert-success alert-dismissable justify">
<button type="button" class="close" data-dismiss="alert" aria-hidden="true">&times;</button>
<p>This page allows you to upgrade the esp32 firmware/filepack. Please read the documentation if you need help.</p>
<p>This page allows you to upgrade the esp32 firmware/filepack.</p>
</div>
</div>
</div><!-- /.row -->

View File

@@ -96,9 +96,9 @@
<ul class="dropdown-menu">
<li style="margin-left: 1em;"><b>Action Menu</b></li>
<li class="divider"></li>
<li><a href="tasks/changefloppy?diskno=0"><i class="fa fa-floppy-o"></i><span style="padding-left: 10px;">Change Floppy Disk 1 (%SK_FLOPPY1%)</a></li>
<li><a href="tasks/changefloppy?diskno=1"><i class="fa fa-floppy-o"></i><span style="padding-left: 10px;">Change Floppy Disk 2 (%SK_FLOPPY2%)</a></li>
<li><a href="tasks/changeqd"><i class="fa fa-file"></i><span style="padding-left: 10px;">Change QD Disk (%SK_QDDISK%)</a></li>
<li><a href="tasks/changefloppy?diskno=0"><i class="fa fa-floppy-o"></i><span style="padding-left: 10px;">Change Floppy Disk 1 (%SK_FLOPPY1%)</span></a></li>
<li><a href="tasks/changefloppy?diskno=1"><i class="fa fa-floppy-o"></i><span style="padding-left: 10px;">Change Floppy Disk 2 (%SK_FLOPPY2%)</span></a></li>
<li><a href="tasks/changeqd"><i class="fa fa-file"></i><span style="padding-left: 10px;">Change QD Disk (%SK_QDDISK%)</span></a></li>
<hr style="margin-top: 10px; margin-bottom: 10px;">
<li><a href="#" onclick="reloadConfig(); return false;"><i class="fa fa-repeat"></i><span style="padding-left: 10px;">Reload RP2350 Config</a></li>
</ul>
@@ -118,7 +118,7 @@
</ol>
<div class="alert alert-success alert-dismissable justify">
<button type="button" class="close" data-dismiss="alert" aria-hidden="true">&times;</button>
<p>This page allows you to upgrade the rp2350 firmware. Please read the documentation if you need help.</p>
<p>This page allows you to upgrade the rp2350 firmware.</p>
</div>
</div>
</div><!-- /.row -->

View File

@@ -94,9 +94,9 @@
<ul class="dropdown-menu">
<li style="margin-left: 1em;"><b>Action Menu</b></li>
<li class="divider"></li>
<li><a href="tasks/changefloppy?diskno=0"><i class="fa fa-floppy-o"></i><span style="padding-left: 10px;">Change Floppy Disk 1 (%SK_FLOPPY1%)</a></li>
<li><a href="tasks/changefloppy?diskno=1"><i class="fa fa-floppy-o"></i><span style="padding-left: 10px;">Change Floppy Disk 2 (%SK_FLOPPY2%)</a></li>
<li><a href="tasks/changeqd"><i class="fa fa-file"></i><span style="padding-left: 10px;">Change QD Disk (%SK_QDDISK%)</a></li>
<li><a href="tasks/changefloppy?diskno=0"><i class="fa fa-floppy-o"></i><span style="padding-left: 10px;">Change Floppy Disk 1 (%SK_FLOPPY1%)</span></a></li>
<li><a href="tasks/changefloppy?diskno=1"><i class="fa fa-floppy-o"></i><span style="padding-left: 10px;">Change Floppy Disk 2 (%SK_FLOPPY2%)</span></a></li>
<li><a href="tasks/changeqd"><i class="fa fa-file"></i><span style="padding-left: 10px;">Change QD Disk (%SK_QDDISK%)</span></a></li>
<hr style="margin-top: 10px; margin-bottom: 10px;">
<li><a href="tasks/reloadcfg"><i class="fa fa-repeat"></i><span style="padding-left: 10px;">Reload RP2350 Config</a></li>
<li><a href="#" onclick="reloadConfig(); return false;"><i class="fa fa-repeat"></i><span style="padding-left: 10px;">Reload RP2350 Config</a></li>

View File

@@ -1 +1 @@
2.07
2.18

View File

@@ -94,9 +94,9 @@
<ul class="dropdown-menu">
<li style="margin-left: 1em;"><b>Action Menu</b></li>
<li class="divider"></li>
<li><a href="tasks/changefloppy?diskno=0"><i class="fa fa-floppy-o"></i><span style="padding-left: 10px;">Change Floppy Disk 1 (%SK_FLOPPY1%)</a></li>
<li><a href="tasks/changefloppy?diskno=1"><i class="fa fa-floppy-o"></i><span style="padding-left: 10px;">Change Floppy Disk 2 (%SK_FLOPPY2%)</a></li>
<li><a href="tasks/changeqd"><i class="fa fa-file"></i><span style="padding-left: 10px;">Change QD Disk (%SK_QDDISK%)</a></li>
<li><a href="tasks/changefloppy?diskno=0"><i class="fa fa-floppy-o"></i><span style="padding-left: 10px;">Change Floppy Disk 1 (%SK_FLOPPY1%)</span></a></li>
<li><a href="tasks/changefloppy?diskno=1"><i class="fa fa-floppy-o"></i><span style="padding-left: 10px;">Change Floppy Disk 2 (%SK_FLOPPY2%)</span></a></li>
<li><a href="tasks/changeqd"><i class="fa fa-file"></i><span style="padding-left: 10px;">Change QD Disk (%SK_QDDISK%)</span></a></li>
<hr style="margin-top: 10px; margin-bottom: 10px;">
<li><a href="tasks/reloadcfg"><i class="fa fa-repeat"></i><span style="padding-left: 10px;">Reload RP2350 Config</a></li>
<li><a href="#" onclick="reloadConfig(); return false;"><i class="fa fa-repeat"></i><span style="padding-left: 10px;">Reload RP2350 Config</a></li>

View File

@@ -39,11 +39,13 @@
#include "pico/binary_info.h"
#include "hardware/spi.h"
#include "hardware/dma.h"
#include "hardware/clocks.h"
#include "hardware/watchdog.h"
#include "debug.h"
#include "cJSON.h"
#include "rp2350.h"
#include "FSPI.h"
#include "Z80CPU.h"
#include "ESP.h"
#include "flash_ram.h"
#include "usb_bridge.h"
@@ -113,9 +115,11 @@ bool ESP_pollNop(char *cmdBuf, size_t cmdBufLen)
// ESP_sendVersionInfo — sends the flash partition header to ESP32.
// Uses the binary SPI IPC protocol (IPCF_CMD_INF).
// ---------------------------------------------------------------------------
bool ESP_sendVersionInfo(void)
bool ESP_sendVersionInfo(const void *cpuPtr)
{
uint32_t payloadLen = 0;
const t_Z80CPU *cpu = (const t_Z80CPU *) cpuPtr;
const t_drivers *drivers = cpu ? &cpu->_drivers : NULL;
// Build extended INF payload: partition header + runtime core config.
static t_FlashInfoPayload infPayload;
@@ -126,13 +130,17 @@ bool ESP_sendVersionInfo(void)
(FLASH_APP_CONFIG_SIZE * (infPayload.header.activeApp - 1)));
if (appCfg->s.configured == FLASH_APP_CONFIGURED_FLAG)
{
infPayload.cpufreq = appCfg->s.cpufreq;
// Report the actual running frequency, not the configured value.
// The PLL may apply a divider so the real frequency can differ from
// what was requested in the JSON config (e.g. 300MHz requested may
// yield 302MHz or 296MHz depending on the PLL VCO constraints).
infPayload.cpufreq = (int32_t) clock_get_hz(clk_sys);
infPayload.psramfreq = appCfg->s.psramfreq;
infPayload.voltage = appCfg->s.voltage;
}
else
{
infPayload.cpufreq = 0;
infPayload.cpufreq = (int32_t) clock_get_hz(clk_sys);
infPayload.psramfreq = 0;
infPayload.voltage = 0;
}
@@ -140,6 +148,44 @@ bool ESP_sendVersionInfo(void)
// Hardware sizes — always available regardless of config.
infPayload.flashSize = PICO_FLASH_SIZE_BYTES;
infPayload.psramSize = psramSize; // Set during psram_init() at boot
infPayload.hostClkHz = cpu ? cpu->hostClkHz : 0;
infPayload.emulSpeedHz = cpu ? cpu->emulSpeedHz : 0;
// Build active driver summary string: "Driver(V/P) IF1(V/P) IF2(V/P), ..."
infPayload.driverSummary[0] = '\0';
if (drivers && drivers->drvCount > 0)
{
char *p = infPayload.driverSummary;
size_t remain = sizeof(infPayload.driverSummary);
for (int d = 0; d < drivers->drvCount && remain > 1; d++)
{
const t_drvConfig *drv = &drivers->driver[d];
if (!drv->name)
continue;
// Add separator between drivers.
if (p != infPayload.driverSummary)
{
int n = snprintf(p, remain, ", ");
p += n; remain -= n;
}
// Driver name with type indicator.
int n = snprintf(p, remain, "%s(%s)", drv->name, drv->isPhysical ? "P" : "V");
p += n; remain -= n;
// Append enabled interfaces.
for (int i = 0; i < drv->ifCount && remain > 1; i++)
{
const t_drvIFConfig *ifc = &drv->ifConfig[i];
if (!ifc->name)
continue;
n = snprintf(p, remain, " %s(%s)", ifc->name, ifc->isPhysical ? "P" : "V");
p += n; remain -= n;
}
}
}
// Send INF command — payload is the extended flash info.
// Only attempt if ESP32 HS is already HIGH (CommandProcessor ready).

View File

@@ -52,6 +52,7 @@
#include "drivers/Sharp/MZ80A.h" // Sharp MZ80A Persona driver.
#endif
//-----------------------------------------
// .----._.----.
// A11 <-01--|1 o|--40-> A10
@@ -125,6 +126,9 @@ const t_doubleValuePair voltageMap[] = {
{1.20, VREG_VOLTAGE_1_20}, ///< 1.20 V
{1.25, VREG_VOLTAGE_1_25}, ///< 1.25 V
{1.30, VREG_VOLTAGE_1_30}, ///< 1.30 V
{1.35, VREG_VOLTAGE_1_35}, ///< 1.35 V (overclocking — requires vreg_disable_voltage_limit)
{1.40, VREG_VOLTAGE_1_40}, ///< 1.40 V (overclocking — requires vreg_disable_voltage_limit)
{1.50, VREG_VOLTAGE_1_50}, ///< 1.50 V (overclocking — requires vreg_disable_voltage_limit)
};
const size_t voltageMapSize = sizeof(voltageMap) / sizeof(voltageMap[0]);
@@ -402,7 +406,8 @@ bool Z80CPU_configMemoryFromJSON(t_Z80CPU *cpu, t_FlashAppConfigHeader *appConfi
// physical VRAM and vis-a-viz.
for (uint16_t idx = 0; idx < blockSize; idx++)
{
cpu->_z80PSRAM->memioPtr[blockAddr + idx] = ((memType == MEMBANK_TYPE_PHYSICAL || memType == MEMBANK_TYPE_PHYSICAL_HW) ? NULL : thisFunc);
t_MemoryFunc func = ((memType == MEMBANK_TYPE_PHYSICAL || memType == MEMBANK_TYPE_PHYSICAL_HW) ? NULL : thisFunc);
MEMIO_SET(cpu, blockAddr + idx, func);
}
switch (memType)
{
@@ -2040,57 +2045,6 @@ void Z80CPU_taskMirrorInternalRAMToPhysical(t_Z80CPU *cpu, uint32_t intAddr, uin
return;
}
// Method, called as an optional function to an internal memory fetch, which will activate hardware to ensure
// any motherboard hosted DRAM is refreshed.
// 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);
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
@@ -2472,6 +2426,8 @@ t_Z80CPU *Z80CPU_init(void)
cpu._memAttr[bank][idx].tCycSync = false;
}
}
// Clear the memioPtr bitmap and arrays.
memset(cpu.memioMap, 0, sizeof(cpu.memioMap));
for (int idx = 0; idx < MEMORY_PAGE_SIZE; idx++)
{
cpu._z80PSRAM->memPtr[idx] = 0x00000000;
@@ -2712,6 +2668,7 @@ void __func_in_RAM(Z80CPU_cpu_init)(t_Z80CPU *cpu)
void __func_in_RAM(Z80CPU_cpu)(t_Z80CPU *cpu)
{
// Locals.
static int pollCnt = 0;
// Notify core 0 than we are configured and running.
multicore_fifo_push_blocking(1);
@@ -2728,17 +2685,22 @@ void __func_in_RAM(Z80CPU_cpu)(t_Z80CPU *cpu)
cpu->holdAck = false;
}
// Call any driver poll handler periodically.
for (int idx = 0; idx < cpu->_drivers.drvCount; idx++)
// Call any driver poll handler periodically (approx 256ms).
if(pollCnt++ > 9999)
{
if (cpu->_drivers.driver[idx].pollPtr != NULL)
pollCnt = 0;
for (int idx = 0; idx < cpu->_drivers.drvCount; idx++)
{
cpu->_drivers.driver[idx].pollPtr(cpu);
if (cpu->_drivers.driver[idx].pollPtr != NULL)
{
cpu->_drivers.driver[idx].pollPtr(cpu);
}
}
}
// Step the CPU, giving a break to check signals.
z80_run(&cpu->_Z80, 2048);
z80_run(&cpu->_Z80, 64);
cpu->emulLoopCount++;
// Check for hardware reset, if enabled, process and clear.
if (cpu->forceReset || (pio_2->irq & (1u << 3)) != 0)
@@ -2772,58 +2734,62 @@ uint8_t __func_in_RAM(Z80CPU_readMem)(t_Z80CPU *cpu, uint16_t addr, uint32_t mem
{
// Locals.
uint16_t blockaddr = (addr & (MEMORY_BLOCK_SIZE - 1)); // Isolate the lower 10bit of the address.
uint32_t RAMaddr = (membankptr & 0x7fffff); // Isolate the full RAM address.
//uint16_t block = (membankptr << 10); // Shift by MEMORY_BLOCK_SIZE to obtain the block number.
uint8_t data = 0x00;
uint32_t RAMaddr = (membankptr & 0x7fffff); // Isolate the full RAM address.
uint16_t block = (membankptr << 10); // Shift by MEMORY_BLOCK_SIZE to obtain the block number.
uint8_t bank = (membankptr >> 16);
uint8_t data = 0x00;
switch (membankptr >> 24) // Top 6 bits stores the memory type.
{
// Underlying is a physical device on the motherboard, probably RAM/ROM.
case MEMBANK_TYPE_PHYSICAL:
case MEMBANK_TYPE_PHYSICAL_VRAM:
case MEMBANK_TYPE_PHYSICAL_HW:
data = Z80CPU_readPhysicalMem(cpu, addr);
break;
// Underlying is a physical device on the motherboard, probably RAM/ROM.
case MEMBANK_TYPE_PHYSICAL:
case MEMBANK_TYPE_PHYSICAL_VRAM:
case MEMBANK_TYPE_PHYSICAL_HW:
data = Z80CPU_readPhysicalMem(cpu, addr);
break;
// Underlying is a space within the PSRAM allocated area (4MB).
case MEMBANK_TYPE_RAM:
case MEMBANK_TYPE_VRAM:
case MEMBANK_TYPE_ROM:
// Execute any associated function with this location. Returned data is taken in preference to underlying RAM if a function is given.
//if(cpu->_z80PSRAM->memioPtr[block | blockaddr])
if (cpu->_z80PSRAM->memioPtr[addr])
{
// Call the function passing in the data from the current RAM address. If the function doesnt provide or
// override the data, then return what is in the RAM.
data = (uint8_t)(uintptr_t) cpu->_z80PSRAM->memioPtr[addr](cpu, true, addr, cpu->_z80PSRAM->RAM[RAMaddr + blockaddr]);
}
else
{
// Read direct from RAM, no physical access.
data = cpu->_z80PSRAM->RAM[RAMaddr + blockaddr];
}
// Underlying is a space within the PSRAM allocated area (4MB).
case MEMBANK_TYPE_RAM:
case MEMBANK_TYPE_VRAM:
case MEMBANK_TYPE_ROM:
if (MEMIO_BIT_TEST(cpu, addr) && cpu->_z80PSRAM->memioPtr[addr])
{
data = (uint8_t)(uintptr_t) cpu->_z80PSRAM->memioPtr[addr](cpu, true, addr, cpu->_z80PSRAM->RAM[RAMaddr + blockaddr]);
}
else
{
data = cpu->_z80PSRAM->RAM[RAMaddr + blockaddr];
}
// Any wait states setup, wait given number of T cycles.
if (waitStates)
Z80CPU_waitPhysicalStates(cpu, (uint32_t) waitStates);
break;
// If T-Cycle sync enabled for this memory block, wait for the rising edge of T1.
if(cpu->_memAttr[bank][block].tCycSync && waitStates == 0)
{
Z80CPU_syncPhysical(cpu);
} else
// Sync to host clock with wait states.
if(cpu->_memAttr[bank][block].tCycSync && waitStates > 0)
{
Z80CPU_syncPhysicalStates(cpu, waitStates);
} else
// Any wait states setup, wait given number of T cycles.
if (waitStates)
Z80CPU_waitPhysicalStates(cpu, (uint32_t) waitStates);
break;
// Underlying is provided by execution of a function which returns a byte.
case MEMBANK_TYPE_FUNC:
//if(cpu->_z80PSRAM->memioPtr[block | blockaddr])
if (cpu->_z80PSRAM->memioPtr[addr])
{
//data = cpu->_z80PSRAM->memioPtr[block | blockaddr](cpu, true, addr, 0);
data = (uint8_t)(uintptr_t) cpu->_z80PSRAM->memioPtr[addr](cpu, true, addr, 0);
}
break;
// Underlying is provided by execution of a function which returns a byte.
case MEMBANK_TYPE_FUNC:
if (MEMIO_BIT_TEST(cpu, addr) && cpu->_z80PSRAM->memioPtr[addr])
{
data = (uint8_t)(uintptr_t) cpu->_z80PSRAM->memioPtr[addr](cpu, true, addr, 0);
}
break;
// Underlying is a pointer which re-indexes back into this function ultimately ending up in a Physical, internal RAM or virtual device.
case MEMBANK_TYPE_PTR:
// Recursion to follow the pointer to the actual RAM bank.
//data = Z80CPU_readMem(cpu, addr, cpu->_z80PSRAM->memPtr[block | blockaddr], waitStates);
data = Z80CPU_readMem(cpu, addr, cpu->_z80PSRAM->memPtr[addr], waitStates);
break;
// Underlying is a pointer which re-indexes back into this function ultimately ending up in a Physical, internal RAM or virtual device.
case MEMBANK_TYPE_PTR:
// Recursion to follow the pointer to the actual RAM bank.
//data = Z80CPU_readMem(cpu, addr, cpu->_z80PSRAM->memPtr[block | blockaddr], waitStates);
data = Z80CPU_readMem(cpu, addr, cpu->_z80PSRAM->memPtr[addr], waitStates);
break;
}
// Return the byte deduced from resolution of the <Memory Type>.
@@ -2836,72 +2802,62 @@ uint8_t __func_in_RAM(Z80CPU_readMem)(t_Z80CPU *cpu, uint16_t addr, uint32_t mem
bool __func_in_RAM(Z80CPU_writeMem)(t_Z80CPU *cpu, uint16_t addr, uint32_t membankptr, uint8_t waitStates, uint8_t data)
{
// Locals.
uint16_t blockaddr = (addr & (MEMORY_BLOCK_SIZE - 1)); // Get lower bits of the address, ie. address within a block.
uint32_t RAMaddr =
(membankptr & 0x7fffff); // Isolate the RAM address. RAM is in banks, ie. <8 bit bank> <16 bit addr> so combined they form a 24bit RAM array address.
//uint16_t block = (membankptr << 10); // Shift by MEMORY_BLOCK_SIZE to obtain the block number.
uint16_t blockaddr = (addr & (MEMORY_BLOCK_SIZE - 1));
uint32_t RAMaddr = (membankptr & 0x7fffff);
bool result = true;
switch (membankptr >> 24) // Top 6 bits stores the memory type.
switch (membankptr >> 24)
{
// Underlying is a physical device on the motherboard, probably RAM/ROM.
case MEMBANK_TYPE_PHYSICAL:
case MEMBANK_TYPE_PHYSICAL_VRAM:
case MEMBANK_TYPE_PHYSICAL_HW:
result = Z80CPU_writePhysicalMem(cpu, addr, data);
break;
case MEMBANK_TYPE_PHYSICAL:
case MEMBANK_TYPE_PHYSICAL_VRAM:
case MEMBANK_TYPE_PHYSICAL_HW:
result = Z80CPU_writePhysicalMem(cpu, addr, data);
break;
// Underlying is a space within the PSRAM allocated area (4MB).
case MEMBANK_TYPE_RAM:
case MEMBANK_TYPE_VRAM:
// Execute any associated function with this location. Data is sent to function and not RAM if provided.
//if(cpu->_z80PSRAM->memioPtr[block | blockaddr])
if (cpu->_z80PSRAM->memioPtr[addr])
{
//data = cpu->_z80PSRAM->memioPtr[block | blockaddr](cpu, true, addr, data);
data = (uint8_t)(uintptr_t) cpu->_z80PSRAM->memioPtr[addr](cpu, false, addr, data);
}
else
{
cpu->_z80PSRAM->RAM[RAMaddr + blockaddr] = data; // Index is the [bank][block][lower bits of address]
}
case MEMBANK_TYPE_RAM:
case MEMBANK_TYPE_VRAM:
if (MEMIO_BIT_TEST(cpu, addr) && cpu->_z80PSRAM->memioPtr[addr])
{
data = (uint8_t)(uintptr_t) cpu->_z80PSRAM->memioPtr[addr](cpu, false, addr, data);
}
else
{
cpu->_z80PSRAM->RAM[RAMaddr + blockaddr] = data;
}
// Any wait states setup, wait given number of T cycles.
if (waitStates)
Z80CPU_waitPhysicalStates(cpu, (uint32_t) waitStates);
break;
// Any wait states setup, wait given number of T cycles.
if (waitStates)
Z80CPU_waitPhysicalStates(cpu, (uint32_t) waitStates);
break;
// Underlying resolves to ROM which isnt writable, just call any attached memio handler.
case MEMBANK_TYPE_ROM:
if (cpu->_z80PSRAM->memioPtr[addr])
{
//data = cpu->_z80PSRAM->memioPtr[block | blockaddr](cpu, true, addr, data);
data = (uint8_t)(uintptr_t) cpu->_z80PSRAM->memioPtr[addr](cpu, false, addr, data);
}
result = false;
break;
// Underlying is provided by execution of a function which returns a byte.
case MEMBANK_TYPE_FUNC:
// Check if a handler function has been installed, call if exists otherwise do nothing.
//if (cpu->_z80PSRAM->memioPtr[block | blockaddr])
if (cpu->_z80PSRAM->memioPtr[addr])
{
//data = cpu->_z80PSRAM->memioPtr[block | blockaddr](cpu, false, addr, data);
data = (uint8_t)(uintptr_t) cpu->_z80PSRAM->memioPtr[addr](cpu, false, addr, data);
}
else
{
// Underlying resolves to ROM which isnt writable, just call any attached memio handler.
case MEMBANK_TYPE_ROM:
if (MEMIO_BIT_TEST(cpu, addr) && cpu->_z80PSRAM->memioPtr[addr])
{
data = (uint8_t)(uintptr_t) cpu->_z80PSRAM->memioPtr[addr](cpu, false, addr, data);
}
result = false;
}
break;
break;
// Underlying is a pointer which re-indexes back into this function ultimately ending up in a Physical, internal RAM or virtual device.
case MEMBANK_TYPE_PTR:
// Recursion to follow the pointer to the actual RAM bank.
//result = Z80CPU_writeMem(cpu, addr, cpu->_z80PSRAM->memPtr[block | blockaddr], waitStates, data);
result = Z80CPU_writeMem(cpu, addr, cpu->_z80PSRAM->memPtr[addr], waitStates, data);
break;
// Underlying is provided by execution of a function which returns a byte.
case MEMBANK_TYPE_FUNC:
if (MEMIO_BIT_TEST(cpu, addr) && cpu->_z80PSRAM->memioPtr[addr])
{
//data = cpu->_z80PSRAM->memioPtr[block | blockaddr](cpu, false, addr, data);
data = (uint8_t)(uintptr_t) cpu->_z80PSRAM->memioPtr[addr](cpu, false, addr, data);
}
else
{
result = false;
}
break;
// Underlying is a pointer which re-indexes back into this function ultimately ending up in a Physical, internal RAM or virtual device.
case MEMBANK_TYPE_PTR:
// Recursion to follow the pointer to the actual RAM bank.
//result = Z80CPU_writeMem(cpu, addr, cpu->_z80PSRAM->memPtr[block | blockaddr], waitStates, data);
result = Z80CPU_writeMem(cpu, addr, cpu->_z80PSRAM->memPtr[addr], waitStates, data);
break;
}
// Return success code.
@@ -2959,6 +2915,7 @@ uint8_t __func_in_RAM(Z80CPU_fetchOpcode)(void *context, uint16_t address)
// Check membank type, call physical reads direct.
uint32_t membankptr = cpu->_membankPtr[block];
//if((membankptr >> 24) & (MEMBANK_TYPE_PHYSICAL|MEMBANK_TYPE_PHYSICAL_VRAM|MEMBANK_TYPE_PHYSICAL_HW))
if ((membankptr >> 24) & (MEMBANK_TYPE_PHYSICAL | MEMBANK_TYPE_PHYSICAL_VRAM))
{
@@ -2966,17 +2923,18 @@ uint8_t __func_in_RAM(Z80CPU_fetchOpcode)(void *context, uint16_t address)
}
else
{
// Bank is stored in bits 23:16 of membankptr. Need to get attributes for the specific block pointer to by the
// membankptr.
uint8_t bank = (membankptr >> 16);
// If T-Cycle sync enabled for this memory block, wait for the rising edge of T1.
if (cpu->_memAttr[bank][block].tCycSync)
{
Z80CPU_syncPhysical(cpu);
}
data = Z80CPU_readMem(cpu, address, membankptr, cpu->_memAttr[bank][block].waitStates);
// M1 cycles take 4 T-states vs 3 for normal reads. readMem with waitStates=2
// gives sync+2 = 3T (correct for memory reads). Add 1 extra wait here for
// M1's 4th T-state so the opcode fetch takes exactly 4T — matching the real Z80.
// This makes delay loop timing fully deterministic at the host clock speed.
if (cpu->_memAttr[bank][block].tCycSync && cpu->_memAttr[bank][block].waitStates > 0)
{
Z80CPU_waitPhysicalStates(cpu, 1);
}
// 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
@@ -3010,10 +2968,7 @@ uint8_t __func_in_RAM(Z80CPU_fetchByte)(void *context, uint16_t address)
}
else
{
// Bank is stored in bits 23:16 of membankptr. Need to get attributes for the specific block pointer to by the
// membankptr.
uint8_t bank = (membankptr >> 16);
data = Z80CPU_readMem(cpu, address, membankptr, cpu->_memAttr[bank][block].waitStates);
}
@@ -3041,24 +2996,18 @@ uint8_t __func_in_RAM(Z80CPU_readByte)(void *context, uint16_t address)
if ((membankptr >> 24) & (MEMBANK_TYPE_PHYSICAL | MEMBANK_TYPE_PHYSICAL_VRAM | MEMBANK_TYPE_PHYSICAL_HW))
{
// Some hardware needs to be virtualised or assisted, so if a hook is installed, call it rather than the hardware.
// The purpose, rather than using FUNC, is hardware is byte level not block, so most hardware will be physical
// except the odd address.
if (cpu->_z80PSRAM->memioPtr[address])
if (MEMIO_BIT_TEST(cpu, address) && cpu->_z80PSRAM->memioPtr[address])
{
data = (uint8_t)(uintptr_t) cpu->_z80PSRAM->memioPtr[address](cpu, true, address, 0);
}
else
{
// Read direct from hardware.
data = Z80CPU_readPhysicalMem(cpu, address);
}
}
else
{
// Bank is stored in bits 23:16 of membankptr. Need to get attributes for the specific block pointer to by the
// membankptr.
uint8_t bank = (membankptr >> 16);
data = Z80CPU_readMem(cpu, address, membankptr, cpu->_memAttr[bank][block].waitStates);
}
@@ -3080,10 +3029,7 @@ void __func_in_RAM(Z80CPU_writeByte)(void *context, uint16_t address, uint8_t va
uint32_t membankptr = cpu->_membankPtr[block];
if ((membankptr >> 24) & (MEMBANK_TYPE_PHYSICAL | MEMBANK_TYPE_PHYSICAL_VRAM | MEMBANK_TYPE_PHYSICAL_HW))
{
// Some hardware needs to be virtualised or assisted, so if a hook is installed, call it rather than the hardware.
// The purpose, rather than using FUNC, is hardware is byte level not block, so most hardware will be physical
// except the odd address.
if (cpu->_z80PSRAM->memioPtr[address])
if (MEMIO_BIT_TEST(cpu, address) && cpu->_z80PSRAM->memioPtr[address])
{
cpu->_z80PSRAM->memioPtr[address](cpu, false, address, value);
}
@@ -3095,11 +3041,7 @@ void __func_in_RAM(Z80CPU_writeByte)(void *context, uint16_t address, uint8_t va
}
else
{
// Bank is stored in bits 23:16 of membankptr. Need to get attributes for the specific block pointer to by the
// membankptr.
uint8_t bank = (membankptr >> 16);
// Default to membankptr from SRAM table
Z80CPU_writeMem(cpu, address, membankptr, cpu->_memAttr[bank][block].waitStates, value);
}
@@ -3251,8 +3193,12 @@ uint8_t __func_in_RAM(Z80CPU_fetchPhysicalMem)(t_Z80CPU *cpu, uint16_t addr)
PUSH_FIFO(pio_0, sm_addr, (((uint16_t) cpu->_Z80.r) << 16) | 0xFFFF); // pre-load refresh address.
WAIT_IRQ_SET(pio_1, 0); // Wait until state machine at start of sequence.
CLEAR_IRQ(pio_1, 0); // Enable read state machine.
CLEAR_IRQ(pio_0, 0); // Enable address load.
// Enable read state machine.
CLEAR_IRQ(pio_1, 0);
// Enable address load.
CLEAR_IRQ(pio_0, 0);
PUSH_INSTR32(pio_1,
smCycle,
@@ -3271,30 +3217,17 @@ uint8_t __func_in_RAM(Z80CPU_fetchPhysicalMem)(t_Z80CPU *cpu, uint16_t addr)
PUSH_INSTR32(pio_1,
smCycle,
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
(0x0000 + offsetCycle + 13)); // jmp offset+13, execute refresh T3/T4.
// Wait for the data to arrive before changing to the Refresh address.
WAIT_RX_FIFO_HAS_DATA(pio_1, smCycle);
CLEAR_IRQ(pio_0, 0); // Enable refresh address load.
// 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);
// 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);
#endif
@@ -3314,31 +3247,38 @@ uint8_t __func_in_RAM(Z80CPU_readPhysicalMem)(t_Z80CPU *cpu, uint16_t addr)
PUSH_FIFO(pio_0, sm_addr, (addr << 16) | 0xFFFF); // Preload Fifo with address.
WAIT_IRQ_SET(pio_1, 0); // Wait until state machine at start of sequence.
CLEAR_IRQ(pio_1, 0); // Enable read state machine.
CLEAR_IRQ(pio_0, 0); // Enable address load.
// Wait until state machine at start of sequence.
WAIT_IRQ_SET(pio_1, 0);
// Enable read state machine.
CLEAR_IRQ(pio_1, 0);
// Enable address load.
CLEAR_IRQ(pio_0, 0);
// Instructions to implement Read Physical.
PUSH_INSTR32(pio_1,
smCycle,
0x2013, // T1 wait 0 gpio, 35
0xf80b); // set pins, 11 /MREQ=0 side 2 /RD=0
0x2013, // T1 wait 0 gpio, 35
0xf80b); // set pins, 11 /MREQ=0 side 2 /RD=0
PUSH_INSTR32(pio_1,
smCycle,
(0x0000 + offsetCycle + 6), // jmp offset+6, wait state loop.
0x2093); // T3 wait 1 gpio, 35
0x2093); // T3 wait 1 gpio, 35
PUSH_INSTR32(pio_1,
smCycle,
0x2013, // T3 wait 0 gpio, 35
0x4008); // in pins, 8
0x2013, // T3 wait 0 gpio, 35
0x4008); // in pins, 8
PUSH_INSTR32(pio_1,
smCycle,
0xfc0f, // set pins, 15 side 3
(0x0000 + offsetCycle)); // jmp start
0xfc0f, // set pins, 15 side 3
(0x0000 + offsetCycle)); // jmp start
WAIT_RX_FIFO_HAS_DATA(pio_1, smCycle); // Wait until last instruction flushed.
data = POP_FIFO(pio_1, smCycle); // Grab input data from FIFO
WAIT_RX_FIFO_HAS_DATA(pio_1, smCycle); // Wait until last instruction flushed.
data = POP_FIFO(pio_1, smCycle); // Grab input data from FIFO
#ifdef DEBUG_PIO
debugf("Read %04x -> %02x\r\n", addr, (uint8_t) data);
@@ -3360,35 +3300,40 @@ bool __func_in_RAM(Z80CPU_writePhysicalMem)(t_Z80CPU *cpu, uint16_t addr, uint8_
PUSH_FIFO(pio_0, sm_data, 0x000000 | (data << 8) | 0x00FF); // Load Fifo with data ready to be loaded.
WAIT_IRQ_SET(pio_1, 0); // T1 - Wait until state machine at start of sequence.
CLEAR_IRQ(pio_1, 0); // Enable read state machine.
CLEAR_IRQ(pio_0, 0); // Enable address load.
// Enable read state machine.
CLEAR_IRQ(pio_1, 0);
// Enable address load.
CLEAR_IRQ(pio_0, 0);
PUSH_INSTR32(pio_1,
smCycle,
0x2013, // T1 wait 0 gpio, 35
0xfc0b); // set pins, 11 /MREQ=0 side 3 /RD=1,/WR=1
0x2013, // T1 wait 0 gpio, 35
0xfc0b); // set pins, 11 /MREQ=0 side 3 /RD=1,/WR=1
WAIT_IRQ_SET(pio_0, 1); // Wait until data SM is ready.
CLEAR_IRQ(pio_0, 1); // Enable data load.
WAIT_IRQ_SET(pio_0, 1); // Wait until data SM is ready.
CLEAR_IRQ(pio_0, 1); // Enable data load.
// Instructions to implement Write Physical.
PUSH_INSTR32(pio_1,
smCycle,
0x2093, // T2 wait 1 gpio, 35
0x2013); // T2 wait 0 gpio, 35
PUSH_INSTR32(pio_1,
smCycle,
0x2093, // T2 wait 1 gpio, 35
0x2013); // T2 wait 0 gpio, 35
0xf40b, // set pins, 11 /MREQ=0 side 1 /WR=0
(0x0000 + offsetCycle + 8));// jmp offset+8, wait state loop eval WAIT stage.
PUSH_INSTR32(pio_1,
smCycle,
0xf40b, // set pins, 11 /MREQ=0 side 1 /WR=0
(0x0000 + offsetCycle + 8)); // jmp offset+8, wait state loop eval WAIT stage.
PUSH_INSTR32(pio_1,
smCycle,
0x2093, // T3 wait 1 gpio, 35
0x2013); // T3 wait 0 gpio, 35
0x2093, // T3 wait 1 gpio, 35
0x2013); // T3 wait 0 gpio, 35
PUSH_INSTR_W32(pio_1,
smCycle,
0xfc0f, // set pins, 15 /IORQ=1,/MREQ=1/RFSH=1/M1=1 side 3 /RD=1,/WR=1
0xfc0f, // set pins, 15 /IORQ=1,/MREQ=1/RFSH=1/M1=1 side 3 /RD=1,/WR=1
(0x0000 + offsetCycle)); // jmp start
#ifdef DEBUG_PIO
@@ -3414,26 +3359,26 @@ uint8_t __func_in_RAM(Z80CPU_readPhysicalIO)(t_Z80CPU *cpu, uint16_t addr)
PUSH_INSTR32(pio_1,
smCycle,
0x2013, // T1 wait 0 gpio, 35
0x2093); // T2 wait 1 gpio, 35
0x2013, // T1 wait 0 gpio, 35
0x2093); // T2 wait 1 gpio, 35
PUSH_INSTR32(pio_1,
smCycle,
0xf807, // set pins, 7 side 2 Set /IORQ low, /RD low
0x2013); // T2 wait 0 gpio, 35
0xf807, // set pins, 7 side 2 Set /IORQ low, /RD low
0x2013); // T2 wait 0 gpio, 35
PUSH_INSTR32(pio_1,
smCycle,
(0x0000 + offsetCycle + 6), // jmp offset+6, wait state loop.
0x2093); // T3 wait 1 gpio, 35
(0x0000 + offsetCycle + 6),// jmp offset+6, wait state loop.
0x2093); // T3 wait 1 gpio, 35
PUSH_INSTR32(pio_1,
smCycle,
0x2013, // T3 wait 0 gpio, 35
0x4008); // in pins, 8
0x2013, // T3 wait 0 gpio, 35
0x4008); // in pins, 8
PUSH_INSTR32(pio_1,
smCycle,
0xfc0f, // set pins, 15 side 3 Set /IORQ high, /RD high
(0x0000 + offsetCycle)); // jmp start
(0x0000 + offsetCycle)); // jmp start
WAIT_RX_FIFO_HAS_DATA(pio_1, smCycle);
data = POP_FIFO(pio_1, smCycle);
@@ -3465,31 +3410,31 @@ bool __func_in_RAM(Z80CPU_writePhysicalIO)(t_Z80CPU *cpu, uint16_t addr, uint8_t
PUSH_INSTR32(pio_1,
smCycle,
0x2013, // T1 wait 0 gpio, 35
0x2093); // T2 wait 1 gpio, 35
0x2013, // T1 wait 0 gpio, 35
0x2093); // T2 wait 1 gpio, 35
WAIT_IRQ_SET(pio_0, 1); // Wait until data SM is ready.
CLEAR_IRQ(pio_0, 1); // Enable data load.
WAIT_IRQ_SET(pio_0, 1); // Wait until data SM is ready.
CLEAR_IRQ(pio_0, 1); // Enable data load.
PUSH_INSTR32(pio_1,
smCycle,
0xf407, // set pins, 7 side 1 Set /IORQ low, /WR low
0x2013); // T2 wait 0 gpio, 35
0xf407, // set pins, 7 side 1 Set /IORQ low, /WR low
0x2013); // T2 wait 0 gpio, 35
PUSH_INSTR32(pio_1,
smCycle,
(0x0000 + offsetCycle + 6), // jmp offset+6, wait state loop.
0x2093); // T3 wait 1 gpio, 35
(0x0000 + offsetCycle + 6),// jmp offset+6, wait state loop.
0x2093); // T3 wait 1 gpio, 35
PUSH_INSTR32(pio_1,
smCycle,
0x2013, // T3 wait 0 gpio, 35
0xfc0f); // set pins, 15 side 3 Set /IORQ high, /WR high
0x2013, // T3 wait 0 gpio, 35
0xfc0f); // set pins, 15 side 3 Set /IORQ high, /WR high
PUSH_INSTR32(pio_1,
smCycle,
0xa021, // nop
(0x0000 + offsetCycle)); // jmp start
(0x0000 + offsetCycle)); // jmp start
// BUSRQ/BUSACK State, INT, NMI? Inline single test for speed expanding if any IRQ is set.
SERVICE_REQUESTS();
@@ -3614,6 +3559,53 @@ uint8_t __func_in_RAM(Z80CPU_fetchPhysicalIntVector)(t_Z80CPU *cpu, uint16_t add
return ((uint8_t) data);
}
// Method, called as an optional function to an internal memory fetch, which will activate hardware to ensure
// any motherboard hosted DRAM is refreshed.
// 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.
uint16_t r_reg = 0;
t_Z80CPU *cpu = (t_Z80CPU *) context;
// The refresh address is a combination of the I register and the R register.
r_reg = ((uint8_t)cpu->_Z80.i << 8) | (uint8_t)cpu->_Z80.r;
// Only need to place the refresh address on the bus, we are not performing T1/T2 M1 cycle.
PUSH_FIFO(pio_0, sm_addr, (((uint16_t) r_reg) << 16) | 0xFFFF); // Pre-load refresh address.
// Wait until the previous cycle completes before commencing new cycle.
WAIT_IRQ_SET(pio_1, smCycle);
// // 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 initial instructions to setup pin inactive state then execute the Refresh T3/T4 cycles.
PUSH_INSTR32(pio_1,
smCycle,
//0x2093, // T3 wait 1 gpio, 35
0xfc0f, // /IORQ, /MREQ, /RFSH, /M1, Side: /WR /RD -> Set /M1 high, /MREQ high, /RD high, /RFSH high
(0x0000 + offsetCycle + 12)); // jmp offset+12, execute refresh T3/T4.
// Enable refresh address load.
CLEAR_IRQ(pio_0, 0);
// Enable smcycle state machine.
CLEAR_IRQ(pio_1, 0);
r_reg = ++r_reg & 0x7f; // R register only 7 bits incrementing.
return (0);
}
// Available SDK calls.
//
// static uint pio_get_gpio_base (PIO pio)

View File

@@ -0,0 +1,421 @@
// -----------------------------------------------------------------------
// Name: PIT8253.c
// Created: April 2026
// Author(s): Philip Smart
// Description: Comprehensive Intel 8253 Programmable Interval Timer emulation.
//
// 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.
//
// This module is standalone — each driver allocates its own t_PIT8253
// context so multiple instances (e.g. MZ-700 and MZ-80A) can coexist.
//
// Copyright: (c) 2019-2026 Philip Smart <philip.smart@net2net.org>
//
// History: April 2026 - Extracted from MZ700.c / MZ80A.c into
// standalone reusable module.
// -----------------------------------------------------------------------
// 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.
// -----------------------------------------------------------------------
#include <string.h>
#include "pico/stdlib.h"
#include "drivers/PIT8253.h"
// -----------------------------------------------------------------------
// Internal helpers
// -----------------------------------------------------------------------
// 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; // Wrap: 0 → 10000 (BCD 0000 → 9999 after decrement).
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.
// A programmed count of 0 means 0x10000 (65536) in binary or 10000 in BCD.
static uint32_t pitEffectiveCount(t_PIT8253Counter *ctr)
{
if (ctr->reload == 0)
return ctr->bcd ? 10000 : 0x10000;
return ctr->reload;
}
// Update a single counter based on elapsed wall-clock time.
static void pitUpdateCounter(t_PIT8253 *pit, int ch)
{
t_PIT8253Counter *ctr = &pit->counter[ch];
if (!ctr->counting)
return;
// Gate must be high for counting in modes 0, 2, 3, 4 (modes 1, 5 are edge-triggered,
// but gate is assumed high, so they also count).
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;
// Calculate clock ticks elapsed.
uint32_t ticks = (uint32_t)((elapsedUs * (int64_t)pit->clockHz) / 1000000);
if (ticks == 0)
return;
ctr->lastUpdate = now;
if (ctr->bcd)
{
// BCD counting — decrement one tick at a time (slow but correct for BCD).
// Limit to prevent excessive CPU usage if many ticks elapsed.
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: // Interrupt on TC — output goes HIGH, counter keeps counting (wraps).
ctr->output = true;
break;
case 1: // One-shot — output goes HIGH at TC.
ctr->output = true;
ctr->counting = false;
return;
case 2: // Rate generator — reload, output pulse LOW for 1 tick then HIGH.
ctr->count = ctr->reload ? ctr->reload : 0x9999;
ctr->output = true;
break;
case 3: // Square wave — toggle output at half count.
ctr->count = ctr->reload ? ctr->reload : 0x9999;
ctr->output = !ctr->output;
break;
case 4: // SW strobe — output LOW for 1 tick at TC, then HIGH.
ctr->output = false;
ctr->counting = false;
return;
case 5: // HW strobe — same as mode 4 but hardware triggered.
ctr->output = false;
ctr->counting = false;
return;
}
}
}
return;
}
// Binary counting.
uint32_t c = ctr->count;
switch (ctr->mode)
{
case 0: // Interrupt on Terminal Count.
// Counter counts down from loaded value. Output goes HIGH when count reaches 0.
// Counter continues to count (wraps through 0xFFFF).
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: // Hardware Retriggerable One-Shot.
// Output goes LOW on gate trigger, HIGH at terminal count.
if (ticks >= c)
{
ctr->output = true;
ctr->count = 0;
ctr->counting = false;
}
else
{
ctr->count = (uint16_t)(c - ticks);
}
break;
case 2: // Rate Generator.
// Counts down from N to 1. Output is HIGH for N-1, goes LOW for 1 tick, then reloads.
{
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; // Output is HIGH except for 1 tick at TC.
}
else
{
ctr->count = (uint16_t)(c - ticks);
ctr->output = (ctr->count != 1); // Output LOW for 1 clock before reload.
}
}
break;
case 3: // Square Wave Generator.
// Output toggles at half the count value. Effective period = reload value.
{
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;
}
// Number of half-period toggles.
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: // Software Triggered Strobe.
// Counts down from loaded value. Output goes LOW for 1 tick at TC, then HIGH.
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: // Hardware Triggered Strobe.
// Same as mode 4 but requires gate rising edge to start.
if (ticks >= c)
{
ctr->output = true;
ctr->count = 0;
ctr->counting = false;
}
else
{
ctr->count = (uint16_t)(c - ticks);
ctr->output = true;
}
break;
}
}
// -----------------------------------------------------------------------
// Public API
// -----------------------------------------------------------------------
void PIT8253_init(t_PIT8253 *pit, uint32_t clockHz)
{
memset(pit, 0, sizeof(t_PIT8253));
pit->clockHz = clockHz;
pit->initialized = false;
}
void PIT8253_reset(t_PIT8253 *pit)
{
pit->initialized = false;
}
uint8_t PIT8253_IO(t_PIT8253 *pit, bool read, uint8_t port, uint8_t data)
{
if (!pit->initialized)
{
for (int i = 0; i < PIT8253_NUM_COUNTERS; i++)
{
memset(&pit->counter[i], 0, sizeof(t_PIT8253Counter));
pit->counter[i].gate = true;
pit->counter[i].output = true; // Output starts HIGH for most modes.
pit->counter[i].readLsbNext = true;
pit->counter[i].writeLsbNext = true;
pit->counter[i].rlMode = 3; // Default LSB then MSB.
pit->counter[i].lastUpdate = get_absolute_time();
}
pit->initialized = true;
}
// Control register (port 3, write only — reads return 0xFF).
if (port == 3)
{
if (!read)
{
uint8_t ch = (data >> 6) & 0x03;
uint8_t rl = (data >> 4) & 0x03;
if (ch == 3)
{
// Read-back command (8254 only, not 8253). Ignore silently.
return 0xFF;
}
if (rl == 0)
{
// Counter Latch Command — snapshot current counter value.
if (!pit->counter[ch].latched)
{
pitUpdateCounter(pit, ch);
pit->counter[ch].latchVal = pit->counter[ch].count;
pit->counter[ch].latched = true;
pit->counter[ch].readLsbNext = true;
}
}
else
{
// Control Word — configure counter mode.
pit->counter[ch].rlMode = rl;
pit->counter[ch].mode = (data >> 1) & 0x07;
pit->counter[ch].bcd = (data & 0x01) != 0;
pit->counter[ch].readLsbNext = true;
pit->counter[ch].writeLsbNext = true;
pit->counter[ch].latched = false;
pit->counter[ch].nullCount = true;
pit->counter[ch].counting = false;
// Set initial output state per mode.
switch (pit->counter[ch].mode)
{
case 0:
pit->counter[ch].output = false; // Mode 0: output starts LOW.
break;
default:
pit->counter[ch].output = true; // Modes 1-5: output starts HIGH.
break;
}
}
}
return 0xFF;
}
// Counter data ports (0-2).
if (port >= PIT8253_NUM_COUNTERS)
return 0xFF;
t_PIT8253Counter *ctr = &pit->counter[port];
if (read)
{
// Read counter value. If latched, return latched value; otherwise return live count.
uint16_t val;
if (ctr->latched)
val = ctr->latchVal;
else
{
pitUpdateCounter(pit, port);
val = ctr->count;
}
uint8_t result;
switch (ctr->rlMode)
{
case 1: // LSB only.
result = (uint8_t)(val & 0xFF);
ctr->latched = false;
break;
case 2: // MSB only.
result = (uint8_t)((val >> 8) & 0xFF);
ctr->latched = false;
break;
case 3: // LSB then MSB.
if (ctr->readLsbNext)
{
ctr->readLsbNext = false;
result = (uint8_t)(val & 0xFF);
}
else
{
ctr->readLsbNext = true;
result = (uint8_t)((val >> 8) & 0xFF);
ctr->latched = false; // Both bytes read — release latch.
}
break;
default:
result = (uint8_t)(val & 0xFF);
ctr->latched = false;
break;
}
return result;
}
else
{
// Write counter reload value.
switch (ctr->rlMode)
{
case 1: // LSB only.
ctr->reload = (ctr->reload & 0xFF00) | data;
ctr->count = ctr->reload;
ctr->counting = true;
ctr->nullCount = false;
ctr->lastUpdate = get_absolute_time();
break;
case 2: // MSB only.
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: // LSB then MSB.
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;
}
}

View File

@@ -0,0 +1,345 @@
// -----------------------------------------------------------------------
// Name: PPI8255.c
// Created: April 2026
// Author(s): Philip Smart
// Description: Intel 8255 Programmable Peripheral Interface emulation.
//
// Full Mode 0 (simple I/O) implementation with:
// - Independent direction for PA, PB, PC upper, PC lower
// - Bit Set/Reset (BSR) for Port C
// - Output latching (ports retain last written value)
// - Per-port input callbacks (driver provides live values)
// - Output change callback (driver notified on writes)
// - Input injection (driver pushes values for input ports)
// - Modes 1 & 2 accepted but operate as Mode 0
//
// Register map (accent offset from base):
// +0 Port A data
// +1 Port B data
// +2 Port C data
// +3 Control register
//
// Copyright: (c) 2019-2026 Philip Smart <philip.smart@net2net.org>
//
// History: April 2026 - New standalone reusable module.
// -----------------------------------------------------------------------
// 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.
// -----------------------------------------------------------------------
#include <string.h>
#include "drivers/PPI8255.h"
// -----------------------------------------------------------------------
// Internal helpers
// -----------------------------------------------------------------------
// Decode the control word (D7=1) and update direction/mode fields.
static void ppiDecodeControlWord(t_PPI8255 *ppi, uint8_t cw)
{
ppi->controlWord = cw;
ppi->groupAMode = (cw >> 5) & 0x03; // D6:D5
ppi->portAInput = (cw & 0x10) != 0; // D4
ppi->portCUpperInput = (cw & 0x08) != 0; // D3
ppi->groupBMode = (cw >> 2) & 0x01; // D2
ppi->portBInput = (cw & 0x02) != 0; // D1
ppi->portCLowerInput = (cw & 0x01) != 0; // D0
// On control word write, all output latches are cleared to 0.
ppi->portA = 0;
ppi->portB = 0;
ppi->portC = 0;
}
// Build the Port C read value, combining output latch bits with input bits.
static uint8_t ppiBuildPortCRead(t_PPI8255 *ppi)
{
uint8_t value = 0;
// Upper nibble (PC7-PC4).
if (ppi->portCUpperInput)
{
// Input: get value from callback or injected state.
if (ppi->inputCallbackC)
{
uint8_t ext = ppi->inputCallbackC(PPI_PORT_C, ppi->callbackCtx);
value |= (ext & 0xF0);
}
else
{
value |= (ppi->portC & 0xF0); // Use injected value.
}
}
else
{
// Output: return latched output.
value |= (ppi->portC & 0xF0);
}
// Lower nibble (PC3-PC0).
if (ppi->portCLowerInput)
{
if (ppi->inputCallbackC)
{
uint8_t ext = ppi->inputCallbackC(PPI_PORT_C, ppi->callbackCtx);
value |= (ext & 0x0F);
}
else
{
value |= (ppi->portC & 0x0F);
}
}
else
{
value |= (ppi->portC & 0x0F);
}
return value;
}
// -----------------------------------------------------------------------
// Public API
// -----------------------------------------------------------------------
void PPI8255_init(t_PPI8255 *ppi)
{
memset(ppi, 0, sizeof(t_PPI8255));
// Default: Mode 0, all ports input (control word 0x9B).
ppiDecodeControlWord(ppi, 0x9B);
ppi->initialized = true;
}
void PPI8255_reset(t_PPI8255 *ppi)
{
// Preserve callbacks across reset.
t_PPIOutputCallback outCb = ppi->outputCallback;
t_PPIInputCallback inA = ppi->inputCallbackA;
t_PPIInputCallback inB = ppi->inputCallbackB;
t_PPIInputCallback inC = ppi->inputCallbackC;
void *ctx = ppi->callbackCtx;
memset(ppi, 0, sizeof(t_PPI8255));
ppiDecodeControlWord(ppi, 0x9B);
ppi->outputCallback = outCb;
ppi->inputCallbackA = inA;
ppi->inputCallbackB = inB;
ppi->inputCallbackC = inC;
ppi->callbackCtx = ctx;
ppi->initialized = true;
}
void PPI8255_setOutputCallback(t_PPI8255 *ppi, t_PPIOutputCallback cb, void *ctx)
{
ppi->outputCallback = cb;
ppi->callbackCtx = ctx;
}
void PPI8255_setInputCallback(t_PPI8255 *ppi, t_PPIPort port, t_PPIInputCallback cb)
{
switch (port)
{
case PPI_PORT_A: ppi->inputCallbackA = cb; break;
case PPI_PORT_B: ppi->inputCallbackB = cb; break;
case PPI_PORT_C: ppi->inputCallbackC = cb; break;
default: break;
}
}
uint8_t PPI8255_IO(t_PPI8255 *ppi, bool read, uint8_t port, uint8_t data)
{
switch (port)
{
// -------------------------------------------------------------------
// Port A (offset +0)
// -------------------------------------------------------------------
case 0:
if (read)
{
if (ppi->portAInput)
{
// Input port: return live value from callback or injected state.
if (ppi->inputCallbackA)
return ppi->inputCallbackA(PPI_PORT_A, ppi->callbackCtx);
return ppi->portA; // Injected value.
}
// Output port: return latched output value.
return ppi->portA;
}
else
{
if (!ppi->portAInput)
{
// Output port: latch the value and notify.
ppi->portA = data;
if (ppi->outputCallback)
ppi->outputCallback(PPI_PORT_A, data, -1, -1, ppi->callbackCtx);
}
// Writes to input-configured ports are ignored (data is lost).
return 0;
}
// -------------------------------------------------------------------
// Port B (offset +1)
// -------------------------------------------------------------------
case 1:
if (read)
{
if (ppi->portBInput)
{
if (ppi->inputCallbackB)
return ppi->inputCallbackB(PPI_PORT_B, ppi->callbackCtx);
return ppi->portB;
}
return ppi->portB;
}
else
{
if (!ppi->portBInput)
{
ppi->portB = data;
if (ppi->outputCallback)
ppi->outputCallback(PPI_PORT_B, data, -1, -1, ppi->callbackCtx);
}
return 0;
}
// -------------------------------------------------------------------
// Port C (offset +2)
// -------------------------------------------------------------------
case 2:
if (read)
{
return ppiBuildPortCRead(ppi);
}
else
{
// Write only affects output-direction bits.
uint8_t mask = 0;
if (!ppi->portCUpperInput) mask |= 0xF0;
if (!ppi->portCLowerInput) mask |= 0x0F;
ppi->portC = (ppi->portC & ~mask) | (data & mask);
if (ppi->outputCallback && mask)
ppi->outputCallback(PPI_PORT_C, ppi->portC, -1, -1, ppi->callbackCtx);
return 0;
}
// -------------------------------------------------------------------
// Control Register (offset +3)
// -------------------------------------------------------------------
case 3:
if (read)
{
// Control register is write-only on the 8255.
// Some implementations return the last control word; others return 0xFF.
return ppi->controlWord;
}
else
{
if (data & 0x80)
{
// I/O Mode Control Word (D7=1).
// Reconfigure port directions and modes. Output latches cleared.
uint8_t oldPortC = ppi->portC;
ppiDecodeControlWord(ppi, data);
// Notify output callback that ports were reset.
if (ppi->outputCallback)
{
ppi->outputCallback(PPI_PORT_A, 0, -1, -1, ppi->callbackCtx);
ppi->outputCallback(PPI_PORT_B, 0, -1, -1, ppi->callbackCtx);
if (oldPortC != 0)
ppi->outputCallback(PPI_PORT_C, 0, -1, -1, ppi->callbackCtx);
}
}
else
{
// Bit Set/Reset (BSR) Mode (D7=0).
// Affects a single Port C bit without changing other bits or modes.
uint8_t bitNum = (data >> 1) & 0x07; // D3:D1 = bit number (0-7).
uint8_t setVal = data & 0x01; // D0 = 1=set, 0=reset.
uint8_t bitMask = (1 << bitNum);
// BSR only works on output-direction bits.
bool isOutput;
if (bitNum >= 4)
isOutput = !ppi->portCUpperInput;
else
isOutput = !ppi->portCLowerInput;
if (isOutput)
{
if (setVal)
ppi->portC |= bitMask;
else
ppi->portC &= ~bitMask;
if (ppi->outputCallback)
ppi->outputCallback(PPI_PORT_C, ppi->portC, bitNum, setVal, ppi->callbackCtx);
}
}
return 0;
}
default:
return 0xFF;
}
}
void PPI8255_injectInput(t_PPI8255 *ppi, t_PPIPort port, uint8_t value)
{
switch (port)
{
case PPI_PORT_A:
if (ppi->portAInput)
ppi->portA = value;
break;
case PPI_PORT_B:
if (ppi->portBInput)
ppi->portB = value;
break;
case PPI_PORT_C:
{
// Only inject into input-direction nibbles.
uint8_t mask = 0;
if (ppi->portCUpperInput) mask |= 0xF0;
if (ppi->portCLowerInput) mask |= 0x0F;
ppi->portC = (ppi->portC & ~mask) | (value & mask);
break;
}
default:
break;
}
}
uint8_t PPI8255_getOutput(t_PPI8255 *ppi, t_PPIPort port)
{
switch (port)
{
case PPI_PORT_A:
return ppi->portAInput ? 0 : ppi->portA;
case PPI_PORT_B:
return ppi->portBInput ? 0 : ppi->portB;
case PPI_PORT_C:
{
uint8_t value = 0;
if (!ppi->portCUpperInput) value |= (ppi->portC & 0xF0);
if (!ppi->portCLowerInput) value |= (ppi->portC & 0x0F);
return value;
}
default:
return 0;
}
}
uint8_t PPI8255_getPortCBit(t_PPI8255 *ppi, uint8_t bit)
{
if (bit > 7)
return 0;
return (ppi->portC >> bit) & 0x01;
}

View File

@@ -145,9 +145,15 @@ uint8_t MZ1E05_Reset(t_Z80CPU *cpu)
}
// Poll handler for the MZ1E05 Floppy Disk Interface.
uint8_t MZ1E05_PollCB(t_Z80CPU *cpu)
//uint8_t MZ1E05_PollCB(t_Z80CPU *cpu)
uint8_t __func_in_RAM(MZ1E05_PollCB)(t_Z80CPU *cpu)
{
// Locals.
// Drain any pending WD1773 responses from the shared intercore queue.
// Guard avoids calling flash-resident SDK queue functions when idle.
if (mz1e05Ctrl && (mz1e05Ctrl->fdc.opState.loadPending || mz1e05Ctrl->fdc.opState.writePending))
{
wd1773_processResponses(&mz1e05Ctrl->fdc);
}
return (0);
}
@@ -181,7 +187,7 @@ uint8_t MZ1E05_TaskProcessor(t_Z80CPU *cpu, enum Z80CPU_TASK_NAME task, char *pa
return (result);
}
uint8_t MZ1E05_IO_WD1773(t_Z80CPU *cpu, bool read, uint16_t addr, uint8_t data)
uint8_t __func_in_RAM(MZ1E05_IO_WD1773)(t_Z80CPU *cpu, bool read, uint16_t addr, uint8_t data)
{
// Locals.
uint8_t result = 0;
@@ -198,7 +204,7 @@ uint8_t MZ1E05_IO_WD1773(t_Z80CPU *cpu, bool read, uint16_t addr, uint8_t data)
return (result);
}
uint8_t MZ1E05_IO_DriveSel(t_Z80CPU *cpu, bool read, uint16_t addr, uint8_t data)
uint8_t __func_in_RAM(MZ1E05_IO_DriveSel)(t_Z80CPU *cpu, bool read, uint16_t addr, uint8_t data)
{
// Locals.
uint8_t result = 0;
@@ -215,7 +221,7 @@ uint8_t MZ1E05_IO_DriveSel(t_Z80CPU *cpu, bool read, uint16_t addr, uint8_t data
return (result);
}
uint8_t MZ1E05_IO_SideSel(t_Z80CPU *cpu, bool read, uint16_t addr, uint8_t data)
uint8_t __func_in_RAM(MZ1E05_IO_SideSel)(t_Z80CPU *cpu, bool read, uint16_t addr, uint8_t data)
{
// Locals.
uint8_t result = 0;
@@ -226,7 +232,7 @@ uint8_t MZ1E05_IO_SideSel(t_Z80CPU *cpu, bool read, uint16_t addr, uint8_t data)
}
// DDEN select (0 = MFM, 1 = FM)
uint8_t MZ1E05_IO_DDENSel(t_Z80CPU *cpu, bool read, uint16_t addr, uint8_t data)
uint8_t __func_in_RAM(MZ1E05_IO_DDENSel)(t_Z80CPU *cpu, bool read, uint16_t addr, uint8_t data)
{
// Locals.
uint8_t result = 0;

View File

@@ -142,7 +142,8 @@ uint8_t MZ1E14_Reset(t_Z80CPU *cpu)
}
// Poll handler for the MZ1E14 ROM-Less QD MZ-1E14 interface.
uint8_t MZ1E14_PollCB(t_Z80CPU *cpu)
//uint8_t MZ1E14_PollCB(t_Z80CPU *cpu)
uint8_t __func_in_RAM(MZ1E14_PollCB)(t_Z80CPU *cpu)
{
// Locals.
@@ -182,7 +183,7 @@ uint8_t MZ1E14_TaskProcessor(t_Z80CPU *cpu, enum Z80CPU_TASK_NAME task, char *pa
}
// I/O Handler for the Quick Disk SIO registers.
uint8_t MZ1E14_IO_QD(t_Z80CPU *cpu, bool read, uint16_t addr, uint8_t data)
uint8_t __func_in_RAM(MZ1E14_IO_QD)(t_Z80CPU *cpu, bool read, uint16_t addr, uint8_t data)
{
// Locals.
uint8_t result = 0;

View File

@@ -145,7 +145,7 @@ uint8_t MZ1E19_Reset(t_Z80CPU *cpu)
// Read a byte from Z80 address space (for diagnostics — no side effects on FUNC/memioPtr).
// Uses _membankPtr[block] like the real Z80 fetch path, NOT memPtr[addr].
static uint8_t MZ1E19_peekZ80(t_Z80CPU *cpu, uint16_t addr)
static uint8_t __func_in_RAM(MZ1E19_peekZ80)(t_Z80CPU *cpu, uint16_t addr)
{
uint8_t block = addr / MEMORY_BLOCK_SIZE;
uint32_t membankptr = cpu->_membankPtr[block];
@@ -192,7 +192,8 @@ static void MZ1E19_dumpContext(t_Z80CPU *cpu, const char *tag)
}
// Poll handler for the MZ1E19 ROM-Less QD MZ-1E19 interface.
uint8_t MZ1E19_PollCB(t_Z80CPU *cpu)
//uint8_t MZ1E19_PollCB(t_Z80CPU *cpu)
uint8_t __func_in_RAM(MZ1E19_PollCB)(t_Z80CPU *cpu)
{
// Locals.
static uint32_t stallPollCount = 0;
@@ -270,7 +271,7 @@ uint8_t MZ1E19_TaskProcessor(t_Z80CPU *cpu, enum Z80CPU_TASK_NAME task, char *pa
}
// I/O Handler for the Quick Disk SIO registers.
uint8_t MZ1E19_IO_QD(t_Z80CPU *cpu, bool read, uint16_t addr, uint8_t data)
uint8_t __func_in_RAM(MZ1E19_IO_QD)(t_Z80CPU *cpu, bool read, uint16_t addr, uint8_t data)
{
// Locals.
uint8_t result = 0;

View File

@@ -156,9 +156,22 @@ uint8_t MZ1R12_Reset(t_Z80CPU *cpu)
}
// Poll handler for the MZ1R12 S-RAM Board.
uint8_t MZ1R12_PollCB(t_Z80CPU *cpu)
//uint8_t MZ1R12_PollCB(t_Z80CPU *cpu)
uint8_t __func_in_RAM(MZ1R12_PollCB)(t_Z80CPU *cpu)
{
// Locals.
// Drain any pending MZ-1R12 responses from the shared intercore queue.
// Guard avoids calling flash-resident SDK queue functions when idle.
if (mz1r12Ctrl)
{
for (int i = 0; i < mz1r12Count; i++)
{
if (mz1r12Ctrl[i].loadPending || mz1r12Ctrl[i].writePending)
{
MZ1R12_ProcessQueueResponses(cpu, i);
break;
}
}
}
return (0);
}
@@ -244,7 +257,7 @@ int MZ1R12_ProcessQueueResponses(t_Z80CPU *cpu, int boardNo)
// MZ-1R12 S-Ram Register Handler.
// 0xF8 - MZ-1R12 S-Ram Set High Address (W) or Reset (R)
uint8_t MZ1R12_IO_Reg1(t_Z80CPU *cpu, bool read, uint16_t addr, uint8_t data)
uint8_t __func_in_RAM(MZ1R12_IO_Reg1)(t_Z80CPU *cpu, bool read, uint16_t addr, uint8_t data)
{
// Locals.
uint8_t result = 0;
@@ -278,7 +291,7 @@ uint8_t MZ1R12_IO_Reg1(t_Z80CPU *cpu, bool read, uint16_t addr, uint8_t data)
// MZ-1R12 S-Ram Register Handler.
// 0xF9 - MZ-1R12 S-Ram Set Low Address (W) or Read Data (R)
uint8_t MZ1R12_IO_Reg2(t_Z80CPU *cpu, bool read, uint16_t addr, uint8_t data)
uint8_t __func_in_RAM(MZ1R12_IO_Reg2)(t_Z80CPU *cpu, bool read, uint16_t addr, uint8_t data)
{
// Locals.
uint8_t result = 0;
@@ -315,7 +328,7 @@ uint8_t MZ1R12_IO_Reg2(t_Z80CPU *cpu, bool read, uint16_t addr, uint8_t data)
// MZ-1R12 S-Ram Register Handler.
// 0xFA - MZ-1R12 S-Ram Write Date (W).
uint8_t MZ1R12_IO_Reg3(t_Z80CPU *cpu, bool read, uint16_t addr, uint8_t data)
uint8_t __func_in_RAM(MZ1R12_IO_Reg3)(t_Z80CPU *cpu, bool read, uint16_t addr, uint8_t data)
{
// Locals.
uint8_t result = 0;

View File

@@ -102,7 +102,8 @@ uint8_t MZ1R18_Reset(t_Z80CPU *cpu)
}
// Poll handler for the MZ1R18 Ram File Board.
uint8_t MZ1R18_PollCB(t_Z80CPU *cpu)
//uint8_t MZ1R18_PollCB(t_Z80CPU *cpu)
uint8_t __func_in_RAM(MZ1R18_PollCB)(t_Z80CPU *cpu)
{
// Locals.
@@ -134,7 +135,7 @@ uint8_t MZ1R18_TaskProcessor(t_Z80CPU *cpu, enum Z80CPU_TASK_NAME task, char *pa
// MZ-1R18 Ram File Data Register Handler.
// 0xEA - MZ-1R18 Ram File Data Register.
uint8_t MZ1R18_DataReg(t_Z80CPU *cpu, bool read, uint16_t addr, uint8_t data)
uint8_t __func_in_RAM(MZ1R18_DataReg)(t_Z80CPU *cpu, bool read, uint16_t addr, uint8_t data)
{
// Locals.
uint8_t result = 0;
@@ -154,7 +155,7 @@ uint8_t MZ1R18_DataReg(t_Z80CPU *cpu, bool read, uint16_t addr, uint8_t data)
// MZ-1R18 Ram File Control Register Handler.
// 0xEB - MZ-1R18 Ram File Control Register.
uint8_t MZ1R18_CtrlReg(t_Z80CPU *cpu, bool read, uint16_t addr, uint8_t data)
uint8_t __func_in_RAM(MZ1R18_CtrlReg)(t_Z80CPU *cpu, bool read, uint16_t addr, uint8_t data)
{
// Locals.
uint8_t ctrl = 0;

View File

@@ -599,7 +599,7 @@ void MZ700_readROMData(void *ctx, void *cfg, char *buf, int len)
// I/O handler for unconnected SIO ports (RS-232C at 0xB0-0xB3, etc.)
// Returns idle Z80-SIO status when read: TX Buffer Empty (bit 2), no Rx data (bit 0 = 0).
// This prevents BASIC from spinning on SIO status polls when the RS-232C board is not present.
uint8_t MZ700_IO_SIO_NotPresent(t_Z80CPU *cpu, bool read, uint16_t addr, uint8_t data)
uint8_t __func_in_RAM(MZ700_IO_SIO_NotPresent)(t_Z80CPU *cpu, bool read, uint16_t addr, uint8_t data)
{
if (read)
{
@@ -618,7 +618,7 @@ uint8_t MZ700_IO_SIO_NotPresent(t_Z80CPU *cpu, bool read, uint16_t addr, uint8_t
return 0;
}
uint8_t MZ700_IO_MemoryBankPorts(t_Z80CPU *cpu, bool read, uint16_t addr, uint8_t data)
uint8_t __func_in_RAM(MZ700_IO_MemoryBankPorts)(t_Z80CPU *cpu, bool read, uint16_t addr, uint8_t data)
{
// Locals.
uint8_t port = (uint8_t) addr & 0xff;
@@ -698,7 +698,7 @@ uint8_t MZ700_IO_MemoryBankPorts(t_Z80CPU *cpu, bool read, uint16_t addr, uint8_
}
// Debug handler, any byte output to this port will be sent to the debugf channel.
uint8_t MZ700_IO_Debug(t_Z80CPU *cpu, bool read, uint16_t addr, uint8_t data)
uint8_t __func_in_RAM(MZ700_IO_Debug)(t_Z80CPU *cpu, bool read, uint16_t addr, uint8_t data)
{
// Locals.
uint8_t port = (uint8_t) addr & 0xff;
@@ -798,7 +798,8 @@ uint8_t MZ700_Reset(t_Z80CPU *cpu)
// I/O access. The code should be absolute minimum in time as it will affect Z80 emulation timing accuracy,
// but needed as core 0 running the task could clash with core 1 and use of mutex will add similar delay.
// Any long running tasks should be assigned to core 0.
uint8_t MZ700_PollCB(t_Z80CPU *cpu)
//uint8_t MZ700_PollCB(t_Z80CPU *cpu)
uint8_t __func_in_RAM(MZ700_PollCB)(t_Z80CPU *cpu)
{
// Locals.
uint8_t result = 0;
@@ -955,6 +956,7 @@ uint8_t MZ700_Init(t_Z80CPU *cpu, t_FlashAppConfigHeader *appConfig, t_drvConfig
{
cpu->_z80PSRAM->memioPtr[idx] = (t_MemoryFunc) NULL;
}
memset(cpu->memioMap, 0, MEMIO_BITMAP_SIZE);
for (int idx = 0; idx < IO_PAGE_SIZE; idx++)
{
t_MemoryFunc ioPtr = (t_MemoryFunc) NULL;

View File

@@ -62,6 +62,13 @@ static t_InterfaceFuncMap interfaceFuncMap[] = {
};
static const size_t interfaceFuncMapSize = sizeof(interfaceFuncMap) / sizeof(interfaceFuncMap[0]);
// MZ-80A driver state for MEMSW handling.
static bool mz80aIsPhysical = false; // True when the MZ80A driver is in PHYSICAL mode.
static bool mz80aMemSwitch = false; // True when the memory swap (0x0000<->0xC000) is active.
// RFS control structure — needed to keep rfsCtrl->memSwitch in sync for RFS bank select offset.
extern t_RFSCtrl *rfsCtrl;
// -----------------------------------------------------------------------
// Comprehensive Intel 8253 PIT (Programmable Interval Timer) emulation.
// The MZ-80A uses an 8253 PIT at memory-mapped addresses 0xE004-0xE007
@@ -522,7 +529,7 @@ void MZ80A_readROMData(void *ctx, void *cfg, char *buf, int len)
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->_membankPtr[idx] = (MEMBANK_TYPE_RAM << 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;
}
@@ -535,8 +542,99 @@ void MZ80A_readROMData(void *ctx, void *cfg, char *buf, int len)
return;
}
// MZ-80A Memory Swap handler.
// On the MZ-80A, a read or write to 0xE00C toggles a hardware latch that swaps the Monitor ROM
// at 0x0000:0x0FFF with RAM at 0xC000:0xCFFF. This puts RAM at 0x0000 so CP/M can use the full
// low memory area. In PHYSICAL mode we must also trigger the hardware latch via a physical bus cycle;
// in VIRTUAL mode only the internal memory map needs updating.
uint8_t __func_in_RAM(MZ80A_IO_MEMSW)(t_Z80CPU *cpu, bool read, uint16_t addr, uint8_t data)
{
uint8_t result = data;
// Already switched — nothing to do.
if (mz80aMemSwitch)
return (result);
// In PHYSICAL mode, trigger the hardware swap latch via the physical bus.
if (mz80aIsPhysical)
{
Z80CPU_readPhysicalMem(cpu, MEMSW);
}
// Swap the internal memory map: 0x0000:0x0FFF <-> 0xC000:0xCFFF.
uint8_t offset = 0xC000 / MEMORY_BLOCK_SIZE;
for (int idx = 0; idx < (0x1000 / MEMORY_BLOCK_SIZE); idx++)
{
uint32_t memptr = cpu->_membankPtr[idx];
uint8_t waitStates = cpu->_memAttr[MROM_BANK][idx].waitStates;
bool tCycSync = cpu->_memAttr[MROM_BANK][idx].tCycSync;
if (idx == 0)
{
debugf("MZ80A_MEMSW: %08x -> %08x (phys=%d)\r\n",
cpu->_membankPtr[idx], cpu->_membankPtr[idx + offset], mz80aIsPhysical);
sleep_ms(1);
}
cpu->_membankPtr[idx] = cpu->_membankPtr[idx + offset];
cpu->_memAttr[MROM_BANK][idx].waitStates = cpu->_memAttr[MROM_BANK][idx + offset].waitStates;
cpu->_memAttr[MROM_BANK][idx].tCycSync = cpu->_memAttr[MROM_BANK][idx + offset].tCycSync;
cpu->_membankPtr[idx + offset] = memptr;
cpu->_memAttr[MROM_BANK][idx + offset].waitStates = waitStates;
cpu->_memAttr[MROM_BANK][idx + offset].tCycSync = tCycSync;
}
mz80aMemSwitch = true;
if (rfsCtrl)
rfsCtrl->memSwitch = true;
return (result);
}
// MZ-80A Memory Swap Reset handler.
// A read or write to 0xE010 reverses the swap: ROM returns to 0x0000, RAM returns to 0xC000.
uint8_t __func_in_RAM(MZ80A_IO_MEMSWR)(t_Z80CPU *cpu, bool read, uint16_t addr, uint8_t data)
{
uint8_t result = data;
// Not switched — nothing to do.
if (!mz80aMemSwitch)
return (result);
// In PHYSICAL mode, trigger the hardware reverse-swap latch via the physical bus.
if (mz80aIsPhysical)
{
Z80CPU_readPhysicalMem(cpu, MEMSWR);
}
// Reverse the swap: 0x0000:0x0FFF <-> 0xC000:0xCFFF.
uint8_t offset = 0xC000 / MEMORY_BLOCK_SIZE;
for (int idx = 0; idx < (0x1000 / MEMORY_BLOCK_SIZE); idx++)
{
uint32_t memptr = cpu->_membankPtr[idx];
uint8_t waitStates = cpu->_memAttr[MROM_BANK][idx].waitStates;
bool tCycSync = cpu->_memAttr[MROM_BANK][idx].tCycSync;
if (idx == 0)
{
debugf("MZ80A_MEMSWR: %08x -> %08x (phys=%d)\r\n",
cpu->_membankPtr[idx], cpu->_membankPtr[idx + offset], mz80aIsPhysical);
sleep_ms(1);
}
cpu->_membankPtr[idx] = cpu->_membankPtr[idx + offset];
cpu->_memAttr[MROM_BANK][idx].waitStates = cpu->_memAttr[MROM_BANK][idx + offset].waitStates;
cpu->_memAttr[MROM_BANK][idx].tCycSync = cpu->_memAttr[MROM_BANK][idx + offset].tCycSync;
cpu->_membankPtr[idx + offset] = memptr;
cpu->_memAttr[MROM_BANK][idx + offset].waitStates = waitStates;
cpu->_memAttr[MROM_BANK][idx + offset].tCycSync = tCycSync;
}
mz80aMemSwitch = false;
if (rfsCtrl)
rfsCtrl->memSwitch = false;
return (result);
}
// 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 __func_in_RAM(MZ80A_IO_Debug)(t_Z80CPU *cpu, bool read, uint16_t addr, uint8_t data)
{
uint8_t port = (uint8_t) addr & 0xff;
(void)port;
@@ -587,6 +685,7 @@ uint8_t MZ80A_Reset(t_Z80CPU *cpu)
Z80CPU_writePhysicalMem(cpu, 0xE003, 0x04);
// Call interface reset handlers if board installed.
// RFS_Reset will swap-back _membankPtr and clear rfsCtrl->memSwitch if MEMSW was active.
for (size_t i = 0; i < interfaceFuncMapSize; i++)
{
debugf("IF_RST[%d]: %s active=%d\r\n", i, interfaceFuncMap[i].interfaceFuncName, interfaceFuncMap[i].active);
@@ -596,11 +695,15 @@ uint8_t MZ80A_Reset(t_Z80CPU *cpu)
}
}
// Reset MEMSW state. The hardware latch is cleared by the /RESET signal; the internal
// _membankPtr swap-back was handled by RFS_Reset above (if RFS interface is active).
mz80aMemSwitch = false;
return (result);
}
// Poll Callback handler.
uint8_t MZ80A_PollCB(t_Z80CPU *cpu)
uint8_t __func_in_RAM(MZ80A_PollCB)(t_Z80CPU *cpu)
{
// Locals.
uint8_t result = 0;
@@ -688,61 +791,61 @@ uint8_t MZ80A_Init(t_Z80CPU *cpu, t_FlashAppConfigHeader *appConfig, t_drvConfig
bool tCycSync = false;
switch (idx)
{
// 0x0000:0x0FFF = Monitor ROM.
case 0 ... 7:
if (!isPhysical)
{
memType = MEMBANK_TYPE_ROM;
waitStates = 1;
tCycSync = true;
}
break;
// 0x0000:0x0FFF = Monitor ROM.
case 0 ... 7:
if (!isPhysical)
{
memType = MEMBANK_TYPE_RAM;
// 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;
// 0x1000:0xCFFF = RAM (includes 0xC000:0xCFFF MEMSW swap target)
case 8 ... 103:
if (!isPhysical)
{
memType = MEMBANK_TYPE_RAM;
waitStates = 0;
tCycSync = false;
}
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;
// 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;
// 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;
// 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;
// 0xE800:0xEFFF = User ROM / RFS
case 116 ... 119:
if (!isPhysical)
{
memType = MEMBANK_TYPE_ROM;
waitStates = 0;
tCycSync = true;
}
break;
// 0xF000:0xFFFF = Floppy ROM (MZ80AFI)
case 120 ... 127:
if (!isPhysical)
{
waitStates = 1;
tCycSync = true;
memType = MEMBANK_TYPE_ROM;
}
break;
// 0xF000:0xFFFF = Floppy ROM (MZ80AFI)
case 120 ... 127:
if (!isPhysical)
{
waitStates = 0;
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;
@@ -753,6 +856,7 @@ uint8_t MZ80A_Init(t_Z80CPU *cpu, t_FlashAppConfigHeader *appConfig, t_drvConfig
{
cpu->_z80PSRAM->memioPtr[idx] = (t_MemoryFunc) NULL;
}
memset(cpu->memioMap, 0, MEMIO_BITMAP_SIZE);
for (int idx = 0; idx < IO_PAGE_SIZE; idx++)
{
t_MemoryFunc ioPtr = (t_MemoryFunc) NULL;
@@ -830,6 +934,31 @@ uint8_t MZ80A_Init(t_Z80CPU *cpu, t_FlashAppConfigHeader *appConfig, t_drvConfig
cpu->_membankPtr[idx] = (MEMBANK_TYPE_PHYSICAL << 24) | (MZ80A_MEMBANK_0 << 16) | (idx * MEMORY_BLOCK_SIZE);
}
}
// Install MZ-80A MEMSW/MEMSWR handlers, overriding any installed by RFS_Init.
// The MZ-80A has hardware MEMSW support — in PHYSICAL mode the handler must also
// trigger the hardware latch via a physical bus cycle. RFS_Init installs its own
// MEMSW handlers for use on machines without hardware MEMSW (e.g. MZ-700).
mz80aIsPhysical = config->isPhysical;
mz80aMemSwitch = false;
MEMIO_SET(cpu, MEMSW, (t_MemoryFunc) MZ80A_IO_MEMSW);
MEMIO_SET(cpu, MEMSWR, (t_MemoryFunc) MZ80A_IO_MEMSWR);
/*
// Debug: dump memory attributes for key blocks to verify tCycSync and waitStates.
debugf("MZ80A_Init: Memory attributes (bank 0):\r\n");
for (int idx = 0; idx < MEMORY_PAGE_BLOCKS; idx++)
{
uint32_t mbp = cpu->_membankPtr[idx];
uint8_t ws = cpu->_memAttr[0][idx].waitStates;
bool ts = cpu->_memAttr[0][idx].tCycSync;
uint8_t type = mbp >> 24;
if (type != MEMBANK_TYPE_PHYSICAL || ws != 0 || ts)
{
debugf(" [%3d] %04X: type=%d ws=%d sync=%d mbp=%08X\r\n",
idx, idx * MEMORY_BLOCK_SIZE, type, ws, ts, mbp);
}
}
*/
}
}

View File

@@ -133,8 +133,8 @@ uint8_t MZ80AFI_Init(t_Z80CPU *cpu, t_drvIFConfig *config)
// 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;
MEMIO_SET(cpu, 0xF3FE, (t_MemoryFunc) MZ80AFI_IO_A10Toggle);
MEMIO_SET(cpu, 0xF3FF, (t_MemoryFunc) MZ80AFI_IO_A10Toggle);
}
}
@@ -160,9 +160,15 @@ uint8_t MZ80AFI_Reset(t_Z80CPU *cpu)
}
// Poll handler for the MZ80AFI Floppy Disk Interface.
uint8_t MZ80AFI_PollCB(t_Z80CPU *cpu)
//uint8_t MZ80AFI_PollCB(t_Z80CPU *cpu)
uint8_t __func_in_RAM(MZ80AFI_PollCB)(t_Z80CPU *cpu)
{
// Locals.
// Drain any pending WD1773 responses from the shared intercore queue.
// Guard avoids calling flash-resident SDK queue functions when idle.
if (mz80afiCtrl && (mz80afiCtrl->fdc.opState.loadPending || mz80afiCtrl->fdc.opState.writePending))
{
wd1773_processResponses(&mz80afiCtrl->fdc);
}
return (0);
}
@@ -200,7 +206,7 @@ uint8_t MZ80AFI_TaskProcessor(t_Z80CPU *cpu, enum Z80CPU_TASK_NAME task, char *p
// 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)
uint8_t __func_in_RAM(MZ80AFI_IO_A10Toggle)(t_Z80CPU *cpu, bool read, uint16_t addr, uint8_t data)
{
if (!read)
return data;
@@ -219,7 +225,7 @@ uint8_t MZ80AFI_IO_A10Toggle(t_Z80CPU *cpu, bool read, uint16_t addr, uint8_t da
// 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)
uint8_t __func_in_RAM(MZ80AFI_IO_WD1773)(t_Z80CPU *cpu, bool read, uint16_t addr, uint8_t data)
{
// Locals.
uint8_t result = 0;
@@ -233,10 +239,10 @@ uint8_t MZ80AFI_IO_WD1773(t_Z80CPU *cpu, bool read, uint16_t addr, uint8_t data)
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);
//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++;
}
}
@@ -253,7 +259,7 @@ uint8_t MZ80AFI_IO_WD1773(t_Z80CPU *cpu, bool read, uint16_t addr, uint8_t data)
return (result);
}
uint8_t MZ80AFI_IO_DriveSel(t_Z80CPU *cpu, bool read, uint16_t addr, uint8_t data)
uint8_t __func_in_RAM(MZ80AFI_IO_DriveSel)(t_Z80CPU *cpu, bool read, uint16_t addr, uint8_t data)
{
// Locals.
uint8_t result = 0;
@@ -270,7 +276,7 @@ uint8_t MZ80AFI_IO_DriveSel(t_Z80CPU *cpu, bool read, uint16_t addr, uint8_t dat
return (result);
}
uint8_t MZ80AFI_IO_SideSel(t_Z80CPU *cpu, bool read, uint16_t addr, uint8_t data)
uint8_t __func_in_RAM(MZ80AFI_IO_SideSel)(t_Z80CPU *cpu, bool read, uint16_t addr, uint8_t data)
{
// Locals.
uint8_t result = 0;
@@ -281,7 +287,7 @@ uint8_t MZ80AFI_IO_SideSel(t_Z80CPU *cpu, bool read, uint16_t addr, uint8_t data
}
// DDEN select (0 = MFM, 1 = FM)
uint8_t MZ80AFI_IO_DDENSel(t_Z80CPU *cpu, bool read, uint16_t addr, uint8_t data)
uint8_t __func_in_RAM(MZ80AFI_IO_DDENSel)(t_Z80CPU *cpu, bool read, uint16_t addr, uint8_t data)
{
// Locals.
uint8_t result = 0;

View File

@@ -34,7 +34,7 @@ static int qdDiagDataReadCount; // Reset in qdReset to ensure diagnostics afte
// SIO Control Write (ports 0xF6 / 0xF7)
// Channel A = data channel (spiral track)
// Channel B = motor/status channel
void qdSioWriteControl(t_QdDrive *qd, t_sioChannel *ch, uint8_t val, bool isChannelB)
void __not_in_flash_func(qdSioWriteControl)(t_QdDrive *qd, t_sioChannel *ch, uint8_t val, bool isChannelB)
{
// First write after reset = WR0 (command or pointer)
if (ch->pointer == 0)
@@ -240,7 +240,7 @@ void qdSioWriteControl(t_QdDrive *qd, t_sioChannel *ch, uint8_t val, bool isChan
void qdUpdateStatus(t_QdDrive *qd);
// SIO Control Read
uint8_t qdSioReadControl(t_QdDrive *qd, t_sioChannel *ch, bool isChannelB)
uint8_t __not_in_flash_func(qdSioReadControl)(t_QdDrive *qd, t_sioChannel *ch, bool isChannelB)
{
qdUpdateStatus(qd);
uint8_t reg = ch->pointer;
@@ -295,7 +295,7 @@ int qdGetDiskSize(t_QdDrive *qd, int machineType)
}
// Process intercore message response.
int qdProcessDeviceResponses(t_QdDrive *qd, queue_t *responseQueue)
int __not_in_flash_func(qdProcessDeviceResponses)(t_QdDrive *qd, queue_t *responseQueue)
{
int processedCount = 0;
int skipped = 0;
@@ -321,26 +321,26 @@ int qdProcessDeviceResponses(t_QdDrive *qd, queue_t *responseQueue)
switch (response.type)
{
case MSG_LOAD_COMPLETE:
qd->opState.loadPending = false;
qd->diskLoaded = response.response.success;
if (!response.response.success)
qdSetStatusFlag(qd, QD_CRC_ERROR_BIT);
qd->intrq = true;
break;
case MSG_WRITE_COMPLETE:
if (response.requestId == qd->opState.pendingWriteId)
{
qd->opState.writePending = false;
case MSG_LOAD_COMPLETE:
qd->opState.loadPending = false;
qd->diskLoaded = response.response.success;
if (!response.response.success)
qdSetStatusFlag(qd, QD_CRC_ERROR_BIT);
qd->intrq = true;
}
break;
break;
default:
break;
case MSG_WRITE_COMPLETE:
if (response.requestId == qd->opState.pendingWriteId)
{
qd->opState.writePending = false;
if (!response.response.success)
qdSetStatusFlag(qd, QD_CRC_ERROR_BIT);
qd->intrq = true;
}
break;
default:
break;
}
processedCount++;
}
@@ -348,13 +348,19 @@ int qdProcessDeviceResponses(t_QdDrive *qd, queue_t *responseQueue)
}
// Process all intercore message responses.
void qdProcessResponses(t_QdDrive *qd)
// Only call qdProcessDeviceResponses when a response is actually expected.
// This prevents non-deterministic timing from SDK queue functions (in flash)
// being called on the Z80 emulation hot path when no operations are pending.
void __not_in_flash_func(qdProcessResponses)(t_QdDrive *qd)
{
qdProcessDeviceResponses(qd, qd->responseQueue);
if (qd->opState.loadPending || qd->opState.writePending)
{
qdProcessDeviceResponses(qd, qd->responseQueue);
}
}
// Status Register (RR0/RR1) update.
void qdUpdateStatus(t_QdDrive *qd)
void __not_in_flash_func(qdUpdateStatus)(t_QdDrive *qd)
{
// Locals.
absolute_time_t now = get_absolute_time();
@@ -526,9 +532,10 @@ void qdReset(t_QdDrive *qd)
qd->lastRotation = 0;
qd->huntStartTime = get_absolute_time();
// Clear any pending async operations — stale responses will be harmlessly processed.
qd->opState.loadPending = false;
qd->opState.writePending = false;
// Preserve loadPending/writePending — in-flight intercore responses must still
// be consumed by qdProcessDeviceResponses. Clearing them here causes the guard
// to block queue processing, leaving responses (and those of other devices sharing
// the queue) unprocessed, eventually filling the queue and stalling Core 0.
}
// QD Initialisation.
@@ -611,7 +618,7 @@ void qdChangeDisk(t_QdDrive *qd, const char *newFilename, int diskNo)
}
// Set status flag.
void qdSetStatusFlag(t_QdDrive *qd, uint8_t flag)
void __not_in_flash_func(qdSetStatusFlag)(t_QdDrive *qd, uint8_t flag)
{
qd->statusFlags |= flag;
}
@@ -623,7 +630,7 @@ void qdClearStatusFlags(t_QdDrive *qd)
}
// SIO Data Read.
uint8_t qdRead(t_QdDrive *qd, uint8_t offset)
uint8_t __not_in_flash_func(qdRead)(t_QdDrive *qd, uint8_t offset)
{
uint8_t value = 0xFF;
@@ -737,7 +744,7 @@ uint8_t qdRead(t_QdDrive *qd, uint8_t offset)
}
// SIO Data port write.
void qdWrite(t_QdDrive *qd, uint8_t offset, uint8_t val)
void __not_in_flash_func(qdWrite)(t_QdDrive *qd, uint8_t offset, uint8_t val)
{
qdProcessResponses(qd);

View File

@@ -98,16 +98,45 @@ uint8_t RFS_Init(t_Z80CPU *cpu, t_drvIFConfig *config)
// Setup the initial state of the latch up-counter, used to enable access to the programmable registers.
rfsCtrl->upCntr = ((rfsCtrl->regCtrl & 0x20) >> 2) | ((rfsCtrl->regCtrl & 0x10) >> 2) | ((rfsCtrl->regCtrl & 0x08) >> 2);
// Install hardware one-shot timers on I/O ports 0x51, 0x52, 0x53 (all upper-byte variants).
// Timer delays = body-only T-states at 2MHz (excluding CALL/RET overhead
// which still executes at emulation speed).
// DLY3: NEG(8)+NEG(8)+LD(7)+JP(10)+42×(DEC(4)+JP(10))+RET(10) = 631T = 315µs
// DLY2: LD(7)+13×(DEC(4)+JP(10))+RET(10) = 199T = 100µs
// DLY1: LD(7)+14×(DEC(4)+JP(10))+RET(10) = 213T = 107µs
rfsCtrl->timer[0].delayUs = 315; // DLY3: 631 T-states = 315.5µs at 2MHz.
rfsCtrl->timer[0].running = false;
rfsCtrl->timer[1].delayUs = 100; // DLY2/L0760: 199 T-states = 99.5µs at 2MHz.
rfsCtrl->timer[1].running = false;
rfsCtrl->timer[2].delayUs = 107; // DLY1/L0759: 213 T-states = 106.5µs at 2MHz.
rfsCtrl->timer[2].running = false;
for (int hi = 0; hi < 256; hi++)
{
cpu->_z80PSRAM->ioPtr[(hi << 8) | RFS_TIMER0_PORT] = (t_MemoryFunc) RFS_IO_Timer0;
cpu->_z80PSRAM->ioPtr[(hi << 8) | RFS_TIMER1_PORT] = (t_MemoryFunc) RFS_IO_Timer1;
cpu->_z80PSRAM->ioPtr[(hi << 8) | RFS_TIMER2_PORT] = (t_MemoryFunc) RFS_IO_Timer2;
}
// Set waitStates=2 for the Monitor ROM (0x0000:0x0FFF) so each memory access
// syncs to T1 + waits 2 additional clocks = 3 T-states (correct for Z80 memory
// read M-cycles). Combined with the +1 M1 extra wait in fetchOpcode, this gives
// 4T per M1 cycle — matching native 2MHz Z80 bus timing exactly.
// This makes DLY3/DLY1/DLY2 delay loops deterministic for CMT tape timing.
// for (int idx = 0; idx < (0x1000 / MEMORY_BLOCK_SIZE); idx++)
// {
// cpu->_memAttr[0][idx].waitStates = 1;
// }
// 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[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;
MEMIO_SET(cpu, BNKCTRLRST, (t_MemoryFunc) RFS_IO_BNKCTRLRST);
MEMIO_SET(cpu, BNKCTRLDIS, (t_MemoryFunc) RFS_IO_BNKCTRLDIS);
MEMIO_SET(cpu, HWSPIDATA, (t_MemoryFunc) RFS_IO_HWSPIDATA);
MEMIO_SET(cpu, HWSPISTART, (t_MemoryFunc) RFS_IO_HWSPISTART);
MEMIO_SET(cpu, BNKSELMROM, (t_MemoryFunc) RFS_IO_BNKSELMROM);
MEMIO_SET(cpu, BNKSELUSER, (t_MemoryFunc) RFS_IO_BNKSELUSER);
MEMIO_SET(cpu, BNKCTRL, (t_MemoryFunc) RFS_IO_BNKCTRL);
MEMIO_SET(cpu, MEMSW, (t_MemoryFunc) RFS_IO_MEMSW);
MEMIO_SET(cpu, MEMSWR, (t_MemoryFunc) RFS_IO_MEMSWR);
// Invoke the reset handler as it sets the initial state of the RFS board.
RFS_Reset(cpu);
@@ -171,38 +200,39 @@ uint8_t RFS_Reset(t_Z80CPU *cpu)
bool tCycSync = true;
switch (idx)
{
// 0x0000:0x0FFF = Monitor ROM.
case 0 ... 7:
cpu->_membankPtr[idx] = (memType << 24) | (MROM_BANK << 16) | (idx * MEMORY_BLOCK_SIZE);
cpu->_memAttr[MROM_BANK][idx].waitStates = waitStates;
cpu->_memAttr[MROM_BANK][idx].tCycSync = tCycSync;
break;
// 0x0000:0x0FFF = Monitor ROM.
case 0 ... 7:
//cpu->_membankPtr[idx] = (memType << 24) | (MROM_BANK << 16) | (idx * MEMORY_BLOCK_SIZE);
cpu->_membankPtr[idx] = (MEMBANK_TYPE_RAM << 24) | (MROM_BANK << 16) | (idx * MEMORY_BLOCK_SIZE);
//cpu->_memAttr[MROM_BANK][idx].waitStates = waitStates;
cpu->_memAttr[MROM_BANK][idx].tCycSync = tCycSync;
break;
// 0x1000:0xCFFF = RAM
case 8 ... 103:
// Leave as defined in config, physical or virtual.
break;
// 0x1000:0xCFFF = RAM
case 8 ... 103:
// Leave as defined in config, physical or virtual.
break;
// 0xD000:0xDFFF = VRAM
case 104 ... 111:
// This region needs to be left physical as it controls underlying hardware.
break;
// 0xD000:0xDFFF = VRAM
case 104 ... 111:
// This region needs to be left physical as it controls underlying hardware.
break;
// 0xE000:0xE7FF = Physical IO
case 112 ... 115:
// This region needs to be left physical as it controls underlying hardware.
break;
// 0xE000:0xE7FF = Physical IO
case 112 ... 115:
// This region needs to be left physical as it controls underlying hardware.
break;
// 0xE800:0xEFFF = RFS
case 116 ... 119:
cpu->_membankPtr[idx] = (memType << 24) | (USER_ROM_I_BANK << 16) | (UROM_RFS_BANK_OFFSET + ((idx - 116) * MEMORY_BLOCK_SIZE));
cpu->_memAttr[USER_ROM_I_BANK][idx].waitStates = 0;
cpu->_memAttr[USER_ROM_I_BANK][idx].tCycSync = false;
break;
// 0xE800:0xEFFF = RFS
case 116 ... 119:
cpu->_membankPtr[idx] = (memType << 24) | (USER_ROM_I_BANK << 16) | (UROM_RFS_BANK_OFFSET + ((idx - 116) * MEMORY_BLOCK_SIZE));
cpu->_memAttr[USER_ROM_I_BANK][idx].waitStates = 0;
cpu->_memAttr[USER_ROM_I_BANK][idx].tCycSync = false;
break;
// 0xF000:0xFFFF = Empty
case 120 ... 127:
break;
// 0xF000:0xFFFF = Empty
case 120 ... 127:
break;
}
}
@@ -210,9 +240,15 @@ uint8_t RFS_Reset(t_Z80CPU *cpu)
}
// Poll handler for the RFS S-RAM Board.
uint8_t RFS_PollCB(t_Z80CPU *cpu)
//uint8_t RFS_PollCB(t_Z80CPU *cpu)
uint8_t __func_in_RAM(RFS_PollCB)(t_Z80CPU *cpu)
{
// Locals.
// Drain any pending RFS responses from the shared intercore queue.
// Guard avoids calling flash-resident SDK queue functions when idle.
if (rfsCtrl && rfsCtrl->loadPending)
{
RFS_ProcessQueueResponses(cpu);
}
return (0);
}
@@ -296,7 +332,7 @@ int RFS_ProcessQueueResponses(t_Z80CPU *cpu)
}
// Bank control reset, returns all registers to power up default.
uint8_t RFS_IO_BNKCTRLRST(t_Z80CPU *cpu, bool read, uint16_t addr, uint8_t data)
uint8_t __func_in_RAM(RFS_IO_BNKCTRLRST)(t_Z80CPU *cpu, bool read, uint16_t addr, uint8_t data)
{
// Locals.
uint8_t result = data;
@@ -316,7 +352,7 @@ uint8_t RFS_IO_BNKCTRLRST(t_Z80CPU *cpu, bool read, uint16_t addr, uint8_t data)
}
// Disable bank control registers by resetting the coded latch.
uint8_t RFS_IO_BNKCTRLDIS(t_Z80CPU *cpu, bool read, uint16_t addr, uint8_t data)
uint8_t __func_in_RAM(RFS_IO_BNKCTRLDIS)(t_Z80CPU *cpu, bool read, uint16_t addr, uint8_t data)
{
// Locals.
uint8_t result = 0;
@@ -340,7 +376,7 @@ uint8_t RFS_IO_BNKCTRLDIS(t_Z80CPU *cpu, bool read, uint16_t addr, uint8_t data)
}
// Hardware SPI Data register (read/write).
uint8_t RFS_IO_HWSPIDATA(t_Z80CPU *cpu, bool read, uint16_t addr, uint8_t data)
uint8_t __func_in_RAM(RFS_IO_HWSPIDATA)(t_Z80CPU *cpu, bool read, uint16_t addr, uint8_t data)
{
// Locals.
uint8_t result = data;
@@ -368,7 +404,7 @@ uint8_t RFS_IO_HWSPIDATA(t_Z80CPU *cpu, bool read, uint16_t addr, uint8_t data)
}
// Start an SPI transfer.
uint8_t RFS_IO_HWSPISTART(t_Z80CPU *cpu, bool read, uint16_t addr, uint8_t data)
uint8_t __func_in_RAM(RFS_IO_HWSPISTART)(t_Z80CPU *cpu, bool read, uint16_t addr, uint8_t data)
{
// Locals.
uint8_t result = data;
@@ -394,7 +430,7 @@ uint8_t RFS_IO_HWSPISTART(t_Z80CPU *cpu, bool read, uint16_t addr, uint8_t data)
}
// Select RFS Bank1 (MROM)
uint8_t RFS_IO_BNKSELMROM(t_Z80CPU *cpu, bool read, uint16_t addr, uint8_t data)
uint8_t __func_in_RAM(RFS_IO_BNKSELMROM)(t_Z80CPU *cpu, bool read, uint16_t addr, uint8_t data)
{
// Locals.
uint8_t result = 0;
@@ -436,7 +472,7 @@ uint8_t RFS_IO_BNKSELMROM(t_Z80CPU *cpu, bool read, uint16_t addr, uint8_t data)
// Monitor ROM is located at 0x0000 (+offset if MZ80A and memory switch invoked).
uint8_t bank = (uint8_t) (rfsCtrl->mromAddr / MEMORY_PAGE_SIZE) + MROM_BANK;
uint16_t bankAddr = (rfsCtrl->mromAddr & 0xF000) | (idx * MEMORY_BLOCK_SIZE);
cpu->_membankPtr[idx + memSwitchOffset] = (MEMBANK_TYPE_ROM << 24) | (bank << 16) | (bankAddr);
cpu->_membankPtr[idx + memSwitchOffset] = (MEMBANK_TYPE_RAM << 24) | (bank << 16) | (bankAddr);
cpu->_memAttr[bank][idx + memSwitchOffset].waitStates = 1;
cpu->_memAttr[bank][idx + memSwitchOffset].tCycSync = true;
}
@@ -447,7 +483,7 @@ uint8_t RFS_IO_BNKSELMROM(t_Z80CPU *cpu, bool read, uint16_t addr, uint8_t data)
}
// Select RFS Bank2 (User ROM)
uint8_t RFS_IO_BNKSELUSER(t_Z80CPU *cpu, bool read, uint16_t addr, uint8_t data)
uint8_t __func_in_RAM(RFS_IO_BNKSELUSER)(t_Z80CPU *cpu, bool read, uint16_t addr, uint8_t data)
{
// Locals.
uint8_t result = 0;
@@ -507,7 +543,7 @@ uint8_t RFS_IO_BNKSELUSER(t_Z80CPU *cpu, bool read, uint16_t addr, uint8_t data)
}
// Bank Control register (read/write).
uint8_t RFS_IO_BNKCTRL(t_Z80CPU *cpu, bool read, uint16_t addr, uint8_t data)
uint8_t __func_in_RAM(RFS_IO_BNKCTRL)(t_Z80CPU *cpu, bool read, uint16_t addr, uint8_t data)
{
// Locals.
uint8_t result = 0;
@@ -562,7 +598,7 @@ uint8_t RFS_IO_BNKCTRL(t_Z80CPU *cpu, bool read, uint16_t addr, uint8_t data)
}
// Function to perform a memory swap aka MZ-80A, where RAM at C000 is swapped to 0000 and ROM is swapped to C000.
uint8_t RFS_IO_MEMSW(t_Z80CPU *cpu, bool read, uint16_t addr, uint8_t data)
uint8_t __func_in_RAM(RFS_IO_MEMSW)(t_Z80CPU *cpu, bool read, uint16_t addr, uint8_t data)
{
// Locals.
uint8_t result = data;
@@ -581,7 +617,7 @@ uint8_t RFS_IO_MEMSW(t_Z80CPU *cpu, bool read, uint16_t addr, uint8_t data)
if (idx == 0)
{
// debugf("MEMSW: %08x -> %08x\r\n", cpu->_membankPtr[idx], cpu->_membankPtr[idx + offset]);
debugf("MEMSW: %08x -> %08x\r\n", cpu->_membankPtr[idx], cpu->_membankPtr[idx + offset]);
sleep_ms(1);
}
cpu->_membankPtr[idx] = cpu->_membankPtr[idx + offset];
@@ -597,7 +633,7 @@ uint8_t RFS_IO_MEMSW(t_Z80CPU *cpu, bool read, uint16_t addr, uint8_t data)
}
// Function to perform a memory swap reset aka MZ-80A, where RAM at 0000 is swapped to C000 and ROM is swapped to 0000.
uint8_t RFS_IO_MEMSWR(t_Z80CPU *cpu, bool read, uint16_t addr, uint8_t data)
uint8_t __func_in_RAM(RFS_IO_MEMSWR)(t_Z80CPU *cpu, bool read, uint16_t addr, uint8_t data)
{
// Locals.
uint8_t result = data;
@@ -616,7 +652,7 @@ uint8_t RFS_IO_MEMSWR(t_Z80CPU *cpu, bool read, uint16_t addr, uint8_t data)
if (idx == 0)
{
// debugf("MEMSWR: %08x -> %08x\r\n", cpu->_membankPtr[idx], cpu->_membankPtr[idx + offset]);
debugf("MEMSWR: %08x -> %08x\r\n", cpu->_membankPtr[idx], cpu->_membankPtr[idx + offset]);
sleep_ms(1);
}
cpu->_membankPtr[idx] = cpu->_membankPtr[idx + offset];
@@ -636,7 +672,7 @@ uint8_t RFS_IO_MEMSWR(t_Z80CPU *cpu, bool read, uint16_t addr, uint8_t data)
//
// All SD commands are added to the processor but logic (apart from a standard response) is only added for commands which RFS uses.
//
void rfsSDCard(t_Z80CPU *cpu)
void __func_in_RAM(rfsSDCard)(t_Z80CPU *cpu)
{
// Locals.
//
@@ -736,11 +772,11 @@ void rfsSDCard(t_Z80CPU *cpu)
//debugf("Writing %d bytes, CRC(%02x,%02x)\r\n", noBytes, rfsCtrl->sd.cmdBuf[SD_SECSIZE+2], rfsCtrl->sd.cmdBuf[SD_SECSIZE+3]);
// Write the sector to the SD card image.
debugf("SD WR LBA:%08x\r\n", rfsCtrl->sd.lbaAddr);
//debugf("SD WR LBA:%08x\r\n", rfsCtrl->sd.lbaAddr);
if (ESP_writeSector(SD_CARD_FILENAME, rfsCtrl->sd.lbaAddr * SD_SECSIZE, &rfsCtrl->sd.cmdBuf[1], SD_SECSIZE) == false)
{
sdSendResponse[0] = 0x06; // Write error.
debugf("SD WR FAIL LBA:%08x\r\n", rfsCtrl->sd.lbaAddr);
//debugf("SD WR FAIL LBA:%08x\r\n", rfsCtrl->sd.lbaAddr);
}
// Send response.
@@ -757,7 +793,7 @@ void rfsSDCard(t_Z80CPU *cpu)
{
rfsCtrl->sd.rcvCntr = 0;
debugf("SD CMD:%02x LBA:%02x%02x%02x%02x\r\n", rfsCtrl->sd.cmdBuf[0],rfsCtrl->sd.cmdBuf[1],rfsCtrl->sd.cmdBuf[2],rfsCtrl->sd.cmdBuf[3],rfsCtrl->sd.cmdBuf[4]);
//debugf("SD CMD:%02x LBA:%02x%02x%02x%02x\r\n", rfsCtrl->sd.cmdBuf[0],rfsCtrl->sd.cmdBuf[1],rfsCtrl->sd.cmdBuf[2],rfsCtrl->sd.cmdBuf[3],rfsCtrl->sd.cmdBuf[4]);
switch (rfsCtrl->sd.cmdBuf[0])
{
case CMD0: // GO_IDLE_STATE
@@ -853,7 +889,7 @@ void rfsSDCard(t_Z80CPU *cpu)
// Assemble response buffer including SD card sector.
memset(sdRcvResponse, 0, sizeof(sdRcvResponse));
int readBytes = ESP_readSector(SD_CARD_FILENAME, rfsCtrl->sd.lbaAddr * SD_SECSIZE, &sdRcvResponse[2], SD_SECSIZE);
debugf("SD RD LBA:%08x bytes:%d\r\n", rfsCtrl->sd.lbaAddr, readBytes);
//debugf("SD RD LBA:%08x bytes:%d\r\n", rfsCtrl->sd.lbaAddr, readBytes);
if (readBytes == SD_SECSIZE)
{
sdRcvResponse[0] = 0x00; // Response R1
@@ -972,3 +1008,59 @@ void rfsSDCard(t_Z80CPU *cpu)
return;
}
// ---------------------------------------------------------------------------
// RFS_IO_Timer0 / RFS_IO_Timer1 — One-shot microsecond timers.
//
// Write (OUT (C),A): Configure the delay. addr[15:8] = B (high byte),
// data = A (low byte). Combined 16-bit value = delay in µs.
//
// Read (IN A,(port)):
// - First read after expiry (or initial): starts the countdown, returns 0.
// - While counting: returns 0.
// - When expired: returns 1 and auto-resets (next read starts a new cycle).
//
// Used by sa1510.asm CMT routines:
// Port 0x51 (timer[0]): DLY3 — 317µs bit-sampling delay.
// Port 0x52 (timer[1]): L0760 — 101µs write timing delay.
// ---------------------------------------------------------------------------
static uint8_t __func_in_RAM(rfs_timer_handler)(int idx, bool read, uint16_t addr, uint8_t data)
{
if(read)
{
if (!rfsCtrl->timer[idx].running)
{
rfsCtrl->timer[idx].targetTime = time_us_64() + rfsCtrl->timer[idx].delayUs;
rfsCtrl->timer[idx].running = true;
return 0;
}
if (time_us_64() >= rfsCtrl->timer[idx].targetTime)
{
rfsCtrl->timer[idx].running = false;
return 1;
}
return 0;
}
else
{
debugf("Timer%d Write: %02x, %04x, %04x\r\n", idx, data, addr, rfsCtrl->timer[idx].delayUs);
rfsCtrl->timer[idx].delayUs = (addr & 0xFF00) | data;
rfsCtrl->timer[idx].running = false;
return 0;
}
}
uint8_t __func_in_RAM(RFS_IO_Timer0)(t_Z80CPU *cpu, bool read, uint16_t addr, uint8_t data)
{
return rfs_timer_handler(0, read, addr, data);
}
uint8_t __func_in_RAM(RFS_IO_Timer1)(t_Z80CPU *cpu, bool read, uint16_t addr, uint8_t data)
{
return rfs_timer_handler(1, read, addr, data);
}
uint8_t __func_in_RAM(RFS_IO_Timer2)(t_Z80CPU *cpu, bool read, uint16_t addr, uint8_t data)
{
return rfs_timer_handler(2, read, addr, data);
}

View File

@@ -151,7 +151,8 @@ uint8_t TZFS_Reset(t_Z80CPU *cpu)
}
// Poll handler for the TZFS S-RAM Board.
uint8_t TZFS_PollCB(t_Z80CPU *cpu)
//uint8_t TZFS_PollCB(t_Z80CPU *cpu)
uint8_t __func_in_RAM(TZFS_PollCB)(t_Z80CPU *cpu)
{
// Locals.

View File

@@ -412,7 +412,7 @@ void wd1773_changeDisk(t_WD1773 *wd, const char *name, int diskNo)
/**
* @brief Select head
*/
void wd1773_setHead(t_WD1773 *wd, uint8_t head)
void __not_in_flash_func(wd1773_setHead)(t_WD1773 *wd, uint8_t head)
{
wd->currentHead = head & 1;
}
@@ -420,7 +420,7 @@ void wd1773_setHead(t_WD1773 *wd, uint8_t head)
/**
* @brief Set density mode
*/
void wd1773_setDensity(t_WD1773 *wd, uint8_t single)
void __not_in_flash_func(wd1773_setDensity)(t_WD1773 *wd, uint8_t single)
{
wd->densityMfm = !single;
}
@@ -481,7 +481,7 @@ uint8_t wd1773_getStatus(t_WD1773 *wd)
/**
* @brief Read FDC register (Z80 sees inverted data)
*/
uint8_t wd1773_read(t_WD1773 *wd, uint8_t offset)
uint8_t __not_in_flash_func(wd1773_read)(t_WD1773 *wd, uint8_t offset)
{
wd1773_processResponses(wd);
uint8_t val = 0;
@@ -547,7 +547,7 @@ uint8_t wd1773_read(t_WD1773 *wd, uint8_t offset)
/**
* @brief Write FDC register (Z80 sends inverted data)
*/
void wd1773_write(t_WD1773 *wd, uint8_t offset, uint8_t val)
void __not_in_flash_func(wd1773_write)(t_WD1773 *wd, uint8_t offset, uint8_t val)
{
wd1773_processResponses(wd);
val = ~val;

View File

@@ -39,6 +39,7 @@
#include "hardware/dma.h"
#include "cJSON.h"
#include "ipc_protocol.h"
#include "flash_ram.h"
// Constants.
#define ESP_MAX_CMDBUF_LEN 128
@@ -71,7 +72,7 @@ typedef struct
bool ESP_init(void);
void ESP_deinit(void);
bool ESP_pollNop(char *cmdBuf, size_t cmdBufLen);
bool ESP_sendVersionInfo(void);
bool ESP_sendVersionInfo(const void *cpuPtr);
int ESP_readFloppyDiskFile(const char *fileName,
void *ctx,
void *cfg,

View File

@@ -69,9 +69,10 @@
// Peripheral and SPI settings
#define FSPI_PERIPHERAL spi1
// At 50MHz the SPI RX FIFO (4 bytes deep) can overflow when core-1 Z80/PSRAM
// At 50MHz the SPI RX FIFO (4 bytes deep) can overflow when core-1 Z80/PSRAM
// accesses stall the AHB crossbar, causing dropped bytes mid-transfer and CRC mismatches.
// If too many CRC errors are seen in the log, reduce the frequency.
// 25MHz is reliable at sys_clk <= 320MHz. If overclocking above 320MHz, reduce to
// 12MHz (48MHz clk_peri / 4 = 2.67µs per FIFO fill) to prevent AHB contention overflow.
#define FSPI_CLK_FREQ 25 * 1000 * 1000
#define FSPI_DATABITS_PER_XFER 8
#define FSPI_DATA_POLARITY SPI_CPOL_1

View File

@@ -280,12 +280,13 @@ typedef struct
{
uint8_t RAM[MEMORY_PAGE_BANKS * MEMORY_PAGE_SIZE];
uint32_t memPtr[MEMORY_PAGE_SIZE];
t_MemoryFunc memioPtr[MEMORY_PAGE_SIZE];
t_MemoryFunc memioPtr[MEMORY_PAGE_SIZE]; // Function pointer array in PSRAM (64K × 4 bytes).
t_MemoryFunc ioPtr[IO_PAGE_SIZE];
//uint8_t (*memioPtr[MEMORY_PAGE_SIZE])(bool read, uint16_t addr, uint8_t data);
//uint8_t (*ioPtr[IO_PAGE_SIZE])(uint16_t addr, uint8_t data);
} t_Z80PSRAM;
// Size of the SRAM bitmap for memioPtr: 1 bit per Z80 address = 64K/8 = 8KB.
#define MEMIO_BITMAP_SIZE (MEMORY_PAGE_SIZE / 8)
// Struct for string-value pair
typedef struct
{
@@ -398,14 +399,18 @@ struct Z80CPU
uint32_t _membankPtr[MEMORY_PAGE_BLOCKS]; // Fast RAM - quickest RAM which is RP2350's 'limited' main memory used for indexing CPU address to address in RAM array.
t_memAttr _memAttr[MEMORY_PAGE_BANKS][MEMORY_PAGE_BLOCKS]; // Fast RAM - attributes associated with each RAM block.
t_Z80PSRAM *_z80PSRAM; // PSRAM - Slower RAM as it is accessed via SPI, used for banked RAM and general storage.
uint8_t memioMap[MEMIO_BITMAP_SIZE]; // SRAM bitmap: 1 bit per Z80 addr, set if memioPtr[addr] != NULL.
t_drivers _drivers; // Handle to array of structures to specify active drivers and their configuration.
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.
bool refreshEnable; // Enable DRAM refresh during virtual memory fetches. Set via JSON config "z80refresh" 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.
uint32_t hostClkHz; // Measured host clock frequency in Hz (GPIO35, measured at boot).
uint32_t emulSpeedHz; // Effective emulation speed in Hz (computed from Z80 cycle counter).
volatile uint32_t emulLoopCount; // Incremented by Core 1 after each z80_run() call (32-bit, atomic on ARM).
// Pending task from Core 0 — processed by Core 1 in the emulation loop.
// Core 0 writes these, Core 1 reads and clears pendingTask.
@@ -414,6 +419,19 @@ struct Z80CPU
char pendingTaskParam[128];// Parameter string for the task.
};
// Bitmap helpers for memioMap — 1 bit per Z80 address in fast SRAM.
// Check before accessing memioPtr in PSRAM to avoid slow SPI reads on the hot path.
#define MEMIO_BIT_SET(cpu, addr) ((cpu)->memioMap[(addr) >> 3] |= (1 << ((addr) & 7)))
#define MEMIO_BIT_CLR(cpu, addr) ((cpu)->memioMap[(addr) >> 3] &= ~(1 << ((addr) & 7)))
#define MEMIO_BIT_TEST(cpu, addr) ((cpu)->memioMap[(addr) >> 3] & (1 << ((addr) & 7)))
// Set a memioPtr entry and update the SRAM bitmap.
#define MEMIO_SET(cpu, addr, func) do { \
(cpu)->_z80PSRAM->memioPtr[(addr)] = (t_MemoryFunc)(func); \
if ((func) != NULL) MEMIO_BIT_SET(cpu, addr); \
else MEMIO_BIT_CLR(cpu, addr); \
} while(0)
// Struct for double-value pair
typedef struct
{

View File

@@ -0,0 +1,71 @@
// -----------------------------------------------------------------------
// Name: PIT8253.h
// Created: April 2026
// Author(s): Philip Smart
// Description: Intel 8253 Programmable Interval Timer emulation.
// Standalone module usable by any Sharp MZ series driver.
// Supports all 6 counter modes, BCD/binary counting,
// counter latching, and LSB/MSB/LSB-then-MSB read/load modes.
//
// Copyright: (c) 2019-2026 Philip Smart <philip.smart@net2net.org>
//
// History: April 2026 - Extracted from MZ700.c / MZ80A.c into
// standalone reusable module.
// -----------------------------------------------------------------------
// 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.
// -----------------------------------------------------------------------
#ifndef PIT8253_H
#define PIT8253_H
#include <stdint.h>
#include <stdbool.h>
#include "pico/time.h"
#include "Z80CPU.h"
#define PIT8253_NUM_COUNTERS 3
// Individual counter state.
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_PIT8253Counter;
// PIT context — one instance per driver.
typedef struct
{
t_PIT8253Counter counter[PIT8253_NUM_COUNTERS];
uint32_t clockHz; // Clock input frequency (e.g., 895000 for MZ-700/MZ-80A).
bool initialized;
} t_PIT8253;
// Initialise a PIT context. Call once at driver init time.
// clockHz = input clock frequency in Hz (typically 895000 for Sharp MZ series).
void PIT8253_init(t_PIT8253 *pit, uint32_t clockHz);
// Reset a PIT context. Forces re-initialisation on next I/O access.
void PIT8253_reset(t_PIT8253 *pit);
// I/O handler. addr bits 1:0 select the port (0-2 = counter data, 3 = control).
// read = true for IN, false for OUT. data = byte written (ignored on read).
// Returns the byte read (or 0 for writes).
uint8_t PIT8253_IO(t_PIT8253 *pit, bool read, uint8_t port, uint8_t data);
#endif // PIT8253_H

View File

@@ -0,0 +1,123 @@
// -----------------------------------------------------------------------
// Name: PPI8255.h
// Created: April 2026
// Author(s): Philip Smart
// Description: Intel 8255 Programmable Peripheral Interface emulation.
// Standalone module usable by any Sharp MZ series driver.
//
// Supports:
// - Mode 0 (simple I/O) for all ports
// - Modes 1 & 2 (strobed/bidirectional) — stubbed
// - Bit Set/Reset (BSR) for Port C
// - Independent direction control for PA, PB, PCU, PCL
// - Per-bit callbacks for output changes
// - Per-port input injection for reads
//
// Copyright: (c) 2019-2026 Philip Smart <philip.smart@net2net.org>
//
// History: April 2026 - New standalone reusable module.
// -----------------------------------------------------------------------
// 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.
// -----------------------------------------------------------------------
#ifndef PPI8255_H
#define PPI8255_H
#include <stdint.h>
#include <stdbool.h>
// Port identifiers.
typedef enum
{
PPI_PORT_A = 0,
PPI_PORT_B = 1,
PPI_PORT_C = 2,
PPI_CONTROL = 3
} t_PPIPort;
// Callback for output changes.
// port = PPI_PORT_A / B / C
// value = new 8-bit output value for the port
// bit = specific bit that changed (0-7), or -1 if full-port write
// state = bit value (0 or 1) for BSR, or -1 for full-port write
// ctx = user context pointer registered at init time
typedef void (*t_PPIOutputCallback)(t_PPIPort port, uint8_t value, int bit, int state, void *ctx);
// Callback for input reads.
// port = PPI_PORT_A / B / C
// ctx = user context pointer
// Returns the 8-bit value to present on the port.
typedef uint8_t (*t_PPIInputCallback)(t_PPIPort port, void *ctx);
// PPI context — one instance per emulated 8255.
typedef struct
{
// Latched output values.
uint8_t portA;
uint8_t portB;
uint8_t portC;
// Control word (last I/O mode control word written with D7=1).
uint8_t controlWord;
// Direction flags derived from control word.
// true = input, false = output.
bool portAInput; // Port A direction.
bool portBInput; // Port B direction.
bool portCUpperInput; // PC7-PC4 direction.
bool portCLowerInput; // PC3-PC0 direction.
// Group modes (from control word).
uint8_t groupAMode; // 0, 1, or 2.
uint8_t groupBMode; // 0 or 1.
// Callbacks.
t_PPIOutputCallback outputCallback; // Called when an output port changes.
t_PPIInputCallback inputCallbackA; // Called when Port A is read (if input).
t_PPIInputCallback inputCallbackB; // Called when Port B is read (if input).
t_PPIInputCallback inputCallbackC; // Called when Port C is read (if input).
void *callbackCtx; // User context passed to all callbacks.
bool initialized;
} t_PPI8255;
// -----------------------------------------------------------------------
// API
// -----------------------------------------------------------------------
// Initialise a PPI context. All ports default to Mode 0 input, values = 0.
void PPI8255_init(t_PPI8255 *ppi);
// Reset a PPI context. Clears all ports and sets Mode 0 all-input.
void PPI8255_reset(t_PPI8255 *ppi);
// Register the output callback (called whenever an output port value changes).
void PPI8255_setOutputCallback(t_PPI8255 *ppi, t_PPIOutputCallback cb, void *ctx);
// Register per-port input callbacks (called when a port configured as input is read).
void PPI8255_setInputCallback(t_PPI8255 *ppi, t_PPIPort port, t_PPIInputCallback cb);
// I/O handler. port = 0 (PA), 1 (PB), 2 (PC), 3 (Control).
// read = true for read, false for write. data = byte written (ignored on read).
// Returns the byte read (or 0 for writes).
uint8_t PPI8255_IO(t_PPI8255 *ppi, bool read, uint8_t port, uint8_t data);
// Inject a value into an input port (for use by the driver to provide external state).
// Only affects ports configured as input. For Port C, the value is masked by
// the input bits (upper/lower nibble direction).
void PPI8255_injectInput(t_PPI8255 *ppi, t_PPIPort port, uint8_t value);
// Read the current output latch value of a port (for use by the driver to
// query what the Z80 last wrote). For Port C, returns the combined
// output bits with input bits masked to 0.
uint8_t PPI8255_getOutput(t_PPI8255 *ppi, t_PPIPort port);
// Read the current state of a specific Port C bit (0-7).
// Returns the output latch bit for output-direction bits, or the injected
// input bit for input-direction bits.
uint8_t PPI8255_getPortCBit(t_PPI8255 *ppi, uint8_t bit);
#endif // PPI8255_H

View File

@@ -43,6 +43,8 @@ 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_IO_MEMSW(t_Z80CPU *cpu, bool read, uint16_t addr, uint8_t data);
uint8_t MZ80A_IO_MEMSWR(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);

View File

@@ -92,6 +92,18 @@ extern "C"
#define BNKCTRLDEF BBMOSI + SDCS + BBCLK // Default on startup for the Bank Control register.
// picoZ80 hardware microsecond timer — I/O port accessible from Z80.
// Write: two bytes (low then high) = delay in microseconds, starts on high-byte write.
// Read: returns 0 while counting, 1 when expired.
// Provides CPU-speed-independent delays for CMT tape timing etc.
// picoZ80 hardware one-shot timers — two I/O ports for CMT tape timing.
// Write (OUT (C),A): sets delay in µs. addr[15:8]=B (high byte), data=A (low byte).
// Read (IN A,(port)): first read starts countdown & returns 0.
// subsequent reads return 0 while counting, 1 when expired (auto-resets).
#define RFS_TIMER0_PORT 0x51 // DLY3 timer (~324µs for CMT bit sampling)
#define RFS_TIMER1_PORT 0x52 // DLY2/L0760 timer (~101µs for CMT write timing)
#define RFS_TIMER2_PORT 0x53 // DLY1/L0759 timer (~107µs for CMT timing)
// RFS Board ROM rom load and size definitions.
#define ROM_MROM_LOAD_ADDR 0x000000
#define ROM_USER_I_LOAD_ADDR 0x080000
@@ -170,6 +182,11 @@ extern "C"
uint8_t memSwitch; // MZ-80A can relocate the lower 4K ROM by swapping RAM at 0xC000.
bool loadPending; // A queued read is pending receipt when true.
t_SDCtrl sd; // SD Control.
struct {
uint16_t delayUs; // Configured delay in microseconds.
uint64_t targetTime; // Absolute target time (time_us_64).
bool running; // True while countdown is active.
} timer[3]; // timer[0]=port 0x51, timer[1]=port 0x52, timer[2]=port 0x53.
} t_RFSCtrl;
// Public prototypes.
@@ -189,6 +206,9 @@ extern "C"
uint8_t RFS_IO_BNKCTRL(t_Z80CPU *cpu, bool read, uint16_t addr, uint8_t data);
uint8_t RFS_IO_MEMSW(t_Z80CPU *cpu, bool read, uint16_t addr, uint8_t data);
uint8_t RFS_IO_MEMSWR(t_Z80CPU *cpu, bool read, uint16_t addr, uint8_t data);
uint8_t RFS_IO_Timer0(t_Z80CPU *cpu, bool read, uint16_t addr, uint8_t data);
uint8_t RFS_IO_Timer1(t_Z80CPU *cpu, bool read, uint16_t addr, uint8_t data);
uint8_t RFS_IO_Timer2(t_Z80CPU *cpu, bool read, uint16_t addr, uint8_t data);
void rfsSDCard(t_Z80CPU *cpu);

View File

@@ -153,7 +153,11 @@ typedef struct
t_FlashPartitionInstance config[FLASH_APP_MAX_INSTANCES];
} t_FlashPartitionHeader;
// Extended INF payload — partition header + runtime core config.
// Maximum size of the active driver summary string sent via INF IPC.
// Format: "DriverName(V/P) InterfaceName(V/P) ..." e.g. "MZ700(V) RFS(V)"
#define FLASHHDR_DRIVER_SUMMARY_SIZE 128
// Extended INF payload — partition header + runtime core config + active drivers.
// Sent by RP2350 ESP_sendVersionInfo, received by ESP32 storeRP2350Info.
typedef struct
{
@@ -163,6 +167,9 @@ typedef struct
int32_t voltage; // Core voltage setting (VREG enum)
uint32_t flashSize; // RP2350 Flash size in bytes (from PICO_FLASH_SIZE_BYTES)
uint32_t psramSize; // RP2350 PSRAM size in bytes (from psram_init)
uint32_t hostClkHz; // Measured host clock frequency in Hz (GPIO35 at boot)
uint32_t emulSpeedHz; // Effective Z80 emulation speed in Hz
char driverSummary[FLASHHDR_DRIVER_SUMMARY_SIZE]; // Active drivers and interfaces (human-readable)
} t_FlashInfoPayload;
// Structure to describe a single file stored in ROM. Indexed by its filename (matching a filename appearing in JSON including path)

View File

@@ -34,6 +34,7 @@
#define PSRAM_LOCATION _u(0x11000000)
extern void psram_set_qmi_timing();
extern void psram_set_flash_timing_for_freq(uint32_t target_sys_hz);
extern void psram_timing(int freq);
extern size_t psram_init(uint cs_pin, int freq);

View File

@@ -759,7 +759,7 @@ void processSPICommands(const char *cmd)
if (strcmp(cmd, "INF") == 0)
{
// Send the version information to the ESP32.
ESP_sendVersionInfo();
ESP_sendVersionInfo(z80CPU);
}
// Hardware RESET, HRST
@@ -1376,14 +1376,42 @@ int main(void)
if (flashAppConfigHeader->s.configured == FLASH_APP_CONFIGURED_FLAG && flashAppConfigHeader->s.voltage != 0)
voltage = flashAppConfigHeader->s.voltage;
// Guard against psramFreq == 0 from uninitialized or legacy config data.
if (psramFreq <= 0)
psramFreq = 133000000;
// Setup runtime Voltage / Frequency. Overclock to maximise emulation accuracy.
//voltage = VREG_VOLTAGE_1_25;
//cpuFreq = 300000000;
//psramFreq = 109000000;
bootStage(BOOTP_CLK_SET);
vreg_disable_voltage_limit(); // Allow voltages above 1.30V for overclocking
vreg_set_voltage(voltage);
sleep_ms(5);
clkInit = set_sys_clock_hz(cpuFreq, false);
// Pre-set flash QMI timing for the target CPU frequency BEFORE raising the clock.
// set_sys_clock_hz() does NOT update QMI M0 timing — without this, the flash SPI
// clock exceeds 133MHz spec when sys_clk goes above ~266MHz, causing a hang.
psram_set_flash_timing_for_freq(cpuFreq);
// The PLL can only produce frequencies that are VCO/(postdiv1*postdiv2) where
// VCO is 750-1600 MHz and postdiv values are 1-7. Not all frequencies are
// achievable. Search downward in 1 MHz steps from the requested frequency
// to find the nearest one the PLL can lock to.
clkInit = false;
{
uint32_t tryFreq = cpuFreq;
uint32_t minFreq = (cpuFreq > 10000000) ? cpuFreq - 10000000 : 50000000;
while (tryFreq >= minFreq)
{
if (set_sys_clock_hz(tryFreq, false))
{
psram_set_qmi_timing(); // Optimize flash M0 timing for actual clock — runs from RAM
clkInit = true;
cpuFreq = tryFreq;
break;
}
tryFreq -= 1000000; // Step down 1 MHz
}
}
// clkInit result checked later after debug is online.
// Initialise the PSRAM - all heap space is stored in PSRAM (see main_memmap.ld).
@@ -1428,9 +1456,75 @@ int main(void)
// Delayed message as debug was not online.
if (!clkInit)
{
// Fallback to safe 133 MHz
// No achievable frequency found within 10 MHz of the requested value.
set_sys_clock_khz(133000, true);
debugf("Setting cpufreq to %dKHz failed, defaulting to 133MHz.\r\n", cpuFreq / 1000);
debugf("Setting cpufreq to %dMHz failed (no PLL solution within 10MHz), defaulting to 133MHz.\r\n", cpuFreq / 1000000);
}
else
{
debugf("CPU clock set to %lu MHz (actual %lu Hz)\r\n",
(unsigned long)(cpuFreq / 1000000), (unsigned long)clock_get_hz(clk_sys));
}
// Measure the host clock frequency on GPIO 35 before PIO takes over the pin.
// Uses the PWM counter in input mode — the hardware counts rising edges on the
// GPIO pin while we time a 100ms window. Accurate and reliable even at MHz rates.
uint32_t hostClkHz = 0;
{
const uint HOST_CLK_PIN = 35;
const uint32_t MEASURE_US = 100000; // 100ms measurement window
uint slice = pwm_gpio_to_slice_num(HOST_CLK_PIN);
uint chan = pwm_gpio_to_channel(HOST_CLK_PIN);
// Configure GPIO as PWM input (B channel counts rising edges).
gpio_set_function(HOST_CLK_PIN, GPIO_FUNC_PWM);
pwm_config cfg = pwm_get_default_config();
pwm_config_set_clkdiv_mode(&cfg, PWM_DIV_B_RISING); // Count rising edges on channel B
pwm_config_set_clkdiv(&cfg, 1); // No clock divider
pwm_init(slice, &cfg, false); // Don't start yet
// Clear counter then run for the measurement window.
pwm_set_counter(slice, 0);
pwm_set_enabled(slice, true);
uint64_t startUs = time_us_64();
while ((time_us_64() - startUs) < MEASURE_US)
tight_loop_contents();
pwm_set_enabled(slice, false);
uint16_t count = pwm_get_counter(slice);
// PWM counter is 16-bit (max 65535). Use a 10ms window for good accuracy.
// At 3.5 MHz that's ~35,468 edges (within range). At 6.5 MHz (max for 16-bit
// in 10ms) it would be ~65,000. For clocks > 6.5 MHz use 1ms window instead.
uint32_t windowUs = 10000; // 10ms default
pwm_set_counter(slice, 0);
pwm_set_enabled(slice, true);
startUs = time_us_64();
while ((time_us_64() - startUs) < windowUs)
tight_loop_contents();
pwm_set_enabled(slice, false);
count = pwm_get_counter(slice);
// If counter is near overflow, reduce window and re-measure.
if (count > 60000)
{
windowUs = 1000; // 1ms window for higher frequencies
pwm_set_counter(slice, 0);
pwm_set_enabled(slice, true);
startUs = time_us_64();
while ((time_us_64() - startUs) < windowUs)
tight_loop_contents();
pwm_set_enabled(slice, false);
count = pwm_get_counter(slice);
}
hostClkHz = (uint32_t)count * (1000000 / windowUs);
debugf("Host clock (GPIO35): %u edges in %lums = %lu.%03lu MHz\r\n",
(unsigned)count, (unsigned long)(windowUs / 1000),
(unsigned long)(hostClkHz / 1000000),
(unsigned long)((hostClkHz % 1000000) / 1000));
// Release the pin — PIO will reconfigure it.
gpio_deinit(HOST_CLK_PIN);
}
// Initialise PIO to perform low level CPU interface.
@@ -1441,6 +1535,8 @@ int main(void)
// Initialise the Z80 CPU.
bootStage(BOOTP_Z80_INIT);
z80CPU = Z80CPU_init();
if (z80CPU)
z80CPU->hostClkHz = hostClkHz;
// Z80CPU result checked below.
if (pioInitResult || // PIO not initialised?
@@ -1748,11 +1844,31 @@ int main(void)
bootStage(BOOTP_ML_SPI_CMD);
processSPICommands(spiCmd);
}
if (lastInfoMs == 0 && nowMs >= 5000)
if (nowMs - lastInfoMs >= 5000)
{
bootStage(BOOTP_ML_INF);
// Compute effective Z80 emulation speed from the loop counter.
// emulLoopCount is a 32-bit counter incremented on Core 1 after each
// z80_run(64) call — a single aligned 32-bit read is atomic on ARM.
// Each loop iteration executes ~64 Z80 T-states.
if (z80CPU)
{
static uint32_t prevLoops = 0;
static uint32_t prevMs = 0;
uint32_t curLoops = z80CPU->emulLoopCount;
if (prevMs > 0 && nowMs > prevMs)
{
uint32_t deltaLoops = curLoops - prevLoops; // handles 32-bit wrap
uint32_t deltaMs = nowMs - prevMs;
z80CPU->emulSpeedHz = (uint32_t)((uint64_t)deltaLoops * 64ULL * 1000ULL / deltaMs);
}
prevLoops = curLoops;
prevMs = nowMs;
}
lastInfoMs = nowMs;
ESP_sendVersionInfo();
ESP_sendVersionInfo(z80CPU);
}
}

View File

@@ -1 +1 @@
2.083
2.221

View File

@@ -47,12 +47,16 @@ void __no_inline_not_in_flash_func(psram_set_qmi_timing)()
break;
}
// Use the minimum divisor assuming a 133MHz flash.
// RX delay equal to the divisor means sampling at the same time as the next falling edge of SCK after the
// falling edge that generated the data. This is pretty tight at 133MHz but seems to work with the Winbond flash chips.
const int max_flash_freq = 133000000;
const int divisor = (clock_get_hz(clk_sys) + max_flash_freq - 1) / max_flash_freq;
const int rxdelay = divisor;
// At sys_clk <= 320MHz, use the full 133MHz flash spec (divisor=3, SCK=106.7MHz,
// rxdelay=3 — sample point at the SCK period boundary, safe with PCB round-trip
// margin). Above 320MHz, the shorter system-clock period makes rxdelay=divisor
// marginal at divisor=3, so fall back to 100MHz limit (divisor=4, more headroom).
const int clock_hz = clock_get_hz(clk_sys);
const int max_flash_freq = (clock_hz <= 320000000) ? 133000000 : 100000000;
const int divisor = (clock_hz + max_flash_freq - 1) / max_flash_freq;
int rxdelay = divisor;
if (rxdelay > 7)
rxdelay = 7;
qmi_hw->m[0].timing = (1 << QMI_M0_TIMING_COOLDOWN_LSB) | (rxdelay << QMI_M0_TIMING_RXDELAY_LSB) | (divisor << QMI_M0_TIMING_CLKDIV_LSB);
// Force a read through XIP to ensure the timing is applied
@@ -60,6 +64,40 @@ void __no_inline_not_in_flash_func(psram_set_qmi_timing)()
(void) *ptr;
}
void __no_inline_not_in_flash_func(psram_set_flash_timing_for_freq)(uint32_t target_sys_hz)
{
// Pre-set flash QMI M0 timing conservatively for a target system clock frequency.
// Called BEFORE set_sys_clock_hz() — the timing must be safe at BOTH the current
// boot frequency AND the target frequency, because set_sys_clock_pll() runs from
// flash and the clock transitions mid-function.
//
// Uses 100MHz max flash SCK so the divisor is high enough to keep flash safe
// during the entire transition. psram_set_qmi_timing() is called immediately
// AFTER set_sys_clock_hz() to apply the final optimized timing.
int timeout = 10000;
while ((ioqspi_hw->io[1].status & IO_QSPI_GPIO_QSPI_SS_STATUS_OUTTOPAD_BITS) != IO_QSPI_GPIO_QSPI_SS_STATUS_OUTTOPAD_BITS)
{
timeout--;
if (!timeout)
break;
}
// Conservative 100MHz limit ensures safe flash access at all points during the
// clock transition (boot freq → 48MHz USB PLL → target freq).
const int max_flash_freq = 100000000;
const uint32_t current_hz = clock_get_hz(clk_sys);
const uint32_t max_hz = (target_sys_hz > current_hz) ? target_sys_hz : current_hz;
const int divisor = (max_hz + max_flash_freq - 1) / max_flash_freq;
int rxdelay = divisor;
if (rxdelay > 7)
rxdelay = 7;
qmi_hw->m[0].timing = (1 << QMI_M0_TIMING_COOLDOWN_LSB) | (rxdelay << QMI_M0_TIMING_RXDELAY_LSB) | (divisor << QMI_M0_TIMING_CLKDIV_LSB);
// Force a read through XIP to ensure the timing is applied.
volatile uint32_t *ptr = (volatile uint32_t *) 0x14000000;
(void) *ptr;
}
// Busy-wait helper with timeout. Returns true if condition cleared, false on timeout.
// Timeout is in iterations (~10ns each at 150MHz), not wall-clock.
#define QMI_BUSY_TIMEOUT 100000
@@ -201,9 +239,9 @@ void __no_inline_not_in_flash_func(psram_timing)(int freq)
//
// Using an rxdelay equal to the divisor isn't enough when running the APS6404 close to 133MHz.
// So: don't allow running at divisor 1 above 100MHz (because delay of 2 would be too late),
// and add an extra 1 to the rxdelay if the divided clock is > 100MHz (i.e. sys clock > 200MHz).
//const int max_psram_freq = 133000000;
//const int max_psram_freq = 100000000;
// and add an extra 1 to the rxdelay if the divided clock is >= 100MHz (i.e. sys clock >= 200MHz).
if (freq <= 0)
freq = 133000000;
const int max_psram_freq = freq;
const int clock_hz = clock_get_hz(clk_sys);
int divisor = (clock_hz + max_psram_freq - 1) / max_psram_freq;
@@ -212,15 +250,19 @@ void __no_inline_not_in_flash_func(psram_timing)(int freq)
divisor = 2;
}
int rxdelay = divisor;
if (clock_hz / divisor > 100000000)
if (clock_hz / divisor >= 100000000)
{
rxdelay += 1;
}
if (rxdelay > 7)
rxdelay = 7;
// - Max select must be <= 8us. The value is given in multiples of 64 system clocks.
// - Min deselect must be >= 18ns. The value is given in system clock cycles - ceil(divisor / 2).
const int clock_period_fs = 1000000000000000ll / clock_hz;
const int maxSelect = (125 * 1000000) / clock_period_fs; // 125 = 8000ns / 64
int maxSelect = (125 * 1000000) / clock_period_fs; // 125 = 8000ns / 64
if (maxSelect > 63)
maxSelect = 63;
const int minDeselect = (18 * 1000000 + (clock_period_fs - 1)) / clock_period_fs - (divisor + 1) / 2;
qmi_hw->m[1].timing = 1 << QMI_M1_TIMING_COOLDOWN_LSB | QMI_M1_TIMING_PAGEBREAK_VALUE_1024 << QMI_M1_TIMING_PAGEBREAK_LSB |

View File

@@ -207,13 +207,12 @@ cycle_nowait:
out exec, 16
jmp cycle_nowait ; Loop until jmp start executed.
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
wait 0 gpio Z80_PIN_CLK [3] ; 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
.wrap

View File

@@ -1 +1 @@
2.079
2.208