Dopo diversi giorni dal mio ultimo articolo sul mio computer autocostruito basato sullo Z80 sono tornato per parlarvi delle mie esperienze durante questo viaggio indietro verso i giorni passati degli 8 bit. Questo articolo vi mostrerà come sono riuscito a far comunicare il mio piccolo computer ed il mio Mac usando una comunicazione seriale sulla porta USB. Per fare questo ho dovuto trovare una periferica Z80 SIO (Serial Input/Output), un chip della grande famiglia Z80 sviluppato espressamente per le comunicazioni seriali. Ho anche comprato un convertitore USB/seriale basato sul sempreverde chip FT232 di FTDI, necessario non solo per pilotare i segnali RX/TX ma anche i segnali di sincronizzazione (come CTS) usati per sincronizzare le trasmissioni tra i dispositivi locale e remoto.
Lo Z80 SIO fa parte della famiglia Z80 perciò condivide con gli altri membri di questa serie le linee di controllo che permettono ai chip di essere connessi alla CPU Z80 e l’uno con l’altro senza componenti esterni. Come il PIO, che abbiamo usato in un precedente test, il SIO è un chip dedicato allo scambio di dati con il mondo esterno: a differenza del PIO, però, il SIO usa il protocollo seriale. In un protocollo parallelo i dati sono presentanti su linee complementari: ogni linea trasporta un singolo bit, e leggendo o scrivendo su quelle linee il protocollo trasporta l’informazione tra il dispositivo locale ed il server remoto. Ricordate i LED? Erano accesi/spenti passando un byte alla volta. Invece in un’interfaccia seriale i singoli bit dell’informazione sono spediti sequenzialmente, uno ad uno, su una singola linea. Questo permette di ridurre le connessioni richieste per collegare il trasmittente al ricevente, anche se ciò richiede più lavoro per scambiare tale informazione perché il trasmittente deve “disassemblare” il byte nei suoi singoli bit ed il ricevente deve a sua volta “riassemblare” questi bit in un byte. Inoltre, ci sono diversi altri segnali che il SIO deve gestire perché questo integrato è nato per collare un terminale locale ad un computer remoto via modem, segnali che erano usati per sincronizzare le trasmissioni. Per i nostri scopi abbiamo bisogno di poche linee perché l’USB è un versione più moderna ed efficiente delle vecchie porte seriali: accanto alle linee RX e TX, richieste ovviamente perché trasportano i dati scambiati fra i sistemi, andremo ad usare solo un’altra linea di output del SIO, il pin RTS. RTS sta per “Request To Send” ed è spedito da un dispositivo che vorrebbe iniziare una trasmissione. E’ come un allarme inviato al ricevente per informarlo che il trasmittente è pronto ad inviare i dati. Questa cosa era utile con i primi modem, che erano dispositivi half-duplex, ossia che non potevano inviare e ricevere nello stesso tempo, per cui questo meccanismo fu sviluppato per funzionare com un semaforo, per segnalare quale dispositivo doveva spedire i dati e quale doveva riceverli.
Adesso dobbiamo collegare il SIO all’LM80C. Prima di iniziare, vorrei spendere un paio di parole circa le versioni di SIO disponibili. Ci sono 3 differenti versioni del SIO (per le meno quando parliamo di formato DIP, che è il nostro caso): SIO/0, SIO/1 e SIO/2. Differiscono ognuno per una diversa piedinatura. Dato che il formato DIP del chip ha solo 40 piedini, non c’è abbastanza spazio per portare fuori tutti i segnali interni del dispositivo per cui gli ingegneri hanno fatto delle scelte ed hanno unito alcuni segnali insieme, per rispondere alle diverse richieste degli utenti. Per i nostri scopi queste versioni sono assolutamente equivalenti: personalmente ho scelto il SIO/0 perché ho trovato questa versione.
Iniziamo a collegare il chip: Dobbiamo collegare:
- i pin D7..D0 al bus dati;
- M1, /IORQ, /RD, /INT e CLK ai corrispondenti piedini della CPU;
- C/D e B/A sono usati per selezionare la modalità “command/data” e la porta seriale B/A, rispettivamente (ho per caso dimenticato di dire che il SIO ha 2 porte seriali? Sì, le ha perciò potete usare un singolo SIO per creare 2 linee seriali, ad esempio per collegare il SIO ad un’altra periferica seriale e ad un computer remoto). Collegateli rispettivamente a A1 e A0, (vi spiegherò le funzioni di queste connessioni in un’altra parte dell’articolo);
- IEI e IEO sono uisati per inserire il chip nella catena di gestione degli interrupt della famiglia Z80: personalmente ho scelto il SIO come dispositivo con la priorità più alta nella catena, seguito poi dal CTC ed infine dal PIO, in ultima posizione. La posizione nella catena di gestione degli interrupt è importante per impostare la priorità: più il dispositivo è posto in cima alla catena e più velocemente verrà servito il suo interrupt. Ho considerato che ricevere un carattere dalla linea seriale era più importante del segnale dato dal CTC per aggiornare un contatore. Per ottenere questo basta collegare il piedino IEI ai+5V attraverso un resistore da 10 K e l’IEO al piedino IEI del CTC; poi bisogna collegare l’IEO del CTC all’IEI del PIO.
- VCC e GND a 5V e massa, rispettivamente (non scordatevi il condensatore di disaccoppiamento, ovviamente).
Adesso dobbiamo confrontarci con un paio di problemi. Il primo da risolvere è come fare per selezionare (leggi: abilitare) il chip. Questo è facile da risolvere dato che abbiamo una coppia di uscite non connesse nel decoder degli indirizzi 74139 che possiamo utilizzare per selezionare il SIO. Basta connettere la linea Y2 (pin 10) del 74139 al piedino /CE del SIO. Dobbiamo anche fare un’altra modifica al precedente circuito che abbiamo realizzato quando abbiamo aggiunto il CTC: il piedino n. 14 del 74139 deve essere scollegato da massa e collegato invece al pin A5 della CPU. In questo modo possiamo selezionare la periferica SIO durante un’operazione di I/O semplicemente impostando A5 e A4 a 1 e 0, rispettivamente. Il secondo problema è più complesso. Il SIO utilizza 2 differenti clock: uno è il clock di sistema che è usato internamente dal chip per le classiche temporizzazioni mentre l’altro è usato per sincronizzare i canali RX e TX. Questo clock influisce in maniera diretta il baud rate delle porte seriali. In ogni manuale dello Z80 viene suggerito di usare il CTC per fornire tale clock così che si possa cambiare la velocità seriale via software semplicemente cambiando la frequenza del segnale di sincronizzazione generato dal CTC. Il SIO può impostare un prescaler per dividere il clock seriale di un fattore 1, 16, 32 o 64. Il baud rate è legato a specifiche velocità di clock: una di questa è 1.843.200 Hz. Vi ricordate che, all’inizio del nostro viaggio ai tempi degli 8 bit, abbiamo scelto uno specifico clock per il nostro computer, 3.686.400 Hz? Infatti, 3.686.400 MHz è esattamente il doppio della frequenza su menzionata. A prima vista questo sembra un problema che possa essere risolto facilmente: basterebbe scegliere il prescale di 1 e collegare il clock di sistema ai pin del clock del trasmettitore/ricevitore. Fatemi dire subito che questa soluzione NON funziona! Perché i clock di RX/TX devono essere almeno 16 volte il baud rate. Qualcuno potrebbe pensare che la soluzione risieda nell’usare il CTC per ottenere il segnale a 3.686.400/16=230.400 Hz necessario per pilotare i pin RX e TX. Altro errore! Non possiamo farlo perché i timer del CTC accettano in input solo clock con una frequenza massima che è la metà di quella del clock di sistema: nel nostro caso, questo significa un clock di 3.686.400/2=1.843.200 Hz. La soluzione è quella di aggiungere un altro flip-flop di tipo D ed usarlo per dimezzare la frequenza del clock di sistema, come abbiamo fatto per la frequenza iniziale di 7,37 MHz del cristallo di quarzo che stiamo usando. Nuovamente, non possiamo usare questo segnale per temporizzare il timer perché è esterno al CTC: useremo perciò il clock da 1,84 MHz come clock in ingresso di una delle unità del CTC. Ogni unità del CTC può operare sia in modalità timer che in modalità contatore. Quest’ultima fa semplicemente ciò che dice il suo nome: conta eventi che avvengono sul suo piedino di ingresso. Collegheremo pertanto l’output del flip-flop di tipo D al pin di innesco del timer/contatore del CTC e conteremo un certo numero di impulsi per generare la frequenza di clock di 230.400 necessaria ai pin di clock di RX/TX del SIO. Ma quanti impulsi dobbiamo contare? Per prima, dobbiamo scegliere la velocità della linea seriale che intendiamo impostare fra lo Z80 ed il nostro computer; dopo andremo ad impostare il CTC per generare tale clock.
Ho considerato che un buon compromesso fra velocità, stabilità e compatibilità sia 19.200 bps (baud per secondo) perciò, dato che il clock TX/RX deve essere almeno 16 volte il baud rate, abbiamo bisogno di un clock TX/RX di 19.200 x 16 = 307.200 Hz. Avendo un clock di input sul pin di innesco del CTC di 1.834.200 Hz, per ottenere il clock di 307.200 Hz clock il CTC deve contare 1.843.200 / 307.200 = 6 impulsi di clock. Impostiamo così il valore iniziale nel registro interno a 6 ed impostiamo l’unità per operare in modalità contatore: ogni impulso sul pin di innesco decrementerà il contenuto del registro contatore. Se ricordate, il CTC invia automaticamente un segnale basso sul piedino di uscita TOx della corripondente unità timer/contatore non appena il registro interno raggiunge il valore zero. Facendo così abbiamo impostato un sistema per avere specifici baud rate che possiamo aggiustare via software, se abbiamo bisogno di cambiare la velocità seriale in un futuro momento.
Siamo vicini al traguardo. Prima di testare la comunicazione seriale dobbiamo collegare il modulo FT232. Useremo la porta seriale A del SIO, riferendoci allo schema più sotto per trovare i piedini corrispondenti:
- collegare il pin TX dell’FT232 al pin RX della porta seriale A del SIO;
- fate lo stesso collegano il pin RX dell’FT232 al pin TX della porta A del SIO (ovviamente dobbiamo incrociare le linee RX/TX perché la lina RX del trasmittente va alla linea TX del ricevente, e viceversa);
- collegare la linea CTS dell’FT232 al pin RTS della porta A del SIO (questo segnale è inviato dal SIO al dispositivo remoto per avvisare quando vuole iniziare la trasmissione);
- infine, collegare GND dell’FT232 alla massa del circuito.
ATTENZIONE! NON collegate il pin 5V che viene dal modulo FT232 alla linea 5V del computer a meno che non intendiate alimentare l’LM80C attraverso l’FT232. Se l’LM80C ha già una sua alimentazione lasciate scollegata la linea 5V/VCC dell’FT232.
Se guardate allo schema qui sotto noterete che abbiamo aggiunto dei resistori sulle linee RX/TX/CTS che vengono dal modulo FT232: questo è fatto per limitare la corrente tra il modulo e l’LM80C quando vengono usate 2 diverse alimentazioni. Infatti, se alimentate l’LM80C prima di collegare l’FT232 al PC vedrete che scorre della corrente attraverso i collegamenti per via del fatto che si illumineranno i LED del modulo seriale: ciò è devastante per i circuiti integrati! Mai spedire segnali sui pin di un chip non alimentato.
Ci sono ancora delle piccole cose da sistemare. Dato che stiamo usando il SIO per comunicare con una linea seriale semplificata rappresentata dal nostro adattatore USB, molti dei segnali di sincronizzazione usati nelle trasmissioni via modem non servono ai nostri scopi: dobbiamo perciò impostare un segnale alto sul pin SYNCA mediante un resistore da 10 K collegato a 5V e collegare invece a massa i pin CTCA e DCDA (sono usati per attivare il ricevitore seriale, ma noi vogliamo che il nostro ricevitore sia sempre attivo). Questa foto mostra il mio prototipo montato sulla breadboard: a sinistra si vede il modulo FT232.
L’hardware è pronto. Adesso pensiamo al software. Il nostro programma è un’evoluzione di quello usato per provare gli interrupt del CTC: abbiamo aggiunto una porzione di codice per impostare il SIO e le routine di interrupt che ricevono i caratteri spediti all’LM80C dal computer remoto. Per provare la comunicazione colleghate il modulo FT232 al computer ospite, poi lanciate un emulatore di terminale seriale di vostro gradimento (ho usato CoolTerm sul mio Mac ma ci sono un sacco di software simili tra cui scegliere) ed impostare la porta seriale ad una velocità di 19.200 bps con l’impostazione 8N1: questa sigla indica che state usando 8 bit per i dati, nessun bit di parità ed 1 bit di stop, le stesse impostazioni usate nella configurazione del SIO (date un’occhiata al codice). Adesso provate a digitare qualche lettera e vedrete che l’LM80C rispedisce indietro i tasti premuti sull’emulatore seriale. Ciò funziona perché il SIO è impostato in modo che sollevi un interrupt ogni volta che viene ricevuto un carattere. La CPU riconosce l’interrupt (grazie al fatto che il SIO è inserito nella catena di gestione degli interrupt) e gestisce la corrispondente routine di servizio dell’interrupt. Il carattere è prelevato dal buffer di ingresso del SIO e spedito indietro al computer remoto. Grazie al fatto che l’interfaccia seriale è pilotata dagli interrupt la CPU può continuare ad eseguire il codice principale, senza nessun apparente arresto causato dalla comunicazione seriale, come potete vedere dai LED che continuano il conteggio binario. Un altro segnale che lo Z80 sta ricevendo i caratteri è dato dai 2 pin di output più significativi del PIO che sono accesi/spenti non appena un nuovo carattere è disponibile dal SIO.
Una menzione speciale va a Mario Blunk: ha scritto una guida davvero ottima su come programmare le periferiche dello Z80, che ho usato per impostare il codice base dei chip dello Z80.
Codice e schema sono disponibili su questo repository di GitHub.