Arduino

 
...ovvero: l' estensione che mancava al PC...

 



Riflessioni...

Questa non e' un' introduzione ad Arduino, in rete se ne trovano molte, assieme a forum tematici e riferimenti riguardanti sia l'hardware che la programmazione. Piuttosto e' una raccolta di esperimenti personali su questa schedina, che Ammetto di aver "snobbato" per diversi anni grosso modo per i seguenti pregiudizi:

Ma, nonostante non sia un sistema particolarmente potente se paragonato ad altre architetture, ha spopolato in tutto il mondo riscuotendo un successo strepitoso, e il motivo e' molto semplice, Arduino e' l'estensione che mancava al PC, e' la "user port" programmabile che serve a tutti coloro che vogliono usare i moderni PC per realizzare controlli hardware non troppo complessi per interagire con il mondo esterno in modo semplice, economico, immediato, multipiattaforma, e soprattutto senza bisogno di studiare l'architettura di un microcontroller , acquistare appositi sistemi di sviluppo o essere esperti in elettronica e informatica.

Al momento, per chi vuole collegare qualcosa di facilmente programmabile ad un PC, non c'e' nient'altro di cosi' pratico da utilizzare: un solo cavetto USB, senza alimentatori e nient'altro, e come linguaggio di programmazione il C, ampliato con alcune funzioni di alto livello che si occupano in modo trasparente degli aspetti hardware del micro.

Altro che gadget... Arduino e' "l'uovo di colombo", e' la periferica del PC che "permette di fare le cose", uno straordinario (in quanto semplice) strumento di lavoro e prototipazione, e queste doti fanno passare decisamente in secondo piano i paragoni con altre architetture piu' potenti, ma, alla fine, troppo brigose o complesse per i compiti per cui invece Arduino e' adatto. La sua stessa forma permette di innestarci sopra qualsiasi circuito da controllare (in gergo uno "shield"), o di collegare i suoi terminali ad una bredboard, e realizzare un prototipo funzionante e' veramente una questione di pochissimo tempo.

Tutto cio' e' ben spiegato dall' inventore di Arduino in questo talk di 20 minuti:
http://www.youtube.com/watch?v=9y77NPD4BCk




Un po' piu' da vicino...

Mi riferisco principalmente alla versione "Arduino 2009". La scheda e' composta fondamentalmente da un microcontroller ATmega della Atmel (precaricato con un bootloader seriale), un regolatore di tensione, e un convertitore USB/seriale. Infatti il microcontroller dialoga con il PC esclusivamente attraverso il protocollo seriale asincrono, pertanto se venisse collegato direttamente ad una porta seriale (interponendo solo un adattatore di livello EIA/TTL) , il tutto continuerebbe a funzionare regolarmente (questo e' il sistema da usare con le schedine Arduino "mini" interamente SMD che non hanno a bordo il convertitore USB/seriale).

E' quindi vero che per programmare Arduino occorre il proprio ambiente di sviluppo, ma una volta programmato per una certa funzione Arduino e' autonomo e puo' dialogare con la seriale con qualsiasi altro programma, e quindi le possibilita' di utilizzo diventano enormi (e questo anche senza contare la miriade di shields e librerie che sono nate nel corso del tempo).

Di base abbiamo 6 ingressi analogici con 10 bit di risoluzione e 14 ingressi/uscite digitali, di cui 6 possono opzionalmente generare un segnale PWM, 2 sevono per la comunicazione seriale, 3 per la SPI. La comunicazione I2C va invece ad impegnare due ingressi analogici. Se nessuna di queste funzioni speciali viene utilizzata tutti i pin sono usabili come normali ingressi/uscite. Il microcontroller e' un ATmega328 cloccato a 16MHz, con di 32K per il programma e 2K di RAM.

Il linguaggio, oltre a funzioni di alto livello per usare in modo semplice bus I2C, SPI, shift register, servocomandi ecc, prevede l'uso della virgola mobile ed e' completo delle funzioni trigonometriche e logaritmiche. Il punto di forza e' appunto l'IDE multipiattaforma, che con il click su un'icona compila, trasferisce i programmi ("sketch") e li avvia, in un'unica operazione.

Rivediamo i "pregiudizi" di prima:


Scrittura su shift register

