chipKIT Basic I/O Shield™

Qualche giorno fa è stata presentata la rassegna stampa illustrante due nuovi shield realizzati dalla Digilent:il chipKIT Basic I/O Shield™, utilizzabile sia con il chipKit Uno32™ che con il chipKit Max32™, e il chipKIT Network Shield™, utilizzabile esclusivamente con il chipKit Max32™. Si tratta di due shield molto particolari e versatili. Non sono i soliti shield con i quali siamo abituati con Arduino, aventi funzioni specifiche: a bordo di questi shield c’è davvero molto materiale con cui lavorare per cui ognuno di questi shield non è destinato ad una sola applicazione o ad un gruppo di applicazioni incentrate sulla stessa funzione, piuttosto si tratta di shields “multifunzione” data la varietà di elementi presenti a bordo.

Ringrazio gli ingegneri della Digilent A. Wong e G. Apperson (quest’ ultimo è il progettista del chipKit Uno32™ e del chipKIT Basic I/O Shield™) che hanno fornito a settorezero in anteprima, ancor prima della rassegna stampa, un chipKIT Basic I/O Shield™ ma soprattutto tanta, tantissima collaborazione e si sono mostrati molto rapidi e precisi nel rispondere a tutte le mie domande fornendomi molte informazioni utili che attualmente non troverete da nessun’altra parte e che fanno di questo articolo un vero “must” per chi comincia con questo shield.

Il Basic I/O Shield

Questo articolo, come dice il titolo, è incentrato esclusivamente sul Basic I/O Shield, ne analizzeremo quindi le caratteristiche e vedremo qualche demo interessante (sia ufficiale che non) per illustrarne il funzionamento. A dispetto di quanto il nome possa far sembrare, tanto “basic” poi non è!

Su questo shield abbiamo a disposizione:

  • Un display grafico SPI in miniatura a matrice organica di led (OLED) da 128×32 pixel
  • Un sensore di temperatura con allarme e isteresi in I2C
  • Una memoria eeprom I2C da 256Kbit
  • 4 uscite a collettore aperto a mosfet, utili per commutare carichi funzionanti a massimo 12V e con un assorbimento massimo di 3A. I mosfet sono addirittura provvisti di diodi di ricircolo per cui sono anche adatti a pilotare carichi induttivi (motori, relè, solenoidi ecc).
  • 4 pulsanti
  • 4 interruttori a slitta
  • 8 led
  • Un trimmer da 10K
  • Connettori esterni per collegare in cascata altri dispositivi I2C

Andremo ad analizzare tutti questi componenti un po’ per volta, ecco perchè non vi ho ancora dato le sigle ;)

In ogni caso è assolutamente necessario avere sottomano il manuale dello shield, almeno per rendersi conto a quali pin sono collegati i vari utilizzatori presenti sulla scheda e per reperire altre informazioni utili.

Iniziamo!

Prima di partire, qualora non l’abbiate ancora fatto, è conveniente che vi leggiate l’articolo precedente riguardante il chipKit Uno32™. Come è bene fare per ogni software, assicuriamoci di avere l’ultima versione di MPIDE installata, scarichiamo quindi le librerie disponibili per il Basic I/O Shield nella pagina ufficiale del prodotto sul sito della Digilent. Le librerie da scaricare si trovano in fondo alla pagina alla sezione Support Documents. Attualmente il file da scaricare è quello che riporta il numero di documento DSD-0000311.

Una volta scaricato l’archivio ed estrattolo, abbiamo una cartella dal nome ChipKit IOShield Library all’interno della quale sono presenti la documentazione e 3 librerie che permettono di interfacciarsi con i componenti chiave presenti sul basic I/O Shield, ovvero eeprom, display oled e sensore di temperatura:

  • IOShieldEEPROM
  • IOShieldOled
  • IOShieldTemp

Ogni libreria ha la sua documentazione in formato PDF, molto ricca ed esauriente, nonchè degli esempi di utilizzo.

Ora la prima cosa da fare è sicuramente quella di leggere il file Readme (IOShield Libraries Readme.pdf) nel quale ci sono due informazioni fondamentali:

  • Le nuove librerie, essendo librerie di terze parti, vanno copiate in una cartella “libraries” presente nella cartella degli sketch (non la cartella libraries dell’IDE!)
  • Quando vogliamo usare le librerie per sfruttare l’eeprom e il sensore di temperatura, essendo questi circuiti integranti funzionanti in I2C, dobbiamo ricordarci di includere la libreria wire.h

