Emulatore di BUS parallelo 8 bit con Arduino

>>> LINK A PAGINA AGGIORNATA <<<

  • .
  • .
  • .
  • .
  • .
  • .
  • .
  • .
  • .
  • .
  • .
  • .
  • .
  • .
  • .

 

 

 

Il BUS parallelo

A fine anni 80 inizi anni 90 del secolo scorso, era comune interfacciare un computer con circuiti esterni usando il bus parallelo di sistema.

Il bus del computer portava all’esterno i terminali della CPU (dati, indirizzi, abilitazioni, clock, interrupt ecc). Il clock (4..8MHz) era ancora abbastanza lento da essere gestibile con cablaggi non troppo curati.  Sugli “IBM compatibili”, come si chiamavano allora, si usava il bus ISA, che prevedeva otto indirizzi da 768 a 775 liberamente utilizzabili per prototipi.

Attraverso una piccola interfaccia buffer (alcuni integrati logici) e scrivendo o leggendo sulle porte collegate al bus con poche istruzioni BASIC (eventualmente completate con subroutine assembly per rispettare le temporizzazioni più critiche), era possibile comandare relé, leggere pulsanti, e fare altre cose più sofisticate, come pilotare programmatori di EEPROM, decodificare trasmissioni radio CW, effettuare conversioni analogico/digitali ecc.

 

Oggi…

Facciamo un salto in avanti di decenni, abbiamo PC potentissimi, ma “blindati”, privi di slot e porte hardware utilizzabili in modo semplice. Oggi per fare queste cose si usano i microcontrollori, che in qualche aspetto sono perfino più “potenti” dei PC di una volta.

Supponiamo di dover controllare un vecchio circuito come il seguente, progettato per essere collegato ad un bus parallelo (quando ancora si progettava con carta e penna):

SCHEMA ELETTRICO CODICI DI CONTROLLO

Arduino, tramite un piccolo shield, ci permette di nuovo di pilotare quel vecchio circuito usando poche istruzioni in un qualsiasi linguaggio “moderno”. Certo in questo caso specifico andiamo fino a 100 volte più lenti di quanto si poteva fare con sistemi 400 volte più lenti di quelli attuali, pazienza, perché quello che conta è poterlo comunque fare… e poi le “nuove leve” non lo sanno 😀

 

L’emulatore

Il circuito del programmatore di EPROM visibile sopra è stato inizialmente usato con un PC XT “IMB-compatibile” (ante 80286) tramite un’interfaccia da collegare al BUS ISA di sistema.

Lo schema seguente emula quel bus parallelo bidirezionale a 8 bit. Dispone di 3 bit di indirizzamento A0..A2 (quindi gestisce 8 indirizzi da 0 a 7), un segnale di abilitazione /E, e due segnali per abilitare la lettura o la scrittura /RD /WR. Quando /RD va basso le uscite dello shift register CD4094 vanno in alta impedenza per permettere ai bit in arrivo sul bus di raggiungere gli ingressi del CD4021 da cui vengono letti.

Schema elettrico emulatore bus parallelo


//__________________________________________________________
//
// EMULATORE DI BUS PARALLELO - By C.Fin 2012
// COMANDI:
//   0 addr        = lettura da bus
//   1 addr n      = scrittura su bus
//   2 addr n n2 t = doppia scrittura con
//                   't' ms di intervallo
//__________________________________________________________

# define SIPO_STROBE 2
# define SIPO_DATA   3
# define SH_CLK      4
# define PISO_LOAD   5
# define PISO_DATA   6
# define IO_RD       7
# define IO_WR       8
# define IO_EN       9
# define IO_A0      10
# define IO_A1      11
# define IO_A2      12
# define LED        13
//__________________________________________________________

unsigned long ledTime = millis();
byte ledStat = 1;
//__________________________________________________________

void setup()
{
    pinMode(SIPO_STROBE, OUTPUT);
    digitalWrite(SIPO_STROBE, 0);
    pinMode(SIPO_DATA, OUTPUT); 
    pinMode(SH_CLK, OUTPUT);
    digitalWrite(SH_CLK, 0); 
    pinMode(PISO_LOAD, OUTPUT);
    digitalWrite(PISO_LOAD, 0); 
    pinMode(IO_RD, OUTPUT);
    digitalWrite(IO_RD, 1); 
    pinMode(IO_WR, OUTPUT);
    digitalWrite(IO_WR, 1);   
    pinMode(IO_EN, OUTPUT);
    digitalWrite(IO_EN, 1);  
    pinMode(IO_A0, OUTPUT); 
    pinMode(IO_A1, OUTPUT); 
    pinMode(IO_A2, OUTPUT); 
    pinMode(LED, OUTPUT); 
    Serial.begin(38400);
    Serial.flush();
    digitalWrite(LED, ledStat);
}
//__________________________________________________________

void loop()
{
    while (Serial.available() == 0) blinkLed();     //attesa comando
    int cmd = Serial.read();
    if (cmd==0 || cmd==1 || cmd==2)
    {
        while (Serial.available() == 0) blinkLed(); //attesa indirizzo
        int addr = Serial.read();
        if (cmd == 0) { Serial.write(busRead(addr)); return; }
        while (Serial.available() == 0) blinkLed(); //attesa dato
        int n = Serial.read();
        if (cmd == 1) { busWrite(addr, n); return; }
        while (Serial.available() == 0) blinkLed(); //attesa dato2
        int n2 = Serial.read();
        while (Serial.available() == 0) blinkLed(); //attesa tempo
        busDoubleWrite(addr, n, n2, Serial.read());
   }
}
//__________________________________________________________