Verificare se la funzione shiftOut e' adatta a comandare uno shift register CD4094.
La resistenza sul reset serve per disabilitare l'autoreset di Arduino, che avviene ogni volta che la porta seriale emulata su USB viene aperta, e serve per mettere il bootloader in ricezione.  Se entro poco tempo il bootloader non riceve dati allora avvia il programma attualmente caricato. Pero' diventa deleterio quando con un altro programma si vuole comunicare con un'applicazione gia' in funzione su Arduino, perche' ad ogni apertura della porta seriale si causerebbe il reset.



Sketch per testare la trasmissione di un numero binario crescente da visualizzare sui led (Q8 e' il bit piu' significativo).
All'inizio si associano dei nomi significativi ai numeri delle uscite di Arduino, e si inizializza una variabile globale di tipo byte.
Seguono le due funzioni principali di ogni programma per Arduino, setup viene eseguita solo una volta all'inizio e serve appunto per i settaggi iniziali, mentre loop viene eseguita in continuazione e rappresenta il mainloop, cioe' il ciclo principale del programma.

//----------------------------------------------------------
// Test funzione shiftOut su shift register sipo CD4094
//----------------------------------------------------------
#define sipo_strobe  2
#define sipo_data    3
#define sipo_clock   4
//----------------------------------------------------------
byte counter = 0;
//----------------------------------------------------------
void setup()
{
    pinMode(sipo_strobe, OUTPUT);
    pinMode(sipo_data, OUTPUT);
    pinMode(sipo_clock, OUTPUT);
    digitalWrite(sipo_strobe, LOW); 
    digitalWrite(sipo_clock, LOW); 
}
//----------------------------------------------------------
void loop()
{
    shiftOut(sipo_data, sipo_clock, MSBFIRST, counter);
    digitalWrite(sipo_strobe, HIGH); 
    digitalWrite(sipo_strobe, LOW); 
    counter += 1;
    delay(100);
}
//----------------------------------------------------------


Il codice prodotto dalla compilazione occupa 1202 byte dei quasi 32k disponibili.
Ha funzionato al primo colpo e togliendo la funzione delay (che ha il solo scopo di rallentare il processo per poter osservare comodamente il movimento dei led) i risultati sono stati i seguenti: il mainloop dura 130uS, effettua cioe' 7694 scritture complete al secondo sullo shift register, equivalenti a piu' di 61kbit/s di velocita', questo vuol dire ad esempio poter aggiornare 770 volte al secondo 80 uscite digitali ottenute da una serie di 10 shift register. Per determinare il numero di scritture al secondo e' stato sufficiente misurare la frequenza del segnale sipo_strobe con un frequenzimetro (questo segnale infatti e' una conferma che trasferisce sulle uscite dello shift register gli 8 bit appena ricevuti).




La prova successiva e' stata quella di ricevere dall'esterno i valori da trasmettere allo shift register, usando la porta seriale:


//----------------------------------------------------------
// Test funzione shiftOut su shift register sipo CD4094
// comandata da PC
//----------------------------------------------------------
#define sipo_strobe  2
#define sipo_data    3
#define sipo_clock   4
//----------------------------------------------------------
void setup()
{
    pinMode(sipo_strobe, OUTPUT);
    pinMode(sipo_data, OUTPUT);
    pinMode(sipo_clock, OUTPUT);
    digitalWrite(sipo_strobe, LOW); 
    digitalWrite(sipo_clock, LOW);
    Serial.begin(38400);
}
//----------------------------------------------------------
void scriviSipo(int n)
{
    shiftOut(sipo_data, sipo_clock, MSBFIRST, n);
    digitalWrite(sipo_strobe, HIGH); 
    digitalWrite(sipo_strobe, LOW); 
}
//----------------------------------------------------------
void loop()
{
    if (Serial.available() > 0) scriviSipo(Serial.read());
}
//----------------------------------------------------------



Il programma trasmittente su PC per inviare i dati ad Arduino e' stato scritto in Python (2 e 3), al posto dei puntini va scritta la propria porta seriale, ad esempio com1,  /dev/ttyS0,  /dev/ttyUSB0,  /dev/ttyACM0:


import serial
import time
import struct
#------------------------------------------------------------------------------
PORTA = "..."
ser = serial.Serial(PORTA, 38400, 8, "N", 1)
for n in range(256):
    ser.write(struct.pack("B", n))
    time.sleep(0.1)
ser.close()


Anche questa volta ha funzionato tutto al primo colpo.



Ed infine, lo sketh precedente e' stato modificato per rendere lampeggiante il led presente sul terminale 13 (e' molto utile avere un indicatore di funzionamento). Si e' cosi' provata anche la funzione millis per produrre ritardi controllati senza usare funzioni delay, che durante l'attesa bloccherebbero completamente qualsiasi altra elaborazione. Il codice prodotto e' risultato di 2580 byte.