Localizziamo quindi la cartella dove vengono salvati gli sketch. Tale cartella viene impostata in MPIDE (così come nell’ide di Arduino) dal menù File -> Preferences. Generalmente è Documenti\Arduino. Bene, andiamo in questa cartella e creiamo una cartella “libraries” qualora non fosse già presente e copiamo all’interno le 3 cartelle (IOShieldEEPROM, IOShieldOled e IOShieldTemp).

Lanciamo quindi MPIDE e dal menù File -> Examples possiamo accedere ai 3 nuovi esempi:

Il Display OLED

Si tratta di un display grafico OLED di tipo COG (Chip-On-Glass) che comunica su bus SPI. E’ prodotto dalla Univision – Wisechip e la sua sigla è UG-2832HSWEG04. Le specifiche tecniche del modulo si trovano nella pagina ufficiale a questo indirizzo. Sul sito ufficiale non è purtroppo presente un datasheet, che però potete trovare facendo una ricerca con Google. Più che il datasheet del display in sè stesso è forse più interessante sapere che il controller del display è l’ SSD1306 prodotto dalla Soloman Systech , utilizzato in molti altri display Oled e per il quale ho messo il datasheet in fondo all’articolo.

Questo display è molto piccolo (3cm di larghezza, 1.15 cm di altezza e solo 1.45mm di spessore!) e con un assorbimento bassissimo, è l’ideale per essere utilizzato in applicazioni portatili, d’altronde il punto di forza degli OLED è proprio questo. I pin di pilotaggio del display sono riportati su un flat, saldato sulla faccia inferiore della scheda:

Il display è fissato alla faccia superiore mediante una striscia di biadesivo imbottito. Dicevo che abbiamo di fronte un display di tipo grafico: viene pilotato pixel per pixel ma la libreria fornita dalla Digilent ci permette di utilizzarlo anche come se fosse un display da 16 caratteri x 4 righe (modalità testo) per cui sono presenti alcune funzioni, anzi metodi, necessari per poter scrivere stringhe di testo e posizionare il cursore:

void putChar(char ch)
void putString(char *sz) // *sz è un puntatore ad una stringa terminata con NULL
void setCursor(int xch, int ych)

Le funzioni di scrittura possono anche essere utilizzate in modalità grafica anzichè testo. Qual è la differenza? In modalità testo i caratteri vengono scritti nella posizione in cui si trova il cursore il quale segue un andamento di riga e colonna basato sulla grandezza del font e non sul singolo pixel. In modalità grafica i caratteri invece vengono stampati a partire dalla posizione in cui si trova il puntatore grafico il quale è grande appunto un pixel. In pratica utilizzando putChar e putString si possono posizionare i caratteri in maniera “ordinata” per righe e colonne aventi rispettivamente altezza e larghezza pari a quelle di un carattere (8×8 pixel). Utilizzando invece i metodi di scrittura in modalità grafica, che sono:

void drawChar(char ch)
void drawstring(char *sz)

è invece possibile scrivere il testo in un punto qualsiasi definito dal puntatore grafico, il quale viene posizionato tramite il metodo:

void moveTo(int xco, int yco)

Per ora non sono presenti funzioni per poter scrivere numeri interi o float ma a questo si ovvia facilmente utilizzando funzioni personalizzate o usando più semplicemente (ma con maggiore consumo di memoria… ma è un problema con 128K? Direi proprio di no!) la funzione sprintf (vedremo più avanti un esempio). Sono inoltre presenti funzioni per disegnare linee, rettangoli, bitmap e personalizzare caratteri ed è anche possibile eseguire riempimenti di figure utilizzando dei pattern predefiniti.

La modalità grafica viene gestita in maniera intelligente: è possibile scegliere se un pixel va a coprire o meno i pixel presenti al di sotto qualora si vada a scrivere o disegnare su un’area già occupata da altra grafica. Questo è possibile perchè il display viene anche gestito in lettura. Fate riferimento al manuale in PDF fornito con la libreria e agli esempi.

