LXESP32






Antonio Giuliana, 2025
2
In questa seconda parte del documento verrà esaminata la scheda LXESP32 con le relative
funzionalità. La versione esaminata è preliminare (e in parte superata dallo sviluppo della
prossima versione) ma implementa le funzionalità principali e può essere esaminata in dettaglio.
Come detto nella prima parte, l’adozione dell’ESP32 era stata pensata con l’intento di dotare
il sistema di un device di storage per memorizzare codice e dati da rileggere in un secondo
momento. La disponibilità di librerie pronte per l’utilizzo di una SDCARD con un file system
come il FAT con relative funzionalità ad alto livello, facilita la memorizzazione di dati e codice
anche in considerazione della possibilità di utilizzare sistemi operativi come il NE-DOS e il
SONE (CP/M). Anche per il BASIC da 16 K su EPROM, la necessità di salvare il codice su
tape (o su disco, con opportune modifiche) è preminente, se si vuole utilizzare il sistema anche
solo per hobby. Questa possibilità viene offerta dall’ESP32 ma implica la scrittura di parecchio
codice apposito per implementarle, almeno per l’interprete BASIC. Per il NE-DOS e il SONE
diventa impraticabile perché bisognerebbe intervenire pesantemente al loro interno per adattare
le operazioni di I/O al nuovo device.
A questo punto si è reso necessario cambiare strategia, cercando di rendere trasparente più
possibile il funzionamento del device rispetto a tutto il codice già disponibile. Ciò è stato reso
possibile emulando il funzionamento del controller del tape, il chip CDP1854 e del controller
dei floppy disk, il chip FD1771. In questo modo il codice presente nelle ROM e nei dischi del
NE-DOS sono stati eseguiti senza modifiche sostanziali.
Le maggiori difficoltà sono nate nello sviluppo del firmware dell’ESP32, compito reso
comunque più agevole dall’utilizzo del C/C++ e dalle librerie già disponibili.
3
La famiglia ESP32
L’ESP32 è una famiglia di microcontrolleri potenti e con capacità notevoli e, soprattutto, di
basso costo. Sono stati progettati dalla ESPRESSIF SYSTEMS e costruiti dalla TSMC (in
pratica costituiscono la versione successiva della famiglia ESP8266).
Tra le capacità di tali device sono comprese le interfacce Wi-Fi e Blutetooth, l’elaborazione
tramite CPU Xtensa dual-core a 32 bit LX6 o LX7, operante fino a 240 MHz. Per le
comunicazioni wireless è prevista una antenna e amplificatori a basso rumore per la ricezione
e la trasmissione.
Le varie versioni della famiglia sono offerte per essere incluse in un circuito stampato o
montate su moduli (essenzialmente destinati allo sviluppo, ma comodi da utilizzare) che
contemplano anche altri componenti dedicati alla comunicazione seriale via USB e alla
programmazione dello stesso. Sono disponibili vari tipi di moduli, con diverse configurazioni
a seconda del modello e del costruttore.
Essendo compatibili con molte librerie Arduino, è possibile utilizzare l’IDE dello stesso e il
linguaggio C/C++ per scrivere codice e programmare tale componente, considerando che sono
disponibili moltissime librerie di diverso tipo.
La versione di ESP32 adottata
Nel sistema NEZ80 ho utilizzato, in questa prima versione, un modulo pronto della
FREENOVE denominato ESP32-WROVER che ha le seguenti caratteristiche
CPU Dual core 32 bit a 240 MHz
520 KB di SRAM - 4 MB di Flash - 8 MB di PSRAM
WiFi a 2.4 GHz e Bluetooth 4.2
Slot integrato per microSD
Pin di I/O generici e connettore per CAM
4
Questo modello, abbastanza economico, è stato scelto soprattutto perché dispone di uno slot
per microSD integrato sullo stampato (visibile sul lato inferiore) e l’antenna del WiFi incisa
sul circuito stampato del modulo stesso. Dispone anche di un connettore per una CAM che
però non viene utilizzata in questo progetto ed è programmabile facilmente tramite un
connettore USB (sul lato superiore) tramite l’IDE Arduino
Lato superiore Lato inferiore
Il modulo prevede 40 pin, 20 per lato, ed è un po’ più grande di un DIP da 40 pin. Le funzioni
di ogni pin sono le seguenti (in grassetto, il nome della funzione nella scheda LXESP32)
N. Pin Nome Funzione N. Pin Nome Funzione
1
3.3V
Uscita a 3.3V derivata dalla 5V
40
2
EN
ENABLE
39
IO23
I/O 23
(
SDA
)
3
IO36
I
N
36
(
CPUA2
)
38
IO22
I/O 22 (
CPUD7
)
4
IO39
I
N
39
(
RX1
)
37
IO
1 (
TX
)
I/O 1 (
CPUINT
)
5
IO34
I/O 34
(
CPUA0
)
36
IO3 (
RX
)
I/O 3 (
RX
)
6
IO35
I/O 35
(
CPUA1
)
35
IO21
I/O 21
(
CPUD6
)
7
IO32
I/O 32
(
CPUCSRD
)
34
8
IO33
I/O 33
(
CPUCSWR
)
33
IO19
I/O 19
(
CPUD5
)
9
IO25
I/O 25
(
SCL
)
32
IO18
I/O 18
(
CPUD4
)
10
IO26
I/O 26
(
TX1
)
31
IO5
I/O 5
(
CPUD1
)
11
IO27
I/O 27
(
CPURESW
)
30
12
IO14
I/O 14
(
SDCLK
)
29
13
IO12
I/O 12
(
CPUD2
)
28
IO4
I/O 4
(
CPUD0
)
14
GND
GND
27
IO0
I/O 0
(
CPUA3
)
15
IO13
I/O 13
(
CPUD3
)
26
IO2
I/O 2
(
SDDAT
)
16
3.3V
Uscita a 3.3 V
25
IO15
I/O 15
(
SDCMD
)
17
3.3V
Uscita a 3.3 V
24
18
3.3V
Uscita a 3.3 V
23
19
5V
Ingresso 5V
22
20
5V
Ingresso 5V
21
5
Come tutti i modelli di ESP32, anche quello montato sul modulo lavora con la tensione di 3.3V
ma nel modulo è incluso uno stabilizzatore che accetta la tensione a 5V dal circuito esterno e
alimenta il microcontrollore a 3.3V. Tale tensione viene anche messa a disposizione su alcuni
pin ed è utilizzata per alimentare i buffer della CPLD in modo che questa possa risultare
compatibile a livello elettrico (vedere la caratteristica MultiVolt della CPLD).
In una prossima versione della scheda, userò la versione S3 del modulo, con un micro che
dispone di maggiore memoria e un numero maggiore di linee di I/O.
La scheda LXESP32
La scheda LXESP32, in questa versione preliminare, è stata progettata per interfacciarsi al Bus
del NEZ80 tramite la CPLD per garantire la compatibilità elettrica con i 5V.
Dallo schema che segue si nota infatti che
a) il modulo ESP32 e il core della CPLD sono alimentati a 5V dal Bus
b) internamente al modulo esiste il circuito di alimentazione a 3.3V che alimenta il micro
ESP32 posizionato sullo stesso
c) l’alimentazione a 3.3V viene inviata all’esterno del modulo verso le porte di I/O del
CPLD, per sfruttare la caratteristica MultiVolt già esaminata in precedenza
d) tutti i segnali che provengono dal Bus o vanno al Bus sono collegati alla CPLD (e non
direttamente al modulo ESP32) e ciò avviene anche per i segnali del modulo, che non
vanno verso il Bus ma solo verso la CPLD
6
I segnali che verranno gestiti dal modulo saranno divisi in tre gruppi
1) 8 bit del bus dati, in input/output (da CPUD0 a CPUD7), collegati tramite buffer tristate
bidirezionali interni alla CPLD, agli 8 bit dati del Bus (da BD0 a BD7)
2) 4 bit del bus indirizzi, in input (da CPUA0 a CPUA3), collegati tramite la CPLD, ai 4
bit indirizzi del Bus (da BA0 a BA3)
3) segnali di controllo, in particolare
a. CPUCSRD generato dalla CPLD (da high a low); quando la CPU vuole
leggere da una porta con uno dei 16 indirizzi 0x03, 0xE8, 0xE9, 0xEC…0xEF,
0xD0…0xD7, genera un interrupt nell’ESP32 per informarlo che la CPU vuole
leggere una porta assegnata a tale device e fa partire la routine del firmware
associata;
b. CPUCSWR in uscita dalla CPLD (da high a low); quando la CPU vuole
scrivere su una delle 16 porte con indirizzi 0x03, 0xE8, 0xE9, 0xEC…0xEF,
7
0xD0…0xD7, genera un interrupt nell’ESP32 per informarlo che la CPU vuole
scrivere una porta assegnata a tale device e fa partire la routine del firmware
associata;
c. CPURESW è un segnale generato dal firmware dell’ESP32 per gestire il
WAIT dello Z80, permettendo una sincronizzazione valida tra l’esecuzione del
codice dello Z80 e l’esecuzione del firmware dell’ESP32. Ogni qualvolta viene
generato uno dei due segnali CPUCSRD o CPUCSWR, viene automaticamente
attivato un flip flop interno alla CPLD che genera il segnale di WAIT dello Z80.
In questo modo lo Z80 viene bloccato e l’ESP32 può gestire gli indirizzi e i dati
che rimangono stabili, può fare tutte le elaborazioni necessarie a seconda della
richiesta ricevuta e, solo alla fine di queste (per un tempo breve ma variabile a
seconda del compito da eseguire), utilizza il segnale CPURESW per resettare il
flip flop e far tornare inattivo il WAIT, consentendo allo Z80 di continuare
l’esecuzione del proprio codice
d. CPUINT – è un segnale generato (in hardware) dall’ESP32 e inviato, tramite
la CPLD (pin BINT sul Bus, ingresso INT dello Z80); è un segnale periodico
negativo a 50 Hz per la generazione di interrupt gestiti dallo Z80.
Nello schema sono visibili altri segnali, riportati su connettori esterni non utilizzati in questa
versione. Ad esempio, i segnali TX1/RX1 potranno essere utilizzati per dialogare con un
dispositivo seriale (un PC ad esempio), tramite un convertitore USB e i segnali SDA/SCL
possono essere utilizzati da diversi device che supportano l’interfaccia I2C (in una futura
versione).
Non ho utilizzato la USB già disponibile sul modulo per la comunicazione seriale dato che
l’alimentazione della USB a 5V interferirebbe con quella presente fornita esternamente al
modulo. Questa interfaccia USB va utilizzata solo per programmare il firmware quando il
8
modulo non è montato sulla scheda; questa si presente come nell’immagine seguente (anche
se i componenti a destra, portabatteria per batteria tampone e integrato RTC NON sono
utilizzati in questa versione)
Nella prossima versione (già “in cantiere”) disporrà di un modulo ESP32-S3, orientato
diversamente nel circuito stampato, con delle soluzioni diverse (magari sarà oggetto di un
articolo di aggiornamento).
La CPLD LXESP32
La CPLD utilizzata dalla scheda implementa il circuito mostrato di seguito ed è una
EPM7064SLC84 (o in alternativa una ATF1504AS, come nell’immagine della scheda)
9
Come si può vedere, il flip flop FFWAIT è attivato (tramite il suo ingresso di clock) dal segnale
CSESP32 ogni volta che viene indirizzata una porta di I/O tra quelle gestite dall’ESP32.
L’uscita negata del flip flop è utilizzata per il segnale di WAIT dello Z80 sul Bus. Dunque, nel
momento in cui lo Z80 indirizza una porta (in lettura o scrittura), lo stesso viene fermato nello
stato di attesa mantenendo le linee di indirizzi e dati congelati fino alla disattivazione del WAIT
stesso tramite CPURESW. L’ESP32 riceve un segnale in base al tipo di operazione (RD o WR)
e genera l’interrupt relativo all’input interessato; in questo modo viene avviata la corretta
routine del firmware che gestisce le operazioni richieste.
In basso, è implementata la rete con i buffer tristate per permettere ai dati di procedere nella
giusta direzione solo se la scheda viene attivata.
Il segnale CPUINT, inoltre, viene inviato all’ingresso INT dello Z80 sul Bus tramite buffer
open drain.
Infine, il blocco visibile in alto, denominato XIODEC, è responsabile della generazione del
segnale di selezione CSESP32 quando lo Z80 indirizza una delle porte coinvolte. Per comodità,
il blocco è implementato tramite un piccolo codice VHDL, visibile di seguito
10
---
LXESP32
---
--- I/O Port Decoder
---
---- PRINTER 0x03
---- HDISK 0xB8 .. 0xBA
---- VRS 0xBB
---- FDD 0xD0 .. 0xD3, 0xD6 .. 0xD7
---- FDDEXT 0xD4 .. 0xD5
---- CMATH 0xEC .. 0xED
---- TAPE 0xEE .. 0xEF
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
entity IO_DEC is
Port (
BM1 : IN STD_LOGIC;
BIORQ : IN STD_LOGIC;
BA : IN STD_LOGIC_VECTOR(7 DOWNTO 0);
CSESP32 : OUT STD_LOGIC
);
end IO_DEC;
architecture Behavioral of IO_DEC is
begin
process (BIORQ, BM1, BA)
begin
--- Disable CSESP32 Access
CSESP32 <= '1';
if (BIORQ = '0' AND BM1 = '1') then
if (
(BA = x"03") OR
(BA >= x"B8" AND BA <= x"BB") OR
(BA >= x"EC" AND BA <= x"EF") OR
(BA >= x"D0" AND BA <= x"D7")
) then
CSESP32 <= '0';
end if;
end if;
end process;
end Behavioral;
Questo codice sintetizza il seguente circuito logico
11
Il firmware
Il circuito è abbastanza semplice e tutto il lavoro è demandato al firmware che gira sull’ESP32
e gestisce il dialogo con lo Z80 da una parte e la SDCARD dall’altro.
Questo è scritto in C/C++ ed è necessariamente “corposo”, anche se non particolarmente
complicato da comprendere. Si basa sul funzionamento di un normale programma scritto per
Arduino/ESP32 ovvero con uno “scheletro” di base costituito da due funzioni
- setup(), in cui viene eseguito tutto il codice che è necessario, all’avvio del
microcontrollore, per preparare risorse (costanti e variabili), creare e inizializzare
oggetti specifici, eseguire operazioni una tantum su array, allocare memoria e altro;
- loop(), che viene continuamente chiamata, dopo la fine del setup, dal sistema operativo
sottostante (RTOS) in modo che venga eseguita ciclicamente per realizzare tutte le
funzionalità svolte dalla scheda.
Lo schema semplificato delle attività principali del firmware è il seguente
12
Una parte fondamentale è svolta da altre due funzioni per la gestione interrupt, che vengono
richiamate quando si presentano i segnali di lettura/scrittura delle porte da parte dello Z80,
mediate dalla CPLD, da una terza funzione di interrupt comandata dal timer interno (che viene
eseguita ogni 20 mS) e dalla funzionalità PWM, gestita dall’hardware, che genera l’interrupt a
50 Hz per lo Z80 (emula il funzionamento della scheda LX547).
Il codice è abbastanza lungo e non è comodo esaminarlo tutto in dettaglio. Verranno visti i
principali componenti quando necessario, associandoli alle funzionalità espresse nel resto del
presente documento (altri dettagli sul codice nella terza parte di questo documento).
La gestione dell’interrupt a 50 Hz
Per emulare il funzionamento della scheda LX547, come appena accennato, l’ESP32 genera
un segnale a 50 Hz sulla linea dell’interrupt dello Z80 e in questo modo il NE-DOS può
aggiornare l’orario e gestire altre funzionalità. Tale segnale viene generato sfruttando la
funzionalità hardware PWM (Pulse Width Modulation) dell’ESP32 che produce il segnale in
maniera autonoma (indipendente dall’esecuzione del codice) e con un duty cycle opportuno,
affinché la parte negativa sia molto stretta (circa 1 uS).
13
Il segnale CPUINT viene generato su un pin che, tramite la CPLD arriva al Bus sulla linea
open collector INT. Il seguente codice, eseguito nella funzione setup(), è sufficiente a
configurare il modulo PWM e avviare il segnala alla frequenza e duty cycle corretti.
#define CPUINT 1
pinMode(CPUINT, OUTPUT);
// Imposta PWM 50 Hz, risoluzione 16 bit, 99,99% duty cycle
ledcAttach(CPUINT, 50, 16);
ledcWrite(CPUINT, 65532);
Il parametro CPUINT rappresenta il numero del pin dell’ESP32 su cui viene generato il segnale
(nel nostro caso il pin 1), gli altri la frequenza, il numero di bit utilizzati per il conteggio e il
valore dopo il quale avviene il cambio di livello del segnale (da high a low).
Nelle immagini seguenti, a sinistra il segnale di interrupt diventa LOW per circa 1 uS; a destra
si vede questo segnale che si ripete ogni 20 mS, ovvero alla frequenza di 50 Hz
Il segnale CPUINT viene instradato all’input del pin di INTerrupt della CPU tramite la CPLD,
che garantisce anche una uscita open collector in modo da non influenzare altri segnali di INT
che potrebbero arrivare alla CPU
14
Una volta avviato il NE-DOS, è possibile usare alcuni comandi che sfruttano l’interrupt, come
mostrato nella seguente immagine
In particolare, il comando CLOCK attiva e disattiva la visualizzazione continua nella prima
linea dell’orario (che va impostato all’inizio con il comando TIME o con il nuovo comando
TIMESYNC/CMD
1
che sincronizza l’orario da Internet) e il comando TRACE che fa lo stesso
nella seconda riga
2
per il valore del Program Counter attuale (in tempo reale).
Se non viene utilizzato il NE-DOS questo segnale non serve, ad esempio con il SONE deve
essere disabilitato. Per farlo è possibile modificare lo schema della rete logica nella CPLD e
riprogrammarla. In una versione successiva della scheda verrà previsto anche un semplice
ponticello.
L’emulazione del Tape
In questa prima versione è stata implementata l’emulazione del funzionamento del chip
CDP1854 presente sulla scheda LX385, al fine di poter gestire l’interfacciamento del NEZ80
tramite le istruzioni di gestione Tape.
15
La comunicazione tra il sistema e l’ESP32 per quanto riguarda le operazioni di I/O su cassette,
avviene tramite le porte poste agli indirizzi 0xEE e 0xEF. La memorizzazione dei dati e
programmi può avvenire su due Tape diversi (Tape1 e Tape2), ad esempio tramite la CSAVE
e la CLOAD del BASIC su ROM da 16K, ma con delle limitazioni
1) il sistema funziona solamente con il BASIC 16 K su ROM;
2) la CSAVE salverà sempre un solo programma per volta, eliminando quello
eventualmente salvato in precedenza.
La memorizzazione avviene su SDCARD, in due file separati posti nella root, ovvero
Nome file
Descrizione
NEZ80_TP1
rappresenta la cassetta sul Tape1. Per la CSAVE del
BASIC da 16 K su ROM è una sequenza di dati binari
preceduti da un header costituito dai seguenti byte
0xD3 0xD3 0xD3 ‘X’
in cui il singolo carattere ‘X’ è il nome di riferimento dato
al programma.
La sua dimensione è variabile e dipende dalla lunghezza
del programma salvato con il comando
CSAVE “X”
NEZ80_TP2
come per il precedente, questo rappresenta la cassetta sul
Tape2 del sistema
Il salvataggio avviene tramite il comando
CSAVE#-2,”X”
I registri del chip CDP1854 e della scheda LX385 sono rappresentati in un array nel firmware
/*
CDP1854/Tape interface Registers array
RT_TXHR EE [0] WR Transmitter Hold Register
RT_FF EF [1] WR FlipFlop Control Register
RT_RXHR EE [2] RD Receiver Hold Register
RT_STATUS EF [3] RD Status Register x x x DA THRE PE FE OE
*/
uint8_t regTape[4]; // Array registri Tape
16
Il registro 0xEE è il registro dei dati (in R/W) e il registro 0xEF è il Controllo FF/STATUS.
Una scrittura nel registro di Controllo FF/STATUS, attiva o disattiva i due tape, fatto che
apre/chiude i file associati ai tape. Il registro Dati (in R/W) è utilizzato per leggere/scrivere i
byte sequenzialmente nei file.
Come accennato, il BASIC 16 K utilizza i comandi
CSAVE
CSAVE#-2
per salvare il programma in memoria su Tape (il -2 è usato per il Tape2) e le istruzioni
CLOAD
CLOAD#-2
per rileggere il programma in memoria.
L’emulazione della Stampante
Per l’emulazione della scheda LX389, interfaccia stampante, ho utilizzato solo la porta con
indirizzo 0x03 dato che il NE-DOS, il BASIC 2.1 e il BASIC 16K utilizzano soltanto questa
porta per la stampante, non sembra attualmente una limitazione (sebbene l’hardware della
LX389 permetta di utilizzare porte alternative).
L’output della stampante viene reindirizzato su file (attualmente è possibile utilizzare un solo
file per la stampante) creato sulla SD e denominato /NEZ80_PRN, aperto (e creato) durante il
setup del microcontrollore
prnFile = SD_MMC.open("/NEZ80_PRN", "a");
Il file conterrà tutto il testo stampato in sequenza dai vari programmi che utilizzano la
stampante (sia a livello BASIC che NE-DOS) e potrà essere esaminato su un PC con Windows
copiando il file NEZ80_PRN con un editor. Il file si comporta un po’ come un “modulo
continuo di carta” in cui viene stampato tutto in sequenza.
17
Utilizzando i nuovi comandi per il NE-DOS appositamente creati, è possibile cancellare il file
corrente (comando PRNRESET/CMD
3
) e anche importare il file NEZ80_PRN all’interno di
un disco NE-DOS per leggerlo senza dover usare un PC esterno (comando SDCOPY/CMD
4
).
La decodifica della porta 0x03 avviene in lettura (STATUS) e scrittura (DATI) tramite la
CPLD e il firmware sulla ESP32 sarà attivato per simulare le operazioni di stampa. Quando
viene inviato un byte sulla porta 0x03 e il bit7 è settato ad 1, il carattere (a 7 bit) viene aggiunto
al file della stampante come se fosse stato inviato su carta. Se la stessa porta viene letta, questa
indicherà se la stampante è pronta ad accettare altri dati o è occupata; nel nostro caso sarà
sempre pronta (non esistono parti meccaniche in movimento e le operazioni sono sincronizzate
con la WAIT) e quindi sarà possibile accettare un altro carattere immediatamente.
Dato che viene unicamente utilizzata la porta 0x03 per la stampa, questa operazione è del tutto
trasparente rispetto al comando o software utilizzato. Ad esempio, il comando LLIST del
BASIC invierà il listato del programma BASIC in memoria alla porta 0x03 e quindi al file su
SDCARD senza altri accorgimenti; anche il comando DISKTEST/CMD del NE-DOS, ad
esempio, potrà inviare l’output alla stampante accodandolo al file su SDCARD.
Nel dettaglio, se viene rilevato un interrupt in lettura da porta 0x03 (cpuAddr = 0x03), viene
memorizzata una richiesta di lettura dello STATUS e, durante la gestione della richiesta, viene
restituito alla CPU il contenuto della variabile associata il cui bit0 (BUSY) è sempre resettato,
indicando che la stampante è pronta a stampare (assunzione di default)
cpuData = regPRN[RGPR_STATUS]; // Vale sempre 0
Il BASIC 16 K per la stampa di un carattere, ad esempio, entra in questo loop
WRCHLP
CALL PRRDIN
JR NZ,WRCHLP
...
PRRDIN IN A,PORT_PRINT
AND 0x01
RET
18
nell’attesa che il bit0 (BUSY) diventi 0.
Se viene rilevato un interrupt in scrittura su porta 0x03, il dato viene subito memorizzato nella
variabile dedicata ai dati che arrivano alla stampante (compreso il bit di STROBE)
regPRN[RGPR_DATA] = cpuData;
e, durante la gestione della richiesta, inviato al file (già aperto) associato alla stampante solo se
il bit7 di tale dato è a 0 (l’interfaccia su stampante e a 7 bit e l’ottavo, ovvero il bit7, è lo
STROBE)
if ((regPRN[RGPR_DATA] & 0x80) == 0) {
prnFile.write(regPRN[RGPR_DATA]);
prnFile.flush();
}
Ad esempio, la routine del BASIC 16 K che invia un carattere alla stampante esegue, tra l’altro,
le seguenti linee
SET 7,A
OUT PORT_PRINT,A
RES 7,A ; STROBE = 0
OUT PORT_PRINT,A ; DATO VALIDO
SET 7,A
OUT PORT_PRINT,A
per creare un impulso negativo sullo STROBE (bit7) e convalidare il dato in output); l’ESP32
scrive solamente il dato che si presenta con il bit7 a 0 in modo che venga scritto su file in
maniera certa una sola volta.
19
Nella figura seguente, un esempio di output su file di stampa /NEZ80_PRN è quello del
comando DISKTEST/CMD del NE-DOS, visualizzato con Notepad++ su un PC
L’emulazione dell’interfaccia per Floppy Disk
L’emulazione del chip FD1771 e della scheda LX390 di interfaccia per Floppy Disk avviene
su due piani differenti e complementari. Il supporto su cui vengono registrati i dati non è più,
ovviamente, un floppy disk ma un file binario dal contenuto equivalente, scritto in una
SDCARD collegata al microcontrollore; il controllo dell’I/O dei dati avviene tramite una
emulazione del chip FD1771 realizzata dal firmware contenuto nel micro ESP32.
Il computer NEZ80 accede alle porte dell’intervallo 0xD0 0xD7 per gestire il dialogo con
l’interfaccia dei dischi, anche se sono effettivamente usati solamente gli indirizzo 0xD0
0xD2 e 0xD6 … 0xD7.
20
La rete di circuiti necessaria al corretto dialogo è implementata nella CPLD, che si occupa
anche di adattare la tensione dei 5V a cui opera lo Z80 a quella dei 3.3V con cui lavorano il
microcontrollore e la SDCARD. Il seguente schema mostra, a grandi linee, questa architettura
La tabella che segue, schematizza le porte utilizzate per l’interfaccia dischi, individuate dal
microcontrollore tramite il valore del nibble basso (0, 1, 2, 6 e 7)
A2 A1 A0 Indirizzo porta
0
0
0
A3=0
0xD0
CMD/STATUS FDD
0
0
1
0xD1
TRACK FDD
0
1
0
0xD2
SECTOR FDD
1 1 0 0xD6 SELECT DRIVE FDD
1
1
1
0xD7
DATA FDD
Dunque, la CPLD individua l’indirizzo completo della porta (0xD0 0xD7), genera un
segnale di interrupt per il micro ESP32 su due differenti pin, a seconda se la CPU vuole leggere
o scrivere e l’ESP32 determina la porta tramite la lettura del nibble basso dell’indirizzo.
Il registro 0xD6 è presente sulla scheda LX390, gli altri nel chip FD1771. Il registro 0xD6 è
utilizzato per selezionare il drive su cui si intende operare ed è utilizzato soltanto in scrittura.
I drive utilizzabili (dal NE-DOS) sono 4 (numerati da 0 a 3) e vengono attivati se il bit
corrispondente è a 1, così che i valori associati a tale registro possono essere
0x00 Nessun drive selezionato
0x01
Selezionato drive 0
0x02
Selezionato drive 1
0x04
Selezionato drive 2
0x08
Selezionato drive 3
21
Altri valori nel nibble alto vengono ignorati.
I dischi sono simulati tramite dei file binari scritti e letti su SDCARD e associati ai singoli
drive. Questi file avranno i seguenti nomi su SDCARD e saranno posizionati sulla root
Numero Drive Nome File Associato
0 NEZ80_FD0
1 NEZ80_FD1
2 NEZ80_FD2
3 NEZ80_FD3
Ogni file è logicamente organizzato in 40 tracce da 10 settori di 256 byte ciascuno e la
dimensione totale di ciascuno è dunque di 102.400 byte (solo per i dati utente, ovviamente non
esistono byte per sincronismi originati dalla formattazione). I settori sono allocati in sequenza
(traccia 0/settore 0, settore 1 settore 9, traccia 1/settore 0, settore 1 …) e l’accesso a
specifiche tracce/settori avviene tramite funzioni di seek del filesystem. Il puntatore al primo
byte del settore viene calcolato con la semplice formula
Offset = Traccia * 2560 + Settore * 256
cosi che, ad esempio, il settore 3 della traccia 17 inizia all’indirizzo (17 * 2560 + 3 * 256 =>
0xAD00) del file.
Per effettuare i test ho generato un file binario a partire dall’immagine del disco 27 di NE
5
alla
quale ho apportato alcune modifiche in modo che il NE-DOS utilizzasse la memoria video
dall’indirizzo 0xFE00 (versioni F dei software utilizzati, NE-DOS e BASIC 16K).
I comandi emulati
Al momento non sono stati implementati tutti i comandi eseguibili dal chip FD1771, dato che
molti non sono mai utilizzati dal NE-DOS. La seguente lista elenca i comandi gestibili
dall’ESP32, quando sono inviati alla porta 0xD0
22
Codice comando Mnemonico Descrizione
0x00
RESTORE
Seek a traccia 0
0x10 SEEK Seek a traccia e settore
specifici
0x50
STEPIN
Avanzamento di una traccia
0x80
READ
Lettura settore
0xA0
WRITE
Scrittura settore
0xD0
FORCE INTERRUPT
Interruzione comando attuale
0xF0
WRITETRK
Scrittura intera traccia
seguendo i protocolli di scambio dati indicati dal datasheet del chip FD1771.
Grazie a questa emulazione, il codice delle ROM e del sistema operativo NE-DOS non è stato
modificato nelle parti di gestione disco; in alcuni casi, quando possibile, ho eliminato le
istruzioni che introducevano un ritardo necessario a volte per attendere la risposta “meccanica”
dei drive, essendo il tutto adesso sincronizzato con il segnale WAIT.
Il boot e il caricamento del sistema operativo NE-DOS avviene tramite la ROM EP1390
presente a partire dall’indirizzo 0xF000. Nel sistema che ho replicato, il boot avviene sempre
dall’indirizzo 0x10000 della NVRAM (da 128 K) che comunque il micro Z80 vede come fosse
l’indirizzo 0x0000. Dall’indirizzo 0x10000 esiste il codice del boot loader che permette di far
partire la CPU dall’indirizzo 0x0F000 avviando il boot del SO. La schermata iniziale sarà la
seguente (la EP1390 l’ho modificata in piccola parte per la gestione della memoria video da
0xFE00)
Avviato il sistema, questo verrà caricato in memoria leggendolo dal disco NEZ80_FD0
presente nella SDCARD e in pochi secondi apparirà la seguente schermata
23
Possiamo subito controllare la presenza dei 4 dischi eseguendo il comando FREE del NE-DOS
(vedi video FREE.mp4), il cui risultato potrà, ad esempio, essere il seguente
Attualmente i 4 dischi, rappresentati da altrettanti file binari nella SDCARD, sono stati creati
montando la SDCARD su Windows e copiando l’immagine dei dischi originali del NE-DOS.
In una prossima versione, implementerò un sistema che consentirà di gestire il mount/umount
da sistema NE-DOS di varie immagini memorizzate su SDCARD così da poter gestire una
“libreria” di dischi disponibili e utilizzabili in maniera più pratica.
INDEX e HENGAGE
Per il funzionamento corretto, dato che il NE-DOS e il comando FORMAT rilevano anche la
presenza dell’impulso relativo al passaggio del foro del disco (INDEX) e dell’ingaggio della
testina (HENGAGE), viene simulata la generazione dei segnali corrispondenti tramite la
scrittura temporizzata sul registro di STATUS, come se avvenissero davvero (ma solo durante
l’esecuzione di alcuni comandi). Questa funzionalità sfrutta un timer hardware del
24
microcontrollore, che è impostato per generare interrupt ogni 20 mS. Opportuni controlli nel
codice di interrupt fanno in modo che il segnale INDEX sia attivo per 20 mS e inattivo per
180 mS, come mostra questa immagine in cui il segnale è stato riportato – solo per test – su un
pin e rilevato da un oscilloscopio digitale
In modo analogo per il segnale HENGAGE, dopo che viene richiesto l’ingaggio della testina,
si attiva un conteggio che, passati 400 mS, disattiva tale segnale che ne simula il disingaggio
Questi segnali rendono possibile il corretto funzionamento del programma FORMAT, che
effettua l’inizializzazione di un nuovo disco senza problemi, come mostrato nel video
FORMAT.mp4, da cui è stata ottenuta la seguente immagine
25
File presenti sul floppy disk
Naturalmente il comando utilizzato più spesso è il DIR che ci permette di controllare il
contenuto del disco, in particolare se lo usiamo chiedendo anche di vedere i file con attributi
particolari, scrivendo DIR (I,S) otterremo l’elenco dei file presenti anche se di sistema. Ad
esempio, questo elenco potrà essere il seguente, ottenuto in due schermate differenti
Come molti sapranno, il file BOOT/SYS e i vari SYSx/SYS sono i file che costituiscono il
sistema operativo NE-DOS, organizzati in modo che possano essere caricate le varie parti in
memoria quando necessario (gestione degli overlay). La questione ci porterebbe molto lontano
ma, ad esempio, nel file SYS6/SYS esiste il codice per la gestione di alcuni comandi come LIB,
26
FREE, LIST, PRINT mentre il file SYS3/SYS implementa i comandi KILL e CLOSE e il
SYS5/SYS il DEBUG.
Per ottenere la completa lista potrete usare il comando LIB
In questa versione del NE-DOS di NE il codice del comando COPY implementato in
SYS6/SYS è disabilitato e il suo posto viene preso dal programma COPY/CMD presente su
disco. Questo consente di copiare file in un disco o tra dischi e, soprattutto, consente di copiare
un intero disco scrivendo, ad esempio
COPY :0 TO :1 08/03/25
che potrete vedere all’opera nel video COPY.mp4.
Il BASIC 2.1
Una delle applicazioni più importanti presenti sul floppy è l’interprete BASIC 2.1 che si avvia
lanciando il comando
BASIC
Questo carica in memoria ed esegue l’interprete, come da schermata seguente
27
Dopo qualche secondo appare la versione del BASIC e il suo prompt (READY). A questo
punto è possibile caricare o eseguire un file BASIC con il comando LOAD o RUN. Ad esempio,
il file MOSTRA/BAS presente sul disco come dimostrativo di NUOVA ELETTRONICA, può
essere caricato con
LOAD “MOSTRA/BAS”
ed eseguito con RUN, oppure direttamente caricato ed eseguito con
RUN “MOSTRA/BAS”
Per l’esecuzione vedere il video BASIC.mp4.
1
Per una descrizione dettagliata del comando TIMESYNC/CMD, vedere la parte III del
documento
2
In realtà la TRACE visualizza anch’essa la propria informazione sulla prima linea, ho
modificato nel NE-DOS l’indirizzo video di destinazione per poter visualizzare l’orologio e il PC
contemporaneamente
3
Per una descrizione dettagliata del comando PRNRESET/CMD, vedere la parte III del
documento
4
Per una descrizione dettagliata del comando SDCOPY/CMD, vedere la parte III del
documento
5
L’immagine del file binario è stata ottenuta con il tool IMDU eseguito in DOSBOX e
utilizzato per convertire il file .DMK