//----------------------------------------------------------
// Test funzione shiftOut su shift register sipo CD4094
// comandata da PC + led stato lampeggiante a 10Hz
//----------------------------------------------------------
#define sipo_strobe  2
#define sipo_data    3
#define sipo_clock   4
#define led         13
//----------------------------------------------------------
unsigned long tempo = millis();
int stato_led = HIGH;
//----------------------------------------------------------
void setup()
{
    pinMode(sipo_strobe, OUTPUT);
    pinMode(sipo_data, OUTPUT);
    pinMode(sipo_clock, OUTPUT);
    digitalWrite(sipo_strobe, LOW); 
    digitalWrite(sipo_clock, LOW);
    Serial.begin(38400);
    pinMode(led, OUTPUT);
    digitalWrite(led, stato_led);
}
//----------------------------------------------------------
void scriviSipo(int n)
{
    shiftOut(sipo_data, sipo_clock, MSBFIRST, n);
    digitalWrite(sipo_strobe, HIGH); 
    digitalWrite(sipo_strobe, LOW); 
}
//----------------------------------------------------------
void loop()
{
    if (Serial.available() > 0)  scriviSipo(Serial.read());

    if (millis() - tempo >= 50)
    {
        tempo += 50;
        stato_led = !stato_led;
        digitalWrite(led, stato_led);
    }
}
//----------------------------------------------------------

Lettura da shift register

Prova per verificare se la funzione shiftIn e' adatta a comandare uno shift register CD4021.



Sketch per testare la lettura da shift register e ritrasmissione su porta seriale (P8 e' il bit piu' significativo).


//----------------------------------------------------------
// Test funzione shiftIn su shift register piso CD4021
//----------------------------------------------------------
#define piso_load    2
#define piso_data    3
#define piso_clock   4
int n;
//----------------------------------------------------------
void setup()
{
    pinMode(piso_load, OUTPUT);
    pinMode(piso_data, INPUT);
    pinMode(piso_clock, OUTPUT);
    digitalWrite(piso_load, LOW); 
    Serial.begin(38400);
}
//----------------------------------------------------------
void loop()
{
    digitalWrite(piso_clock, HIGH); 
    digitalWrite(piso_load, HIGH); 
    digitalWrite(piso_load, LOW); 
    n = shiftIn(piso_data, piso_clock, MSBFIRST);
    Serial.write(n);
    delay(100);
}
//----------------------------------------------------------


Non ha funzionato al primo colpo per una questione di polarita' del clock (il CD 4021 shifta i dati in corrispondenza del fronte di salita del clock). Le soluzioni sono due, o si inverte il livello di piso_clock con un transistor, o, come in questo sketch, si predispone ogni volta il livello di piso_clock alto prima dell'impulso di acquisizione piso_load.
Verificato il funzionamento tramite un programma ricevente scritto in Python, si e' di nuovo misurata la velocita' massima togliendo la trasmissione seriale e il delay. I risultati sono stati molto simili a quelli precedenti: 7480 letture al secondo.

Programma ricevente su PC in Python (2 e 3), il numero binario viene visualizzato in una piccola finestra.



try:
    import tkinter as tk
except ImportError:
    import Tkinter as tk
import serial
import struct
#------------------------------------------------------------------------------
def ricevi():
    try:
        n = struct.unpack("B", ser.read(1))[0]
        testo.configure(text=bin(n)[2:].rjust(8, "0"))
    except:
        testo.configure(text="No ricezione")
    gui.after(20, ricevi)
#------------------------------------------------------------------------------
PORTA = "..."
ser = serial.Serial(PORTA, 38400, 8, "N", 1, timeout=1)
gui = tk.Tk()
gui.resizable(False, False)
testo = tk.Label(gui, width=20, font=("monospace", 20))
testo.pack()
ricevi()
gui.mainloop()
ser.close()