Attenzione! Controllate il manuale del Basic I/O Shield per sapere quali sono i pin utilizzati dal display in modo da evitare di utilizzarli per altro!

Demo display OLED

Il primo esempio che vi vengo ad illustrare è proprio quello del display OLED. Collegate il vostro Chipkit UNO32™, attendete che venga riconosciuta la porta seriale, assicuratevi di:

  • Aver selezionato chipKit UNO32 nel menù Tools -> Board
  • Aver selezionato la porta seriale corretta nel menù Tools -> Serial port

Clicchiamo quindi sul pulsante Upload e attendiamo la fine del caricamento:

Stacchiamo quindi l’alimentazione, colleghiamo il basic I/O Shield e ridiamo corrente.

Possiamo anche eseguire tutte le operazioni con lo shield già montato!

Prima che la demo parta sul display dobbiamo attendere qualche secondo: non è istantanea.

Noterete subito che la qualità del display è sorprendente, anche se è molto piccolo, il contrasto è molto elevato e si legge molto bene anche controluce.

La demo scrive sul display delle semplici stringhe di testo in maniera molto raffinata, con una linea orizzontale che si muove verso il basso scoprendo il testo (ma dietro le quinte è in realtà un rettangolo che cambia dimensione e cancella il testo), dopodichè il display viene fatto lampeggiare (è possibile difatti spegnere il display rimanendo però la parte grafica memorizzata), la linea quindi si muove verso l’alto cancellando tutto e infine appare un rettangolo al centro che viene riempito con diversi pattern.  La demo in pratica sfrutta quasi tutte le funzioni offerte dalla libreria.

Ho fatto un video della demo:

Tenete conto che filmando con la videocamera il display sfarfalla e appare meno luminoso di quello che realmente è, ma vi posso assicurare che ad occhio nudo viene visualizzato in maniera eccezionale.  Questo display è pilotato in SPI per cui la velocità di aggiornamento è molto rapida e vi assicuro che non noterete nessun movimento anomalo sul display guardandolo in maniera diretta.

Demo avanzata display OLED

Attualmente questa demo non è ancora presente nei download ufficiali del Basic I/O Shield. Si tratta di una demo molto più completa che illustra molto meglio le qualità grafiche e sarà probabilmente disponibile a breve (il nome “demo avanzata” gliel’ho dato io ma non ho idea di come la chiameranno). E’ stata realizzata dal già citato ing. Gene Apperson che me l’ha inviata via email e ve la rigiro qui, in anteprima per gli utenti di settorezero!

Un video è meglio di mille parole:

Questa demo è scaricabile in fondo all’articolo.

La personalizzazione dei caratteri e dei font

Dato che per un esempio che ho preparato è necessario saper personalizzare i caratteri e non è da me spiattellare del codice senza spiegare da dove viene fuori, spendo qui qualche parola su come fare questa operazione dato che nel manuale questa cosa non è illustrata passo passo e qualcuno potrebbe trovare difficoltà. Innanzitutto ci può essere utile sapere che i caratteri predefiniti (il font) presenti nella libreria si trovano nel file ChrFont0.c nella cartella \libraries\IOShieldOled\utility. Qui un carattere è costituito da una matrice 8×8 con il bit meno significativo in alto e il più significativo in basso. Prendiamo ad esempio la lettera A (codice ASCII 0x41):

Chi è abituato a lavorare coi display grafici o disegnava da sè gli “sprites” con il Commodore 64 ha già capito come fare le somme guardando l’immagine. Da notare che l’ultima riga e l’ultima colonna si devono lasciare vuote se non si vuole che le lettere si attacchino tra loro. Supponiamo ora di voler includere il simbolo di grado, non presente nella libreria. Disegnerò una cosa del genere:

Sfrutterò quindi il metodo:

int defineUserChar(char ch, BYTE *pbDef)

Dove ch è il “segnaposto” per il mio carattere ed è un numero che va da 0 a 31 e *pbDef è il puntatore all’array che contiene la definizione del carattere. La funzione ha un valore di ritorno che è zero se ch > 31. Definiamo quindi il nostro nuovo carattere in un modo che poi ci renda facile ricordarne il segnaposto:

#define degree 0
PROGMEM BYTE degreeSymbol[]={0x06,0x09,0x09,0x06,0x00,0x00,0x00,0x00};

memorizzo quindi il nuovo simbolo nel display richiamando la funzione in questo modo (dove IOShieldOled è il nome dell’istanza dell’oggetto OLED):

IOShieldOled.defineUserChar(degree, degreeSymbol);

Per stampare il simbolo posso ricorrere al metodo PutChar che mi permette di stampare un singolo carattere:

IOShieldOled.putChar(degree);

Se poi il font di default della libreria non vi piace… il sistema per disegnarne di nuovi è lo stesso, basta che sostituiate tutte le definizioni dei caratteri nel file ChrFont0.c

La presenza dello zero nel nome del file fa intuire che anzichè modificare il file stesso è meglio crearne uno nuovo che magari si chiami ChrFont1.c

Sensore di temperatura

Il sensore di temperatura montato su questo shield è prodotto da mamma Microchip. E’ un TCN75A capace di rilevare temperature tra -40 e +125 °C con un’accuratezza tipica di 0.5°C  ed una risoluzione massima (impostabile) fino a 0.0625°C. Il sensore si  trova sulla faccia superiore dello shield in basso a destra, contrassegnato come IC2. Comunica in I2C e l’indirizzo a 7 bit è 0x48. Per poter utilizzare il sensore è necessario spostare i due jumper JP6 e JP8 del Chipkit UNO32™ verso il basso, in pratica in posizione RG3 e e RG2 rispettivamente. Questo serve per poter utilizzare i pin A4 e A5 (gli stessi di Arduino) del connettore di espansione J7 come canali di comunicazione I2C (rispettivamente SDA e SCL) piuttosto che come ingressi analogici.

Su Arduino non c’è questo jumper di selezione perchè sul Chipkit UNO32™ la funzione analogica di A4 e A5 (sul connettore di espansione) viene affidata ai pin del pic RB12 ed RB14 rispettivamente, mentre quando si utilizza l’I2C, A4 e A5 vengono “dirottati”, tramite i jumpers JP6 e JP8 sui pin RG3 e RG2 rispettivamente.

Una volta caricata la demo (File -> Examples -> IOShieldTemp -> IOShield_Temp_Demo) sul Chipkit UNO32™ è necessario avviare il monitor seriale per visualizzare l’output (Tools -> Serial monitor) :

La demo visualizza, in pratica, il valore di temperatura a 2 decimali sia in °C che in °F, mostrando un “ALERT!” affianco se il valore di temperatura supera 25.5°C con un valore di isteresi pari a 24°C.

Questo “Alert” non viene mostrato facendo un semplice confronto della temperatura rilevata con un valore fisso: Il TCN75A ha un pin di allarme che sul Basic I/O shield viene connesso al pin n°2 del Chipkit UNO32™. Il TCN75A viene quindi settato via firmware per rilevare un valore di soglia con tanto di isteresi e la condizione di allarme viene appunto segnalata portando a livello alto tale pin.

Come detto: il pin di allarme del sensore è collegato al pin 2 del chipKit. Al pin 2, sul Basic I/O Shield, è anche collegato l’interruttore a slitta siglato SW1 (l’ultimo, sulla destra). Quando utilizzate il pin di allarme non potete usare l’interruttore SW1 e dovete tenerlo verso l’alto, oppure SW1 può essere utilizzato per tacitare l’allarme.

So già cosa state pensando: sul Basic I/O Shield c’è il display, perchè su questa demo hanno predisposto l’output su seriale piuttosto che mostrare le temperature direttamente sul display? In realtà è la stessa cosa che ho pensato anch’io. E’ ovvio comunque che una demo deve essere qualcosa di basilare per poter permettere a tutti di apprendere il funzionamento di un componente per volta. In ogni caso la demo fatta in questo modo l’ho preparata io. Fa la stessa cosa ma usando il display e in aggiunta, come ho anticipato in precedenza, nella mia demo ci sono due esempi interessanti: come definire un carattere personalizzato (il simbolo di grado in questo caso) e come mostrare un valore float sul display:

Anche questa demo è possibile scaricarla in fondo all’articolo.

EEprom esterna