void blinkLed()
{
    if (millis() > ledTime + 40)
    {
        ledTime = millis();
        ledStat = !ledStat;
        digitalWrite(LED, ledStat);
    }
}
//__________________________________________________________

byte busRead(int addr)
{
    setBusAddr(addr);
    digitalWrite(IO_EN, 0);
    digitalWrite(IO_RD, 0);
    digitalWrite(SH_CLK, 1);
    digitalWrite(PISO_LOAD, 1);
    digitalWrite(PISO_LOAD, 0);
    byte n = shiftIn(PISO_DATA, SH_CLK, MSBFIRST);
    digitalWrite(IO_RD, 1);
    digitalWrite(IO_EN, 1);
    return n;
}
//__________________________________________________________

void busWrite(int addr, int n)
{
    setBusAddr(addr);
    shiftOut(SIPO_DATA, SH_CLK, MSBFIRST, n);
    digitalWrite(SIPO_STROBE, 1);
    digitalWrite(SIPO_STROBE, 0);
    busWritePulse();
}
//__________________________________________________________

void busDoubleWrite(int addr, int n1, int n2, int t)
{
    setBusAddr(addr);
    shiftOut(SIPO_DATA, SH_CLK, MSBFIRST, n1);
    digitalWrite(SIPO_STROBE, 1);
    digitalWrite(SIPO_STROBE, 0);
    busWritePulse();
    if (t > 0)
    {
        delayMicroseconds(830);
        if (t > 1) delay(t-1);
    }
    shiftOut(SIPO_DATA, SH_CLK, MSBFIRST, n2);
    digitalWrite(SIPO_STROBE, 1);
    digitalWrite(SIPO_STROBE, 0);
    busWritePulse();
}
//__________________________________________________________

void busWritePulse()
{
    digitalWrite(IO_EN, 0);
    digitalWrite(IO_WR, 0);
    digitalWrite(IO_WR, 1);
    digitalWrite(IO_EN, 1);
}
//__________________________________________________________

void setBusAddr(int addr)
{
    digitalWrite( IO_A0, addr&1);
    digitalWrite( IO_A1, (addr>>1) &1 );
    digitalWrite( IO_A2, (addr>>2)&1 );
}
//__________________________________________________________

Questo è il prototipo di shield realizzato per ottenere il bus. Su Arduino2009 il ponticello blu permette di attivare (se tolto) o disattivare (se inserito) l’autoreset.

bus emulatorbus emulatorUn connettore di Arduino (segnali da D8 ad AREF) è sfalsato di mezzo passo rispetto alla foratura delle basette millefori. Questo obbliga ad asportare un pezzo di basetta e saldare i terminali “in diagonale”. Un’alternativa potrebbe essere usare un connettore separato collegato alla millefori con un corto flat-cable (vedere anche articolo del professor Maffucci).

A questo punto, tramite lo sketch riportato sopra, si può comandare (leggere/scrivere) il bus tramite una connessione USB/seriale. La funzione busDoubleWrite del seguente programma Python permette di creare impulsi di t millisecondi (max 255) tramite due scritture successive. Se t vale 0 si ottiene un impulso di circa 170µs, pari al tempo di scrittura (o lettura) del bus.


import serial
import time
import struct
#___________________________________________________________

def bus_doublewrite(addr, n1, n2, t):
    ser.write(struct.pack("BBBBB", 2, addr, n1, n2, t))
    time.sleep(t/1000.)
#___________________________________________________________

def bus_write(addr, n):
    ser.write(struct.pack("BBB", 1, addr, n))
#___________________________________________________________

def bus_read(addr, n):
    ser.write(struct.pack("BB", 0, addr))
    return struct.unpack("B", ser.read(1))[0]
#___________________________________________________________

PORTA = "..."
ser = serial.Serial(PORTA, 38400, 8, "N", 1, timeout=1)
bus_write(0, 0x80)             #scrive 128 a indirizzo 0
n = bus_read(6)                #legge byte da indirizzo 6
bus_doublewrite(5, 128, 0, 12) #scrive 128 a indirizzo 5
                               #e dopo 12ms scrive 0
ser.close()

E il vecchio programmatore di EPROM

È quindi possibile comandare il vecchio programmatore di EPROM, autocostruito nel 1990, con un moderno PC dotato di sole porte USB. Per generare le temporizzazioni non è neppure necessario ricorrere a particolari subroutine assembly, perché ci pensa Arduino con la funzione busDoubleWrite.

Per motivi di velocità è comunque bene spostare su Arduino la maggior parte possibile della logica di controllo dell’hardware, lasciando al PC il solo compito di “gestione” ad alto livello. Ad esempio facendosi trasmettere da Arduino il contenuto dell’intera EPROM da 32 kbyte, in 18 secondi abbiamo i dati, mentre pilotando il bus con comandi elementari dal PC occorrerebbero parecchi minuti.

eprom programmer

Nota finale

Questo progetto è stato il primo shield realizzato per apprendere l’uso di Arduino. È possibile usare direttamente i pin di Arduino senza interporre gli shift register (andrebbero usati anche A0..A5 in modalità digitale). Però il programma sarebbe un po’ più complesso, perché bisognerebbe continuare a modificare la configurazione dei pin del bus dati da ingresso a uscita e viceversa. Nel fare questo bisognerebbe stare attenti ad evitare “collisioni”, cioè collegamenti momentanei uscita con uscita che rischierebbero di “far saltare” Arduino. Gli shift register invece si comportano da buffer elettrico di protezione, e permettono di lasciare liberi diversi pin di Arduino.

 

(2012)

 

Lascia un commento

Il tuo indirizzo email non sarà pubblicato. I campi obbligatori sono contrassegnati *