Controllo luci con joystick

Da una discussione su un progetto, e' nata la curiosita' per Arduino, e Arduino e' diventato una possibile soluzione per quel progetto chiudendo il cerchio: si tratta di un controllo luci tramite joystick avente le seguenti caratteristiche:


Il circuito prevede l'uso di un multiplexer analogico CD4051 per ampliare gli ingressi analogici. Su CH0 CH1 CH2 sono collegati rispettivamente i potenziometri nord-sud est-ovest e master del joystick 1, e il pulsante cprrispondente e' quello collegato al terminale D2 di Arduino. Stessa sequenza per il joystick 2 collegato agli altri ingressi del 4051, con pulsante collegato al terminale D4 di Arduino.



Il circuito provato su bredboard simula i 2 assi di un solo joystick, il potenziometro master, il trimmer del tempo di fading, i pulsanti on/off dei due joy, oltre alle 4 uscite PWM che comandano 4 led ad alta luminosita'.



Lo sketch occupa 5 kbyte. L'elaborazione completa, compresi i numerosi calcoli floating point, dura circa 1.9millisecondi. Il programma viene comunque rallentato in modo da portare avanti circa 200 elaborazioni al secondo, e passa oltre il 60% del tempo in un ciclo di attesa. Le letture dagli ingressi analogici sono risultate un po' "ballerine", pertanto viene fatta una media mobile delle ultime 8 letture memorizzandole in un array. Inevitabilmente si notano degli "scalini" nella luminosita' quanto piu' la luminosita' master e' bassa e il tempo di fading lungo, per ridurre il piu' possibile questo effetto tutti i calcoli sono stati realizzati con valori floating point e la risoluzione temporale e' stata portata a 5 ms dai 20 iniziali.