L’eeprom esterna montata sul Basic I/O Shield è una 24LC256 prodotta dalla Microchip. Questa eeprom consente una memorizzazione di 32Kbytes di dati (256Kbit) ed è accessibile via I2C (l’indirizzo a 7 bit è 0x50). E’ posizionata in basso a sinistra sulla faccia inferiore della scheda. Carichiamo la demo di esempio: File -> Examples -> IOShieldEEprom -> IOShield_EEprom_Demo. La demo illustra una possibilità interessante presente in questa libreria: quella di poter memorizzare intere stringhe di testo di lunghezza arbitraria mediante il metodo:

void writeString(uint16_t address, char *sz)

la quale calcola in automatico la lunghezza della stringa e memorizza i caratteri in posizioni consecutive a partire dalla locazione address usando la scrittura bulk. Sono presenti altri overload del metodo writeString che permettono di specificare manualmente la dimensione della stringa. Analogamente è presente una funzione readstring. Fate riferimento al manualetto della libreria in formato PDF.

Interruttori, Trimmer, Led, Pulsanti, Mosfet

Le demo ufficiali per ora non prevedono l’utilizzo di questi altri componenti presenti sul Basic I/O Shield ed è normale dal momento che non necessitano di alcuna libreria per poter essere utilizzati, basta solo sapere a quali pin sono collegati ed utilizzare quindi le funzioni di base. E” semplicissimo conoscere i pin a cui gli utilizzatori sono collegati: affianco alla sigla di ogni componente è difatti riportato, spesso con un font di dimensioni maggiori, il numero del pin del chipKit al quale è connesso:

Vedete dalla foto che è facile capire che il trimmer, VR1, è collegato al pin A0, che il pulsanteBTN1 è collegato al pin 4, l’interruttore SW al pin 2, i led da LD1 a LD8 sono collegati ai pin dal 26 al 33 ecc. Non c’è quindi bisogno di impazzire quando si va a prototipare un nuovo esperimento, abbiamo tutto sottomano.

In ogni caso ho preparato un template, scaricabile in fondo all’articolo, che può facilitare la scrittura del codice.

Utilizzo interruttori e pulsanti

Per utilizzare interruttori e pulsanti dobbiamo ricordarci, nel codice, di impostare il relativo pin come ingresso in fase di setup:

// slide switches
#define SW1  2
#define SW2  7
#define SW3  8
#define SW4  35
 
// pushbuttons
#define BTN1  4
#define BTN2  34
#define BTN3  36
#define BTN4  37
 
void setup(void)
  {
  pinMode(SW1, INPUT);
  pinMode(SW2, INPUT);
  pinMode(SW3, INPUT);
  pinMode(SW4, INPUT);
  pinMode(BTN1, INPUT);
  pinMode(BTN2, INPUT);
  pinMode(BTN3, INPUT);
  pinMode(BTN4, INPUT);
  }

Quando dobbiamo andare a leggere pulsanti ed interruttori dobbiamo ricordarci che un pulsante premuto o un interruttore chiuso restituiscono lo stato HIGH:

if (digitalRead(BTN1)==HIGH) // BTN1 pressed
    {
    }

Utilizzo Led

Per utilizzare i led dobbiamo impostare i relativi pin come uscite:

// leds
#define LD1  26
#define LD2  27
#define LD3  28
#define LD4  29
#define LD5  30
#define LD6  31
#define LD7  32
#define LD8  33
 
void setup(void)
  {
  pinMode(LD1, OUTPUT);
  pinMode(LD2, OUTPUT);
  pinMode(LD3, OUTPUT);
  pinMode(LD4, OUTPUT);
  pinMode(LD5, OUTPUT);
  pinMode(LD6, OUTPUT);
  pinMode(LD7, OUTPUT);
  pinMode(LD8, OUTPUT);
  }

Nel codice quindi accendiamo o spegniamo un led utilizzando la funzione digitalWrite:

digitalWrite(LD1,HIGH); // Led LED1 ON
digitalWrite(LD1,LOW); // Led LED1 OFF

Utilizzo Trimmer

Il trimmer da 10K, siglato come VR1, è collegato ad A0. Per i pin analogici non è necessario eseguire nessun setup. Per facilitarci le cose possiamo solo utilizzare dei nomi che ci sono più congeniali, per cui prima della funzione di setup possiamo scrivere:

