Updates for the MZ80A version and ESP32 NCM
This commit is contained in:
32
projects/tzpuPico/esp32/dependencies.lock
vendored
32
projects/tzpuPico/esp32/dependencies.lock
vendored
@@ -1,4 +1,33 @@
|
||||
dependencies:
|
||||
espressif/esp_tinyusb:
|
||||
component_hash: 6a50305bc61c7a361da8c0833642be824e92dacb0a6001719a832a4e96e471bf
|
||||
dependencies:
|
||||
- name: idf
|
||||
require: private
|
||||
version: '>=5.0'
|
||||
- name: espressif/tinyusb
|
||||
registry_url: https://components.espressif.com
|
||||
require: public
|
||||
version: '>=0.14.2'
|
||||
source:
|
||||
registry_url: https://components.espressif.com/
|
||||
type: service
|
||||
version: 1.7.6~2
|
||||
espressif/tinyusb:
|
||||
component_hash: 5ea9d3b6d6b0734a0a0b3491967aa0e1bece2974132294dbda5dd2839b247bfa
|
||||
dependencies:
|
||||
- name: idf
|
||||
require: private
|
||||
version: '>=5.0'
|
||||
source:
|
||||
registry_url: https://components.espressif.com
|
||||
type: service
|
||||
targets:
|
||||
- esp32s2
|
||||
- esp32s3
|
||||
- esp32p4
|
||||
- esp32h4
|
||||
version: 0.19.0~2
|
||||
espressif/zlib:
|
||||
component_hash: 999ec50086ac1c82b8321d8f540dc9fd10f5622948b935558aa16b4b66e95d9d
|
||||
dependencies:
|
||||
@@ -14,8 +43,9 @@ dependencies:
|
||||
type: idf
|
||||
version: 5.4.0
|
||||
direct_dependencies:
|
||||
- espressif/esp_tinyusb
|
||||
- espressif/zlib
|
||||
- idf
|
||||
manifest_hash: 2f0b66dbb85a50adc3a11060f7e5fbd3b793495f9be963bfb1fd35580274858d
|
||||
manifest_hash: 8ed9abdcca0db125fb3f8ee86dea821c12c99daf5033378c2158023f4a6971c7
|
||||
target: esp32s3
|
||||
version: 2.0.0
|
||||
|
||||
2
projects/tzpuPico/esp32/filepack_version.txt
vendored
2
projects/tzpuPico/esp32/filepack_version.txt
vendored
@@ -1 +1 @@
|
||||
2.03
|
||||
2.07
|
||||
|
||||
47
projects/tzpuPico/esp32/main/Kconfig.projbuild
vendored
47
projects/tzpuPico/esp32/main/Kconfig.projbuild
vendored
@@ -139,21 +139,23 @@ menu "pZ80 Configuration"
|
||||
|
||||
config IF_WIFI_ENABLED
|
||||
bool "Enable WiFi connectivity"
|
||||
default false
|
||||
default true
|
||||
help
|
||||
Allow interface to act as an Access Point to allow external connectivity. Once connected the WiFi is intended to be used for making
|
||||
key mapping changes.
|
||||
Note: Using WiFi on a custom board with the ESP32-S3-PICO-1 module requires FCC/RED certification.
|
||||
If certification has not been obtained, disable this option and use USB NCM instead.
|
||||
|
||||
config IF_WIFI_SSID
|
||||
string "Default SSID in Access Point Mode"
|
||||
default "pZ80"
|
||||
default "pZ80"
|
||||
depends on IF_WIFI_ENABLED
|
||||
help
|
||||
The SSID broadcast whilst the sharpkey module advertises wireless connectivity.
|
||||
|
||||
config IF_WIFI_DEFAULT_SSID_PWD
|
||||
string "Default password for initial connection to Access Point Mode"
|
||||
default "pZ80pZ80"
|
||||
default "pZ80pZ80"
|
||||
depends on IF_WIFI_ENABLED
|
||||
help
|
||||
The initial password needed to connect and logon to access point.
|
||||
@@ -161,7 +163,7 @@ menu "pZ80 Configuration"
|
||||
config IF_WIFI_MAX_RETRIES
|
||||
int "Maximum number of connection retries."
|
||||
range 0 100
|
||||
default 10
|
||||
default 10
|
||||
depends on IF_WIFI_ENABLED
|
||||
help
|
||||
Number of retries allowed for making a wireless connection with a client.
|
||||
@@ -169,15 +171,15 @@ menu "pZ80 Configuration"
|
||||
config IF_WIFI_AP_CHANNEL
|
||||
int "Channel of the Access Point."
|
||||
range 0 13
|
||||
default 7
|
||||
default 7
|
||||
depends on IF_WIFI_ENABLED
|
||||
help
|
||||
Channel use by the Access Point, default is 7.
|
||||
|
||||
|
||||
config IF_WIFI_SSID_HIDDEN
|
||||
int "Broadcast SSID?"
|
||||
range 0 1
|
||||
default 0
|
||||
default 0
|
||||
depends on IF_WIFI_ENABLED
|
||||
help
|
||||
Broadcast the SSID (0) or hide it (1).
|
||||
@@ -185,12 +187,41 @@ menu "pZ80 Configuration"
|
||||
config IF_WIFI_MAX_CONNECTIONS
|
||||
int "Maximum simultaneous connections."
|
||||
range 0 20
|
||||
default 5
|
||||
default 5
|
||||
depends on IF_WIFI_ENABLED
|
||||
help
|
||||
Maximum number of simultaneous open connections supported.
|
||||
endmenu
|
||||
|
||||
menu "USB NCM Network"
|
||||
|
||||
config IF_USB_NCM_ENABLED
|
||||
bool "Enable USB NCM network interface"
|
||||
default true
|
||||
help
|
||||
Present a USB Ethernet (CDC-NCM) adapter on the ESP32 USB OTG port (GPIO 19/20).
|
||||
The host PC sees a network adapter and can browse to the configured IP address
|
||||
for status, configuration and firmware updates.
|
||||
This provides the same browser-based interface as WiFi but over USB, and does not
|
||||
require FCC/RED certification as no intentional RF emission is involved.
|
||||
|
||||
config IF_USB_NCM_IP
|
||||
string "USB NCM interface IP address"
|
||||
default "192.168.7.1"
|
||||
depends on IF_USB_NCM_ENABLED
|
||||
help
|
||||
IP address of the ESP32 on the USB NCM network. The host PC will access the
|
||||
configuration pages at this address (e.g. http://192.168.7.1).
|
||||
|
||||
config IF_USB_NCM_NETMASK
|
||||
string "USB NCM interface netmask"
|
||||
default "255.255.255.0"
|
||||
depends on IF_USB_NCM_ENABLED
|
||||
help
|
||||
Netmask for the USB NCM network interface.
|
||||
|
||||
endmenu
|
||||
|
||||
choice LOGGING_OPTIONS
|
||||
prompt "Logging Output Mode"
|
||||
default USE_RP2350_OUTPUT
|
||||
|
||||
@@ -38,7 +38,7 @@
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#include "sdkconfig.h"
|
||||
#if defined(CONFIG_IF_WIFI_ENABLED)
|
||||
#if defined(CONFIG_IF_WIFI_ENABLED) || defined(CONFIG_IF_USB_NCM_ENABLED)
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
@@ -60,7 +60,9 @@
|
||||
#include "esp_mac.h"
|
||||
#include "freertos/event_groups.h"
|
||||
#include "esp_system.h"
|
||||
#if defined(CONFIG_IF_WIFI_ENABLED)
|
||||
#include "esp_wifi.h"
|
||||
#endif
|
||||
#include "esp_event.h"
|
||||
#include "esp_ota_ops.h"
|
||||
#include "esp_timer.h"
|
||||
@@ -91,8 +93,10 @@ extern "C"
|
||||
extern void CP_queueCmd(const char *cmd);
|
||||
extern bool CP_sendCmd(const char *cmd, uint32_t timeoutMs);
|
||||
|
||||
#if defined(CONFIG_IF_WIFI_ENABLED)
|
||||
// FreeRTOS event group to signal when we are connected
|
||||
static EventGroupHandle_t sWifiEventGroup;
|
||||
#endif
|
||||
|
||||
// globals (consider protecting with mutex if worried about concurrent users)
|
||||
std::string gLastSelectedFloppy; // only used briefly between ?file= and processing
|
||||
@@ -678,24 +682,30 @@ esp_err_t WiFi::expandVarsAndSend(httpd_req_t *req, std::string str)
|
||||
pairs.push_back(keyValue);
|
||||
{
|
||||
static char txPwrStr[8];
|
||||
snprintf(txPwrStr, sizeof(txPwrStr), "%d", wifiConfig.params.txPower);
|
||||
keyValue.name = "%SK_TXPOWER%";
|
||||
keyValue.value = txPwrStr;
|
||||
pairs.push_back(keyValue);
|
||||
static char txPwrDbmStr[16];
|
||||
static char rssiStr[8];
|
||||
#if defined(CONFIG_IF_WIFI_ENABLED)
|
||||
snprintf(txPwrStr, sizeof(txPwrStr), "%d", wifiConfig.params.txPower);
|
||||
int8_t actualPwr = 0;
|
||||
esp_wifi_get_max_tx_power(&actualPwr);
|
||||
snprintf(txPwrDbmStr, sizeof(txPwrDbmStr), "%.1f", actualPwr * 0.25f);
|
||||
keyValue.name = "%SK_TXPOWERDBM%";
|
||||
keyValue.value = txPwrDbmStr;
|
||||
pairs.push_back(keyValue);
|
||||
// Current RSSI (signal strength). Only meaningful in client mode when connected.
|
||||
static char rssiStr[8];
|
||||
wifi_ap_record_t apInfo;
|
||||
if (wifiCtrl.client.connected && esp_wifi_sta_get_ap_info(&apInfo) == ESP_OK)
|
||||
snprintf(rssiStr, sizeof(rssiStr), "%d", apInfo.rssi);
|
||||
else
|
||||
snprintf(rssiStr, sizeof(rssiStr), "N/A");
|
||||
#else
|
||||
snprintf(txPwrStr, sizeof(txPwrStr), "0");
|
||||
snprintf(txPwrDbmStr, sizeof(txPwrDbmStr), "N/A");
|
||||
snprintf(rssiStr, sizeof(rssiStr), "N/A");
|
||||
#endif
|
||||
keyValue.name = "%SK_TXPOWER%";
|
||||
keyValue.value = txPwrStr;
|
||||
pairs.push_back(keyValue);
|
||||
keyValue.name = "%SK_TXPOWERDBM%";
|
||||
keyValue.value = txPwrDbmStr;
|
||||
pairs.push_back(keyValue);
|
||||
keyValue.name = "%SK_RSSI%";
|
||||
keyValue.value = rssiStr;
|
||||
pairs.push_back(keyValue);
|
||||
@@ -721,6 +731,42 @@ esp_err_t WiFi::expandVarsAndSend(httpd_req_t *req, std::string str)
|
||||
keyValue.name = "%SK_ERRMSG%";
|
||||
keyValue.value = wifiCtrl.run.errorMsg;
|
||||
pairs.push_back(keyValue);
|
||||
keyValue.name = "%SK_WIFINAVVISIBLE%";
|
||||
#if defined(CONFIG_IF_WIFI_ENABLED)
|
||||
keyValue.value = "";
|
||||
#else
|
||||
keyValue.value = "style=\"display:none\"";
|
||||
#endif
|
||||
pairs.push_back(keyValue);
|
||||
keyValue.name = "%SK_USBNCMVISIBLE%";
|
||||
#if !defined(CONFIG_IF_WIFI_ENABLED) && defined(CONFIG_IF_USB_NCM_ENABLED)
|
||||
keyValue.value = "";
|
||||
#else
|
||||
keyValue.value = "style=\"display:none\"";
|
||||
#endif
|
||||
pairs.push_back(keyValue);
|
||||
keyValue.name = "%SK_NETPANELTITLE%";
|
||||
#if defined(CONFIG_IF_WIFI_ENABLED)
|
||||
keyValue.value = "WiFi Configuration";
|
||||
#else
|
||||
keyValue.value = "Network Configuration";
|
||||
#endif
|
||||
pairs.push_back(keyValue);
|
||||
keyValue.name = "%SK_NETPANELICON%";
|
||||
#if defined(CONFIG_IF_WIFI_ENABLED)
|
||||
keyValue.value = "fa-wifi";
|
||||
#else
|
||||
keyValue.value = "fa-usb";
|
||||
#endif
|
||||
pairs.push_back(keyValue);
|
||||
#if defined(CONFIG_IF_USB_NCM_ENABLED)
|
||||
keyValue.name = "%SK_USBNCMIP%";
|
||||
keyValue.value = CONFIG_IF_USB_NCM_IP;
|
||||
pairs.push_back(keyValue);
|
||||
keyValue.name = "%SK_USBNCMNM%";
|
||||
keyValue.value = CONFIG_IF_USB_NCM_NETMASK;
|
||||
pairs.push_back(keyValue);
|
||||
#endif
|
||||
keyValue.name = "%SK_DEVICE%";
|
||||
keyValue.value = wifiCtrl.run.hostDeviceDisplayName;
|
||||
pairs.push_back(keyValue);
|
||||
@@ -1482,12 +1528,14 @@ esp_err_t WiFi::defaultDataGETHandler(httpd_req_t *req)
|
||||
// Lightweight JSON endpoint for AJAX polling of WiFi signal + system status.
|
||||
char json[256];
|
||||
int8_t txPwr = 0;
|
||||
esp_wifi_get_max_tx_power(&txPwr);
|
||||
wifi_ap_record_t apInfo;
|
||||
int rssi = 0;
|
||||
bool connected = pThis->wifiCtrl.client.connected;
|
||||
#if defined(CONFIG_IF_WIFI_ENABLED)
|
||||
esp_wifi_get_max_tx_power(&txPwr);
|
||||
wifi_ap_record_t apInfo;
|
||||
if (connected && esp_wifi_sta_get_ap_info(&apInfo) == ESP_OK)
|
||||
rssi = apInfo.rssi;
|
||||
#endif
|
||||
// Compute uptime string
|
||||
int64_t us = esp_timer_get_time();
|
||||
int secs = (int) (us / 1000000);
|
||||
@@ -3541,12 +3589,13 @@ esp_err_t WiFi::sendFileManagerDir(httpd_req_t *req)
|
||||
// Slot 5: gap + Delete (always, one slot space from last icon)
|
||||
htmlStr.append("<span style=\"display:inline-block;width:30px;\"></span>");
|
||||
htmlStr.append("<span style=\"display:inline-block;width:30px;\">");
|
||||
htmlStr.append("<form method=\"GET\" style=\"display:inline\" action=\"?cmd=del\">");
|
||||
htmlStr.append("<form method=\"GET\" style=\"display:inline\">");
|
||||
htmlStr.append("<input type=\"hidden\" name=\"cmd\" value=\"del\">");
|
||||
htmlStr.append("<input type=\"hidden\" name=\"id\" value=\"").append(std::to_string(entryCnt)).append("\">");
|
||||
htmlStr.append("<input type=\"hidden\" name=\"name\" value=\"").append(entry->d_name).append("\">");
|
||||
htmlStr.append("<input type=\"hidden\" name=\"dir\" value=\"").append(directory).append("\">");
|
||||
htmlStr.append("<button type=\"submit\" name=\"cmd\" value=\"del\" class=\"fa fa-trash wm-button-small\" "
|
||||
"aria-hidden=\"true\" title=\"Delete\" onclick=\"return confirmDelete('")
|
||||
htmlStr.append("<button type=\"submit\" class=\"fa fa-trash wm-button-small\" "
|
||||
"aria-hidden=\"true\" title=\"Delete\" onclick=\"return confirmDelete(event, '")
|
||||
.append(entry->d_name)
|
||||
.append("');\"></button>");
|
||||
htmlStr.append("</form>");
|
||||
@@ -3606,21 +3655,33 @@ esp_err_t WiFi::defaultRebootHandler(httpd_req_t *req)
|
||||
{
|
||||
if (uriStr.substr(0, 5) == "esp32")
|
||||
{
|
||||
// Build a response message.
|
||||
if (!pThis->wifiConfig.clientParams.useDHCP)
|
||||
// Determine the redirect IP from the request's Host header so we return
|
||||
// to the same interface (WiFi or USB NCM) the user is currently using.
|
||||
char hostBuf[64] = {};
|
||||
std::string redirectIP;
|
||||
if (httpd_req_get_hdr_value_str(req, "Host", hostBuf, sizeof(hostBuf)) == ESP_OK)
|
||||
{
|
||||
resp = "<head> <meta http-equiv=\"refresh\" content=\"3; URL=http://" + std::string(pThis->wifiConfig.clientParams.ip) +
|
||||
"/\" /> </head><body><font size=\"6\" face=\"verdana\" color=\"red\"/>Rebooting... </font><font size=\"5\" face=\"verdana\" "
|
||||
"color=\"black\"/>Please wait.</font></body>";
|
||||
redirectIP = hostBuf;
|
||||
// Strip port number if present (e.g. "192.168.7.1:80" -> "192.168.7.1")
|
||||
size_t colonPos = redirectIP.find(':');
|
||||
if (colonPos != std::string::npos)
|
||||
redirectIP = redirectIP.substr(0, colonPos);
|
||||
}
|
||||
else if (!pThis->wifiConfig.clientParams.useDHCP)
|
||||
{
|
||||
redirectIP = pThis->wifiConfig.clientParams.ip;
|
||||
}
|
||||
else
|
||||
{
|
||||
resp = "<head> <meta http-equiv=\"refresh\" content=\"3; URL=http://" + std::string(pThis->wifiCtrl.client.ip) +
|
||||
"/\" /> </head><body><font size=\"6\" face=\"verdana\" color=\"red\"/>Rebooting... </font><br><font size=\"4\" face=\"verdana\" "
|
||||
"color=\"black\"/><p>If this screen doesnt auto-refresh, please look in your router admin panel for the assigned IP address and enter "
|
||||
"http://<router assigned ip address> into browser to continue.</p></font></body>";
|
||||
redirectIP = pThis->wifiCtrl.client.ip;
|
||||
}
|
||||
|
||||
// Build a response with a meta-refresh back to the originating IP.
|
||||
resp = "<head> <meta http-equiv=\"refresh\" content=\"5; URL=http://" + redirectIP +
|
||||
"/\" /> </head><body><font size=\"6\" face=\"verdana\" color=\"red\"/>Rebooting... </font><br><font size=\"4\" face=\"verdana\" "
|
||||
"color=\"black\"/><p>Redirecting to http://" + redirectIP + "/ in 5 seconds. "
|
||||
"If this screen doesnt auto-refresh, please reload manually.</p></font></body>";
|
||||
|
||||
// Send the response and wait a while, then request reboot.
|
||||
result = httpd_resp_send(req, resp.c_str(), resp.size() + 1);
|
||||
if (result == ESP_OK)
|
||||
@@ -4286,15 +4347,14 @@ bool WiFi::startWebserver(void)
|
||||
// Set to 14 to handle 12 concurrent requests with 2 slots spare.
|
||||
// CONFIG_LWIP_MAX_SOCKETS must be >= max_open_sockets + system sockets (~2);
|
||||
// it is set to 16 in sdkconfig.
|
||||
config.max_open_sockets = 16; // LWIP_MAX_SOCKETS(20) - 3 internal - 1 spare = 16
|
||||
config.max_open_sockets = 20; // LWIP_MAX_SOCKETS(24) - 3 internal - 1 spare = 20
|
||||
// Keep keep-alive enabled (HTTP/1.1 default) so the browser can multiplex
|
||||
// multiple resource requests over one TCP connection. Disabling it forces
|
||||
// a new TCP connection per resource; with max_open_sockets=7 the browser's
|
||||
// 6-8 parallel requests exhaust the pool and JS/CSS files are silently
|
||||
// dropped. Socket errors are now handled by explicit close() calls so
|
||||
// keep-alive is safe — responses always terminate with the zero-length chunk.
|
||||
config.recv_wait_timeout = 30; // close idle/broken sockets after 30 s
|
||||
config.send_wait_timeout = 30;
|
||||
// multiple resource requests over one TCP connection. USB NCM at Full Speed
|
||||
// (12 Mbps) is much slower than WiFi, so active transfers hold sockets longer.
|
||||
// 20 sockets with a 5s idle timeout gives enough headroom for pages that load
|
||||
// 12+ resources in parallel without LRU-purging active transfers.
|
||||
config.recv_wait_timeout = 5;
|
||||
config.send_wait_timeout = 10;
|
||||
|
||||
// Setup the required paths and descriptors then register them with the server.
|
||||
const httpd_uri_t dataPOST = {.uri = "/data", .method = HTTP_POST, .handler = defaultDataPOSTHandler, .user_ctx = this};
|
||||
@@ -4364,6 +4424,7 @@ void WiFi::stopWebserver(void)
|
||||
wifiCtrl.run.server = NULL;
|
||||
}
|
||||
|
||||
#if defined(CONFIG_IF_WIFI_ENABLED)
|
||||
// Event handler for Client mode Wifi event callback.
|
||||
void WiFi::wifiClientHandler(void *arg, esp_event_base_t event_base, int32_t event_id, void *event_data)
|
||||
{
|
||||
@@ -4488,18 +4549,21 @@ bool WiFi::setupWifiClient(void)
|
||||
{
|
||||
ESP_LOGI(WIFITAG, "Couldnt create event group, disabling WiFi.");
|
||||
}
|
||||
// Setup the network interface.
|
||||
// Setup the network interface. esp_netif_init() is idempotent — safe if USB NCM called it first.
|
||||
else if (esp_netif_init() != ESP_OK)
|
||||
{
|
||||
ESP_LOGI(WIFITAG, "Couldnt initialise netif, disabling WiFi.");
|
||||
}
|
||||
// Setup the event loop.
|
||||
else if (esp_event_loop_create_default() != ESP_OK)
|
||||
{
|
||||
ESP_LOGI(WIFITAG, "Couldnt initialise event loop, disabling WiFi.");
|
||||
}
|
||||
// Setup the event loop. Tolerate ESP_ERR_INVALID_STATE (already created by USB NCM init).
|
||||
else
|
||||
{
|
||||
esp_err_t loopErr = esp_event_loop_create_default();
|
||||
if (loopErr != ESP_OK && loopErr != ESP_ERR_INVALID_STATE)
|
||||
{
|
||||
ESP_LOGI(WIFITAG, "Couldnt initialise event loop, disabling WiFi.");
|
||||
}
|
||||
else
|
||||
{
|
||||
// Setup the wifi client (station).
|
||||
netConfig = esp_netif_create_default_wifi_sta();
|
||||
// If fixed IP is configured, set it up.
|
||||
@@ -4590,12 +4654,29 @@ bool WiFi::setupWifiClient(void)
|
||||
ESP_LOGI(WIFITAG, "TX power using default: %d (%.1f dBm)", actual, actual * 0.25f);
|
||||
}
|
||||
|
||||
// Waiting until either the connection is established (WIFI_CONNECTED_BIT) or connection failed for the maximum
|
||||
// number of re-tries (WIFI_FAIL_BIT). The bits are set by wifiClientHandler() (see above)
|
||||
// When USB NCM is enabled, don't block the main task waiting for WiFi —
|
||||
// the webserver is already running on the USB NCM interface and needs
|
||||
// the main task responsive. WiFi will connect asynchronously via the
|
||||
// event handler (wifiClientHandler), which starts the webserver if needed.
|
||||
// Without USB NCM, keep the original blocking behaviour to maintain
|
||||
// backwards compatibility.
|
||||
#if defined(CONFIG_IF_USB_NCM_ENABLED)
|
||||
// Non-blocking: wait briefly then return success regardless.
|
||||
// WiFi connection continues in the background via event handlers.
|
||||
bits = xEventGroupWaitBits(sWifiEventGroup, WIFI_CONNECTED_BIT | WIFI_FAIL_BIT, pdFALSE, pdFALSE, pdMS_TO_TICKS(5000));
|
||||
if (bits & WIFI_CONNECTED_BIT)
|
||||
{
|
||||
ESP_LOGI(WIFITAG, "WiFi connected.");
|
||||
}
|
||||
else
|
||||
{
|
||||
ESP_LOGI(WIFITAG, "WiFi connecting in background (USB NCM active).");
|
||||
}
|
||||
retVal = true; // Don't reboot — USB NCM is functional.
|
||||
#else
|
||||
bits = xEventGroupWaitBits(sWifiEventGroup, WIFI_CONNECTED_BIT | WIFI_FAIL_BIT, pdFALSE, pdFALSE, portMAX_DELAY);
|
||||
if (bits & WIFI_CONNECTED_BIT)
|
||||
{
|
||||
//ESP_LOGI(WIFITAG, "Connected: SSID:%s password:%s", this->wifiConfig.clientParams.ssid, this->wifiConfig.clientParams.pwd);
|
||||
retVal = true;
|
||||
}
|
||||
else if (bits & WIFI_FAIL_BIT)
|
||||
@@ -4606,6 +4687,8 @@ bool WiFi::setupWifiClient(void)
|
||||
{
|
||||
ESP_LOGE(WIFITAG, "Unknown event, bits:%ld", (unsigned long) bits);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4633,17 +4716,21 @@ bool WiFi::setupWifiAP(void)
|
||||
wifiConfig.ap.beacon_interval = 100;
|
||||
bool retVal = false;
|
||||
|
||||
// Initialize the network interface.
|
||||
// Initialize the network interface. esp_netif_init() is idempotent.
|
||||
if (esp_netif_init() != ESP_OK)
|
||||
{
|
||||
ESP_LOGI(WIFITAG, "Couldnt initialise network interface, disabling WiFi.");
|
||||
}
|
||||
else if ((retCode = esp_event_loop_create_default()) != ESP_OK)
|
||||
{
|
||||
ESP_LOGI(WIFITAG, "Couldnt create default loop(%d), disabling WiFi.", retCode);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Tolerate ESP_ERR_INVALID_STATE (already created by USB NCM init).
|
||||
retCode = esp_event_loop_create_default();
|
||||
if (retCode != ESP_OK && retCode != ESP_ERR_INVALID_STATE)
|
||||
{
|
||||
ESP_LOGI(WIFITAG, "Couldnt create default loop(%d), disabling WiFi.", retCode);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Create the default Access Point.
|
||||
wifiAP = esp_netif_create_default_wifi_ap();
|
||||
// Setup the base parameters of the Access Point which may differ from ESP32 defaults.
|
||||
@@ -4741,6 +4828,7 @@ bool WiFi::setupWifiAP(void)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Return fail/success.
|
||||
@@ -4812,6 +4900,7 @@ void WiFi::run(void)
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif // CONFIG_IF_WIFI_ENABLED
|
||||
|
||||
// readRP2350Info — superseded by binary IPC.
|
||||
// The RP2350 now sends the flash partition header proactively via the INF IPC command
|
||||
@@ -4900,13 +4989,19 @@ void WiFi::init(bool defaultMode, uint16_t device, NVS *nvs, SDCard *sdcard, cJS
|
||||
wifiConfig.clientParams.ip[0] = '\0';
|
||||
wifiConfig.clientParams.netmask[0] = '\0';
|
||||
wifiConfig.clientParams.gateway[0] = '\0';
|
||||
#if defined(CONFIG_IF_WIFI_ENABLED)
|
||||
strncpy(wifiConfig.apParams.ssid, CONFIG_IF_WIFI_SSID, MAX_WIFI_SSID_LEN);
|
||||
strncpy(wifiConfig.apParams.pwd, CONFIG_IF_WIFI_DEFAULT_SSID_PWD, MAX_WIFI_PWD_LEN);
|
||||
wifiConfig.params.wifiMode = WIFI_CONFIG_AP;
|
||||
#else
|
||||
wifiConfig.apParams.ssid[0] = '\0';
|
||||
wifiConfig.apParams.pwd[0] = '\0';
|
||||
wifiConfig.params.wifiMode = WIFI_OFF;
|
||||
#endif
|
||||
strncpy(wifiConfig.apParams.ip, WIFI_AP_DEFAULT_IP, MAX_WIFI_IP_LEN);
|
||||
strncpy(wifiConfig.apParams.netmask, WIFI_AP_DEFAULT_NETMASK, MAX_WIFI_NETMASK_LEN);
|
||||
strncpy(wifiConfig.apParams.gateway, WIFI_AP_DEFAULT_GW, MAX_WIFI_GATEWAY_LEN);
|
||||
wifiConfig.params.wifiMode = WIFI_CONFIG_AP;
|
||||
wifiConfig.params.txPower = 0; // 0 = use ESP-IDF default.
|
||||
wifiConfig.params.txPower = 0;
|
||||
|
||||
if (!nvs->persistData(wifiCtrl.run.thisClass.c_str(), &this->wifiConfig, sizeof(t_wifiConfig)))
|
||||
{
|
||||
@@ -5002,13 +5097,19 @@ WiFi::WiFi(bool defaultMode, uint16_t device, NVS *nvs, SDCard *sdcard, cJSON *c
|
||||
wifiConfig.clientParams.ip[0] = '\0';
|
||||
wifiConfig.clientParams.netmask[0] = '\0';
|
||||
wifiConfig.clientParams.gateway[0] = '\0';
|
||||
#if defined(CONFIG_IF_WIFI_ENABLED)
|
||||
strncpy(wifiConfig.apParams.ssid, CONFIG_IF_WIFI_SSID, MAX_WIFI_SSID_LEN);
|
||||
strncpy(wifiConfig.apParams.pwd, CONFIG_IF_WIFI_DEFAULT_SSID_PWD, MAX_WIFI_PWD_LEN);
|
||||
wifiConfig.params.wifiMode = WIFI_CONFIG_AP;
|
||||
#else
|
||||
wifiConfig.apParams.ssid[0] = '\0';
|
||||
wifiConfig.apParams.pwd[0] = '\0';
|
||||
wifiConfig.params.wifiMode = WIFI_OFF;
|
||||
#endif
|
||||
strncpy(wifiConfig.apParams.ip, WIFI_AP_DEFAULT_IP, MAX_WIFI_IP_LEN);
|
||||
strncpy(wifiConfig.apParams.netmask, WIFI_AP_DEFAULT_NETMASK, MAX_WIFI_NETMASK_LEN);
|
||||
strncpy(wifiConfig.apParams.gateway, WIFI_AP_DEFAULT_GW, MAX_WIFI_GATEWAY_LEN);
|
||||
wifiConfig.params.wifiMode = WIFI_CONFIG_AP;
|
||||
wifiConfig.params.txPower = 0; // 0 = use ESP-IDF default.
|
||||
wifiConfig.params.txPower = 0;
|
||||
|
||||
if (!nvs->persistData(wifiCtrl.run.thisClass.c_str(), &this->wifiConfig, sizeof(t_wifiConfig)))
|
||||
{
|
||||
|
||||
18
projects/tzpuPico/esp32/main/idf_component.yml
vendored
18
projects/tzpuPico/esp32/main/idf_component.yml
vendored
@@ -1,17 +1,11 @@
|
||||
## IDF Component Manager Manifest File
|
||||
dependencies:
|
||||
espressif/zlib: "^1.3.0"
|
||||
espressif/esp_tinyusb:
|
||||
version: "^1.3.0"
|
||||
rules:
|
||||
- if: "idf_version >=5.0"
|
||||
- if: "target in [esp32s2, esp32s3]"
|
||||
## Required IDF version
|
||||
idf:
|
||||
version: ">=4.1.0"
|
||||
# # Put list of dependencies here
|
||||
# # For components maintained by Espressif:
|
||||
# component: "~1.0.0"
|
||||
# # For 3rd party components:
|
||||
# username/component: ">=1.0.0,<2.0.0"
|
||||
# username2/component2:
|
||||
# version: "~1.0.0"
|
||||
# # For transient dependencies `public` flag can be set.
|
||||
# # `public` flag doesn't have an effect dependencies of the `main` component.
|
||||
# # All dependencies of `main` are public by default.
|
||||
# public: true
|
||||
version: ">=5.0"
|
||||
|
||||
12
projects/tzpuPico/esp32/main/include/WiFi.h
vendored
12
projects/tzpuPico/esp32/main/include/WiFi.h
vendored
@@ -30,10 +30,12 @@
|
||||
#ifndef WIFI_H
|
||||
#define WIFI_H
|
||||
|
||||
#if defined(CONFIG_IF_WIFI_ENABLED)
|
||||
#if defined(CONFIG_IF_WIFI_ENABLED) || defined(CONFIG_IF_USB_NCM_ENABLED)
|
||||
#include "freertos/event_groups.h"
|
||||
#include "esp_system.h"
|
||||
#if defined(CONFIG_IF_WIFI_ENABLED)
|
||||
#include "esp_wifi.h"
|
||||
#endif
|
||||
#include "esp_event.h"
|
||||
#include "nvs_flash.h"
|
||||
#include "lwip/err.h"
|
||||
@@ -161,7 +163,9 @@ class WiFi
|
||||
WiFi(bool defaultMode, uint16_t device, NVS *nvs, SDCard *sdcard, cJSON *config, const char *fsPath, t_versionList *versionList);
|
||||
WiFi(void);
|
||||
~WiFi(void);
|
||||
#if defined(CONFIG_IF_WIFI_ENABLED)
|
||||
void run(void);
|
||||
#endif
|
||||
void init(bool defaultMode, uint16_t device, NVS *nvs, SDCard *sdcard, cJSON *config, const char *fsPath, t_versionList *versionList);
|
||||
void readRP2350Info(const std::string &inParam, FSPI &fspi);
|
||||
bool doReboot(void);
|
||||
@@ -169,6 +173,7 @@ class WiFi
|
||||
bool setFloppyDiskFile(const std::string ¶m1, int diskNo);
|
||||
bool setQuickDiskFile(const std::string ¶m1, int diskNo);
|
||||
bool setRamFile(const std::string ¶m1, int ramfileNo);
|
||||
bool startWebserver(void);
|
||||
|
||||
// Non Volatile Storage handle.
|
||||
NVS *nvs;
|
||||
@@ -341,10 +346,11 @@ class WiFi
|
||||
t_wifiControl wifiCtrl;
|
||||
|
||||
// Prototypes.
|
||||
#if defined(CONFIG_IF_WIFI_ENABLED)
|
||||
bool setupWifiClient(void);
|
||||
bool setupWifiAP(void);
|
||||
bool stopWifi(void);
|
||||
bool startWebserver(void);
|
||||
#endif
|
||||
void stopWebserver(void);
|
||||
float getVersionNumber(const std::string &name);
|
||||
esp_err_t expandAndSendFile(httpd_req_t *req, const char *basePath, const std::string &fileName);
|
||||
@@ -403,8 +409,10 @@ class WiFi
|
||||
static esp_err_t defaultTasksHandler(httpd_req_t *req);
|
||||
static esp_err_t defaultRecoverHandler(httpd_req_t *req);
|
||||
static esp_err_t defaultFileHandler(httpd_req_t *req);
|
||||
#if defined(CONFIG_IF_WIFI_ENABLED)
|
||||
static void wifiAPHandler(void *arg, esp_event_base_t event_base, int32_t event_id, void *event_data);
|
||||
static void wifiClientHandler(void *arg, esp_event_base_t event_base, int32_t event_id, void *event_data);
|
||||
#endif
|
||||
};
|
||||
#endif
|
||||
|
||||
|
||||
@@ -71,6 +71,15 @@
|
||||
#include "SDCard.h"
|
||||
#include "cJSON.h"
|
||||
#include "CommandProcessor.h"
|
||||
#if defined(CONFIG_IF_USB_NCM_ENABLED)
|
||||
#include "tinyusb.h"
|
||||
#include "tinyusb_net.h"
|
||||
#include "tusb_cdc_acm.h"
|
||||
#include "tusb_console.h"
|
||||
#include "esp_netif.h"
|
||||
#include "esp_mac.h"
|
||||
#include "lwip/esp_netif_net_stack.h"
|
||||
#endif
|
||||
|
||||
// Defined in CommandProcessor.cpp — ESP32→RP2350 reverse command dispatch.
|
||||
extern void CP_queueCmd(const char *cmd); // async: fire and forget
|
||||
@@ -213,7 +222,7 @@ float version(void)
|
||||
// automatically; no setter call needed.
|
||||
// Call this AFTER CommandProcessor::start() — SPI is live by then.
|
||||
//
|
||||
#if defined(CONFIG_IF_WIFI_ENABLED)
|
||||
#if defined(CONFIG_IF_WIFI_ENABLED) || defined(CONFIG_IF_USB_NCM_ENABLED)
|
||||
|
||||
WiFi *initWiFi(NVS &nvs, SDCard &sdcard, cJSON *esp32Config, bool defaultMode, uint16_t device, WiFi::t_versionList **versionListOut)
|
||||
{
|
||||
@@ -300,7 +309,146 @@ void buildVersionList(WiFi::t_versionList *versionList, NVS &nvs, SDCard &sdcard
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
#endif // CONFIG_IF_WIFI_ENABLED || CONFIG_IF_USB_NCM_ENABLED
|
||||
|
||||
// USB NCM network interface setup.
|
||||
// Creates a USB Ethernet (CDC-NCM) adapter on the ESP32-S3 USB OTG port (GPIO 19/20).
|
||||
// The host PC sees a network adapter and can browse to the configured IP for configuration.
|
||||
// This provides the same browser-based interface as WiFi but over USB, without requiring
|
||||
// FCC/RED certification as no intentional RF emission is involved.
|
||||
#if defined(CONFIG_IF_USB_NCM_ENABLED)
|
||||
|
||||
static esp_netif_t *s_usb_ncm_netif = NULL;
|
||||
|
||||
static void usb_ncm_l2_free(void *h, void *buffer)
|
||||
{
|
||||
free(buffer);
|
||||
}
|
||||
|
||||
static esp_err_t usb_ncm_netif_transmit(void *h, void *buffer, size_t len)
|
||||
{
|
||||
if (tinyusb_net_send_sync(buffer, len, NULL, pdMS_TO_TICKS(1000)) != ESP_OK) {
|
||||
ESP_LOGE(MAINTAG, "USB NCM: failed to send buffer");
|
||||
}
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
static esp_err_t usb_ncm_recv_callback(void *buffer, uint16_t len, void *ctx)
|
||||
{
|
||||
if (s_usb_ncm_netif) {
|
||||
void *buf_copy = malloc(len);
|
||||
if (!buf_copy) {
|
||||
return ESP_ERR_NO_MEM;
|
||||
}
|
||||
memcpy(buf_copy, buffer, len);
|
||||
return esp_netif_receive(s_usb_ncm_netif, buf_copy, len, NULL);
|
||||
}
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
// Phase 0: Install TinyUSB composite device (CDC-ACM + CDC-NCM), create the
|
||||
// lwIP network interface, and redirect console to the CDC-ACM serial port.
|
||||
//
|
||||
// CRITICAL: The lwIP netif MUST be created before the host finishes USB
|
||||
// enumeration and starts probing the NCM link (ARP/NDP). If the netif isn't
|
||||
// ready, usb_ncm_recv_callback() drops all packets (s_usb_ncm_netif == NULL),
|
||||
// the host gets no ARP replies, and marks the link as inactive.
|
||||
//
|
||||
// For this reason, esp_netif_init() and esp_event_loop_create_default() are
|
||||
// called here (both are idempotent / tolerate double-init) so the netif can
|
||||
// be created immediately after the TinyUSB NCM class is initialized.
|
||||
bool setupUSBConsole(void)
|
||||
{
|
||||
bool cdcOk = false;
|
||||
bool ncmOk = false;
|
||||
|
||||
// 0. Initialise the TCP/IP stack and event loop early so we can create
|
||||
// the esp_netif before the host starts probing.
|
||||
esp_netif_init();
|
||||
esp_event_loop_create_default();
|
||||
|
||||
// 1. Install TinyUSB driver on the OTG peripheral (GPIO 19/20).
|
||||
tinyusb_config_t tusb_cfg = {};
|
||||
tusb_cfg.external_phy = false;
|
||||
esp_err_t ret = tinyusb_driver_install(&tusb_cfg);
|
||||
if (ret != ESP_OK) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 2. Force a clean USB disconnect/reconnect cycle. After a software reboot
|
||||
// (esp_restart), the USB peripheral may not fully reset — the host sees a
|
||||
// glitch rather than a proper disconnect. macOS then tries to resume the
|
||||
// previous NCM session state, which fails and leaves the link "inactive".
|
||||
// By explicitly pulling D+ low (tud_disconnect) and then re-enabling it
|
||||
// (tud_connect), the host sees a clean device removal followed by a fresh
|
||||
// enumeration, regardless of whether this was a power cycle or soft reboot.
|
||||
tud_disconnect();
|
||||
vTaskDelay(pdMS_TO_TICKS(1500));
|
||||
tud_connect();
|
||||
|
||||
// 3. Initialise CDC-ACM for serial logging.
|
||||
tinyusb_config_cdcacm_t acm_cfg = {};
|
||||
ret = tusb_cdc_acm_init(&acm_cfg);
|
||||
cdcOk = (ret == ESP_OK);
|
||||
|
||||
// 4. Initialise the NCM network class handler.
|
||||
tinyusb_net_config_t net_config = {};
|
||||
net_config.mac_addr[0] = 0x02; net_config.mac_addr[1] = 0x02;
|
||||
net_config.mac_addr[2] = 0x11; net_config.mac_addr[3] = 0x22;
|
||||
net_config.mac_addr[4] = 0x33; net_config.mac_addr[5] = 0x01;
|
||||
net_config.on_recv_callback = usb_ncm_recv_callback;
|
||||
ret = tinyusb_net_init(TINYUSB_USBDEV_0, &net_config);
|
||||
ncmOk = (ret == ESP_OK);
|
||||
|
||||
// 5. Create the lwIP netif IMMEDIATELY so received packets are handled
|
||||
// as soon as the host starts probing (ARP, NDP, DHCP).
|
||||
if (ncmOk) {
|
||||
esp_netif_ip_info_t ip_info = {};
|
||||
ip_info.ip.addr = ipaddr_addr(CONFIG_IF_USB_NCM_IP);
|
||||
ip_info.netmask.addr = ipaddr_addr(CONFIG_IF_USB_NCM_NETMASK);
|
||||
ip_info.gw.addr = ip_info.ip.addr;
|
||||
|
||||
uint8_t lwip_mac[6] = {0x02, 0x02, 0x11, 0x22, 0x33, 0x02};
|
||||
|
||||
esp_netif_inherent_config_t base_cfg = {};
|
||||
base_cfg.flags = (esp_netif_flags_t)(ESP_NETIF_DHCP_SERVER | ESP_NETIF_FLAG_AUTOUP);
|
||||
base_cfg.ip_info = &ip_info;
|
||||
base_cfg.if_key = "usbncm";
|
||||
base_cfg.if_desc = "USB NCM network interface";
|
||||
base_cfg.route_prio = 10;
|
||||
|
||||
esp_netif_driver_ifconfig_t driver_cfg = {};
|
||||
driver_cfg.handle = (void *)1;
|
||||
driver_cfg.transmit = usb_ncm_netif_transmit;
|
||||
driver_cfg.driver_free_rx_buffer = usb_ncm_l2_free;
|
||||
|
||||
struct esp_netif_netstack_config lwip_netif_config = {};
|
||||
lwip_netif_config.lwip.init_fn = ethernetif_init;
|
||||
lwip_netif_config.lwip.input_fn = ethernetif_input;
|
||||
|
||||
esp_netif_config_t cfg = {};
|
||||
cfg.base = &base_cfg;
|
||||
cfg.driver = &driver_cfg;
|
||||
cfg.stack = &lwip_netif_config;
|
||||
|
||||
s_usb_ncm_netif = esp_netif_new(&cfg);
|
||||
if (s_usb_ncm_netif != NULL) {
|
||||
esp_netif_set_mac(s_usb_ncm_netif, lwip_mac);
|
||||
uint32_t lease_opt = 120; // DHCP lease time in minutes
|
||||
esp_netif_dhcps_option(s_usb_ncm_netif, ESP_NETIF_OP_SET,
|
||||
ESP_NETIF_IP_ADDRESS_LEASE_TIME, &lease_opt, sizeof(lease_opt));
|
||||
esp_netif_action_start(s_usb_ncm_netif, 0, 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
// 6. Redirect stdout/stderr/stdin to the TinyUSB CDC-ACM port.
|
||||
if (cdcOk) {
|
||||
esp_tusb_init_console(TINYUSB_CDC_ACM_0);
|
||||
}
|
||||
|
||||
return cdcOk;
|
||||
}
|
||||
#endif // CONFIG_IF_USB_NCM_ENABLED
|
||||
|
||||
// Setup phase 1 — fast path: eFUSE, NVS, SPI slave.
|
||||
// Called before CommandProcessor::start() so the SPI slave is listening as
|
||||
@@ -634,7 +782,9 @@ extern "C"
|
||||
static FSPI fspi;
|
||||
static cJSON *esp32Config;
|
||||
bool rp2350ConfigChanged = false;
|
||||
#if defined(CONFIG_IF_WIFI_ENABLED) || defined(CONFIG_IF_USB_NCM_ENABLED)
|
||||
WiFi *wifi;
|
||||
#endif
|
||||
static int loopCount = 0;
|
||||
static bool wifiStarted = false;
|
||||
static RTC_NOINIT_ATTR int powerOn = 1;
|
||||
@@ -661,15 +811,25 @@ extern "C"
|
||||
IO_init(LOGGING_FRAMED);
|
||||
#endif
|
||||
|
||||
// Install TinyUSB composite device (CDC-ACM + NCM) and redirect console
|
||||
// to CDC-ACM as early as possible so all subsequent ESP_LOG output is visible.
|
||||
#if defined(CONFIG_IF_USB_NCM_ENABLED)
|
||||
if (setupUSBConsole()) {
|
||||
ESP_LOGW(MAINTAG, "USB CDC-ACM console active — log output on TinyUSB serial port.");
|
||||
}
|
||||
#endif
|
||||
|
||||
// Phase 1: fast setup — eFUSE, NVS, SPI slave (~60 ms).
|
||||
// NVS loads tzpuPicoConfig (bootMode, deviceMode) needed by initWiFi().
|
||||
ESP_LOGW(MAINTAG, "Initialising hardware (phase 1: NVS + SPI).");
|
||||
setupEarly(nvs, fspi);
|
||||
|
||||
// Create WiFi object immediately (NVS credentials, empty version list, ~10 ms).
|
||||
ESP_LOGW(MAINTAG, "Initialising WiFi object.");
|
||||
// Create WiFi/Web object immediately (NVS credentials, empty version list, ~10 ms).
|
||||
#if defined(CONFIG_IF_WIFI_ENABLED) || defined(CONFIG_IF_USB_NCM_ENABLED)
|
||||
ESP_LOGW(MAINTAG, "Initialising Web interface object.");
|
||||
WiFi::t_versionList *versionList;
|
||||
wifi = initWiFi(nvs, sdcard, NULL, tzpuPicoConfig.params.bootMode, tzpuPicoConfig.params.deviceMode, &versionList);
|
||||
#endif
|
||||
|
||||
// Start CommandProcessor — SPI slave is NOW listening for RP2350 commands.
|
||||
// Total time from power-on to here: ~170 ms (60 ms setup + 10 ms WiFi + 100 ms task delay).
|
||||
@@ -697,8 +857,10 @@ extern "C"
|
||||
//IO_setLogMode(LOGGING_FILE);
|
||||
|
||||
// Populate the version list in-place — WiFi holds the pointer and sees it automatically.
|
||||
ESP_LOGW(MAINTAG, "Building WiFi version list.");
|
||||
#if defined(CONFIG_IF_WIFI_ENABLED) || defined(CONFIG_IF_USB_NCM_ENABLED)
|
||||
ESP_LOGW(MAINTAG, "Building version list.");
|
||||
buildVersionList(versionList, nvs, sdcard);
|
||||
#endif
|
||||
|
||||
// Loop waiting on callback events and action accordingly.
|
||||
while (true)
|
||||
@@ -737,12 +899,14 @@ extern "C"
|
||||
esp_restart();
|
||||
}
|
||||
|
||||
// Check to see if a reboot is requested by user.
|
||||
// Check to see if a reboot is requested by user via web interface.
|
||||
#if defined(CONFIG_IF_WIFI_ENABLED) || defined(CONFIG_IF_USB_NCM_ENABLED)
|
||||
if (wifi->doReboot() == true)
|
||||
{
|
||||
ESP_LOGW(MAINTAG, "WiFi reboot requested..");
|
||||
ESP_LOGW(MAINTAG, "Web interface reboot requested..");
|
||||
esp_restart();
|
||||
}
|
||||
#endif
|
||||
|
||||
// Check to see if a reboot is requested by rp2350.
|
||||
if (IO_doReboot() == true)
|
||||
@@ -765,17 +929,29 @@ extern "C"
|
||||
CP_queueCmd("CFG0");
|
||||
}
|
||||
|
||||
// Start WiFi ~1 s after the main loop begins (loopCount > 100 at 10 ms/iter).
|
||||
// INF exchange is disabled so there is no longer a risk of WiFi high-priority
|
||||
// tasks (priority 23) interfering with SPI transactions. The short delay
|
||||
// gives the SPI slave (initialized at the end of the CommandProcessor startup
|
||||
// delay) a moment to settle and handle the RP2350's first sector reads before
|
||||
// WiFi stack comes up.
|
||||
// Start network interfaces ~1 s after the main loop begins (loopCount > 100
|
||||
// at 10 ms/iter). The short delay gives the SPI slave (initialized at the end
|
||||
// of the CommandProcessor startup delay) a moment to settle and handle the
|
||||
// RP2350's first sector reads before the network stack comes up.
|
||||
++loopCount;
|
||||
if (!wifiStarted && loopCount > 100)
|
||||
{
|
||||
#if defined(CONFIG_IF_USB_NCM_ENABLED)
|
||||
// USB NCM network + lwIP netif were already created in setupUSBConsole().
|
||||
// Start the webserver now so USB NCM is immediately browsable.
|
||||
// The HTTP server binds to INADDR_ANY:80 so it will also serve on
|
||||
// WiFi once that connection is established.
|
||||
ESP_LOGW(MAINTAG, "Starting webserver on USB NCM.");
|
||||
wifi->startWebserver();
|
||||
#endif
|
||||
|
||||
#if defined(CONFIG_IF_WIFI_ENABLED)
|
||||
// Start WiFi (AP or Client mode). When USB NCM has already started
|
||||
// the webserver, the WiFi event handlers detect that server != NULL
|
||||
// and skip the redundant startWebserver() call.
|
||||
ESP_LOGW(MAINTAG, "Starting WiFi (loopCount=%d).", loopCount);
|
||||
wifi->run();
|
||||
#endif
|
||||
wifiStarted = true;
|
||||
}
|
||||
|
||||
|
||||
144
projects/tzpuPico/esp32/sdkconfig
vendored
144
projects/tzpuPico/esp32/sdkconfig
vendored
@@ -588,15 +588,17 @@ CONFIG_SD_CDDETECT=21
|
||||
#
|
||||
# WiFi
|
||||
#
|
||||
CONFIG_IF_WIFI_ENABLED=y
|
||||
CONFIG_IF_WIFI_SSID="tzpuPico AP"
|
||||
CONFIG_IF_WIFI_DEFAULT_SSID_PWD="tzpuPico1234"
|
||||
CONFIG_IF_WIFI_MAX_RETRIES=20
|
||||
CONFIG_IF_WIFI_AP_CHANNEL=5
|
||||
CONFIG_IF_WIFI_SSID_HIDDEN=0
|
||||
CONFIG_IF_WIFI_MAX_CONNECTIONS=3
|
||||
# CONFIG_IF_WIFI_ENABLED is not set
|
||||
# end of WiFi
|
||||
|
||||
#
|
||||
# USB NCM Network
|
||||
#
|
||||
CONFIG_IF_USB_NCM_ENABLED=y
|
||||
CONFIG_IF_USB_NCM_IP="192.168.7.1"
|
||||
CONFIG_IF_USB_NCM_NETMASK="255.255.255.0"
|
||||
# end of USB NCM Network
|
||||
|
||||
CONFIG_USE_ESP32_USB_OUTPUT=y
|
||||
# CONFIG_USE_RP2350_OUTPUT is not set
|
||||
|
||||
@@ -1219,7 +1221,7 @@ CONFIG_SPI_SLAVE_ISR_IN_IRAM=y
|
||||
#
|
||||
# ESP-Driver:USB Serial/JTAG Configuration
|
||||
#
|
||||
CONFIG_USJ_ENABLE_USB_SERIAL_JTAG=y
|
||||
# CONFIG_USJ_ENABLE_USB_SERIAL_JTAG is not set
|
||||
# end of ESP-Driver:USB Serial/JTAG Configuration
|
||||
|
||||
#
|
||||
@@ -1557,13 +1559,13 @@ CONFIG_ESP_MAIN_TASK_AFFINITY=0x0
|
||||
CONFIG_ESP_MINIMAL_SHARED_STACK_SIZE=4192
|
||||
# CONFIG_ESP_CONSOLE_UART_DEFAULT is not set
|
||||
# CONFIG_ESP_CONSOLE_USB_CDC is not set
|
||||
CONFIG_ESP_CONSOLE_USB_SERIAL_JTAG=y
|
||||
# CONFIG_ESP_CONSOLE_USB_SERIAL_JTAG is not set
|
||||
# CONFIG_ESP_CONSOLE_UART_CUSTOM is not set
|
||||
# CONFIG_ESP_CONSOLE_NONE is not set
|
||||
CONFIG_ESP_CONSOLE_NONE=y
|
||||
CONFIG_ESP_CONSOLE_SECONDARY_NONE=y
|
||||
CONFIG_ESP_CONSOLE_USB_SERIAL_JTAG_ENABLED=y
|
||||
# CONFIG_ESP_CONSOLE_SECONDARY_USB_SERIAL_JTAG is not set
|
||||
CONFIG_ESP_CONSOLE_UART_NUM=-1
|
||||
CONFIG_ESP_CONSOLE_ROM_SERIAL_PORT_NUM=4
|
||||
CONFIG_ESP_CONSOLE_ROM_SERIAL_PORT_NUM=-1
|
||||
CONFIG_ESP_INT_WDT=y
|
||||
CONFIG_ESP_INT_WDT_TIMEOUT_MS=2000
|
||||
CONFIG_ESP_INT_WDT_CHECK_CPU1=y
|
||||
@@ -1896,7 +1898,7 @@ CONFIG_LWIP_DNS_SUPPORT_MDNS_QUERIES=y
|
||||
CONFIG_LWIP_TIMERS_ONDEMAND=y
|
||||
CONFIG_LWIP_ND6=y
|
||||
# CONFIG_LWIP_FORCE_ROUTER_FORWARDING is not set
|
||||
CONFIG_LWIP_MAX_SOCKETS=20
|
||||
CONFIG_LWIP_MAX_SOCKETS=24
|
||||
# CONFIG_LWIP_USE_ONLY_LWIP_SELECT is not set
|
||||
# CONFIG_LWIP_SO_LINGER is not set
|
||||
CONFIG_LWIP_SO_REUSE=y
|
||||
@@ -2497,6 +2499,118 @@ CONFIG_WIFI_PROV_AUTOSTOP_TIMEOUT=30
|
||||
CONFIG_WIFI_PROV_STA_ALL_CHANNEL_SCAN=y
|
||||
# CONFIG_WIFI_PROV_STA_FAST_SCAN is not set
|
||||
# end of Wi-Fi Provisioning Manager
|
||||
|
||||
#
|
||||
# TinyUSB Stack
|
||||
#
|
||||
CONFIG_TINYUSB_DEBUG_LEVEL=1
|
||||
CONFIG_TINYUSB_RHPORT_FS=y
|
||||
|
||||
#
|
||||
# TinyUSB DCD
|
||||
#
|
||||
# CONFIG_TINYUSB_MODE_SLAVE is not set
|
||||
CONFIG_TINYUSB_MODE_DMA=y
|
||||
# end of TinyUSB DCD
|
||||
|
||||
#
|
||||
# TinyUSB task configuration
|
||||
#
|
||||
# CONFIG_TINYUSB_NO_DEFAULT_TASK is not set
|
||||
CONFIG_TINYUSB_TASK_PRIORITY=5
|
||||
CONFIG_TINYUSB_TASK_STACK_SIZE=4096
|
||||
# CONFIG_TINYUSB_TASK_AFFINITY_NO_AFFINITY is not set
|
||||
CONFIG_TINYUSB_TASK_AFFINITY_CPU0=y
|
||||
# CONFIG_TINYUSB_TASK_AFFINITY_CPU1 is not set
|
||||
CONFIG_TINYUSB_TASK_AFFINITY=0x0
|
||||
CONFIG_TINYUSB_INIT_IN_DEFAULT_TASK=y
|
||||
# end of TinyUSB task configuration
|
||||
|
||||
#
|
||||
# Descriptor configuration
|
||||
#
|
||||
|
||||
#
|
||||
# You can provide your custom descriptors via tinyusb_driver_install()
|
||||
#
|
||||
CONFIG_TINYUSB_DESC_USE_ESPRESSIF_VID=y
|
||||
CONFIG_TINYUSB_DESC_USE_DEFAULT_PID=y
|
||||
CONFIG_TINYUSB_DESC_BCD_DEVICE=0x0100
|
||||
CONFIG_TINYUSB_DESC_MANUFACTURER_STRING="Espressif Systems"
|
||||
CONFIG_TINYUSB_DESC_PRODUCT_STRING="Espressif Device"
|
||||
CONFIG_TINYUSB_DESC_SERIAL_STRING="123456"
|
||||
CONFIG_TINYUSB_DESC_CDC_STRING="Espressif CDC Device"
|
||||
# end of Descriptor configuration
|
||||
|
||||
#
|
||||
# Massive Storage Class (MSC)
|
||||
#
|
||||
# CONFIG_TINYUSB_MSC_ENABLED is not set
|
||||
|
||||
#
|
||||
# TinyUSB FAT Format Options
|
||||
#
|
||||
CONFIG_TINYUSB_FAT_FORMAT_ANY=y
|
||||
# CONFIG_TINYUSB_FAT_FORMAT_FAT is not set
|
||||
# CONFIG_TINYUSB_FAT_FORMAT_FAT32 is not set
|
||||
# CONFIG_TINYUSB_FAT_FORMAT_EXFAT is not set
|
||||
# CONFIG_TINYUSB_FAT_FORMAT_SFD is not set
|
||||
# end of TinyUSB FAT Format Options
|
||||
# end of Massive Storage Class (MSC)
|
||||
|
||||
#
|
||||
# Communication Device Class (CDC)
|
||||
#
|
||||
CONFIG_TINYUSB_CDC_ENABLED=y
|
||||
CONFIG_TINYUSB_CDC_COUNT=1
|
||||
CONFIG_TINYUSB_CDC_RX_BUFSIZE=512
|
||||
CONFIG_TINYUSB_CDC_TX_BUFSIZE=4096
|
||||
# end of Communication Device Class (CDC)
|
||||
|
||||
#
|
||||
# Musical Instrument Digital Interface (MIDI)
|
||||
#
|
||||
CONFIG_TINYUSB_MIDI_COUNT=0
|
||||
# end of Musical Instrument Digital Interface (MIDI)
|
||||
|
||||
#
|
||||
# Human Interface Device Class (HID)
|
||||
#
|
||||
CONFIG_TINYUSB_HID_COUNT=0
|
||||
# end of Human Interface Device Class (HID)
|
||||
|
||||
#
|
||||
# Device Firmware Upgrade (DFU)
|
||||
#
|
||||
# CONFIG_TINYUSB_DFU_MODE_DFU is not set
|
||||
# CONFIG_TINYUSB_DFU_MODE_DFU_RUNTIME is not set
|
||||
CONFIG_TINYUSB_DFU_MODE_NONE=y
|
||||
# end of Device Firmware Upgrade (DFU)
|
||||
|
||||
#
|
||||
# Bluetooth Host Class (BTH)
|
||||
#
|
||||
# CONFIG_TINYUSB_BTH_ENABLED is not set
|
||||
# end of Bluetooth Host Class (BTH)
|
||||
|
||||
#
|
||||
# Network driver (ECM/NCM/RNDIS)
|
||||
#
|
||||
# CONFIG_TINYUSB_NET_MODE_ECM_RNDIS is not set
|
||||
CONFIG_TINYUSB_NET_MODE_NCM=y
|
||||
# CONFIG_TINYUSB_NET_MODE_NONE is not set
|
||||
CONFIG_TINYUSB_NCM_OUT_NTB_BUFFS_COUNT=3
|
||||
CONFIG_TINYUSB_NCM_IN_NTB_BUFFS_COUNT=3
|
||||
CONFIG_TINYUSB_NCM_OUT_NTB_BUFF_MAX_SIZE=3200
|
||||
CONFIG_TINYUSB_NCM_IN_NTB_BUFF_MAX_SIZE=3200
|
||||
# end of Network driver (ECM/NCM/RNDIS)
|
||||
|
||||
#
|
||||
# Vendor Specific Interface
|
||||
#
|
||||
CONFIG_TINYUSB_VENDOR_COUNT=0
|
||||
# end of Vendor Specific Interface
|
||||
# end of TinyUSB Stack
|
||||
# end of Component config
|
||||
|
||||
# CONFIG_IDF_EXPERIMENTAL_FEATURES is not set
|
||||
@@ -2756,8 +2870,8 @@ CONFIG_SYSTEM_EVENT_TASK_STACK_SIZE=2304
|
||||
CONFIG_MAIN_TASK_STACK_SIZE=32768
|
||||
# CONFIG_CONSOLE_UART_DEFAULT is not set
|
||||
# CONFIG_CONSOLE_UART_CUSTOM is not set
|
||||
# CONFIG_CONSOLE_UART_NONE is not set
|
||||
# CONFIG_ESP_CONSOLE_UART_NONE is not set
|
||||
CONFIG_CONSOLE_UART_NONE=y
|
||||
CONFIG_ESP_CONSOLE_UART_NONE=y
|
||||
CONFIG_CONSOLE_UART_NUM=-1
|
||||
CONFIG_INT_WDT=y
|
||||
CONFIG_INT_WDT_TIMEOUT_MS=2000
|
||||
|
||||
2991
projects/tzpuPico/esp32/sdkconfig.mode_ncm_only
vendored
Normal file
2991
projects/tzpuPico/esp32/sdkconfig.mode_ncm_only
vendored
Normal file
File diff suppressed because it is too large
Load Diff
2997
projects/tzpuPico/esp32/sdkconfig.mode_wifi_and_ncm
vendored
Normal file
2997
projects/tzpuPico/esp32/sdkconfig.mode_wifi_and_ncm
vendored
Normal file
File diff suppressed because it is too large
Load Diff
2988
projects/tzpuPico/esp32/sdkconfig.mode_wifi_only
vendored
Normal file
2988
projects/tzpuPico/esp32/sdkconfig.mode_wifi_only
vendored
Normal file
File diff suppressed because it is too large
Load Diff
2
projects/tzpuPico/esp32/version.txt
vendored
2
projects/tzpuPico/esp32/version.txt
vendored
@@ -1 +1 @@
|
||||
2.03
|
||||
2.27
|
||||
|
||||
2
projects/tzpuPico/esp32/webserver/config.htm
vendored
2
projects/tzpuPico/esp32/webserver/config.htm
vendored
@@ -61,7 +61,7 @@
|
||||
</ul>
|
||||
</li>
|
||||
|
||||
<li>
|
||||
<li %SK_WIFINAVVISIBLE%>
|
||||
<a href="wifimanager.htm">
|
||||
<i style="color: yellow;" class="fa fa-wifi"></i> WiFi Manager
|
||||
</a>
|
||||
|
||||
@@ -61,7 +61,7 @@
|
||||
</ul>
|
||||
</li>
|
||||
|
||||
<li>
|
||||
<li %SK_WIFINAVVISIBLE%>
|
||||
<a href="wifimanager.htm">
|
||||
<i style="color: yellow;" class="fa fa-wifi"></i> WiFi Manager
|
||||
</a>
|
||||
|
||||
46
projects/tzpuPico/esp32/webserver/index.htm
vendored
46
projects/tzpuPico/esp32/webserver/index.htm
vendored
@@ -61,7 +61,7 @@
|
||||
</ul>
|
||||
</li>
|
||||
|
||||
<li>
|
||||
<li %SK_WIFINAVVISIBLE%>
|
||||
<a href="wifimanager.htm">
|
||||
<i style="color: yellow;" class="fa fa-wifi"></i> WiFi Manager
|
||||
</a>
|
||||
@@ -126,54 +126,64 @@
|
||||
<div class="col-lg-12">
|
||||
<div class="panel panel-primary">
|
||||
<div class="panel-heading">
|
||||
<h3 class="panel-title"><i class="fa fa-wifi"></i> WiFi Configuration</h3>
|
||||
<h3 class="panel-title"><i class="fa %SK_NETPANELICON%"></i> %SK_NETPANELTITLE%</h3>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<div class="table-responsive" id="client-wifi-config-area">
|
||||
<!--<div id="client-wifi-config-area">-->
|
||||
<div id="wifiCfg%SK_WIFIMODEAP%">
|
||||
<table class="table table-borderless table-sm">
|
||||
<div class="table-responsive">
|
||||
<div %SK_WIFINAVVISIBLE% id="wifiCfg%SK_WIFIMODEAP%">
|
||||
<table class="table table-borderless table-sm" style="width:auto;">
|
||||
<tbody>
|
||||
<tr style="display: compact;">
|
||||
<td>SSID:</td><td><span style="color: blue;">%SK_APSSID%</span></td>
|
||||
<td>TX Power:</td><td><span class="txpower-live" style="color: blue;">%SK_TXPOWERDBM% dBm</span></td>
|
||||
<td style="padding-left:20px;">TX Power:</td><td><span class="txpower-live" style="color: blue;">%SK_TXPOWERDBM% dBm</span></td>
|
||||
</tr>
|
||||
<tr style="display: compact;">
|
||||
<td>Password:</td><td><span style="color: blue;">%SK_APPWD%</span></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>IP (AP):</td><td><span style="color: blue;">%SK_CURRENTIP%</span></td>
|
||||
<td>NETMASK:</td><td><span style="color: blue;">%SK_CURRENTNM%</span></td>
|
||||
<td>GATEWAY:</td><td><span style="color: blue;">%SK_CURRENTGW%</span></td>
|
||||
<td style="padding-left:20px;">NETMASK:</td><td><span style="color: blue;">%SK_CURRENTNM%</span></td>
|
||||
<td style="padding-left:20px;">GATEWAY:</td><td><span style="color: blue;">%SK_CURRENTGW%</span></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div id="wifiCfg0%SK_WIFIMODECLIENT%">
|
||||
<table class="table table-borderless table-sm">
|
||||
<div %SK_WIFINAVVISIBLE% id="wifiCfg0%SK_WIFIMODECLIENT%">
|
||||
<table class="table table-borderless table-sm" style="width:auto;">
|
||||
<tbody>
|
||||
<tr style="display: compact;">
|
||||
<td>SSID:</td><td><span style="color: blue;">%SK_CLIENTSSID%</span></td>
|
||||
<td>TX Power:</td><td><span class="txpower-live" style="color: blue;">%SK_TXPOWERDBM% dBm</span></td>
|
||||
<td>RSSI:</td><td><span class="rssi-live" style="color: blue;">%SK_RSSI% dBm</span></td>
|
||||
<td style="padding-left:20px;">TX Power:</td><td><span class="txpower-live" style="color: blue;">%SK_TXPOWERDBM% dBm</span></td>
|
||||
<td style="padding-left:20px;">RSSI:</td><td><span class="rssi-live" style="color: blue;">%SK_RSSI% dBm</span></td>
|
||||
</tr>
|
||||
<tr style="display: compact;" id="wifiCfg1%SK_CLIENTDHCPON%">
|
||||
<td>DHCP:</td><td><span style="color: blue;">Enabled</span></td>
|
||||
</tr>
|
||||
<tr id="wifiCfg3%SK_CLIENTDHCPON%">
|
||||
<td>IP (assigned):</td><td><span style="color: blue;">%SK_CURRENTIP%</span></td>
|
||||
<td>NETMASK:</td><td><span style="color: blue;">%SK_CURRENTNM%</span></td>
|
||||
<td>GATEWAY:</td><td><span style="color: blue;">%SK_CURRENTGW%</span></td>
|
||||
<td style="padding-left:20px;">NETMASK:</td><td><span style="color: blue;">%SK_CURRENTNM%</span></td>
|
||||
<td style="padding-left:20px;">GATEWAY:</td><td><span style="color: blue;">%SK_CURRENTGW%</span></td>
|
||||
</tr>
|
||||
<tr id="wifiCfg3%SK_CLIENTDHCPOFF%">
|
||||
<td>IP (fixed):</td><td><span style="color: blue;">%SK_CURRENTIP%</span></td>
|
||||
<td>NETMASK:</td><td><span style="color: blue;">%SK_CURRENTNM%</span></td>
|
||||
<td>GATEWAY:</td><td><span style="color: blue;">%SK_CURRENTGW%</span></td>
|
||||
<td style="padding-left:20px;">NETMASK:</td><td><span style="color: blue;">%SK_CURRENTNM%</span></td>
|
||||
<td style="padding-left:20px;">GATEWAY:</td><td><span style="color: blue;">%SK_CURRENTGW%</span></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div %SK_USBNCMVISIBLE%>
|
||||
<table class="table table-borderless table-sm" style="width:auto;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>Connection:</td><td><span style="color: blue;">USB (CDC-NCM)</span></td>
|
||||
<td style="padding-left:20px;">IP:</td><td><span style="color: blue;">%SK_USBNCMIP%</span></td>
|
||||
<td style="padding-left:20px;">NETMASK:</td><td><span style="color: blue;">%SK_USBNCMNM%</span></td>
|
||||
<td style="padding-left:20px;">GATEWAY:</td><td><span style="color: blue;">%SK_USBNCMIP%</span></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<!-- </div>-->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -4,7 +4,7 @@ var lastStatus = 0;
|
||||
// Uses localStorage to persist the preference across sessions.
|
||||
// To re-enable: localStorage.removeItem('skipDeleteConfirm') in browser console.
|
||||
var _delPendingForm = null;
|
||||
function confirmDelete(filename) {
|
||||
function confirmDelete(ev, filename) {
|
||||
if (localStorage.getItem('skipDeleteConfirm') === 'true') {
|
||||
return true;
|
||||
}
|
||||
@@ -25,7 +25,7 @@ function confirmDelete(filename) {
|
||||
'background:#555;color:#fff;border:1px solid #444;border-radius:4px;">Cancel</button></div>';
|
||||
document.body.appendChild(overlay);
|
||||
// Capture the form that triggered this
|
||||
_delPendingForm = event.target.closest('form');
|
||||
_delPendingForm = ev.target.closest('form');
|
||||
document.getElementById('delOk').onclick = function() {
|
||||
if (document.getElementById('delNoAsk').checked) {
|
||||
localStorage.setItem('skipDeleteConfirm', 'true');
|
||||
|
||||
@@ -62,7 +62,7 @@
|
||||
</ul>
|
||||
</li>
|
||||
|
||||
<li>
|
||||
<li %SK_WIFINAVVISIBLE%>
|
||||
<a href="wifimanager.htm">
|
||||
<i style="color: yellow;" class="fa fa-wifi"></i> WiFi Manager
|
||||
</a>
|
||||
|
||||
@@ -62,7 +62,7 @@
|
||||
</ul>
|
||||
</li>
|
||||
|
||||
<li>
|
||||
<li %SK_WIFINAVVISIBLE%>
|
||||
<a href="wifimanager.htm">
|
||||
<i style="color: yellow;" class="fa fa-wifi"></i> WiFi Manager
|
||||
</a>
|
||||
|
||||
@@ -60,7 +60,7 @@
|
||||
</ul>
|
||||
</li>
|
||||
|
||||
<li>
|
||||
<li %SK_WIFINAVVISIBLE%>
|
||||
<a href="wifimanager.htm">
|
||||
<i style="color: yellow;" class="fa fa-wifi"></i> WiFi Manager
|
||||
</a>
|
||||
|
||||
@@ -1 +1 @@
|
||||
2.03
|
||||
2.07
|
||||
|
||||
@@ -60,7 +60,7 @@
|
||||
</ul>
|
||||
</li>
|
||||
|
||||
<li>
|
||||
<li %SK_WIFINAVVISIBLE%>
|
||||
<a href="wifimanager.htm" class="active">
|
||||
<i style="color: yellow;" class="fa fa-wifi"></i> WiFi Manager
|
||||
</a>
|
||||
|
||||
2
projects/tzpuPico/src/CMakeLists.txt
vendored
2
projects/tzpuPico/src/CMakeLists.txt
vendored
@@ -14,10 +14,12 @@ set(pZ80_common_src
|
||||
|
||||
set(pZ80_drivers_sharp_src
|
||||
${CMAKE_CURRENT_LIST_DIR}/drivers/Sharp/MZ700.c
|
||||
${CMAKE_CURRENT_LIST_DIR}/drivers/Sharp/MZ80A.c
|
||||
${CMAKE_CURRENT_LIST_DIR}/drivers/Sharp/RFS.c
|
||||
${CMAKE_CURRENT_LIST_DIR}/drivers/Sharp/WD1773.c
|
||||
${CMAKE_CURRENT_LIST_DIR}/drivers/Sharp/QDDrive.c
|
||||
${CMAKE_CURRENT_LIST_DIR}/drivers/Sharp/MZ-1E05.c
|
||||
${CMAKE_CURRENT_LIST_DIR}/drivers/Sharp/MZ80AFI.c
|
||||
${CMAKE_CURRENT_LIST_DIR}/drivers/Sharp/MZ-1E14.c
|
||||
${CMAKE_CURRENT_LIST_DIR}/drivers/Sharp/MZ-1E19.c
|
||||
${CMAKE_CURRENT_LIST_DIR}/drivers/Sharp/MZ-1R12.c
|
||||
|
||||
@@ -49,6 +49,7 @@
|
||||
// Include Device drivers.
|
||||
#ifdef INCLUDE_SHARP_DRIVERS
|
||||
#include "drivers/Sharp/MZ700.h" // Sharp MZ700 Persona driver.
|
||||
#include "drivers/Sharp/MZ80A.h" // Sharp MZ80A Persona driver.
|
||||
#endif
|
||||
|
||||
//-----------------------------------------
|
||||
@@ -84,21 +85,29 @@
|
||||
static PIO pio_0 = pio0, pio_1 = pio1, pio_2 = pio2_hw;
|
||||
|
||||
// PIO 0 state machines.
|
||||
static uint sm_addr = PIO_SM_0, offset_addr = 0;
|
||||
static uint sm_data = PIO_SM_1, offset_data = 0;
|
||||
// IRQ 0 - SM0 - ADDRESS LOAD
|
||||
// IRQ 1 - SM1 - DATA LOAD
|
||||
static uint sm_addr = PIO_SM_0, offset_addr = 0; // 4 words
|
||||
static uint sm_data = PIO_SM_1, offset_data = 0; // 6 words
|
||||
|
||||
// PIO 1 state machines.
|
||||
static uint smCycle = PIO_SM_0, offsetCycle = 0;
|
||||
static uint sm_busrq = PIO_SM_1, offset_busrq = 0;
|
||||
static uint __attribute__((unused)) sm_int = PIO_SM_2;
|
||||
static uint __attribute__((unused)) offset_int = 0;
|
||||
static uint sm_nmi = PIO_SM_3, offset_nmi = 0;
|
||||
// IRQ 0 - SM0 - CYCLE START
|
||||
// IRQ 5 - SM3 - REFRESH
|
||||
// IRQ 6 - SM1 - BUSACK
|
||||
static uint smCycle = PIO_SM_0, offsetCycle = 0; // 20 words
|
||||
static uint sm_busrq = PIO_SM_1, offset_busrq = 0; // 10 words
|
||||
//static uint sm_int = PIO_SM_2, offset_int = 0;
|
||||
//static uint sm_refresh = PIO_SM_3, offset_refresh = 0; // 10 words
|
||||
|
||||
// PIO 2 state machines.
|
||||
static uint sm_refresh = PIO_SM_0, offset_refresh = 0;
|
||||
static uint sm_wait = PIO_SM_1, offset_wait = 0;
|
||||
static uint sm_sync = PIO_SM_2, offset_sync = 0;
|
||||
static uint sm_reset = PIO_SM_3, offset_reset = 0;
|
||||
// IRQ 1 - SM1 - WAIT
|
||||
// IRQ 2 - SM2 - SYNC
|
||||
// IRQ 3 - SM3 - RESET
|
||||
// IRQ 4 - SM0 - NMI
|
||||
static uint sm_nmi = PIO_SM_0, offset_nmi = 0; // 3 words
|
||||
static uint sm_wait = PIO_SM_1, offset_wait = 0; // 6 words
|
||||
static uint sm_sync = PIO_SM_2, offset_sync = 0; // 3 words
|
||||
static uint sm_reset = PIO_SM_3, offset_reset = 0; // 2 words
|
||||
|
||||
//-------------------------------------------------------------------------------------------------
|
||||
// JSON configration helper tables.
|
||||
@@ -140,8 +149,8 @@ static const size_t memoryTypeMapSize = sizeof(memoryTypeMap) / sizeof(memoryTyp
|
||||
// This table allows JSON configuration to specify a function by giving a text name which is then mapped to the actual
|
||||
// internal function.
|
||||
static const t_FuncMap funcMap[] = {
|
||||
{"DRAMREFRESH", (t_MemoryFunc) Z80CPU_refreshDRAM}, // This function is intended to perform dummy M1 Fetch cycles in order
|
||||
// to refresh host DRAM during internal RAM access.
|
||||
{"DRAMREFRESH", (t_MemoryFunc) Z80CPU_refreshDRAM}, // This function is intended to perform dummy M1 Fetch cycles in order
|
||||
// to refresh host DRAM during internal RAM access.
|
||||
};
|
||||
static const size_t funcMapSize = sizeof(funcMap) / sizeof(funcMap[0]);
|
||||
|
||||
@@ -167,6 +176,8 @@ static const t_VirtualFuncMap virtualFuncMap[] = {
|
||||
#ifdef INCLUDE_SHARP_DRIVERS
|
||||
{"MZ700", (t_VirtualFunc) MZ700_Init}, // This virtual function creates a Sharp MZ700 'persona' whereby
|
||||
// it sets up devices and memory structure to replicate the MZ700 logic.
|
||||
{"MZ80A", (t_VirtualFunc) MZ80A_Init}, // This virtual function creates a Sharp MZ80A 'persona' whereby
|
||||
// it sets up devices and memory structure to replicate the MZ80A logic.
|
||||
#endif
|
||||
};
|
||||
static const size_t virtualFuncMapSize = sizeof(virtualFuncMap) / sizeof(virtualFuncMap[0]);
|
||||
@@ -1006,6 +1017,18 @@ bool Z80CPU_configFromJSON(t_Z80CPU *cpu, int cfgAppNo)
|
||||
return (false);
|
||||
}
|
||||
|
||||
// Extract core object for runtime parameters.
|
||||
cJSON *core = cJSON_GetObjectItem(z80_obj, "core");
|
||||
if (cJSON_IsObject(core))
|
||||
{
|
||||
// "z80refresh": 1 enables Z80 DRAM refresh during virtual memory opcode fetches.
|
||||
// When the Z80 runs from virtual ROM/RAM, physical DRAM doesn't get refreshed.
|
||||
// Enable this when mixing virtual ROM with physical DRAM (e.g., RFS on MZ-80A).
|
||||
cJSON *z80refresh = cJSON_GetObjectItem(core, "z80refresh");
|
||||
cpu->refreshEnable = (cJSON_IsNumber(z80refresh) && z80refresh->valueint == 1);
|
||||
debugf("Z80 Core: z80refresh(%d)\r\n", cpu->refreshEnable);
|
||||
}
|
||||
|
||||
// Get the memory array
|
||||
cJSON *memorymap = cJSON_GetObjectItem(z80_obj, "memory");
|
||||
if (!cJSON_IsArray(memorymap))
|
||||
@@ -2019,21 +2042,52 @@ void Z80CPU_taskMirrorInternalRAMToPhysical(t_Z80CPU *cpu, uint32_t intAddr, uin
|
||||
|
||||
// Method, called as an optional function to an internal memory fetch, which will activate hardware to ensure
|
||||
// any motherboard hosted DRAM is refreshed.
|
||||
uint8_t __func_in_RAM(Z80CPU_refreshDRAM)(bool read, uint16_t addr, uint8_t data)
|
||||
// This function can be attached to any Memory region or can be globally enabled (refreshEnabled = 1) for all
|
||||
// virtual memory opcode fetches.
|
||||
uint8_t __func_in_RAM(Z80CPU_refreshDRAM)(void *context, bool read, uint16_t addr, uint8_t data)
|
||||
{
|
||||
// Locals.
|
||||
static uint16_t r_reg = 0; // Cant use the Z80 refresh register as it is running at a higher speed in memory.
|
||||
t_Z80CPU *cpu = (t_Z80CPU *) context;
|
||||
|
||||
// If a bus transaction is underway, we dont wait, virtual memory processing is much faster.
|
||||
if(GET_IRQ_STATE(pio_1, 0) == 0)
|
||||
return(0);
|
||||
|
||||
// The refresh address is a combination of the I register and the R register.
|
||||
r_reg = ((uint8_t)cpu->_Z80.i << 8) | r_reg;
|
||||
|
||||
PUSH_FIFO(pio_0, sm_addr, (addr << 16) | 0xFFFF); // Preload Fifo with address.
|
||||
PUSH_FIFO(pio_0, sm_addr, (((uint16_t) r_reg) << 16) | 0xFFFF); // Pre-load refresh address.
|
||||
|
||||
CLEAR_IRQ(pio_1, 0); // Enable read state machine.
|
||||
CLEAR_IRQ(pio_0, 0); // Enable address load.
|
||||
|
||||
// No waiting, we just push 8 instructions which form T1/T2 and exit.
|
||||
PUSH_INSTR32(pio_1,
|
||||
smCycle,
|
||||
0xfc0e, // set pins, 14 side 3 Set /M1 low
|
||||
0x2013); // T1 wait 0 gpio, 35
|
||||
PUSH_INSTR32(pio_1,
|
||||
smCycle,
|
||||
0xf80a, // set pins, 10 side 2 Set /M1 low, /MREQ low, /RD low
|
||||
(0x0000 + offsetCycle + 6)); // jmp offset+6, wait state loop.
|
||||
PUSH_INSTR32(pio_1,
|
||||
smCycle,
|
||||
0x2093, // T3 wait 1 gpio, 35
|
||||
(0x0000 + offsetCycle + 12)); // jmp offset+12, execute refresh T3/T4.
|
||||
//0xfc0d); // set pins, 13 side 3 Set /M1 high, /MREQ high, /RD high, /RFSH low
|
||||
// PUSH_INSTR32(pio_1,
|
||||
// smCycle,
|
||||
// 0x2013, // T3 wait 0 gpio, 35
|
||||
// (0x0000 + offsetCycle + 13)); // jmp offset+12, execute refresh T3/T4.
|
||||
|
||||
// 0x2013, // T3 wait 0 gpio, 35
|
||||
// (0x0000 + offsetCycle + 12)); // jmp offset+12, execute refresh T3/T4.
|
||||
|
||||
r_reg = ++r_reg & 0x7f; // R register only 7 bits incrementing.
|
||||
CLEAR_IRQ(pio_0, 0); // Enable refresh address load.
|
||||
|
||||
return (0);
|
||||
// Only start a refresh if the state machine is waiting, exit otherwise. This is because the hardware will be going
|
||||
// at a much slower rate than internal memory execution of instructions.
|
||||
if (GET_IRQ_STATE(pio_2, 0))
|
||||
{
|
||||
PUSH_FIFO(pio_0, sm_addr, ((r_reg) << 16) | 0xFFFF); // pre-load refresh address.
|
||||
CLEAR_IRQ(pio_0, 0); // Enable address load.
|
||||
CLEAR_IRQ(pio_2, 0); // Enable refresh state machine.
|
||||
r_reg++; // Increment refresh counter.
|
||||
}
|
||||
return (0);
|
||||
}
|
||||
|
||||
@@ -2078,7 +2132,7 @@ int Z80CPU_init_pio(void)
|
||||
gpio_set_slew_rate(i, GPIO_SLEW_RATE_FAST);
|
||||
}
|
||||
|
||||
// Setup output pins.
|
||||
// Setup output pins. FUNCSEL routes the output driver to the pin for a given PIO.
|
||||
for (uint i = Z80_PIN_RD; i <= Z80_PIN_IORQ; i++)
|
||||
{
|
||||
gpio_init(i);
|
||||
@@ -2216,19 +2270,19 @@ int Z80CPU_init_pio(void)
|
||||
*/
|
||||
|
||||
// NMI - Process non-maskable NMI interrupt requests.
|
||||
offset_nmi = pio_add_program(pio_1, &z80_nmi_program);
|
||||
offset_nmi = pio_add_program(pio_2, &z80_nmi_program);
|
||||
pio_sm_config c_nmi = z80_nmi_program_get_default_config(offset_nmi);
|
||||
sm_config_set_jmp_pin(&c_nmi, Z80_PIN_NMI); // for JMP
|
||||
pio_sm_set_consecutive_pindirs(pio_1, sm_nmi, Z80_PIN_NMI, 1, false);
|
||||
pio_sm_set_consecutive_pindirs(pio_2, sm_nmi, Z80_PIN_NMI, 1, false);
|
||||
sm_config_set_clkdiv(&c_nmi, 1.0f);
|
||||
pio_sm_clear_fifos(pio_1, sm_nmi);
|
||||
pio_sm_restart(pio_1, sm_nmi);
|
||||
initResult = pio_sm_init(pio_1, sm_nmi, offset_nmi, &c_nmi);
|
||||
pio_sm_clear_fifos(pio_2, sm_nmi);
|
||||
pio_sm_restart(pio_2, sm_nmi);
|
||||
initResult = pio_sm_init(pio_2, sm_nmi, offset_nmi, &c_nmi);
|
||||
if (initResult != 0)
|
||||
{
|
||||
return (initResult);
|
||||
}
|
||||
pio_sm_set_enabled(pio_1, sm_nmi, true);
|
||||
pio_sm_set_enabled(pio_2, sm_nmi, true);
|
||||
|
||||
// RESET - Process host reset requests.
|
||||
offset_reset = pio_add_program(pio_2, &z80_reset_program);
|
||||
@@ -2270,28 +2324,28 @@ int Z80CPU_init_pio(void)
|
||||
pio_sm_set_enabled(pio_1, smCycle, true);
|
||||
|
||||
// Refresh State Machine. Performs Z80 Refresh cycles.
|
||||
offset_refresh = pio_add_program(pio_2, &z80_refresh_program);
|
||||
pio_sm_config c_refresh = z80_refresh_program_get_default_config(offset_refresh);
|
||||
sm_config_set_in_pins(&c_refresh, Z80_PIN_DATA_0);
|
||||
sm_config_set_jmp_pin(&c_refresh, Z80_PIN_WAIT); // for JMP
|
||||
sm_config_set_set_pins(&c_refresh, Z80_PIN_M1, 4); // /M1, /RFSH, /MREQ, /IORQ
|
||||
sm_config_set_sideset_pins(&c_refresh, Z80_PIN_RD); // /RD, /WR
|
||||
sm_config_set_out_shift(&c_refresh, true, true, 32); // Shift right, auto pull 32 bits.
|
||||
sm_config_set_in_shift(&c_refresh, false, true, 8); // Shift left, auto push 8 bits.
|
||||
pio_sm_set_consecutive_pindirs(pio_2, sm_refresh, Z80_PIN_RD, 2, true);
|
||||
pio_sm_set_consecutive_pindirs(pio_2, sm_refresh, Z80_PIN_M1, 4, true);
|
||||
pio_sm_set_consecutive_pindirs(pio_2, sm_refresh, Z80_PIN_DATA_0, 8, false);
|
||||
pio_sm_set_consecutive_pindirs(pio_2, sm_refresh, Z80_PIN_WAIT, 1, false);
|
||||
pio_sm_set_pins_with_mask(pio_2, sm_refresh, 0b11111111000000000000000000000000, 0b11111111000000000000000000000000);
|
||||
sm_config_set_clkdiv(&c_refresh, 1.0f);
|
||||
pio_sm_clear_fifos(pio_2, sm_refresh);
|
||||
pio_sm_restart(pio_2, sm_refresh);
|
||||
initResult = pio_sm_init(pio_2, sm_refresh, offset_refresh, &c_refresh);
|
||||
if (initResult != 0)
|
||||
{
|
||||
return (initResult);
|
||||
}
|
||||
pio_sm_set_enabled(pio_2, sm_refresh, true);
|
||||
// offset_refresh = pio_add_program(pio_1, &z80_refresh_program);
|
||||
// pio_sm_config c_refresh = z80_refresh_program_get_default_config(offset_refresh);
|
||||
// sm_config_set_in_pins(&c_refresh, Z80_PIN_DATA_0);
|
||||
// sm_config_set_jmp_pin(&c_refresh, Z80_PIN_WAIT); // for JMP
|
||||
// sm_config_set_set_pins(&c_refresh, Z80_PIN_M1, 4); // /M1, /RFSH, /MREQ, /IORQ
|
||||
// sm_config_set_sideset_pins(&c_refresh, Z80_PIN_RD); // /RD, /WR
|
||||
// sm_config_set_out_shift(&c_refresh, true, true, 32); // Shift right, auto pull 32 bits.
|
||||
// sm_config_set_in_shift(&c_refresh, false, true, 8); // Shift left, auto push 8 bits.
|
||||
// pio_sm_set_consecutive_pindirs(pio_1, sm_refresh, Z80_PIN_RD, 2, true);
|
||||
// pio_sm_set_consecutive_pindirs(pio_1, sm_refresh, Z80_PIN_M1, 4, true);
|
||||
// pio_sm_set_consecutive_pindirs(pio_1, sm_refresh, Z80_PIN_DATA_0, 8, false);
|
||||
// pio_sm_set_consecutive_pindirs(pio_1, sm_refresh, Z80_PIN_WAIT, 1, false);
|
||||
// pio_sm_set_pins_with_mask(pio_1, sm_refresh, 0b11111111000000000000000000000000, 0b11111111000000000000000000000000);
|
||||
// sm_config_set_clkdiv(&c_refresh, 1.0f);
|
||||
// pio_sm_clear_fifos(pio_1, sm_refresh);
|
||||
// pio_sm_restart(pio_1, sm_refresh);
|
||||
// initResult = pio_sm_init(pio_1, sm_refresh, offset_refresh, &c_refresh);
|
||||
// if (initResult != 0)
|
||||
// {
|
||||
// return (initResult);
|
||||
// }
|
||||
// pio_sm_set_enabled(pio_1, sm_refresh, true);
|
||||
|
||||
// Wait state generator sm.
|
||||
offset_wait = pio_add_program(pio_2, &z80_wait_program);
|
||||
@@ -2385,6 +2439,7 @@ t_Z80CPU *Z80CPU_init(void)
|
||||
cpu._z80PSRAM = NULL;
|
||||
cpu._drivers.driver = NULL;
|
||||
cpu.halt = false;
|
||||
cpu.refreshEnable = false;
|
||||
cpu.hold = false;
|
||||
cpu.forceReset = false;
|
||||
|
||||
@@ -2921,6 +2976,15 @@ uint8_t __func_in_RAM(Z80CPU_fetchOpcode)(void *context, uint16_t address)
|
||||
Z80CPU_syncPhysical(cpu);
|
||||
}
|
||||
data = Z80CPU_readMem(cpu, address, membankptr, cpu->_memAttr[bank][block].waitStates);
|
||||
|
||||
// DRAM refresh: when fetching opcodes from virtual memory, the physical M1 cycle
|
||||
// (which includes a RFSH bus cycle) is not generated. Physical DRAM on the host
|
||||
// motherboard will decay without a refresh. Trigger a full M1 cycle but ignore
|
||||
// the read opcode.
|
||||
if(cpu->refreshEnable)
|
||||
{
|
||||
Z80CPU_refreshDRAM(context, true, address, data);
|
||||
}
|
||||
}
|
||||
|
||||
// BUSRQ/BUSACK State, INT, NMI? Inline single test for speed expanding if any IRQ is set.
|
||||
@@ -3192,42 +3256,44 @@ uint8_t __func_in_RAM(Z80CPU_fetchPhysicalMem)(t_Z80CPU *cpu, uint16_t addr)
|
||||
|
||||
PUSH_INSTR32(pio_1,
|
||||
smCycle,
|
||||
0xfc0e, // set pins, 14 side 3 Set /M1 low
|
||||
0x2013); // T1 wait 0 gpio, 35
|
||||
0xfc0e, // set pins, 14 side 3 Set /M1 low
|
||||
0x2013); // T1 wait 0 gpio, 35
|
||||
PUSH_INSTR32(pio_1,
|
||||
smCycle,
|
||||
0xf80a, // set pins, 10 side 2 Set /M1 low, /MREQ low, /RD low
|
||||
0xf80a, // set pins, 10 side 2 Set /M1 low, /MREQ low, /RD low
|
||||
(0x0000 + offsetCycle + 6)); // jmp offset+6, wait state loop.
|
||||
|
||||
PUSH_INSTR32(pio_1,
|
||||
smCycle,
|
||||
0x2093, // T3 wait 1 gpio, 35
|
||||
0x4008); // in pins, 8
|
||||
0x2093, // T3 wait 1 gpio, 35
|
||||
0x4008); // in pins, 8
|
||||
|
||||
// Prepare Refresh cycle.
|
||||
PUSH_INSTR32(pio_1,
|
||||
smCycle,
|
||||
0xfc0d, // set pins, 13 side 3 Set /M1 high, /MREQ high, /RD high, /RFSH low
|
||||
0x2013); // T3 wait 0 gpio, 35
|
||||
0xfc0d, // set pins, 13 side 3 Set /M1 high, /MREQ high, /RD high, /RFSH low
|
||||
(0x0000 + offsetCycle + 12)); // jmp offset+13, execute refresh T3/T4.
|
||||
// 0x2013); // T3 wait 0 gpio, 35
|
||||
|
||||
// Retrieve opcode during lull period.
|
||||
WAIT_RX_FIFO_HAS_DATA(pio_1, smCycle);
|
||||
CLEAR_IRQ(pio_0, 0); // Enable refresh address load.
|
||||
data = POP_FIFO(pio_1, smCycle);
|
||||
|
||||
// Enter refresh cycle.
|
||||
PUSH_INSTR32(pio_1,
|
||||
smCycle,
|
||||
0xfc09, // set pins, 9 side 3 Set /M1 high, /MREQ low, /RD high, /RFSH low
|
||||
0x2093); // T4 wait 1 gpio, 35
|
||||
PUSH_INSTR32(pio_1,
|
||||
smCycle,
|
||||
0x2013, // T4 wait 0 gpio, 35
|
||||
0xfd0d); // set pins, 15 side 3 Set /M1 high, /MREQ high, /RD high, /RFSH low delay 1
|
||||
PUSH_INSTR32(pio_1,
|
||||
smCycle,
|
||||
0xfc0f, // set pins, 15 side 3 Set /M1 high, /MREQ high, /RD high, /RFSH high
|
||||
(0x0000 + offsetCycle)); // jmp start
|
||||
// REFRESH Cycle during T3/T4 is performed withn the PIO cycle_program program.
|
||||
|
||||
// // Enter refresh cycle.
|
||||
// PUSH_INSTR32(pio_1,
|
||||
// smCycle,
|
||||
// 0xfc09, // set pins, 9 side 3 Set /M1 high, /MREQ low, /RD high, /RFSH low
|
||||
// 0x2093); // T4 wait 1 gpio, 35
|
||||
// PUSH_INSTR32(pio_1,
|
||||
// smCycle,
|
||||
// 0x2013, // T4 wait 0 gpio, 35
|
||||
// 0xfd0d); // set pins, 15 side 3 Set /M1 high, /MREQ high, /RD high, /RFSH low delay 1
|
||||
// PUSH_INSTR32(pio_1,
|
||||
// smCycle,
|
||||
// 0xfc0f, // set pins, 15 side 3 Set /M1 high, /MREQ high, /RD high, /RFSH high
|
||||
// (0x0000 + offsetCycle)); // jmp start
|
||||
|
||||
#ifdef DEBUG_PIO
|
||||
debugf("Fetch %04x -> %02x\r\n", addr, (uint8_t) data);
|
||||
|
||||
839
projects/tzpuPico/src/drivers/Sharp/MZ80A.c
Normal file
839
projects/tzpuPico/src/drivers/Sharp/MZ80A.c
Normal file
@@ -0,0 +1,839 @@
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// Name: MZ80A.c
|
||||
// Created: Apr 2026
|
||||
// Author(s): Philip Smart
|
||||
// Description: Z80 CPU DRIVER - Sharp MZ-80A
|
||||
// This file contains setup and drivers to mimic a Sharp MZ-80A machine internally to
|
||||
// increase speed through use of internal RAM and provide virtual expansion drivers.
|
||||
// Based on the MZ700 driver but adapted for the MZ-80A memory map and peripherals.
|
||||
// Credits:
|
||||
// Copyright: (c) 2019-2026 Philip Smart <philip.smart@net2net.org>
|
||||
//
|
||||
// History: Apr 2026 v1.0 - Initial write based on MZ700 driver.
|
||||
//
|
||||
// Notes: See Makefile to enable/disable conditional components
|
||||
//
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// This source file is free software: you can redistribute it and#or modify
|
||||
// it under the terms of the GNU General Public License as published
|
||||
// by the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This source file is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#include "Z80CPU.h"
|
||||
#define Z80_STATIC
|
||||
#include "Z80.h"
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <pico/printf.h>
|
||||
#include <pico/stdlib.h>
|
||||
#include "pico/util/queue.h"
|
||||
#include "pico/multicore.h"
|
||||
#include "intercore.h"
|
||||
#include "debug.h"
|
||||
#include "drivers/Sharp/MZ.h"
|
||||
#include "drivers/Sharp/RFS.h"
|
||||
#include "drivers/Sharp/WD1773.h"
|
||||
#include "drivers/Sharp/QDDrive.h"
|
||||
#include "drivers/Sharp/MZ80AFI.h"
|
||||
#include "drivers/Sharp/MZ-1E14.h"
|
||||
#include "drivers/Sharp/MZ-1E19.h"
|
||||
#include "drivers/Sharp/MZ-1R12.h"
|
||||
#include "drivers/Sharp/MZ-1R18.h"
|
||||
#include "drivers/Sharp/MZ80A.h"
|
||||
|
||||
// Map to contain interface name against method to initialise it.
|
||||
static t_InterfaceFuncMap interfaceFuncMap[] = {
|
||||
{"RFS", false, RFS_Init, RFS_Reset, RFS_PollCB, RFS_TaskProcessor},
|
||||
{"MZ80AFI", false, MZ80AFI_Init, MZ80AFI_Reset, MZ80AFI_PollCB, MZ80AFI_TaskProcessor},
|
||||
{"MZ-1E14", false, MZ1E14_Init, MZ1E14_Reset, MZ1E14_PollCB, MZ1E14_TaskProcessor},
|
||||
{"MZ-1E19", false, MZ1E19_Init, MZ1E19_Reset, MZ1E19_PollCB, MZ1E19_TaskProcessor},
|
||||
{"MZ-1R12", false, MZ1R12_Init, MZ1R12_Reset, MZ1R12_PollCB, MZ1R12_TaskProcessor},
|
||||
{"MZ-1R18", false, MZ1R18_Init, MZ1R18_Reset, MZ1R18_PollCB, MZ1R18_TaskProcessor},
|
||||
};
|
||||
static const size_t interfaceFuncMapSize = sizeof(interfaceFuncMap) / sizeof(interfaceFuncMap[0]);
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// Comprehensive Intel 8253 PIT (Programmable Interval Timer) emulation.
|
||||
// The MZ-80A uses an 8253 PIT at memory-mapped addresses 0xE004-0xE007
|
||||
// clocked at ~895 kHz.
|
||||
// Counter 0 = 0xE004, Counter 1 = 0xE005, Counter 2 = 0xE006,
|
||||
// Control = 0xE007.
|
||||
//
|
||||
// Supports all 6 counter modes:
|
||||
// Mode 0: Interrupt on Terminal Count
|
||||
// Mode 1: Hardware Retriggerable One-Shot
|
||||
// Mode 2: Rate Generator
|
||||
// Mode 3: Square Wave Generator
|
||||
// Mode 4: Software Triggered Strobe
|
||||
// Mode 5: Hardware Triggered Strobe
|
||||
//
|
||||
// Supports: BCD/binary counting, counter latch, LSB/MSB/LSB-then-MSB
|
||||
// read/load modes, and proper output pin state tracking.
|
||||
// -----------------------------------------------------------------------
|
||||
#define PIT_NUM_COUNTERS 3
|
||||
#define PIT_CLOCK_HZ 895000 // ~895 kHz clock input on MZ-80A
|
||||
|
||||
typedef struct
|
||||
{
|
||||
uint16_t count; // Current counter value (CE = Counting Element).
|
||||
uint16_t reload; // Reload value (CR = Count Register, written by CPU).
|
||||
uint16_t latchVal; // Latched counter value for reading.
|
||||
bool latched; // True if counter value has been latched (counter latch cmd).
|
||||
uint8_t mode; // Counter mode (0-5).
|
||||
uint8_t rlMode; // Read/Load mode: 1=LSB only, 2=MSB only, 3=LSB then MSB.
|
||||
bool bcd; // True = BCD counting, false = binary.
|
||||
bool readLsbNext; // For RL mode 3 reads: true = next read byte is LSB.
|
||||
bool writeLsbNext; // For RL mode 3 writes: true = next write byte is LSB.
|
||||
bool gate; // Gate input state (active high for modes 0,2,3,4; edge for 1,5).
|
||||
bool output; // Output pin state.
|
||||
bool counting; // Counter has been loaded and is actively counting.
|
||||
bool nullCount; // True while new count written but not yet loaded into CE.
|
||||
bool reloadPending; // For modes 2/3: reload pending on next terminal count.
|
||||
absolute_time_t lastUpdate; // Last time counter was updated.
|
||||
} t_PITCounter;
|
||||
|
||||
static t_PITCounter pitCounter[PIT_NUM_COUNTERS];
|
||||
static bool pitInitialized = false;
|
||||
|
||||
// Decrement a 16-bit value by 1 in BCD mode (4-digit BCD: 0000-9999).
|
||||
static uint16_t pitBcdDecrement(uint16_t val)
|
||||
{
|
||||
uint16_t d0 = (val) & 0xF;
|
||||
uint16_t d1 = (val >> 4) & 0xF;
|
||||
uint16_t d2 = (val >> 8) & 0xF;
|
||||
uint16_t d3 = (val >> 12) & 0xF;
|
||||
uint16_t bin = d0 + d1 * 10 + d2 * 100 + d3 * 1000;
|
||||
if (bin == 0)
|
||||
bin = 10000;
|
||||
bin--;
|
||||
d0 = bin % 10;
|
||||
d1 = (bin / 10) % 10;
|
||||
d2 = (bin / 100) % 10;
|
||||
d3 = (bin / 1000) % 10;
|
||||
return (uint16_t)(d0 | (d1 << 4) | (d2 << 8) | (d3 << 12));
|
||||
}
|
||||
|
||||
// Get the effective count value for terminal count detection.
|
||||
static uint32_t pitEffectiveCount(t_PITCounter *ctr)
|
||||
{
|
||||
if (ctr->reload == 0)
|
||||
return ctr->bcd ? 10000 : 0x10000;
|
||||
return ctr->reload;
|
||||
}
|
||||
|
||||
// Update a PIT counter based on elapsed wall-clock time.
|
||||
static void pitUpdateCounter(int ch)
|
||||
{
|
||||
t_PITCounter *ctr = &pitCounter[ch];
|
||||
|
||||
if (!ctr->counting)
|
||||
return;
|
||||
|
||||
if (!ctr->gate && (ctr->mode == 0 || ctr->mode == 2 || ctr->mode == 3 || ctr->mode == 4))
|
||||
return;
|
||||
|
||||
absolute_time_t now = get_absolute_time();
|
||||
int64_t elapsedUs = absolute_time_diff_us(ctr->lastUpdate, now);
|
||||
if (elapsedUs <= 0)
|
||||
return;
|
||||
|
||||
uint32_t ticks = (uint32_t)((elapsedUs * (int64_t)PIT_CLOCK_HZ) / 1000000);
|
||||
if (ticks == 0)
|
||||
return;
|
||||
|
||||
ctr->lastUpdate = now;
|
||||
|
||||
if (ctr->bcd)
|
||||
{
|
||||
if (ticks > 20000)
|
||||
ticks = 20000;
|
||||
for (uint32_t t = 0; t < ticks; t++)
|
||||
{
|
||||
ctr->count = pitBcdDecrement(ctr->count);
|
||||
if (ctr->count == 0)
|
||||
{
|
||||
switch (ctr->mode)
|
||||
{
|
||||
case 0:
|
||||
ctr->output = true;
|
||||
break;
|
||||
case 1:
|
||||
ctr->output = true;
|
||||
ctr->counting = false;
|
||||
return;
|
||||
case 2:
|
||||
ctr->count = ctr->reload ? ctr->reload : 0x9999;
|
||||
ctr->output = true;
|
||||
break;
|
||||
case 3:
|
||||
ctr->count = ctr->reload ? ctr->reload : 0x9999;
|
||||
ctr->output = !ctr->output;
|
||||
break;
|
||||
case 4:
|
||||
ctr->output = false;
|
||||
ctr->counting = false;
|
||||
return;
|
||||
case 5:
|
||||
ctr->output = false;
|
||||
ctr->counting = false;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Binary counting.
|
||||
uint32_t c = ctr->count;
|
||||
|
||||
switch (ctr->mode)
|
||||
{
|
||||
case 0:
|
||||
if (ticks >= c)
|
||||
{
|
||||
ctr->output = true;
|
||||
uint32_t rem = ticks - c;
|
||||
ctr->count = (uint16_t)(0x10000 - (rem % 0x10000));
|
||||
if (ctr->count == 0x10000)
|
||||
ctr->count = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
ctr->count = (uint16_t)(c - ticks);
|
||||
}
|
||||
break;
|
||||
|
||||
case 1:
|
||||
if (ticks >= c)
|
||||
{
|
||||
ctr->output = true;
|
||||
ctr->count = 0;
|
||||
ctr->counting = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
ctr->count = (uint16_t)(c - ticks);
|
||||
}
|
||||
break;
|
||||
|
||||
case 2:
|
||||
{
|
||||
uint32_t rl = pitEffectiveCount(ctr);
|
||||
if (ticks >= c)
|
||||
{
|
||||
uint32_t rem = ticks - c;
|
||||
ctr->count = (uint16_t)(rl - (rem % rl));
|
||||
if (ctr->count == 0)
|
||||
ctr->count = (uint16_t)rl;
|
||||
ctr->output = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
ctr->count = (uint16_t)(c - ticks);
|
||||
ctr->output = (ctr->count != 1);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case 3:
|
||||
{
|
||||
uint32_t rl = pitEffectiveCount(ctr);
|
||||
uint32_t halfPeriod = rl / 2;
|
||||
if (halfPeriod == 0)
|
||||
halfPeriod = 1;
|
||||
|
||||
uint32_t totalTicks;
|
||||
if (ticks >= c)
|
||||
totalTicks = ticks - c;
|
||||
else
|
||||
{
|
||||
ctr->count = (uint16_t)(c - ticks);
|
||||
break;
|
||||
}
|
||||
uint32_t toggles = 1 + (totalTicks / halfPeriod);
|
||||
if (toggles & 1)
|
||||
ctr->output = !ctr->output;
|
||||
uint32_t rem = totalTicks % halfPeriod;
|
||||
ctr->count = (uint16_t)(halfPeriod - rem);
|
||||
if (ctr->count == 0)
|
||||
ctr->count = (uint16_t)halfPeriod;
|
||||
}
|
||||
break;
|
||||
|
||||
case 4:
|
||||
if (ticks >= c)
|
||||
{
|
||||
ctr->output = true;
|
||||
ctr->count = 0;
|
||||
ctr->counting = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
ctr->count = (uint16_t)(c - ticks);
|
||||
ctr->output = true;
|
||||
}
|
||||
break;
|
||||
|
||||
case 5:
|
||||
if (ticks >= c)
|
||||
{
|
||||
ctr->output = true;
|
||||
ctr->count = 0;
|
||||
ctr->counting = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
ctr->count = (uint16_t)(c - ticks);
|
||||
ctr->output = true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// I/O handler for the 8253 PIT (memory-mapped at 0xE004-0xE007 on MZ-80A).
|
||||
uint8_t MZ80A_IO_PIT(t_Z80CPU *cpu, bool read, uint16_t addr, uint8_t data)
|
||||
{
|
||||
uint8_t port = addr & 0x03;
|
||||
|
||||
if (!pitInitialized)
|
||||
{
|
||||
for (int i = 0; i < PIT_NUM_COUNTERS; i++)
|
||||
{
|
||||
memset(&pitCounter[i], 0, sizeof(t_PITCounter));
|
||||
pitCounter[i].gate = true;
|
||||
pitCounter[i].output = true;
|
||||
pitCounter[i].readLsbNext = true;
|
||||
pitCounter[i].writeLsbNext = true;
|
||||
pitCounter[i].rlMode = 3;
|
||||
pitCounter[i].lastUpdate = get_absolute_time();
|
||||
}
|
||||
pitInitialized = true;
|
||||
}
|
||||
|
||||
// Control register (port 3, write only).
|
||||
if (port == 3)
|
||||
{
|
||||
if (!read)
|
||||
{
|
||||
uint8_t ch = (data >> 6) & 0x03;
|
||||
uint8_t rl = (data >> 4) & 0x03;
|
||||
|
||||
if (ch == 3)
|
||||
return 0xFF;
|
||||
|
||||
if (rl == 0)
|
||||
{
|
||||
if (!pitCounter[ch].latched)
|
||||
{
|
||||
pitUpdateCounter(ch);
|
||||
pitCounter[ch].latchVal = pitCounter[ch].count;
|
||||
pitCounter[ch].latched = true;
|
||||
pitCounter[ch].readLsbNext = true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
pitCounter[ch].rlMode = rl;
|
||||
pitCounter[ch].mode = (data >> 1) & 0x07;
|
||||
pitCounter[ch].bcd = (data & 0x01) != 0;
|
||||
pitCounter[ch].readLsbNext = true;
|
||||
pitCounter[ch].writeLsbNext = true;
|
||||
pitCounter[ch].latched = false;
|
||||
pitCounter[ch].nullCount = true;
|
||||
pitCounter[ch].counting = false;
|
||||
|
||||
switch (pitCounter[ch].mode)
|
||||
{
|
||||
case 0:
|
||||
pitCounter[ch].output = false;
|
||||
break;
|
||||
case 1:
|
||||
pitCounter[ch].output = true;
|
||||
break;
|
||||
case 2:
|
||||
case 3:
|
||||
pitCounter[ch].output = true;
|
||||
break;
|
||||
case 4:
|
||||
case 5:
|
||||
pitCounter[ch].output = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return 0xFF;
|
||||
}
|
||||
|
||||
// Counter data ports (0-2).
|
||||
if (port >= PIT_NUM_COUNTERS)
|
||||
return 0xFF;
|
||||
|
||||
t_PITCounter *ctr = &pitCounter[port];
|
||||
|
||||
if (read)
|
||||
{
|
||||
uint16_t val;
|
||||
if (ctr->latched)
|
||||
val = ctr->latchVal;
|
||||
else
|
||||
{
|
||||
pitUpdateCounter(port);
|
||||
val = ctr->count;
|
||||
}
|
||||
|
||||
uint8_t result;
|
||||
switch (ctr->rlMode)
|
||||
{
|
||||
case 1:
|
||||
result = (uint8_t)(val & 0xFF);
|
||||
ctr->latched = false;
|
||||
break;
|
||||
case 2:
|
||||
result = (uint8_t)((val >> 8) & 0xFF);
|
||||
ctr->latched = false;
|
||||
break;
|
||||
case 3:
|
||||
if (ctr->readLsbNext)
|
||||
{
|
||||
ctr->readLsbNext = false;
|
||||
result = (uint8_t)(val & 0xFF);
|
||||
}
|
||||
else
|
||||
{
|
||||
ctr->readLsbNext = true;
|
||||
result = (uint8_t)((val >> 8) & 0xFF);
|
||||
ctr->latched = false;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
result = (uint8_t)(val & 0xFF);
|
||||
ctr->latched = false;
|
||||
break;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
else
|
||||
{
|
||||
switch (ctr->rlMode)
|
||||
{
|
||||
case 1:
|
||||
ctr->reload = (ctr->reload & 0xFF00) | data;
|
||||
ctr->count = ctr->reload;
|
||||
ctr->counting = true;
|
||||
ctr->nullCount = false;
|
||||
ctr->lastUpdate = get_absolute_time();
|
||||
break;
|
||||
case 2:
|
||||
ctr->reload = (ctr->reload & 0x00FF) | ((uint16_t)data << 8);
|
||||
ctr->count = ctr->reload;
|
||||
ctr->counting = true;
|
||||
ctr->nullCount = false;
|
||||
ctr->lastUpdate = get_absolute_time();
|
||||
break;
|
||||
case 3:
|
||||
if (ctr->writeLsbNext)
|
||||
{
|
||||
ctr->reload = (ctr->reload & 0xFF00) | data;
|
||||
ctr->writeLsbNext = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
ctr->reload = (ctr->reload & 0x00FF) | ((uint16_t)data << 8);
|
||||
ctr->count = ctr->reload;
|
||||
ctr->counting = true;
|
||||
ctr->nullCount = false;
|
||||
ctr->lastUpdate = get_absolute_time();
|
||||
ctr->writeLsbNext = true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Method to write out a file data buffer into the internal emulation RAM as directed by the configuration array.
|
||||
void MZ80A_readFileData(void *ctx, void *cfg, int filepos, char *buf, int len)
|
||||
{
|
||||
// Locals.
|
||||
t_Z80CPU *cpu = (t_Z80CPU *) ctx;
|
||||
t_drvROMConfig *config = (t_drvROMConfig *) cfg;
|
||||
|
||||
if (cpu == NULL || config == NULL)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
for (uint32_t fileIdx = 0; fileIdx < len; fileIdx++)
|
||||
{
|
||||
for (int loadIdx = 0; loadIdx < config->romAddrCount; loadIdx++)
|
||||
{
|
||||
int baseWindowAddr = (filepos - config->romAddr[loadIdx].position) + fileIdx;
|
||||
if (baseWindowAddr >= 0 && baseWindowAddr < config->romAddr[loadIdx].size)
|
||||
{
|
||||
uint32_t targetAddr =
|
||||
(uint32_t) (config->romAddr[loadIdx].bank * MEMORY_PAGE_SIZE) + (uint32_t) config->romAddr[loadIdx].addr + (uint32_t) baseWindowAddr;
|
||||
cpu->_z80PSRAM->RAM[targetAddr] = buf[fileIdx];
|
||||
}
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Method to write out a stored ROM into the internal emulation RAM as directed by the configuration array.
|
||||
void MZ80A_readROMData(void *ctx, void *cfg, char *buf, int len)
|
||||
{
|
||||
// Locals.
|
||||
t_Z80CPU *cpu = (t_Z80CPU *) ctx;
|
||||
t_drvROMConfig *config = (t_drvROMConfig *) cfg;
|
||||
|
||||
if (cpu == NULL || config == NULL)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
for (int loadIdx = 0; loadIdx < config->romAddrCount; loadIdx++)
|
||||
{
|
||||
uint32_t srcAddr = config->romAddr[loadIdx].position;
|
||||
uint32_t dstAddr = (uint32_t) (config->romAddr[loadIdx].bank * MEMORY_PAGE_SIZE) + (uint32_t) config->romAddr[loadIdx].addr;
|
||||
uint32_t size = (config->romAddr[loadIdx].size > len ? len : config->romAddr[loadIdx].size);
|
||||
size = (size > MEMORY_SIZE ? MEMORY_SIZE : size);
|
||||
|
||||
if (config->romAddr[loadIdx].position > len)
|
||||
continue;
|
||||
|
||||
for (uint32_t idx = 0; idx < size; idx++)
|
||||
{
|
||||
cpu->_z80PSRAM->RAM[dstAddr + idx] = buf[srcAddr + idx];
|
||||
}
|
||||
|
||||
if (config->romAddr[loadIdx].bank == 0)
|
||||
{
|
||||
for (int idx = config->romAddr[loadIdx].addr / MEMORY_BLOCK_SIZE;
|
||||
idx < (config->romAddr[loadIdx].addr + config->romAddr[loadIdx].size) / MEMORY_BLOCK_SIZE;
|
||||
idx++)
|
||||
{
|
||||
cpu->_membankPtr[idx] = (MEMBANK_TYPE_ROM << 24) | (idx * MEMORY_BLOCK_SIZE);
|
||||
cpu->_memAttr[MZ80A_MEMBANK_0][idx].waitStates = config->romAddr[loadIdx].wait;
|
||||
cpu->_memAttr[MZ80A_MEMBANK_0][idx].tCycSync = config->romAddr[loadIdx].sync == 0 ? false : true;
|
||||
}
|
||||
}
|
||||
debugf("ROM_LOAD: file=%s bank=%d addr=%04x size=%d first=%02x\r\n",
|
||||
config->romFile, config->romAddr[loadIdx].bank,
|
||||
config->romAddr[loadIdx].addr, size, buf[srcAddr]);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Debug handler, any byte output to this port will be sent to the debugf channel.
|
||||
uint8_t MZ80A_IO_Debug(t_Z80CPU *cpu, bool read, uint16_t addr, uint8_t data)
|
||||
{
|
||||
uint8_t port = (uint8_t) addr & 0xff;
|
||||
(void)port;
|
||||
|
||||
if (read)
|
||||
return (0xFF);
|
||||
|
||||
debug_putchar(data);
|
||||
|
||||
return (0);
|
||||
}
|
||||
|
||||
// Reset handler.
|
||||
// Reset pointers and control variables to initial state.
|
||||
// Reset all interface cards if installed.
|
||||
uint8_t MZ80A_Reset(t_Z80CPU *cpu)
|
||||
{
|
||||
// Locals.
|
||||
uint8_t result = 0;
|
||||
|
||||
debugf("MZ80A_Reset\r\n");
|
||||
|
||||
// Reset PIT emulation state so it re-initializes on next access.
|
||||
pitInitialized = false;
|
||||
|
||||
// Clear Z80 RAM work areas that may hold stale data from the previous session.
|
||||
// RFS startup variables (0x1000-0x1037) and MZF header area (0x10F0-0x1167).
|
||||
for (uint16_t addr = 0x1000; addr < 0x1168; addr++)
|
||||
{
|
||||
uint8_t block = addr / MEMORY_BLOCK_SIZE;
|
||||
uint32_t membankptr = cpu->_membankPtr[block];
|
||||
uint8_t memType = membankptr >> 24;
|
||||
if (memType == MEMBANK_TYPE_RAM || memType == MEMBANK_TYPE_ROM)
|
||||
{
|
||||
uint32_t RAMaddr = membankptr & 0x7fffff;
|
||||
uint16_t blockaddr = addr & (MEMORY_BLOCK_SIZE - 1);
|
||||
cpu->_z80PSRAM->RAM[RAMaddr + blockaddr] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Reprogram 8253 channel 2 to prevent rapid interrupt flooding after reset.
|
||||
// Control word 0xB0 = channel 2, RW both bytes, mode 0 (interrupt on terminal count).
|
||||
// Counter 0xA8C0 = 43200 = standard 12-hour RTC count used by the monitor.
|
||||
Z80CPU_writePhysicalMem(cpu, 0xE007, 0xB0); // 8253 control: ch2, mode 0, RW both
|
||||
Z80CPU_writePhysicalMem(cpu, 0xE006, 0xC0); // Counter LSB (0xA8C0 & 0xFF)
|
||||
Z80CPU_writePhysicalMem(cpu, 0xE006, 0xA8); // Counter MSB (0xA8C0 >> 8)
|
||||
// Mask PC2 as belt-and-suspenders — QMODE will re-enable it.
|
||||
Z80CPU_writePhysicalMem(cpu, 0xE003, 0x04);
|
||||
|
||||
// Call interface reset handlers if board installed.
|
||||
for (size_t i = 0; i < interfaceFuncMapSize; i++)
|
||||
{
|
||||
debugf("IF_RST[%d]: %s active=%d\r\n", i, interfaceFuncMap[i].interfaceFuncName, interfaceFuncMap[i].active);
|
||||
if (interfaceFuncMap[i].active)
|
||||
{
|
||||
result |= interfaceFuncMap[i].resetFuncPtr(cpu);
|
||||
}
|
||||
}
|
||||
|
||||
return (result);
|
||||
}
|
||||
|
||||
// Poll Callback handler.
|
||||
uint8_t MZ80A_PollCB(t_Z80CPU *cpu)
|
||||
{
|
||||
// Locals.
|
||||
uint8_t result = 0;
|
||||
|
||||
for (size_t i = 0; i < interfaceFuncMapSize; i++)
|
||||
{
|
||||
if (interfaceFuncMap[i].active)
|
||||
{
|
||||
result |= interfaceFuncMap[i].pollFuncPtr(cpu);
|
||||
}
|
||||
}
|
||||
|
||||
return (result);
|
||||
}
|
||||
|
||||
// Task processing handler.
|
||||
uint8_t MZ80A_TaskProcessor(t_Z80CPU *cpu, enum Z80CPU_TASK_NAME task, char *param)
|
||||
{
|
||||
// Locals.
|
||||
uint8_t result = 0;
|
||||
|
||||
for (size_t i = 0; i < interfaceFuncMapSize; i++)
|
||||
{
|
||||
if (interfaceFuncMap[i].active)
|
||||
{
|
||||
result |= interfaceFuncMap[i].taskFuncPtr(cpu, task, param);
|
||||
}
|
||||
}
|
||||
|
||||
return (result);
|
||||
}
|
||||
|
||||
// Driver to configure the emulation as an MZ-80A device to enable execution of code from hardware or
|
||||
// within internal memory only going hardware such as the video, ports etc.
|
||||
// Parameters: cpu = primary structure containing Z80 CPU configuration and Z80 state.
|
||||
// appConfig = application specific configuration area within the FlashRAM.
|
||||
// config = Structure containing driver configuration information
|
||||
// ifName = When set and config = NULL, validate the interface name against known supported interfaces.
|
||||
uint8_t MZ80A_Init(t_Z80CPU *cpu, t_FlashAppConfigHeader *appConfig, t_drvConfig *config, const char *ifName)
|
||||
{
|
||||
// Locals.
|
||||
uint8_t result = 0;
|
||||
char *fileBuffer = NULL;
|
||||
(void)fileBuffer;
|
||||
|
||||
// Call to check validity of interface for this driver.
|
||||
if (ifName != NULL && config == NULL)
|
||||
{
|
||||
for (size_t i = 0; i < interfaceFuncMapSize; i++)
|
||||
{
|
||||
const char *constant = interfaceFuncMap[i].interfaceFuncName;
|
||||
size_t constantLen = strlen(constant);
|
||||
if (strncasecmp(ifName, constant, constantLen) == 0 && strlen(ifName) == constantLen)
|
||||
{
|
||||
result = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Call to configure the driver.
|
||||
if (ifName == NULL && config != NULL)
|
||||
{
|
||||
bool isPhysical = config->isPhysical;
|
||||
|
||||
// Memory map of the MZ-80A.
|
||||
// 0000 - 0FFF = Monitor ROM (SA-1510) or RAM (via MEMSW at E00C/E010)
|
||||
// 1000 - BFFF = RAM
|
||||
// C000 - CFFF = RAM (swap target for MEMSW 0000:0FFF <-> C000:CFFF)
|
||||
// D000 - D7FF = VRAM (2K, 40x25=1000 chars standard, 80x25=2000 with Kuma 80 mod)
|
||||
// D800 - DFFF = Unused (open bus)
|
||||
// E000 - E003 = 8255 PPI
|
||||
// E004 - E007 = 8253 PIT
|
||||
// E008 - E00B = LS367
|
||||
// E00C = MEMSW (swap ROM 0000:0FFF to C000:CFFF, RAM to 0000:0FFF)
|
||||
// E010 = MEMSWR (reverse the swap)
|
||||
// E800 - EFFF = User ROM socket / RFS
|
||||
// F000 - FFFF = Floppy Disk ROM (MZ80AFI)
|
||||
|
||||
for (int idx = 0; idx < MEMORY_PAGE_BLOCKS; idx++)
|
||||
{
|
||||
uint32_t memType = MEMBANK_TYPE_PHYSICAL;
|
||||
uint32_t memPtr = 0x00000000;
|
||||
uint8_t waitStates = 0;
|
||||
bool tCycSync = false;
|
||||
switch (idx)
|
||||
{
|
||||
// 0x0000:0x0FFF = Monitor ROM.
|
||||
case 0 ... 7:
|
||||
if (!isPhysical)
|
||||
{
|
||||
memType = MEMBANK_TYPE_ROM;
|
||||
waitStates = 1;
|
||||
tCycSync = true;
|
||||
}
|
||||
break;
|
||||
|
||||
// 0x1000:0xCFFF = RAM (includes 0xC000:0xCFFF MEMSW swap target)
|
||||
case 8 ... 103:
|
||||
if (!isPhysical)
|
||||
{
|
||||
memType = MEMBANK_TYPE_RAM;
|
||||
waitStates = 1;
|
||||
tCycSync = true;
|
||||
}
|
||||
break;
|
||||
|
||||
// 0xD000:0xD7FF = VRAM (2K character RAM, A0-A9)
|
||||
// Must remain physical as it controls underlying video hardware.
|
||||
case 104 ... 107:
|
||||
memType = MEMBANK_TYPE_PHYSICAL_VRAM;
|
||||
break;
|
||||
|
||||
// 0xD800:0xDFFF = Unused (open bus on MZ-80A, no colour RAM)
|
||||
case 108 ... 111:
|
||||
break;
|
||||
|
||||
// 0xE000:0xE7FF = Physical IO (8255, 8253, LS367, MEMSW/MEMSWR)
|
||||
// Must remain physical as it controls underlying hardware.
|
||||
case 112 ... 115:
|
||||
memType = MEMBANK_TYPE_PHYSICAL_HW;
|
||||
break;
|
||||
|
||||
// 0xE800:0xEFFF = User ROM / RFS
|
||||
case 116 ... 119:
|
||||
if (!isPhysical)
|
||||
{
|
||||
memType = MEMBANK_TYPE_ROM;
|
||||
waitStates = 1;
|
||||
tCycSync = true;
|
||||
}
|
||||
break;
|
||||
|
||||
// 0xF000:0xFFFF = Floppy ROM (MZ80AFI)
|
||||
case 120 ... 127:
|
||||
if (!isPhysical)
|
||||
{
|
||||
waitStates = 1;
|
||||
tCycSync = true;
|
||||
memType = MEMBANK_TYPE_ROM;
|
||||
}
|
||||
break;
|
||||
}
|
||||
cpu->_membankPtr[idx] = (memType << 24) | (MZ80A_MEMBANK_0 << 16) | (idx * MEMORY_BLOCK_SIZE);
|
||||
cpu->_memAttr[MZ80A_MEMBANK_0][idx].waitStates = waitStates;
|
||||
cpu->_memAttr[MZ80A_MEMBANK_0][idx].tCycSync = tCycSync;
|
||||
cpu->_z80PSRAM->memPtr[idx] = memPtr;
|
||||
}
|
||||
for (int idx = 0; idx < MEMORY_PAGE_SIZE; idx++)
|
||||
{
|
||||
cpu->_z80PSRAM->memioPtr[idx] = (t_MemoryFunc) NULL;
|
||||
}
|
||||
for (int idx = 0; idx < IO_PAGE_SIZE; idx++)
|
||||
{
|
||||
t_MemoryFunc ioPtr = (t_MemoryFunc) NULL;
|
||||
// Debug output handler.
|
||||
if ((idx & 0xff) == 0x50) ioPtr = (t_MemoryFunc) MZ80A_IO_Debug;
|
||||
cpu->_z80PSRAM->ioPtr[idx] = ioPtr;
|
||||
}
|
||||
|
||||
// If not physical, we need to copy the ROMS from the mainboard into internal RAM.
|
||||
if (!isPhysical)
|
||||
{
|
||||
// Copy monitor ROM to Bank 0.
|
||||
Z80CPU_taskMirrorPhysicalToInternalRAM(cpu, (MZ80A_MEMBANK_0 << 16) | 0x0000, 0x0000, 0x1000);
|
||||
|
||||
// Copy User ROM to Bank 0.
|
||||
Z80CPU_taskMirrorPhysicalToInternalRAM(cpu, (MZ80A_MEMBANK_0 << 16) | 0xE800, 0xE800, 0x800);
|
||||
|
||||
// Copy Floppy ROM to Bank 0.
|
||||
Z80CPU_taskMirrorPhysicalToInternalRAM(cpu, (MZ80A_MEMBANK_0 << 16) | 0xF000, 0xF000, 0x1000);
|
||||
}
|
||||
|
||||
// Install reset handler.
|
||||
config->resetPtr = MZ80A_Reset;
|
||||
|
||||
// Install polling loop handler.
|
||||
config->pollPtr = MZ80A_PollCB;
|
||||
|
||||
// Install task processing handler.
|
||||
config->taskPtr = MZ80A_TaskProcessor;
|
||||
|
||||
// Go through each interface and perform necessary configuration.
|
||||
for (int ifIdx = 0; ifIdx < config->ifCount; ifIdx++)
|
||||
{
|
||||
isPhysical = config->ifConfig[ifIdx].isPhysical;
|
||||
|
||||
// Load up any ROM data as specified in the LoadAddr mapping for this interface/ROM.
|
||||
for (int romIdx = 0; romIdx < config->ifConfig[ifIdx].romCount; romIdx++)
|
||||
{
|
||||
if (Z80CPU_ReadROM(appConfig,
|
||||
config->ifConfig[ifIdx].romConfig[romIdx].romFile,
|
||||
cpu,
|
||||
&config->ifConfig[ifIdx].romConfig[romIdx],
|
||||
MZ80A_readROMData,
|
||||
NULL,
|
||||
0,
|
||||
0) == 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// Lookup the interface and execute initialisation routines.
|
||||
for (size_t i = 0; i < interfaceFuncMapSize; i++)
|
||||
{
|
||||
const char *constant = interfaceFuncMap[i].interfaceFuncName;
|
||||
size_t constantLen = strlen(constant);
|
||||
if (strncasecmp(config->ifConfig[ifIdx].name, constant, constantLen) == 0 && strlen(config->ifConfig[ifIdx].name) == constantLen)
|
||||
{
|
||||
if (interfaceFuncMap[i].initFuncPtr(cpu, (t_drvIFConfig *) &config->ifConfig[ifIdx]) != 0)
|
||||
{
|
||||
interfaceFuncMap[i].active = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// When the driver is PHYSICAL, interface ROM loading (MZ80A_readROMData) may have
|
||||
// changed F000-FFFF from PHYSICAL to ROM type. Restore to PHYSICAL so the hardware
|
||||
// ROM is used — virtual ROM + physical FDC hardware doesn't work because the virtual
|
||||
// WD1773 DRQ can't toggle the real hardware's A10 line.
|
||||
if (config->isPhysical)
|
||||
{
|
||||
for (int idx = 0xF000 / MEMORY_BLOCK_SIZE; idx < 0x10000 / MEMORY_BLOCK_SIZE; idx++)
|
||||
{
|
||||
cpu->_membankPtr[idx] = (MEMBANK_TYPE_PHYSICAL << 24) | (MZ80A_MEMBANK_0 << 16) | (idx * MEMORY_BLOCK_SIZE);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return (result);
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
292
projects/tzpuPico/src/drivers/Sharp/MZ80AFI.c
Normal file
292
projects/tzpuPico/src/drivers/Sharp/MZ80AFI.c
Normal file
@@ -0,0 +1,292 @@
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// Name: MZ80AFI.c
|
||||
// Created: Apr 2026
|
||||
// Author(s): Philip Smart
|
||||
// Description: Z80 CPU DRIVER - Sharp MZ-80A - MZ80AFI Floppy Disk Controller
|
||||
// This file contains setup and driver to mimic the MZ-80A Floppy Disk Controller.
|
||||
// Functionally similar to the MZ-1E05 but uses MZ_80A disk format parameters.
|
||||
// Credits:
|
||||
// Copyright: (c) 2019-2026 Philip Smart <philip.smart@net2net.org>
|
||||
//
|
||||
// History: Apr 2026 v1.0 - Initial write based on MZ-1E05.
|
||||
//
|
||||
// Notes: See Makefile to enable/disable conditional components
|
||||
//
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// This source file is free software: you can redistribute it and#or modify
|
||||
// it under the terms of the GNU General Public License as published
|
||||
// by the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This source file is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#include "Z80CPU.h"
|
||||
#define Z80_STATIC
|
||||
#include "Z80.h"
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <pico/printf.h>
|
||||
#include <pico/stdlib.h>
|
||||
#include "pico/util/queue.h"
|
||||
#include "pico/multicore.h"
|
||||
#include "intercore.h"
|
||||
#include "drivers/Sharp/MZ.h"
|
||||
#include "drivers/Sharp/WD1773.h"
|
||||
#include "drivers/Sharp/MZ80AFI.h"
|
||||
|
||||
t_MZ80AFI *mz80afiCtrl; // Control structure for the MZ80AFI Floppy Disk Interface.
|
||||
|
||||
// -----------------------------------------------------------------------------------------------
|
||||
// Interface: Sharp MZ-80A Floppy Disk Interface Board (MZ80AFI).
|
||||
// Description: A Floppy Drive interface including driver ROM to allow use of Floppy Disk Drives
|
||||
// on the MZ-80A. Uses the same WD1773 FDC as the MZ-1E05 but initialised for
|
||||
// MZ-80A disk format.
|
||||
// -----------------------------------------------------------------------------------------------
|
||||
|
||||
// Method to initialise the CPU state to support an MZ80AFI Floppy Disk Interface.
|
||||
uint8_t MZ80AFI_Init(t_Z80CPU *cpu, t_drvIFConfig *config)
|
||||
{
|
||||
// Locals.
|
||||
uint8_t result = 0;
|
||||
|
||||
// Interface logic only for virtual device. Physical device doesnt need configuration.
|
||||
if (config->isPhysical)
|
||||
return (0);
|
||||
|
||||
// First instance, allocate memory.
|
||||
if (mz80afiCtrl == NULL)
|
||||
{
|
||||
mz80afiCtrl = (t_MZ80AFI *) calloc(1, sizeof(t_MZ80AFI));
|
||||
if (!mz80afiCtrl)
|
||||
{
|
||||
return (0);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Only one instance of this board allowed.
|
||||
return (0);
|
||||
}
|
||||
|
||||
// Get raw disk image size.
|
||||
int diskSize = wd1773_getDiskSize(&mz80afiCtrl->fdc, MZ_80A);
|
||||
if (diskSize > 0)
|
||||
{
|
||||
// Allocate space for the disk images. Add overhead for Extended CPC DSK format
|
||||
// which includes a 256-byte disk header and a 256-byte Track-Info block per track.
|
||||
int extDskOverhead = 256 + (mz80afiCtrl->fdc.cylinders * mz80afiCtrl->fdc.heads * 256);
|
||||
int allocSize = diskSize + extDskOverhead;
|
||||
mz80afiCtrl->disk = (uint8_t *) calloc(MAX_MZ80AFI_DISK_DRIVES, allocSize);
|
||||
}
|
||||
if (mz80afiCtrl->disk != NULL)
|
||||
{
|
||||
// Go through the config parameters to obtain any predefined FDC disk file names.
|
||||
for (int idx = 0; idx < MAX_MZ80AFI_DISK_DRIVES; idx++)
|
||||
{
|
||||
if (idx < config->ifParamCount)
|
||||
{
|
||||
mz80afiCtrl->diskName[idx] = strdup(config->ifParam->file);
|
||||
mz80afiCtrl->diskctl = MZ80AFI_FDC_FILE_NOT_LOADED;
|
||||
}
|
||||
else
|
||||
{
|
||||
mz80afiCtrl->diskName[idx] = NULL;
|
||||
mz80afiCtrl->diskctl = MZ80AFI_FDC_NO_FILE;
|
||||
}
|
||||
}
|
||||
int extDskOverhead = 256 + (mz80afiCtrl->fdc.cylinders * mz80afiCtrl->fdc.heads * 256);
|
||||
int allocSize = diskSize + extDskOverhead;
|
||||
if (wd1773_init(&mz80afiCtrl->fdc, &cpu->requestQueue, &cpu->responseQueue, mz80afiCtrl->diskName[0], mz80afiCtrl->disk, allocSize, MZ_80A))
|
||||
{
|
||||
result = 1;
|
||||
|
||||
// Install I/O handlers.
|
||||
// WD1773 0xD8 Status
|
||||
// 0xD9 Track
|
||||
// 0xDA Sector
|
||||
// 0xDB Data
|
||||
// Drive Sel 0xDC D2 = 1 (enable), D1:D0 Drive 0-3, D7 High = Motor On
|
||||
// Side Sel 0xDD D0 = Side
|
||||
// MFM Mode 0xDE D0 = Mode, 0 = DDEN Enable
|
||||
for (int idx = 0; idx < IO_PAGE_SIZE; idx++)
|
||||
{
|
||||
if ((idx & 0xff) >= 0xd8 && (idx & 0xff) < 0xdc)
|
||||
cpu->_z80PSRAM->ioPtr[idx] = (t_MemoryFunc) MZ80AFI_IO_WD1773;
|
||||
if ((idx & 0xff) == 0xdc)
|
||||
cpu->_z80PSRAM->ioPtr[idx] = (t_MemoryFunc) MZ80AFI_IO_DriveSel;
|
||||
if ((idx & 0xff) == 0xdd)
|
||||
cpu->_z80PSRAM->ioPtr[idx] = (t_MemoryFunc) MZ80AFI_IO_SideSel;
|
||||
if ((idx & 0xff) == 0xde)
|
||||
cpu->_z80PSRAM->ioPtr[idx] = (t_MemoryFunc) MZ80AFI_IO_DDENSel;
|
||||
}
|
||||
|
||||
// Install memory-mapped I/O handlers for MZ-80A FDC hardware acceleration.
|
||||
// The MZ-80A FDC ORs the WD1773 DRQ signal with address line A10 for ROM
|
||||
// fetches. The FDC driver places JP (IX) at F3FE and JP (IY) at F7FE.
|
||||
// When DRQ is low, Z80 fetches from F3FE (spin loop). When DRQ goes high,
|
||||
// A10 is forced high so F3FE fetch returns F7FE data (exit to INI handler).
|
||||
cpu->_z80PSRAM->memioPtr[0xF3FE] = (t_MemoryFunc) MZ80AFI_IO_A10Toggle;
|
||||
cpu->_z80PSRAM->memioPtr[0xF3FF] = (t_MemoryFunc) MZ80AFI_IO_A10Toggle;
|
||||
}
|
||||
}
|
||||
|
||||
// Any errors allocating the disk images then we cant activate the interface, free memory and exit.
|
||||
if (!result)
|
||||
{
|
||||
if (mz80afiCtrl->disk != NULL)
|
||||
free(mz80afiCtrl->disk);
|
||||
if (mz80afiCtrl != NULL)
|
||||
free(mz80afiCtrl);
|
||||
mz80afiCtrl = NULL;
|
||||
}
|
||||
|
||||
return (result);
|
||||
}
|
||||
|
||||
// Reset handler for the MZ80AFI Floppy Disk Interface.
|
||||
uint8_t MZ80AFI_Reset(t_Z80CPU *cpu)
|
||||
{
|
||||
// Locals.
|
||||
|
||||
return (0);
|
||||
}
|
||||
|
||||
// Poll handler for the MZ80AFI Floppy Disk Interface.
|
||||
uint8_t MZ80AFI_PollCB(t_Z80CPU *cpu)
|
||||
{
|
||||
// Locals.
|
||||
|
||||
return (0);
|
||||
}
|
||||
|
||||
// Task processor, called by external events to influence/update the driver.
|
||||
uint8_t MZ80AFI_TaskProcessor(t_Z80CPU *cpu, enum Z80CPU_TASK_NAME task, char *param)
|
||||
{
|
||||
// Locals.
|
||||
uint8_t result = 0;
|
||||
|
||||
// Process according to requested task.
|
||||
switch (task)
|
||||
{
|
||||
case FLOPPY_DISK_CHANGE:
|
||||
int diskNo = atoi(strtok(param, ","));
|
||||
char *fileName = strtok(NULL, ",");
|
||||
if (fileName != NULL)
|
||||
{
|
||||
wd1773_changeDisk(&mz80afiCtrl->fdc, fileName, diskNo);
|
||||
}
|
||||
break;
|
||||
|
||||
case QUICK_DISK_CHANGE:
|
||||
break;
|
||||
|
||||
default:
|
||||
result = 1;
|
||||
break;
|
||||
}
|
||||
|
||||
return (result);
|
||||
}
|
||||
|
||||
// MZ-80A FDC hardware acceleration: A10 toggle via DRQ.
|
||||
// On the real MZ-80A, the FDC interface ORs the WD1773 DRQ signal with address line A10
|
||||
// during ROM fetches. This allows a tight spin loop at F3FE (JP (IX)) to automatically
|
||||
// exit to F7FE (JP (IY)) when data is ready, without polling the status register.
|
||||
uint8_t MZ80AFI_IO_A10Toggle(t_Z80CPU *cpu, bool read, uint16_t addr, uint8_t data)
|
||||
{
|
||||
if (!read)
|
||||
return data;
|
||||
|
||||
// When DRQ is active, force A10 high — F3FE reads as F7FE, F3FF reads as F7FF.
|
||||
uint16_t effectiveAddr = addr;
|
||||
if (mz80afiCtrl != NULL && mz80afiCtrl->fdc.drq)
|
||||
{
|
||||
effectiveAddr = addr | 0x0400;
|
||||
}
|
||||
|
||||
return cpu->_z80PSRAM->RAM[effectiveAddr];
|
||||
}
|
||||
|
||||
// The WD1773 emulation already handles MZ-80A bus inversion internally:
|
||||
// wd1773_write() does val=~val, wd1773_read() does return ~val.
|
||||
// No additional inversion needed in the handler.
|
||||
|
||||
uint8_t MZ80AFI_IO_WD1773(t_Z80CPU *cpu, bool read, uint16_t addr, uint8_t data)
|
||||
{
|
||||
// Locals.
|
||||
uint8_t result = 0;
|
||||
uint8_t reg = addr & 0x03;
|
||||
|
||||
if (read)
|
||||
{
|
||||
result = wd1773_read(&mz80afiCtrl->fdc, reg);
|
||||
// Log first 5 status reads to trace ready/busy polling without flooding.
|
||||
static int statusReadCount = 0;
|
||||
if (reg == 0 && statusReadCount < 5)
|
||||
{
|
||||
int64_t elapsed = absolute_time_diff_us(mz80afiCtrl->fdc.motorStartTime, get_absolute_time());
|
||||
debugf("FDC R[0]=%02X mot=%d loaded=%d lpend=%d rpend=%d spinUp=%llu elapsed=%lld\r\n",
|
||||
result, mz80afiCtrl->fdc.motorOn, mz80afiCtrl->fdc.diskLoaded,
|
||||
mz80afiCtrl->fdc.opState.loadPending, mz80afiCtrl->fdc.opState.readPending,
|
||||
mz80afiCtrl->fdc.spinUpUs, elapsed);
|
||||
statusReadCount++;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Log command and register writes (not data transfers).
|
||||
if (reg <= 2)
|
||||
debugf("FDC W[%d] raw=%02X trk=%d hd=%d sec=%d\r\n",
|
||||
reg, data, mz80afiCtrl->fdc.trackReg, mz80afiCtrl->fdc.currentHead,
|
||||
mz80afiCtrl->fdc.sectorReg);
|
||||
wd1773_write(&mz80afiCtrl->fdc, reg, data);
|
||||
}
|
||||
|
||||
return (result);
|
||||
}
|
||||
|
||||
uint8_t MZ80AFI_IO_DriveSel(t_Z80CPU *cpu, bool read, uint16_t addr, uint8_t data)
|
||||
{
|
||||
// Locals.
|
||||
uint8_t result = 0;
|
||||
|
||||
// Check if motor already spinning, if it isnt, preset the spin up time.
|
||||
bool oldMotor = mz80afiCtrl->fdc.motorOn;
|
||||
mz80afiCtrl->fdc.motorOn = (data & 0x80) != 0;
|
||||
if (mz80afiCtrl->fdc.motorOn && !oldMotor)
|
||||
{
|
||||
mz80afiCtrl->fdc.motorStartTime = get_absolute_time();
|
||||
}
|
||||
debugf("FDC DC=%02X mot=%d loaded=%d\r\n", data, mz80afiCtrl->fdc.motorOn, mz80afiCtrl->fdc.diskLoaded);
|
||||
|
||||
return (result);
|
||||
}
|
||||
|
||||
uint8_t MZ80AFI_IO_SideSel(t_Z80CPU *cpu, bool read, uint16_t addr, uint8_t data)
|
||||
{
|
||||
// Locals.
|
||||
uint8_t result = 0;
|
||||
|
||||
// Set head, 0 or 1.
|
||||
wd1773_setHead(&mz80afiCtrl->fdc, data & 0x01);
|
||||
return (result);
|
||||
}
|
||||
|
||||
// DDEN select (0 = MFM, 1 = FM)
|
||||
uint8_t MZ80AFI_IO_DDENSel(t_Z80CPU *cpu, bool read, uint16_t addr, uint8_t data)
|
||||
{
|
||||
// Locals.
|
||||
uint8_t result = 0;
|
||||
|
||||
// Setup density, 0 = MFM, 1 = FM
|
||||
wd1773_setDensity(&mz80afiCtrl->fdc, data & 0x01);
|
||||
return (result);
|
||||
}
|
||||
@@ -101,13 +101,13 @@ uint8_t RFS_Init(t_Z80CPU *cpu, t_drvIFConfig *config)
|
||||
// Install Memory I/O handlers.
|
||||
cpu->_z80PSRAM->memioPtr[BNKCTRLRST] = (t_MemoryFunc) RFS_IO_BNKCTRLRST;
|
||||
cpu->_z80PSRAM->memioPtr[BNKCTRLDIS] = (t_MemoryFunc) RFS_IO_BNKCTRLDIS;
|
||||
cpu->_z80PSRAM->memioPtr[HWSPIDATA] = (t_MemoryFunc) RFS_IO_HWSPIDATA;
|
||||
cpu->_z80PSRAM->memioPtr[HWSPIDATA] = (t_MemoryFunc) RFS_IO_HWSPIDATA;
|
||||
cpu->_z80PSRAM->memioPtr[HWSPISTART] = (t_MemoryFunc) RFS_IO_HWSPISTART;
|
||||
cpu->_z80PSRAM->memioPtr[BNKSELMROM] = (t_MemoryFunc) RFS_IO_BNKSELMROM;
|
||||
cpu->_z80PSRAM->memioPtr[BNKSELUSER] = (t_MemoryFunc) RFS_IO_BNKSELUSER;
|
||||
cpu->_z80PSRAM->memioPtr[BNKCTRL] = (t_MemoryFunc) RFS_IO_BNKCTRL;
|
||||
cpu->_z80PSRAM->memioPtr[MEMSW] = (t_MemoryFunc) RFS_IO_MEMSW;
|
||||
cpu->_z80PSRAM->memioPtr[MEMSWR] = (t_MemoryFunc) RFS_IO_MEMSWR;
|
||||
cpu->_z80PSRAM->memioPtr[BNKCTRL] = (t_MemoryFunc) RFS_IO_BNKCTRL;
|
||||
cpu->_z80PSRAM->memioPtr[MEMSW] = (t_MemoryFunc) RFS_IO_MEMSW;
|
||||
cpu->_z80PSRAM->memioPtr[MEMSWR] = (t_MemoryFunc) RFS_IO_MEMSWR;
|
||||
|
||||
// Invoke the reset handler as it sets the initial state of the RFS board.
|
||||
RFS_Reset(cpu);
|
||||
@@ -116,7 +116,7 @@ uint8_t RFS_Init(t_Z80CPU *cpu, t_drvIFConfig *config)
|
||||
return (result);
|
||||
}
|
||||
|
||||
// Reset handler for the RFS S-RAM Board.
|
||||
// Reset handler for the virtual RFS ROMDISK board.
|
||||
uint8_t RFS_Reset(t_Z80CPU *cpu)
|
||||
{
|
||||
// Locals.
|
||||
@@ -486,8 +486,10 @@ uint8_t RFS_IO_BNKSELUSER(t_Z80CPU *cpu, bool read, uint16_t addr, uint8_t data)
|
||||
// debugf("UROM: bank2=%02x uromAddr=%05lx\r\n", rfsCtrl->regBank2, rfsCtrl->uromAddr);
|
||||
|
||||
// Update memory map to reflect register change.
|
||||
// UROM window is 2K (E800-EFFF), not 4K like MROM — only update 4 blocks, not 8.
|
||||
// Using MROMSIZE (4K) here overwrites F000-F7FF which is the Floppy ROM area.
|
||||
uint8_t offset = 0xE800 / MEMORY_BLOCK_SIZE;
|
||||
for (int idx = 0; idx < (MROMSIZE / MEMORY_BLOCK_SIZE); idx++)
|
||||
for (int idx = 0; idx < (0x800 / MEMORY_BLOCK_SIZE); idx++)
|
||||
{
|
||||
// Memory is both ROM and hardware, the registers share the same address space.
|
||||
// A bank is 64K in size, so bankAddr needs to point to the correct location.
|
||||
@@ -496,11 +498,6 @@ uint8_t RFS_IO_BNKSELUSER(t_Z80CPU *cpu, bool read, uint16_t addr, uint8_t data)
|
||||
cpu->_membankPtr[idx + offset] = (MEMBANK_TYPE_ROM << 24) | (bank << 16) | (bankAddr);
|
||||
cpu->_memAttr[bank][idx + offset].waitStates = 1;
|
||||
cpu->_memAttr[bank][idx + offset].tCycSync = true;
|
||||
//if(idx == 0)
|
||||
//{
|
||||
//debugf("USER: bank:%02x, bankAddr:%04x, ptr:%08x, offset=%02x, uromAddr:%04x, regbank2:%02x, regctrl:%02x\r\n", bank, bankAddr, cpu->_membankPtr[idx+offset], offset, rfsCtrl->uromAddr, rfsCtrl->regBank2, rfsCtrl->regCtrl, cpu->_membankPtr[idx+offset]);
|
||||
//sleep_ms(1);
|
||||
//}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -546,8 +543,9 @@ uint8_t RFS_IO_BNKCTRL(t_Z80CPU *cpu, bool read, uint16_t addr, uint8_t data)
|
||||
rfsCtrl->uromAddr = (uint32_t) (((((rfsCtrl->regCtrl & 0xC0) << 2) | rfsCtrl->regBank2) << 11));
|
||||
|
||||
// Update memory map to reflect register change.
|
||||
// UROM window is 2K (E800-EFFF), not 4K like MROM.
|
||||
uint8_t offset = 0xE800 / MEMORY_BLOCK_SIZE;
|
||||
for (int idx = 0; idx < (MROMSIZE / MEMORY_BLOCK_SIZE); idx++)
|
||||
for (int idx = 0; idx < (0x800 / MEMORY_BLOCK_SIZE); idx++)
|
||||
{
|
||||
// Memory is both ROM and hardware, the registers share the same address space.
|
||||
uint8_t bank = (uint8_t) (rfsCtrl->uromAddr / MEMORY_PAGE_SIZE) + USER_ROM_I_BANK;
|
||||
@@ -555,11 +553,6 @@ uint8_t RFS_IO_BNKCTRL(t_Z80CPU *cpu, bool read, uint16_t addr, uint8_t data)
|
||||
cpu->_membankPtr[idx + offset] = (MEMBANK_TYPE_ROM << 24) | (bank << 16) | (bankAddr);
|
||||
cpu->_memAttr[bank][idx + offset].waitStates = 1;
|
||||
cpu->_memAttr[bank][idx + offset].tCycSync = true;
|
||||
//if(idx == 0)
|
||||
//{
|
||||
//debugf("CTRL: data:%02x, bank:%02x, bankAddr:%04x, ptr:%08x, offset=%02x\r\n", data, bank, bankAddr, cpu->_membankPtr[idx+offset], offset);
|
||||
//sleep_ms(1);
|
||||
//}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -63,6 +63,7 @@ static uint16_t wd1773_crc16Ccitt(const uint8_t *data, size_t len)
|
||||
bool wd1773_setDiskSize(t_WD1773 *wd, int machineType)
|
||||
{
|
||||
wd->machineType = machineType;
|
||||
wd->spinUpUs = SPIN_UP_US; // Default spin-up time; overridden per-machine below if needed.
|
||||
switch (machineType)
|
||||
{
|
||||
case MZ_700:
|
||||
@@ -75,6 +76,13 @@ bool wd1773_setDiskSize(t_WD1773 *wd, int machineType)
|
||||
wd->densityMfm = true;
|
||||
break;
|
||||
case MZ_80A:
|
||||
wd->cylinders = 35;
|
||||
wd->heads = 2;
|
||||
wd->sectorsPerTrack = 17;
|
||||
wd->sectorSize = 256;
|
||||
wd->densityMfm = true;
|
||||
wd->spinUpUs = 1000; // ~1ms — MZ-80A RFS runs at ARM speed (no tCycSync), Z80 delay loops are ~120x faster.
|
||||
break;
|
||||
case MZ_80B:
|
||||
case MZ_2000:
|
||||
case MZ_2200:
|
||||
@@ -342,7 +350,7 @@ bool wd1773_init(t_WD1773 *wd, queue_t *req, queue_t *resp, const char *filename
|
||||
t_CoreMsg msg = {.type = MSG_LOAD_FLOPPYDISK, .context = wd};
|
||||
snprintf(msg.fileOp.filename, MAX_IC_FILENAME_LEN, "%s", filename);
|
||||
msg.fileOp.buffer = img;
|
||||
msg.fileOp.size = wd->diskSize;
|
||||
msg.fileOp.size = bufSize; // Use full buffer size to allow Extended CPC DSK files (larger than raw disk size due to Track-Info headers).
|
||||
msg.fileOp.diskNo = 0;
|
||||
queue_add_blocking(req, &msg);
|
||||
}
|
||||
@@ -439,7 +447,7 @@ void wd1773_clearStatusFlags(t_WD1773 *wd)
|
||||
uint8_t wd1773_getStatus(t_WD1773 *wd)
|
||||
{
|
||||
wd1773_processResponses(wd);
|
||||
uint8_t st = wd->statusFlags;
|
||||
uint8_t st = wd->statusFlags & ~WD1773_STATUS_NOT_READY; // Mask out stale NOT_READY — computed live below.
|
||||
|
||||
if (wd->busy)
|
||||
st |= WD1773_STATUS_BUSY;
|
||||
@@ -448,7 +456,7 @@ uint8_t wd1773_getStatus(t_WD1773 *wd)
|
||||
if (wd->drq)
|
||||
st |= WD1773_STATUS_DRQ;
|
||||
|
||||
bool ready = wd->diskLoaded && wd->motorOn && (absolute_time_diff_us(wd->motorStartTime, get_absolute_time()) >= SPIN_UP_US);
|
||||
bool ready = wd->diskLoaded && wd->motorOn && (absolute_time_diff_us(wd->motorStartTime, get_absolute_time()) >= wd->spinUpUs);
|
||||
if (!ready || wd->opState.loadPending || wd->opState.readPending)
|
||||
st |= WD1773_STATUS_NOT_READY;
|
||||
|
||||
@@ -612,7 +620,7 @@ void wd1773_write(t_WD1773 *wd, uint8_t offset, uint8_t val)
|
||||
*/
|
||||
void wd1773_executeCommand(t_WD1773 *wd)
|
||||
{
|
||||
if (!wd->diskLoaded || !wd->motorOn || absolute_time_diff_us(wd->motorStartTime, get_absolute_time()) < SPIN_UP_US)
|
||||
if (!wd->diskLoaded || !wd->motorOn || absolute_time_diff_us(wd->motorStartTime, get_absolute_time()) < wd->spinUpUs)
|
||||
{
|
||||
wd1773_setStatusFlag(wd, WD1773_STATUS_NOT_READY);
|
||||
wd->busy = false;
|
||||
|
||||
33
projects/tzpuPico/src/include/Z80CPU.h
vendored
33
projects/tzpuPico/src/include/Z80CPU.h
vendored
@@ -162,25 +162,21 @@
|
||||
} \
|
||||
}
|
||||
*/
|
||||
#define SERVICE_REQUESTS() if ((pio_1->irq & ((1u << 6) | (1u << 4))) != 0) \
|
||||
#define SERVICE_REQUESTS() if ((pio_1->irq & (1u << 6)) != 0) \
|
||||
{ \
|
||||
WAIT_TX_FIFO_EMPTY(pio_1, smCycle); \
|
||||
if ((pio_1->irq & (1u << 6)) != 0) \
|
||||
{ \
|
||||
PUSH_FIFO(pio_0, sm_data, 0x000000); \
|
||||
CLEAR_IRQ(pio_0, 1); \
|
||||
PUSH_FIFO(pio_0, sm_addr, 0x00000000); \
|
||||
CLEAR_IRQ(pio_0, 0); \
|
||||
PUSH_FIFO(pio_1, sm_busrq, 0xFFFFFB1C); \
|
||||
WAIT_IRQ_CLEAR(pio_1, 6); \
|
||||
} \
|
||||
\
|
||||
if ((pio_1->irq & (1u << 4)) != 0) \
|
||||
{ \
|
||||
debugf("NMI\r\n"); \
|
||||
z80_nmi(&cpu->_Z80); \
|
||||
CLEAR_IRQ(pio_0, 0); \
|
||||
} \
|
||||
PUSH_FIFO(pio_0, sm_data, 0x000000); \
|
||||
CLEAR_IRQ(pio_0, 1); \
|
||||
PUSH_FIFO(pio_0, sm_addr, 0x00000000); \
|
||||
CLEAR_IRQ(pio_0, 0); \
|
||||
PUSH_FIFO(pio_1, sm_busrq, 0xFFFFFB1C); \
|
||||
WAIT_IRQ_CLEAR(pio_1, 6); \
|
||||
} \
|
||||
if ((pio_2->irq & (1u << 4)) != 0) \
|
||||
{ \
|
||||
debugf("NMI\r\n"); \
|
||||
z80_nmi(&cpu->_Z80); \
|
||||
CLEAR_IRQ(pio_2, 4); \
|
||||
}
|
||||
|
||||
//cpu->interrupt = true;
|
||||
@@ -406,6 +402,7 @@ struct Z80CPU
|
||||
queue_t requestQueue; // Inter core command request queue. Used to dispatch commands from the Z80 to core0 for processing.
|
||||
queue_t responseQueue; // Inter core command wresponse queue. Used to dispatch responses to commands received from the Z80 on core0.
|
||||
bool halt; // Halt state, true = active.
|
||||
bool refreshEnable; // Enable DRAM refresh during virtual memory fetches. Set via JSON config "refresh" parameter.
|
||||
volatile bool hold; // Hold CPU processing, used by Core 0 when access to physical is needed.
|
||||
volatile bool holdAck; // Hold confirmation flag.
|
||||
volatile bool forceReset; // Force a CPU reset asynchronously.
|
||||
@@ -454,7 +451,7 @@ t_VirtualFunc Z80CPU_getVirtualFunc(const char *funcName);
|
||||
|
||||
// Private prototypes for internal memory access functions or dedicated address range functions.
|
||||
// Used and mapped within the funcMap[] array.
|
||||
uint8_t Z80CPU_refreshDRAM(bool read, uint16_t addr, uint8_t data);
|
||||
uint8_t Z80CPU_refreshDRAM(void *context, bool read, uint16_t addr, uint8_t data);
|
||||
|
||||
// Private prototypes for memory setup tasks.
|
||||
// Used and mapped within the taskMap[] array.
|
||||
|
||||
20
projects/tzpuPico/src/include/drivers/Sharp/MZ.h
vendored
20
projects/tzpuPico/src/include/drivers/Sharp/MZ.h
vendored
@@ -30,6 +30,8 @@
|
||||
#ifndef MZ_H
|
||||
#define MZ_H
|
||||
|
||||
#include "Z80CPU.h"
|
||||
|
||||
// Constants.
|
||||
#define MZ700_MEMBANK_0 0 // Primary RAM bank.
|
||||
#define MZ700_MEMBANK_1 1 // RAM bank to use for paging in RAM.
|
||||
@@ -45,4 +47,22 @@
|
||||
#define MZ_2200 6
|
||||
#define MZ_2500 7
|
||||
|
||||
// Function pointer types for virtual driver feature processing.
|
||||
// Shared across all Sharp MZ persona drivers (MZ700, MZ80A, etc.).
|
||||
typedef uint8_t (*t_InitFunc)(t_Z80CPU *cpu, t_drvIFConfig *config);
|
||||
typedef uint8_t (*t_ResetFunc)(t_Z80CPU *cpu);
|
||||
typedef uint8_t (*t_PollFunc)(t_Z80CPU *cpu);
|
||||
typedef uint8_t (*t_TaskFunc)(t_Z80CPU *cpu, enum Z80CPU_TASK_NAME task, char *param);
|
||||
|
||||
// Struct for interface name to function pointer mapping.
|
||||
typedef struct
|
||||
{
|
||||
const char *interfaceFuncName; // Virtual function name from JSON
|
||||
bool active; // Interface active?
|
||||
t_InitFunc initFuncPtr; // Corresponding Init function pointer
|
||||
t_ResetFunc resetFuncPtr; // Corresponding Reset function pointer
|
||||
t_PollFunc pollFuncPtr; // Corresponding Polling function pointer
|
||||
t_TaskFunc taskFuncPtr; // Corresponding Task function pointer
|
||||
} t_InterfaceFuncMap;
|
||||
|
||||
#endif // MZ_H
|
||||
|
||||
@@ -37,25 +37,6 @@
|
||||
|
||||
// Constants.
|
||||
|
||||
// Register control bits.
|
||||
|
||||
// Function pointer type for virtual driver feature processing.
|
||||
typedef uint8_t (*t_InitFunc)(t_Z80CPU *cpu, t_drvIFConfig *config);
|
||||
typedef uint8_t (*t_ResetFunc)(t_Z80CPU *cpu);
|
||||
typedef uint8_t (*t_PollFunc)(t_Z80CPU *cpu);
|
||||
typedef uint8_t (*t_TaskFunc)(t_Z80CPU *cpu, enum Z80CPU_TASK_NAME task, char *param);
|
||||
|
||||
// Struct for interface name to function pointer mapping.
|
||||
typedef struct
|
||||
{
|
||||
const char *interfaceFuncName; // Virtual function name from JSON
|
||||
bool active; // Interface active?
|
||||
t_InitFunc initFuncPtr; // Corresponding Init function pointer
|
||||
t_ResetFunc resetFuncPtr; // Corresponding Reset function pointer
|
||||
t_PollFunc pollFuncPtr; // Corresponding Polling function pointer
|
||||
t_TaskFunc taskFuncPtr; // Corresponding Task function pointer
|
||||
} t_InterfaceFuncMap;
|
||||
|
||||
// Saved memioPtr entry for DRAM/MMIO switching.
|
||||
// On real hardware, virtual devices (RFS etc.) at 0xE800-0xEFFF are not on the bus when DRAM is
|
||||
// selected. We save and NULL their memioPtr entries on E1, restore on E3/E4.
|
||||
|
||||
50
projects/tzpuPico/src/include/drivers/Sharp/MZ80A.h
vendored
Normal file
50
projects/tzpuPico/src/include/drivers/Sharp/MZ80A.h
vendored
Normal file
@@ -0,0 +1,50 @@
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// Name: MZ80A.h
|
||||
// Created: Apr 2026
|
||||
// Author(s): Philip Smart
|
||||
// Description: Z80 CPU DRIVER - Sharp MZ80A
|
||||
// This file contains setup and drivers to mimic a Sharp MZ-80A machine internally to
|
||||
// increase speed through use of internal RAM and provide virtual expansion drivers.
|
||||
// Credits:
|
||||
// Copyright: (c) 2019-2026 Philip Smart <philip.smart@net2net.org>
|
||||
//
|
||||
// History: Apr 2026 v1.0 - Initial write based on MZ700 driver.
|
||||
//
|
||||
// Notes: See Makefile to enable/disable conditional components
|
||||
//
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// This source file is free software: you can redistribute it and#or modify
|
||||
// it under the terms of the GNU General Public License as published
|
||||
// by the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This source file is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#ifndef MZ80A_H
|
||||
#define MZ80A_H
|
||||
|
||||
#include "drivers/Sharp/MZ.h"
|
||||
#include "drivers/Sharp/WD1773.h"
|
||||
|
||||
// Constants.
|
||||
#define MZ80A_MEMBANK_0 0 // Primary RAM bank.
|
||||
#define MZ80A_MEMBANK_1 1 // RAM bank to use for paging in RAM.
|
||||
|
||||
// Private prototypes.
|
||||
void MZ80A_readFileData(void *ctx, void *cfg, int filepos, char *buf, int len);
|
||||
void MZ80A_readROMData(void *ctx, void *cfg, char *buf, int len);
|
||||
uint8_t MZ80A_IO_Debug(t_Z80CPU *cpu, bool read, uint16_t addr, uint8_t data);
|
||||
uint8_t MZ80A_IO_PIT(t_Z80CPU *cpu, bool read, uint16_t addr, uint8_t data);
|
||||
uint8_t MZ80A_PollCB(t_Z80CPU *cpu);
|
||||
uint8_t MZ80A_TaskProcessor(t_Z80CPU *cpu, enum Z80CPU_TASK_NAME task, char *param);
|
||||
uint8_t MZ80A_Init(t_Z80CPU *cpu, t_FlashAppConfigHeader *appConfig, t_drvConfig *config, const char *ifName);
|
||||
|
||||
#endif // MZ80A_H
|
||||
65
projects/tzpuPico/src/include/drivers/Sharp/MZ80AFI.h
vendored
Normal file
65
projects/tzpuPico/src/include/drivers/Sharp/MZ80AFI.h
vendored
Normal file
@@ -0,0 +1,65 @@
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// Name: MZ80AFI.h
|
||||
// Created: Apr 2026
|
||||
// Author(s): Philip Smart
|
||||
// Description: Z80 CPU DRIVER - MZ80AFI Floppy Disk Controller
|
||||
// This file contains setup and driver to mimic an MZ-80A Floppy Disk Controller.
|
||||
// The MZ80AFI is functionally similar to the MZ-1E05 but intended for the MZ-80A.
|
||||
// Credits:
|
||||
// Copyright: (c) 2019-2026 Philip Smart <philip.smart@net2net.org>
|
||||
//
|
||||
// History: Apr 2026 v1.0 - Initial write based on MZ-1E05.
|
||||
//
|
||||
// Notes: See Makefile to enable/disable conditional components
|
||||
//
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// This source file is free software: you can redistribute it and#or modify
|
||||
// it under the terms of the GNU General Public License as published
|
||||
// by the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This source file is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#ifndef MZ80AFI_H
|
||||
#define MZ80AFI_H
|
||||
|
||||
#include "drivers/Sharp/WD1773.h"
|
||||
|
||||
// Constants.
|
||||
#define MAX_MZ80AFI_DISK_DRIVES 2 // Maximum number of Disk Drives attached to the MZ80AFI board.
|
||||
|
||||
enum t_MZ80AFIDiskCtrl
|
||||
{
|
||||
MZ80AFI_FDC_NO_FILE = 0, // No file defined as disk image.
|
||||
MZ80AFI_FDC_FILE_NOT_LOADED = 1, // File has not been loaded into RAM.
|
||||
};
|
||||
|
||||
// Struct for the MZ80AFI Floppy Disk Interface with ROM.
|
||||
typedef struct
|
||||
{
|
||||
t_WD1773 fdc; // FDC Controller instance.
|
||||
uint8_t *disk; // Disk drive images in RAM.
|
||||
char *diskName[MAX_MZ80AFI_DISK_DRIVES]; // Filename of the disk on the ESP32 SD card.
|
||||
enum t_MZ80AFIDiskCtrl diskctl; // Disk control.
|
||||
} t_MZ80AFI;
|
||||
|
||||
// Public prototypes.
|
||||
uint8_t MZ80AFI_Init(t_Z80CPU *cpu, t_drvIFConfig *config);
|
||||
uint8_t MZ80AFI_Reset(t_Z80CPU *cpu);
|
||||
uint8_t MZ80AFI_PollCB(t_Z80CPU *cpu);
|
||||
uint8_t MZ80AFI_TaskProcessor(t_Z80CPU *cpu, enum Z80CPU_TASK_NAME task, char *param);
|
||||
uint8_t MZ80AFI_IO_A10Toggle(t_Z80CPU *cpu, bool read, uint16_t addr, uint8_t data);
|
||||
uint8_t MZ80AFI_IO_WD1773(t_Z80CPU *cpu, bool read, uint16_t addr, uint8_t data);
|
||||
uint8_t MZ80AFI_IO_DriveSel(t_Z80CPU *cpu, bool read, uint16_t addr, uint8_t data);
|
||||
uint8_t MZ80AFI_IO_SideSel(t_Z80CPU *cpu, bool read, uint16_t addr, uint8_t data);
|
||||
uint8_t MZ80AFI_IO_DDENSel(t_Z80CPU *cpu, bool read, uint16_t addr, uint8_t data);
|
||||
|
||||
#endif // MZ80AFI_H
|
||||
@@ -72,7 +72,7 @@
|
||||
#define MAX_DISK_SIZE (80 * 2 * 8 * 512) // MZ-2500 max
|
||||
#define ROTATION_US 200000 // 300 RPM = 200ms
|
||||
#define PULSE_WIDTH_US 2000 // Index pulse duration
|
||||
#define SPIN_UP_US 500000ULL // Motor spin-up time
|
||||
#define SPIN_UP_US 500000ULL // Motor spin-up time (default, real-speed Z80 with tCycSync)
|
||||
|
||||
/* Internal command classification */
|
||||
#define TYPE_I 1 // Movement
|
||||
@@ -159,6 +159,7 @@ typedef struct
|
||||
absolute_time_t lastRotation; // For index pulse emulation
|
||||
bool motorOn; // Motor status
|
||||
absolute_time_t motorStartTime; // Spin-up start time
|
||||
uint64_t spinUpUs; // Motor spin-up time (machine-specific)
|
||||
|
||||
// --- Format Support ---
|
||||
bool diskLoaded; // Image successfully loaded
|
||||
|
||||
@@ -1 +1 @@
|
||||
2.009
|
||||
2.083
|
||||
|
||||
161
projects/tzpuPico/src/z80.pio
vendored
161
projects/tzpuPico/src/z80.pio
vendored
@@ -75,6 +75,7 @@
|
||||
.define PUBLIC Z80_PIN_INT 33
|
||||
.define PUBLIC Z80_PIN_WAIT 34
|
||||
.define PUBLIC Z80_PIN_CLK 35
|
||||
.define PUBLIC Z80_PIN_CLK_S Z80_PIN_CLK - Z80_PIN_DATA_0
|
||||
.define PUBLIC Z80_PIN_NMI 36
|
||||
.define PUBLIC Z80_PIN_RESET 38
|
||||
.define PUBLIC Z80_PIN_RD_OFS Z80_PIN_RD - GPIO_UPPER_START
|
||||
@@ -187,6 +188,7 @@ nmihigh:
|
||||
; mechanism.
|
||||
.program z80_cycle
|
||||
.side_set 2 opt
|
||||
.wrap_target
|
||||
public start_cycle:
|
||||
wait 0 irq 6 ; Pause if BUSACK active.
|
||||
irq set 0
|
||||
@@ -204,35 +206,54 @@ cycle_wait_eval:
|
||||
cycle_nowait:
|
||||
out exec, 16
|
||||
jmp cycle_nowait ; Loop until jmp start executed.
|
||||
|
||||
; A refresh state, performs a fetch but doesnt read the data and then goes onto refresh state. This SM is used
|
||||
; when the RP2350 is executing Z80 code internally and ensures DRAM is refreshed.
|
||||
.program z80_refresh
|
||||
.side_set 2 opt
|
||||
.wrap_target
|
||||
public start_refresh:
|
||||
irq set 0 ; Indicate SM is at start.
|
||||
wait 1 gpio Z80_PIN_CLK ; T1: GPIO35 high
|
||||
wait 0 irq 0 ; Wait to Output PC (Z80_PIN_ADDR_0-15), it is also the refresh address.
|
||||
set pins, 0b1110 side 0b11 ; /IORQ, /MREQ, /RFSH, /M1, Side: /WR /RD -> Set /M1 low
|
||||
wait 0 gpio Z80_PIN_CLK ; T1: GPIO35 low
|
||||
set pins, 0b1010 side 0b11 ; /IORQ, /MREQ, /RFSH, /M1, Side: /WR /RD -> Set /M1 low, /MREQ low, /RD high
|
||||
waitrefresh:
|
||||
wait 1 gpio Z80_PIN_CLK ; T2: GPIO35 high
|
||||
wait 0 gpio Z80_PIN_CLK ; T2: GPIO35 low
|
||||
|
||||
jmp pin, nowaitrefresh ; WAIT is high, so dont add wait states.
|
||||
jmp waitrefresh ; Add wait state TW.
|
||||
nowaitrefresh:
|
||||
wait 1 gpio Z80_PIN_CLK ; T3: GPIO35 high
|
||||
cycle_refresh:
|
||||
; wait 1 gpio Z80_PIN_CLK ; T3: GPIO35 high
|
||||
set pins, 0b1101 side 0b11 ; /IORQ, /MREQ, /RFSH, /M1, Side: /WR /RD -> Set /M1 high, /MREQ high, /RD high, /RFSH low
|
||||
wait 0 gpio Z80_PIN_CLK ; T3: GPIO35 low
|
||||
set pins, 0b1001 side 0b11 ; /IORQ, /MREQ, /RFSH, /M1, Side: /WR /RD -> Set /M1 high, /MREQ low, /RD high, /RFSH low
|
||||
wait 1 gpio Z80_PIN_CLK ; T4: GPIO35 high
|
||||
wait 0 gpio Z80_PIN_CLK ; T4: GPIO35 low
|
||||
set pins, 0b1101 side 0b11 [1]; /IORQ, /MREQ, /RFSH, /M1, Side: /WR /RD -> Set /M1 high, /MREQ high, /RD high, /RFSH low
|
||||
set pins, 0b1111 side 0b11 ; /IORQ, /MREQ, /RFSH, /M1, Side: /WR /RD -> Set /M1 high, /MREQ high, /RD high, /RFSH high
|
||||
.wrap
|
||||
|
||||
; A refresh state, performs a fetch but doesnt read the data and then goes onto refresh state. This SM is used
|
||||
; when the RP2350 is executing Z80 code internally and ensures DRAM is refreshed.
|
||||
;.program z80_refresh
|
||||
;.side_set 2 opt
|
||||
;.wrap_target
|
||||
;public start_refresh:
|
||||
; irq set 5
|
||||
; wait 0 irq 5 ; Wait until Output of PC (Z80_PIN_ADDR_0-15) has been performed.
|
||||
; wait 1 gpio Z80_PIN_CLK ; T3: GPIO35 high
|
||||
; set pins, 0b1101 side 0b11 ; /IORQ, /MREQ, /RFSH, /M1, Side: /WR /RD -> Set /M1 high, /MREQ high, /RD high, /RFSH low
|
||||
; wait 0 gpio Z80_PIN_CLK ; T3: GPIO35 low
|
||||
; set pins, 0b1001 side 0b11 ; /IORQ, /MREQ, /RFSH, /M1, Side: /WR /RD -> Set /M1 high, /MREQ low, /RD high, /RFSH low
|
||||
; wait 1 gpio Z80_PIN_CLK ; T4: GPIO35 high
|
||||
; wait 0 gpio Z80_PIN_CLK ; T4: GPIO35 low
|
||||
; set pins, 0b1101 side 0b11 [3]; /IORQ, /MREQ, /RFSH, /M1, Side: /WR /RD -> Set /M1 high, /MREQ high, /RD high, /RFSH low
|
||||
; set pins, 0b1111 side 0b11 ; /IORQ, /MREQ, /RFSH, /M1, Side: /WR /RD -> Set /M1 high, /MREQ high, /RD high, /RFSH high
|
||||
;
|
||||
; set pins, 0b1110 side 0b11 ; /IORQ, /MREQ, /RFSH, /M1, Side: /WR /RD -> Set /M1 low
|
||||
; wait 0 gpio Z80_PIN_CLK ; T1: GPIO35 low
|
||||
; set pins, 0b1010 side 0b10 ; /IORQ, /MREQ, /RFSH, /M1, Side: /WR /RD -> Set /M1 low, /MREQ low, /RD low
|
||||
;waitrefresh:
|
||||
; wait 1 gpio Z80_PIN_CLK ; T2: GPIO35 high
|
||||
; wait 0 gpio Z80_PIN_CLK ; T2: GPIO35 low
|
||||
;
|
||||
; jmp pin, nowaitrefresh ; WAIT is high, so dont add wait states.
|
||||
; jmp waitrefresh ; Add wait state TW.
|
||||
;nowaitrefresh:
|
||||
; wait 1 gpio Z80_PIN_CLK ; T3: GPIO35 high
|
||||
; set pins, 0b1101 side 0b11 ; /IORQ, /MREQ, /RFSH, /M1, Side: /WR /RD -> Set /M1 high, /MREQ high, /RD high, /RFSH low
|
||||
; wait 0 gpio Z80_PIN_CLK ; T3: GPIO35 low
|
||||
; set pins, 0b1001 side 0b11 ; /IORQ, /MREQ, /RFSH, /M1, Side: /WR /RD -> Set /M1 high, /MREQ low, /RD high, /RFSH low
|
||||
; wait 1 gpio Z80_PIN_CLK ; T4: GPIO35 high
|
||||
; wait 0 gpio Z80_PIN_CLK ; T4: GPIO35 low
|
||||
; set pins, 0b1101 side 0b11 [1]; /IORQ, /MREQ, /RFSH, /M1, Side: /WR /RD -> Set /M1 high, /MREQ high, /RD high, /RFSH low
|
||||
; set pins, 0b1111 side 0b11 ; /IORQ, /MREQ, /RFSH, /M1, Side: /WR /RD -> Set /M1 high, /MREQ high, /RD high, /RFSH high
|
||||
;.wrap
|
||||
|
||||
; State machine to generate a requested number of wait states.
|
||||
; The state machine is used for pacing internal ARM processing.
|
||||
.program z80_wait
|
||||
@@ -270,6 +291,8 @@ resethigh:
|
||||
;-----------------------------------------------------------------------------------------------------------------
|
||||
; These are the basic pio programs for z80 cycle recreation. They have been embedded within the C code of the
|
||||
; Z80CPU.c module. This is primarily due to limited PIO program space.
|
||||
; NB. Z80_PIN_CLK_S is used as the PIO is known. Normally if you write a program using wait x gpio Z80_PIN_CLK
|
||||
; the the initialisation routines would adjust the pin to account for the base offset.
|
||||
;-----------------------------------------------------------------------------------------------------------------
|
||||
|
||||
.program z80_fetch
|
||||
@@ -277,32 +300,32 @@ resethigh:
|
||||
.wrap_target
|
||||
public start_fetch:
|
||||
irq set 1
|
||||
wait 1 gpio Z80_PIN_CLK ; T1: GPIO35 high
|
||||
wait 1 gpio Z80_PIN_CLK_S ; T1: GPIO35 high
|
||||
wait 0 irq 1 ; Wait to Output PC (Z80_PIN_ADDR_0-15)
|
||||
|
||||
;out pins, 16 ; Output PC (Z80_PIN_ADDR_0-15)
|
||||
set pins, 0b1110 side 0b11 ; /IORQ, /MREQ, /RFSH, /M1, Side: /WR /RD -> Set /M1 low
|
||||
wait 0 gpio Z80_PIN_CLK ; T1: GPIO35 low
|
||||
wait 0 gpio Z80_PIN_CLK_S ; T1: GPIO35 low
|
||||
set pins, 0b1010 side 0b10 ; /IORQ, /MREQ, /RFSH, /M1, Side: /WR /RD -> Set /M1 low, /MREQ low, /RD low
|
||||
waitfetch:
|
||||
wait 1 gpio Z80_PIN_CLK ; T2: GPIO35 high
|
||||
wait 0 gpio Z80_PIN_CLK ; T2: GPIO35 low
|
||||
wait 1 gpio Z80_PIN_CLK_S ; T2: GPIO35 high
|
||||
wait 0 gpio Z80_PIN_CLK_S ; T2: GPIO35 low
|
||||
|
||||
jmp pin, nowaitfetch ; WAIT is high, so dont add wait states.
|
||||
jmp waitfetch ; Add wait state TW.
|
||||
nowaitfetch:
|
||||
|
||||
wait 1 gpio Z80_PIN_CLK ; T3: GPIO35 high
|
||||
wait 1 gpio Z80_PIN_CLK_S ; T3: GPIO35 high
|
||||
|
||||
in pins, 8 ; Read opcode (Z80_PIN_DATA_0-7)
|
||||
irq set 1
|
||||
wait 0 irq 1 ; Wait to Output RFSH Address (Z80_PIN_ADDR_0-15)
|
||||
;out pins, 16 ; Output RFSH Address (Z80_PIN_ADDR_0-15)
|
||||
set pins, 0b1101 side 0b11 ; /IORQ, /MREQ, /RFSH, /M1, Side: /WR /RD -> Set /M1 high, /MREQ high, /RD high, /RFSH low
|
||||
wait 0 gpio Z80_PIN_CLK ; T3: GPIO35 low
|
||||
wait 0 gpio Z80_PIN_CLK_S ; T3: GPIO35 low
|
||||
set pins, 0b1001 side 0b11 ; /IORQ, /MREQ, /RFSH, /M1, Side: /WR /RD -> Set /M1 high, /MREQ low, /RD high, /RFSH low
|
||||
wait 1 gpio Z80_PIN_CLK ; T4: GPIO35 high
|
||||
wait 0 gpio Z80_PIN_CLK ; T4: GPIO35 low
|
||||
wait 1 gpio Z80_PIN_CLK_S ; T4: GPIO35 high
|
||||
wait 0 gpio Z80_PIN_CLK_S ; T4: GPIO35 low
|
||||
set pins, 0b1101 side 0b11 [1]; /IORQ, /MREQ, /RFSH, /M1, Side: /WR /RD -> Set /M1 high, /MREQ high, /RD high, /RFSH low
|
||||
set pins, 0b1111 side 0b11 ; /IORQ, /MREQ, /RFSH, /M1, Side: /WR /RD -> Set /M1 high, /MREQ high, /RD high, /RFSH high
|
||||
.wrap
|
||||
@@ -312,22 +335,22 @@ nowaitfetch:
|
||||
.wrap_target
|
||||
public start_mem_read:
|
||||
;irq set 2
|
||||
;wait 1 gpio Z80_PIN_CLK ; T1: GPIO35 high
|
||||
;wait 1 gpio Z80_PIN_CLK_S ; T1: GPIO35 high
|
||||
;wait 0 irq 2 ; Wait till control starts the sequence.
|
||||
|
||||
;out pins, 16 ; Output address (Z80_PIN_ADDR_0-15)
|
||||
wait 0 gpio Z80_PIN_CLK ; EXEC T1: GPIO35 low
|
||||
wait 0 gpio Z80_PIN_CLK_S ; EXEC T1: GPIO35 low
|
||||
|
||||
set pins, 0b1011 side 0b10 ; EXEC /IORQ, /MREQ, /RFSH, /M1, Side: /WR /RD -> Set /MREQ low, /RD low
|
||||
waitread:
|
||||
wait 1 gpio Z80_PIN_CLK ; EXEC T2: GPIO35 high
|
||||
wait 0 gpio Z80_PIN_CLK ; EXEC T2: GPIO35 low
|
||||
wait 1 gpio Z80_PIN_CLK_S ; EXEC T2: GPIO35 high
|
||||
wait 0 gpio Z80_PIN_CLK_S ; EXEC T2: GPIO35 low
|
||||
|
||||
jmp pin, nowaitread ; WAIT is high, so dont add wait states.
|
||||
jmp waitread ; Add wait state TW.
|
||||
nowaitread:
|
||||
wait 1 gpio Z80_PIN_CLK ; EXEC2 T3: GPIO35 high
|
||||
wait 0 gpio Z80_PIN_CLK ; EXEC2 T3: GPIO35 low
|
||||
wait 1 gpio Z80_PIN_CLK_S ; EXEC2 T3: GPIO35 high
|
||||
wait 0 gpio Z80_PIN_CLK_S ; EXEC2 T3: GPIO35 low
|
||||
in pins, 8 ; EXEC2 Read data (Z80_PIN_DATA_0-7)
|
||||
set pins, 0b1111 side 0b11 ; EXEC2 /MREQ, /IORQ, /RFSH, /M1, Side: /RD -> Set /MREQ high, /RD high
|
||||
.wrap
|
||||
@@ -337,25 +360,25 @@ nowaitread:
|
||||
.wrap_target
|
||||
public start_mem_write:
|
||||
irq set 0
|
||||
wait 1 gpio Z80_PIN_CLK ; T1: GPIO35 high
|
||||
wait 1 gpio Z80_PIN_CLK_S ; T1: GPIO35 high
|
||||
wait 0 irq 0 ; Wait till control starts the sequence.
|
||||
|
||||
;out pins, 16 ; Output address (Z80_PIN_ADDR_0-15)
|
||||
wait 0 gpio Z80_PIN_CLK ; T1: GPIO35 low
|
||||
wait 0 gpio Z80_PIN_CLK_S ; T1: GPIO35 low
|
||||
set pins, 0b1011 side 0b11 ; /IORQ, /MREQ, /RFSH, /M1, Side: /WR /RD -> Set /MREQ low, /WR high
|
||||
;out pins, 8 ; Output data (Z80_PIN_DATA_0-7)
|
||||
|
||||
waitwrite:
|
||||
wait 1 gpio Z80_PIN_CLK ; T2: GPIO35 high
|
||||
wait 0 gpio Z80_PIN_CLK ; T2: GPIO35 low
|
||||
wait 1 gpio Z80_PIN_CLK_S ; T2: GPIO35 high
|
||||
wait 0 gpio Z80_PIN_CLK_S ; T2: GPIO35 low
|
||||
set pins, 0b1011 side 0b01 ; /IORQ, /MREQ, /RFSH, /M1, Side: /WR /RD -> Set /MREQ low, /WR low
|
||||
|
||||
jmp pin, nowaitwrite ; WAIT is high, so dont add wait states.
|
||||
jmp waitwrite ; Add wait state TW.
|
||||
nowaitwrite:
|
||||
|
||||
wait 1 gpio Z80_PIN_CLK ; T3: GPIO35 high
|
||||
wait 0 gpio Z80_PIN_CLK ; T3: GPIO35 low
|
||||
wait 1 gpio Z80_PIN_CLK_S ; T3: GPIO35 high
|
||||
wait 0 gpio Z80_PIN_CLK_S ; T3: GPIO35 low
|
||||
set pins, 0b1111 side 0b11 ; /IORQ, /MREQ, /RFSH, /M1, Side: /WR /RD -> Set /MREQ high, /WR high
|
||||
.wrap
|
||||
|
||||
@@ -364,25 +387,25 @@ nowaitwrite:
|
||||
.wrap_target
|
||||
public start:
|
||||
irq set 0
|
||||
wait 1 gpio Z80_PIN_CLK ; T1: GPIO35 high
|
||||
wait 1 gpio Z80_PIN_CLK_S ; T1: GPIO35 high
|
||||
wait 0 irq 0 ; Wait till control starts the sequence.
|
||||
;out pins, 16 ; Output port address (Z80_PIN_ADDR_0-15)
|
||||
wait 0 gpio Z80_PIN_CLK ; T1: GPIO35 low
|
||||
wait 0 gpio Z80_PIN_CLK_S ; T1: GPIO35 low
|
||||
|
||||
wait 1 gpio Z80_PIN_CLK ; EXEC T2: GPIO35 high
|
||||
wait 1 gpio Z80_PIN_CLK_S ; EXEC T2: GPIO35 high
|
||||
set pins, 0b0111 side 0b10 ; EXEC /IORQ, /MREQ, /RFSH, /M1, Side: /WR /RD -> Set /IORQ low, /RD low
|
||||
|
||||
wait 0 gpio Z80_PIN_CLK ; T2: GPIO35 low
|
||||
wait 0 gpio Z80_PIN_CLK_S ; T2: GPIO35 low
|
||||
waitreadio:
|
||||
wait 1 gpio Z80_PIN_CLK ; TW: GPIO35 high
|
||||
wait 0 gpio Z80_PIN_CLK ; TW: GPIO35 low
|
||||
wait 1 gpio Z80_PIN_CLK_S ; TW: GPIO35 high
|
||||
wait 0 gpio Z80_PIN_CLK_S ; TW: GPIO35 low
|
||||
|
||||
jmp pin, nowaitreadio ; WAIT is high, so dont add wait states.
|
||||
jmp waitreadio ; Add wait state TW.
|
||||
nowaitreadio:
|
||||
|
||||
wait 1 gpio Z80_PIN_CLK ; T3: GPIO35 high
|
||||
wait 0 gpio Z80_PIN_CLK ; T3: GPIO35 low
|
||||
wait 1 gpio Z80_PIN_CLK_S ; T3: GPIO35 high
|
||||
wait 0 gpio Z80_PIN_CLK_S ; T3: GPIO35 low
|
||||
in pins, 8 ; Read data (Z80_PIN_DATA_0-7)
|
||||
set pins, 0b1111 side 0b11 ; /MREQ, /IORQ, /RFSH, /M1, Side: /RD -> Set /IORQ high, /RD high
|
||||
.wrap
|
||||
@@ -392,24 +415,24 @@ nowaitreadio:
|
||||
.wrap_target
|
||||
public start:
|
||||
irq set 0
|
||||
wait 1 gpio Z80_PIN_CLK ; T1: GPIO35 high
|
||||
wait 1 gpio Z80_PIN_CLK_S ; T1: GPIO35 high
|
||||
wait 0 irq 0 ; Wait till control starts the sequence.
|
||||
|
||||
;out pins, 16 ; Output address (Z80_PIN_ADDR_0-15)
|
||||
wait 0 gpio Z80_PIN_CLK ; T1: GPIO35 low
|
||||
wait 0 gpio Z80_PIN_CLK_S ; T1: GPIO35 low
|
||||
;out pins, 8 ; Output data (Z80_PIN_DATA_0-7)
|
||||
wait 1 gpio Z80_PIN_CLK ; T2: GPIO35 high
|
||||
wait 1 gpio Z80_PIN_CLK_S ; T2: GPIO35 high
|
||||
set pins, 0b0111 side 0b01 ; /IORQ, /MREQ, /RFSH, /M1, Side: /WR /RD -> Set /IORQ low, /WR low
|
||||
wait 0 gpio Z80_PIN_CLK ; T2: GPIO35 low
|
||||
wait 0 gpio Z80_PIN_CLK_S ; T2: GPIO35 low
|
||||
waitwriteio:
|
||||
wait 1 gpio Z80_PIN_CLK ; TW: GPIO35 high
|
||||
wait 0 gpio Z80_PIN_CLK ; TW: GPIO35 low
|
||||
wait 1 gpio Z80_PIN_CLK_S ; TW: GPIO35 high
|
||||
wait 0 gpio Z80_PIN_CLK_S ; TW: GPIO35 low
|
||||
|
||||
jmp pin, nowaitwriteio ; WAIT is high, so dont add wait states.
|
||||
jmp waitwriteio ; Add wait state TW.
|
||||
nowaitwriteio:
|
||||
wait 1 gpio Z80_PIN_CLK ; T3: GPIO35 high
|
||||
wait 0 gpio Z80_PIN_CLK ; T3: GPIO35 low
|
||||
wait 1 gpio Z80_PIN_CLK_S ; T3: GPIO35 high
|
||||
wait 0 gpio Z80_PIN_CLK_S ; T3: GPIO35 low
|
||||
set pins, 0b1111 side 0b11 ; /IORQ, /MREQ, /RFSH, /M1, Side: /WR /RD -> Set /IORQ high, /WR high
|
||||
.wrap
|
||||
|
||||
@@ -429,39 +452,39 @@ public start_clk_sync:
|
||||
.wrap_target
|
||||
public start:
|
||||
irq set 1
|
||||
wait 1 gpio Z80_PIN_CLK ; T1: GPIO35 high
|
||||
wait 1 gpio Z80_PIN_CLK_S ; T1: GPIO35 high
|
||||
wait 0 irq 1 ; Wait to Output PC (Z80_PIN_ADDR_0-15)
|
||||
|
||||
;out pins, 16 ; Output PC (Z80_PIN_ADDR_0-15)
|
||||
set pins, 0b1110 side 0b11 ; /IORQ, /MREQ, /RFSH, /M1, Side: /WR /RD -> Set /M1 low
|
||||
wait 0 gpio Z80_PIN_CLK ; T1: GPIO35 low
|
||||
wait 0 gpio Z80_PIN_CLK_S ; T1: GPIO35 low
|
||||
|
||||
wait 1 gpio Z80_PIN_CLK ; T2: GPIO35 high
|
||||
wait 0 gpio Z80_PIN_CLK ; T2: GPIO35 low
|
||||
wait 1 gpio Z80_PIN_CLK_S ; T2: GPIO35 high
|
||||
wait 0 gpio Z80_PIN_CLK_S ; T2: GPIO35 low
|
||||
|
||||
wait 1 gpio Z80_PIN_CLK ; TW1: GPIO35 high
|
||||
wait 0 gpio Z80_PIN_CLK ; TW1: GPIO35 low
|
||||
wait 1 gpio Z80_PIN_CLK_S ; TW1: GPIO35 high
|
||||
wait 0 gpio Z80_PIN_CLK_S ; TW1: GPIO35 low
|
||||
set pins, 0b0110 side 0b11 ; /IORQ, /MREQ, /RFSH, /M1, Side: /WR /RD -> Set /M1 low, /IORQ low
|
||||
|
||||
waitinta:
|
||||
wait 1 gpio Z80_PIN_CLK ; TW2: GPIO35 high
|
||||
wait 0 gpio Z80_PIN_CLK ; TW2: GPIO35 low
|
||||
wait 1 gpio Z80_PIN_CLK_S ; TW2: GPIO35 high
|
||||
wait 0 gpio Z80_PIN_CLK_S ; TW2: GPIO35 low
|
||||
|
||||
jmp pin, nowaitinta ; WAIT is high, so dont add wait states.
|
||||
jmp waitinta ; Add wait state TW.
|
||||
nowaitinta:
|
||||
|
||||
in pins, 8 ; Read vector (Z80_PIN_DATA_0-7)
|
||||
wait 1 gpio Z80_PIN_CLK ; T3: GPIO35 high
|
||||
wait 1 gpio Z80_PIN_CLK_S ; T3: GPIO35 high
|
||||
|
||||
irq set 1
|
||||
wait 0 irq 1 ; Wait to Output RFSH Address (Z80_PIN_ADDR_0-15)
|
||||
;out pins, 16 ; Output RFSH Address (Z80_PIN_ADDR_0-15)
|
||||
set pins, 0b1101 side 0b11 ; /IORQ, /MREQ, /RFSH, /M1, Side: /WR /RD -> Set /M1 high, /IORQ high, /RD high, /RFSH low
|
||||
wait 0 gpio Z80_PIN_CLK ; T3: GPIO35 low
|
||||
wait 0 gpio Z80_PIN_CLK_S ; T3: GPIO35 low
|
||||
set pins, 0b1001 side 0b11 ; /IORQ, /MREQ, /RFSH, /M1, Side: /WR /RD -> Set /M1 high, /MREQ low, /RD high, /RFSH low
|
||||
wait 1 gpio Z80_PIN_CLK ; T4: GPIO35 high
|
||||
wait 0 gpio Z80_PIN_CLK ; T4: GPIO35 low
|
||||
wait 1 gpio Z80_PIN_CLK_S ; T4: GPIO35 high
|
||||
wait 0 gpio Z80_PIN_CLK_S ; T4: GPIO35 low
|
||||
set pins, 0b1101 side 0b11 ; /IORQ, /MREQ, /RFSH, /M1, Side: /WR /RD -> Set /M1 high, /MREQ high, /RD high, /RFSH low
|
||||
wait 0 irq 6 ; Halt if BUSRQ is asserted.
|
||||
.wrap
|
||||
|
||||
2
projects/tzpuPico/version.txt
vendored
2
projects/tzpuPico/version.txt
vendored
@@ -1 +1 @@
|
||||
2.011
|
||||
2.079
|
||||
|
||||
Reference in New Issue
Block a user