In questo tutorial vedrai come utilizzare il simpatico display OLED comparso nel video precedente, per visualizzare dati e informazioni del tuo prototipo realizzato con Arduino.
Si tratta di un dispositivo che puoi reperire facilmente sui siti Internet.
Sono mini schermi monocromatici e ne esistono con pixel di vari colori e diversa risoluzione.

Quelli più comuni sono quelli controllati da un chip elettronico chiamato SSD 1306. Nella figura è riportato un esempio.
Esistono diverse librerie che possono essere utilizzate.
Quella che proponiamo in questo tutorial è quella sviluppata da Adafruit. È ben fatta ed affidabile, anche se un pochino pesante.
Viene utilizzata in combinazione con una libreria grafica molto popolare, sviluppata sempre da Adafruit, chiamata GFX.
La GFX permette di generare grafica su molti dispositivi di output a matrice di punti.
Poiché in questa fase non hai ancora molta esperienza per applicazioni di questo tipo, ti sarà d’aiuto l’Intelligenza Artificiale.
Il codice generato in prima battuta da ChatGPT è il seguente.
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#define SCREEN_WIDTH 128 // OLED display width in pixels
#define SCREEN_HEIGHT 64 // OLED display height in pixels
// Configurazione per il display SSD1306 collegato tramite I2C
#define OLED_RESET -1 // Reset pin # (o -1 se condivide il pin di reset dell'Arduino)
#define SCREEN_ADDRESS 0x3C // Indirizzo I2C per il display, 0x3C o 0x3D
// Creazione dell'oggetto display
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);
void setup() {
// Inizializzazione comunicazione seriale
Serial.begin(9600);
// Inizializzazione display SSD1306
if (!display.begin(SSD1306_SWITCHCAPVCC, SCREEN_ADDRESS)) {
Serial.println(F("Impossibile allocare memoria per il display"));
while (true); // Arresto del programma in caso di errore
}
// Pulizia del buffer e configurazione delle impostazioni del testo
display.clearDisplay();
display.setTextSize(2); // Imposta la dimensione del testo (2x)
display.setTextColor(SSD1306_WHITE); // Imposta il colore del testo in bianco
display.setCursor(0, 0); // Posiziona il cursore nell'angolo in alto a sinistra
// Visualizza il messaggio "Ciao Stefano!"
display.println("Ciao Stefano!");
// Mostra il buffer sul display
display.display();
}
void loop() {
// Non è necessario fare nulla qui, il messaggio viene visualizzato tramite `setup()`
}Il codice generato dopo la richiesta di stampare il messaggio su due righe è il seguente.
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#define SCREEN_WIDTH 128 // Larghezza del display OLED in pixel
#define SCREEN_HEIGHT 64 // Altezza del display OLED in pixel
// Configurazione per un display SSD1306 collegato tramite I2C
#define OLED_RESET -1 // Reset pin # (o -1 se si condivide il pin di reset dell'Arduino)
#define SCREEN_ADDRESS 0x3C // Indirizzo I2C per il display, 0x3C o 0x3D
// Creazione dell'oggetto display
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);
void setup() {
// Inizializzazione comunicazione seriale
Serial.begin(9600);
// Inizializzazione display SSD1306
if (!display.begin(SSD1306_SWITCHCAPVCC, SCREEN_ADDRESS)) {
Serial.println(F("Impossibile allocare memoria per il display"));
while (true); // Arresto del programma in caso di errore
}
// Pulizia del buffer e impostazioni per il testo
display.clearDisplay();
display.setTextSize(2); // Imposta la dimensione del testo a 2x
display.setTextColor(SSD1306_WHITE); // Imposta il testo in bianco
// Visualizza "Ciao" sulla prima riga
display.setCursor(0, 0); // Posiziona il cursore nell'angolo in alto a sinistra
display.println("Ciao");
// Visualizza "Stefano" sulla seconda riga
display.setCursor(0, 20); // Posiziona il cursore un po' più in basso
display.println("Stefano");
// Mostra il buffer sul display
display.display();
}
void loop() {
// Non c'è nulla da fare qui, poiché il messaggio è gestito da `setup()`
}Analizziamo il codice per comprendere meglio come usare la libreria.
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
In queste prime tre righe sono presenti le direttive include per caricare i file header relativi alle tre librerie che vengono direttamente o indirettamente utilizzate dallo sketch.
La libreria Wire non viene direttamente utilizzata, ma le sue funzioni sono richiamate all’interno della libreria di Adafruit, per cui è necessario includerla.
La libreria Adafruit_GFX contiene le funzioni di alto livello per scrivere o disegnare sul display. Si tratta di una libreria di alto livello, molto potente, che si adatta alla libreria specifica di gestione del particolare display.
Nel nostro sketch vengono utilizzate prevalentemente le istruzioni della GFX ma dobbiamo comunque includere anche la libreria Adafruit_SSD1306 che contiene le funzioni di gestione del dispositivo OLED.
#define SCREEN_WIDTH 128 // Larghezza del display OLED in pixel
#define SCREEN_HEIGHT 64 // Altezza del display OLED in pixel
// Configurazione per un display SSD1306 collegato tramite I2C
#define OLED_RESET -1 // Reset pin # (o -1 se si condivide il pin di reset dell'Arduino)
#define SCREEN_ADDRESS 0x3C // Indirizzo I2C per il display, 0x3C o 0x3D
A seguire vediamo le direttive di definizione delle MACRO che servono a parametrizzare lo sketch con i dati specifici del nostro prototipo.
Per prima cosa vengono definite le macro relative alle dimensioni in pixel del nostro display.
Il parametro parametrizzato con OLED_RESET si riferisce alla modalità di reset del dispositivo. Nel nostro caso è stato definito -1. Dalla linea di commento apposta dall’IA apprendiamo che ciò vuol dire che non viene utilizzato nessun particolare PIN digitale di Arduino per comandare il reset del dispositivo OLED. Il nostro display non ha un connettore per il reset, per cui ciò va bene.
Infine abbiamo la definizione della macro relativa all’indirizzo I2C impostato a 0x3C, cioè al valore decimale 60, come scoperto nel precedente tutorial.
// Creazione dell'oggetto display
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);
Questa istruzione è una dichiarazione di tipo per un oggetto chiamato display.
Un oggetto è una struttura del linguaggio di programmazione concettualmente simile ad una variabile.
Sappiamo che una variabile corrisponde ad un’area di memoria in cui possiamo memorizzare dei valori. La dimensione dell’area di memoria dipende dal “tipo” assegnato alla variabile. Ad esempio una variabile di tipo int ha dimensione pari a 16 byte, mentre una variabile di tipo char ha una dimensione pari ad 1 byte.
Abbiamo già intuito questi concetti nel corso dei precedenti tutorial.
Un oggetto è come una variabile, ma più complesso. Un oggetto è sempre costituito da un’area di memoria in cui possiamo memorizzare dei dati, ma l’oggetto è anche associato a specifiche funzioni, cioè veri e propri moduli di codice, che gestiscono i dati dell’oggetto stesso.
In questo caso non si parla più di tipo di dato dell’oggetto, ma di classe a cui appartiene l’oggetto.
In altre parole la classe è una generalizzazione del concetto di tipo di dato e l’oggetto è una estensione del concetto di variabile.
I linguaggi che permettono di definire ed usare classi ed oggetti sono chiamati object oriented.
Il C non è un linguaggio object oriented. In C puoi definire variabili ma non oggetti. Il C++ è l’estensione object oriented del linguaggio C. In C++ puoi definire classi e creare oggetti.
Il linguaggio Arduino è in realtà il C++.
Classi e oggetti sono costrutti sintattici molto potenti che aiutano a scrivere programmi in modo modulare e riusabile.
Le librerie Arduino sono solitamente “collezioni” di definizioni di Classi.
Hai già incontrato diversi esempi di classi e di oggetti. Ad esempio Serial è definita come una classe. Serial.begin() oppure Serial.print() sono esempi di funzioni specifiche definite nell’ambito di questa classe.
La libreria Adafruit_SSD1306 definisce una classe che si chiama Adafruit_SSD1306, proprio come una la libreria stessa.
L’istruzione Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET) crea un oggetto, chiamato display, appartenente alla classe Adafruit_SSD1306. Come vedremo questo oggetto, sarà dotato di tutte le funzioni necessarie a gestire il nostro display OLED.
Il nome dell’oggetto viene utilizzato come se fosse una funzione, infatti tra parentesi tonde vengono inseriti diversi parametri.
Questa è la sintassi C++ per creare un oggetto richiamando la sua funzione definita “costruttore” dell’oggetto.
In questo modo è stato creato l’oggetto e al suo interno sono stati memorizzati i parametri che caratterizzano il nostro display.
Si noti il parametro &Wire. Con questa notazione, premettendo il carattere &, si indica un riferimento ad un’area di memoria. L’area di memoria che viene indicata è Wire. Di cosa si tratta?
Wire è il nome della classe definito nella libreria Wire che contiene tutte le funzioni che abbiamo imparato a conoscere nei precedenti tutorial, tipo Wire.available(), Wire.write(), Wire.read(), …
Se ci hai fatto caso, anche la classe Serial ha le stesse funzioni che fanno le stesse cose, ma tramite il protocollo RS232, come appunto Serial.available(), Serial.read(), Serial.write(), …
Ciò non è affatto un caso. Nel progetto Arduino, tutte le librerie di comunicazione sono strutturate nello stesso modo!
La libreria SPI, che non abbiamo visto, ha le stesse funzioni (SPI.available(), SPI,write(), …) e anche la libreria WiFi che useremo nei prossimi tutorial ha le stesse funzioni.
Ecco che cominciamo a capire che in Adafruit sono molto furbi!
La libreria Adafruit_SSD1306 è stata scritta in modo parametrico rispetto al protocollo di comunicazione.
Quando viene creato l’oggetto che rappresenta il dispositivo, al suo costruttore dobbiamo passare il riferimento alla classe principale che rappresenta il protocollo di comunicazione.
Se usiamo I2C perché il nostro dispositivo con chip SSD1306 usa l’I2C passeremo &Wire.
Se abbiamo acquistato un dispositivo che comunica tramite SPI (sono display altrettanto diffusi), passeremo &SPI.
Se trovassimo un dispositivo che comunica tramite seriale RS232 passeremmo &Serial, e così via.
Grazie a questa progettazione elegante della libreria sarà facile adattare il nostro sketch a dispositivi display SSD1306 che utilizzano altri protocolli di comunicazione.
void setup() {
// Inizializzazione comunicazione seriale
Serial.begin(9600);
// Inizializzazione display SSD1306
if (!display.begin(SSD1306_SWITCHCAPVCC, SCREEN_ADDRESS)) {
Serial.println(F("Impossibile allocare memoria per il display"));
while (true); // Arresto del programma in caso di errore
}
Cominciando ad analizzare la funzione setup, vediamo che viene inizializzata la comunicazione seriale verso il PC e successivamente, all’interno del test in una istruzione if, viene invocata la funzione display.begin passando alcuni parametri.
La funzione begin dell’oggetto display attiva il dispositivo OLED. Relativamente alla MACRO SSD1306_SWITCHCAPVCC, il cui valore viene passato come primo parametro alla funzione display.begin, facciamo un atto di fede: essendo una roba scritta in maiuscolo, come abbiamo imparato a riconoscere, si tratterà sicuramente di una MACRO. Nel nostro codice non abbiamo definito nessuna MACRO con quel nome, per cui, evidentemente, sarà definita all’interno della libreria Adafruit e il relativo valore rappresenterà qualche elemento che caratterizza il modello di display.
Potremmo sempre approfondire la cosa leggendo la documentazione della libreria sul sito di Adafruit. In ogni caso, il display ha funzionato, per cui va bene così.
L’altro parametro passato alla funzione display.begin è l’indirizzo I2C, parametrizzato dalla nostra MACRO SCREEN_ADDRESS.
La funzione display.begin restituisce il valore zero se non si sono verificati errori, il display ha risposto sull’indirizzo I2C ed ha segnalato la sua attivazione.
In caso contrario la funzione restituisce un valore diverso da zero che segnala l’errore.
L’operatore ‘!‘ in C++ inverte il risultato di una espressione logica. Ricordando il fatto che un valore zero in C++ viene interpretato come valore logico FALSO, mentre un qualsiasi valore diverso da zero viene interpretato come il valore logico VERO, l’espressione !display.begin(…) assume il valore VERO se viene restituito un codice di errore, oppure FALSO se viene restituito ZERO.
Per cui, se display.begin(…) restituisce un codice di errore viene eseguito il blocco di istruzioni della if.
In questo blocco viene effettuata la stampa di un messaggio di errore verso il terminale seriale e poi viene eseguita l’istruzione while(true). Si tratta di una istruzione che crea un ciclo infinito. In pratica il programma rimane bloccato in questo punto. In effetti sarebbe inutile proseguire se il display non funziona.
Se invece il test nella if fallisce, vuol dire che il display funziona e possiamo proseguire saltando il blocco.
// Pulizia del buffer e impostazioni per il testo
display.clearDisplay();
display.setTextSize(2); // Imposta la dimensione del testo a 2x
display.setTextColor(SSD1306_WHITE); // Imposta il testo in bianco
// Visualizza "Ciao" sulla prima riga
display.setCursor(0, 0); // Posiziona il cursore nell'angolo in alto a sinistra
display.println("Ciao");
// Visualizza "Stefano" sulla seconda riga
display.setCursor(0, 20); // Posiziona il cursore un po' più in basso
display.println("Stefano");
// Mostra il buffer sul display
display.display();
}
Tutte le istruzioni successive sono di facile comprensione. Si tratta di funzioni specifiche dell’oggetto display, definite quindi nella classe Adafruit_SSD1306 definita dall’omonima libreria.
Dal fatto che al termine della sequenza viene chiamata la funzione display.display(), intuiamo che tutte le istruzioni invocate in precedenza hanno predisposto la visualizzazione ma non hanno avuto un effetto visibile. L’effetto si vede sul display quando viene chiamata la funzione display.display().
Poiché tutto ciò che dovevamo provare viene effettuato una sola volta, tutte le istruzioni sono inserite nella funzione setup e non abbiamo scritto nulla nella funzione loop.