// potentiometer
#define VR1 A0
int VR1_value = 0;  // variable to store the value coming from VR1

E quindi nel Loop possiamo leggere il valore di VR1:

VR1_value = analogRead(VR1); // read value from VR1

Il valore sarà 1023 con il trimmer tutto girato sulla destra e 0 con il trimmer tutto girato sulla sinistra.

Utilizzo uscite Open Drain

Sul Basic I/O Shield sono montati 4 Mosfet a canale N contenuti in due integrati di tipo NTHD4508N. Anche se in realtà supportano valori più elevati, sul manuale dello shield è indicata una corrente massima di 3A e sullo shield è chiaramente indicato che non si possono superare i 12V. I 4 mosfet agiscono in pratica da interruttori chiudendo verso massa. La tensione da commutare viene fornita sul terminale a vite siglato J6 (affianco al quale la serigrafia indica anche la polarità e che non devono essere superati 12V). Quindi il carico da controllare avrà il terminale positivo collegato al positivo dell’alimentazione e il terminale negativo collegato ad una delle 4 uscite mosfet presenti sui connettori a vite J7 (a cui fanno capo le uscite 3 e 5) e J8 (a cui fanno capo le uscite 6 e 9).

I pin 3, 5, 6 e 9, quindi, controllano la commutazione dei mosfet: mandando a livello alto queste uscite, il mosfet chiude verso massa attivando l’utilizzatore ad esso collegato. Le uscite “semplici”, cioè direttamente dal PIC senza passare dai mosfet sono riportate in parallelo sugli altri 2 terminali a vite J8 e J9. La cosa bella è che queste 4 uscite sono anche utilizzate dal PWM per cui se colleghiamo alle uscite mosfet un motore, ad esempio, possiamo anche modularne la velocità sfruttando il PWM e stiamo sicuri dal momento che sono presenti anche i diodi di ricircolo. Per utilizzare il PWM basta utilizzare la funzione analogWrite.

Per testare il funzionamento ho fatto una cosa molto semplice, che potete provare a fare anche voi, munitevi di una ventolina da 12V, come quelle che si trovano nei PC. Alimentate il chipKit normalmente attraverso l’USB e applicate quindi una tensione di 12V al terminale a vite J6, rispettando la polarità. Il filo nero della ventolina lo infilate nel terminale a vite J7 nel punto indicato con il numero 3 (che vuol dire fa capo al mosfet pilotato dal pin 3).

Fate attenzione! Le uscite controllate dai Mosfet sono presenti sui terminali a vite posti sul bordo sinistro dello shield, subito al di sotto del connettore per l’alimentazione esterna. I terminali a vite presenti invece sul bordo superiore non fanno capo ai mosfet ma sono direttamente collegati agli stessi I/O del pic e assolutamente non devono essere utilizzati per pilotare carichi eccessivi dal momento che si tratta di uscite a 3.3V a bassa corrente.

Il filo rosso della ventolina lo collegate nel punto + del terminale J6, insieme al filo che porta il ramo positivo dell’alimentazione esterna per intenderci.

Lo sketch da caricare è molto semplice:

#define VR1 A0 // trimmer on A0
int VR1_value = 0;  // variable to store the value coming from VR1
#define MOS1  3 // mosfet1 on 3
 
void setup(void)
  {
  }
 
void loop(void)
  {
  VR1_value = analogRead(VR1);
  analogWrite(MOS1, VR1_value);
  }

Lo sketch fa una cosa molto semplice: legge il valore impostato tramite il potenziometro e lo assegna al PWM sul pin 3, dove abbiamo collegato la ventola. Girando il trimmer facciamo variare la velocità di rotazione della ventola… Nulla di più semplice!

In Italia i prodotti della Digilent vengono distribuiti da Mirifica, e il mio consiglio è di andare sul loro sito per l’acquisto del chipKIT™ Basic I/O Shield.

Documentazione e Downloads

Se questo articolo ti è piaciuto, condividilo su un social:
Se l'articolo ti è piaciuto o ti è stato utile, potresti dedicare un minuto a leggere questa pagina, dove ho elencato alcune cose che potrebbero farmi contento? Grazie :)