Il programma e' strutturato sotto forma di automi a stati finiti per permettere il multitasking cooperativo, e quindi portare avanti piu' compiti in parallelo (debounch pulsanti, fading, scambio master/slave, elaborazione, blink del led).  Nello schema seguente sono indicati in rosso i terminali di Arduino usati dai vari processi (cliccando sui processi si puo' vedere il codice di ciascuno di essi).




Emulatore di BUS parallelo controllato da PC

Versione aggiornata su blog:
http://spectrum.altervista.org/emulatore-bus-parallelo/

 

Ovvero: quello che si poteva fare una volta... e con Arduino (anche se piu' lentamente) si puo' fare ancora :)

Poniamo di dover controllare un vecchio circuito progettato per essere collegato ad un bus parallelo come il seguente:

prog eprom 1 (schema)
prog eprom 2 (utilizzo)



Venti anni fa si usava il bus ISA (che prevedeva 8 indirizzi liberamente utilizzabili da 768 a 775), attraverso una piccola interfaccia buffer, scrivendo e leggendo sulle porte collegate al bus con poche istruzioni BASIC, eventualmente completate con qualche subroutine assembly dove vi fossero delle temporizzazioni critiche da rispettare.

Il circuito del programmatore di EPROM  del link sopra e' stato appunto utilizzato con un bus di un PC XT (ante 286).

Facciamo un salto in avanti di 22 anni, abbiamo PC potentissimi, ma "blindati", privi di slot e porte utilizzabili in modo semplice. Ma arduino ci permette di nuovo di pilotare quel vecchio circuito con poche istruzioni in un qualsiasi linguaggio moderno. Certo in questo caso specifico andiamo fino a 100 volte piu' lenti di quanto si poteva fare con sistemi 400 volte piu' lenti di quelli attuali, pazienza, perche' quello che conta e' poterlo comunque fare... e poi le "nuove leve" non lo sanno per cui gli sembrera' comunque abbastanza veloce :D

Lo schema seguente emula un 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 del CD4094 vanno in alta impedenza per permettere ai bit in arrivo sul bus di raggiungere gli ingressi del CD4021 da cui vengono letti.



//--------------------------------------------------------------------
//   EMULATORE DI BUS PARALLELO - By Claudio Fin 2012
//       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 e' il primo shield realizzato per ottenere il bus. Il ponticello blu permette di attivare (se tolto) o disattivare (se inserito) l'autoreset di Arduino.

 



Una brutta sorpresa, ma comunque superabile con un po' di pazienza: un connettore di Arduino (segnali da D8 ad AREF) e' sfalsato di mezzo passo, questo obbliga ad asportare un pezzo di basetta e saldare i terminali "in diagonale". Un'alternativa potrebbe essere usare strip di contatti con i terminali piu' lunghi in modo che siano leggermente flessibili.
 

 



A questo punto e' possibile comandare (leggere/scrivere) il bus tramite una connessione seriale. La funzione busDoubleWrite 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 all'indirizzo 0
n = bus_read(6)                 # legge un byte dall'indirizzo 6
bus_doublewrite(5, 128, 0, 12)  # scrive 128 all'indirizzo 5
                                # e dopo 12ms scrive 0
ser.close()



E' quindi di nuovo possibile comandare con il PC il vecchio programmatore di EPROM costruito 22 anni fa, non solo, ma non e' neppure necessario ricorrere a subroutines assembly per generare le temporizzazioni perche' ci pensa Arduino "dall' esterno"  usando la funzione busDoubleWrite.

Per motivi di velocita' e' 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.





Controllo diretto delle porte

Il controllo diretto delle porte di ingresso/uscita permette un incremento di velocita' da 4 a 25 volte delle operazioni di I/O stesse. In particolare con le seguenti macro (fonte), che servono per settare/resettare un singolo bit di un registro, il tempo di scrittura su un pin del micro risulta pari a 125nanosecondi:

# define cbi(sfr, bit) (_SFR_BYTE(sfr) &= ~_BV(bit))
# define sbi(sfr, bit) (_SFR_BYTE(sfr) |= _BV(bit))


Tabella pin micro vs terminali Arduino2009/UNO:

+---------+--------------------+---------+------------+---------+------------+
+   PIN   + Terminale Arduino  +   PIN   +  Arduino   +   PIN   +  Arduino   +
+---------+--------------------+---------+------------+---------+------------+
| PB0 (14)| Dig8               | PC0 (23)| Ain0       | PD0 (2) | Dig0 (RX) <----+
| PB1 (15)| Dig9  (PWM)        | PC1 (24)| Ain1       | PD1 (3) | Dig1 (TX) ---->|
| PB2 (16)| Dig10 (PWM)        | PC2 (25)| Ain2       | PD2 (4) | Dig2       |   |
| PB3 (17)| Dig11 (PWM) (MOSI) | PC3 (26)| Ain3       | PD3 (5) | Dig3 (PWM) |   |
| PB4 (18)| Dig12       (MISO) | PC4 (27)| Ain4 (SDA) | PD4 (6) | Dig4       |   |
| PB5 (19)| Dig13 (LED) (CLK)  | PC5 (28)| Ain5 (SCL) | PD5 (11)| Dig5 (PWM) |   |
| PB6 (9) | (osc.)        ^    | PC6 (1) | (rst)  ^   | PD6 (12)| Dig6 (PWM) |   |
| PB7 (10)| (osc.)        |    |         |        |   | PD7 (13)| Dig7       |   |
+---------+---------------|----+---------+--------|---+---------+------------+   |
                          |                       |                              |
                          |                       |                              |
                       SPI/ICSP                  I2C                            UART
 
      NOTE:
           PB6 e PB7 servono per il quarzo dell'oscillatore
           PC6 serve per il reset


Di seguito lo Sketch per il programmatore di EPROM modificato nella sola parte di controllo del BUS parallelo a basso livello, sono state riscritte anche le funzioni shiftIn/shiftOut che rappresentavano il maggiore collo di bottiglia nell'accesso al BUS. Il tempo di scrittura di 32K e' passato da oltre 4 minuti a 1 minuto e 6 secondi (su un limite minimo teorico di circa 40 secondi), il dump su file di 32K o la verifica richiedono 8.8 secondi (8 secondi sono comunque richiesti per la trasmissione seriale), il test blank dell'intera EPROM dura 1.5 secondi.



Procedimento per scrivere in un colpo solo un intero gruppo di bit (in questo caso i bit 4,3,2) sulla porta PORTB senza alterare i rimanenti:

void setBusAddr(int addr)
{
addr <<= 2;
byte v = 0b11100011 | addr;
byte x = PORTB | addr;
PORTB = x & v;
}