In questo tutorial imparerai come coordinare le animazioni delle zone Parola su un display a matrice LED MAX7219. La libreria MD_Parola è una libreria molto popolare che rende la programmazione di animazioni, come lo scorrimento del testo, molto semplice. Permette anche di dividere un display in più zone e di eseguire animazioni diverse in ciascuna zona.
Tuttavia, coordinare le animazioni in più zone non è semplice. Ad esempio, come si può riavviare un’animazione in una zona senza influenzare quella in un’altra zona? Oppure, come si possono riavviare tutte le animazioni solo quando tutte sono terminate, anche se stanno girando a velocità diverse?
Questo tutorial ti fornirà una struttura di codice per gestire facilmente questi scenari di animazione.
Componenti necessari
Ho usato un Arduino Uno per questo progetto, ma qualsiasi altra scheda Arduino, oppure una scheda ESP8266/ESP32, funzionerà ugualmente. Inoltre, ho utilizzato un display a matrice LED MAX7219 con 4 moduli. Tuttavia, il codice e i collegamenti per display con un numero diverso di moduli sono sostanzialmente gli stessi.

Display a matrice LED MAX7219

Arduino Uno

Set di cavi Dupont

Cavo USB per Arduino UNO
Makerguides is a participant in affiliate advertising programs designed to provide a means for sites to earn advertising fees by linking to Amazon, AliExpress, Elecrow, and other sites. As an Affiliate we may earn from qualifying purchases.
Collegamento del display LED MAX7219 con Arduino
In questo tutorial useremo un display a matrice LED MAX7219 per mostrare le animazioni. Il driver del display LED MAX7219 comunica con Arduino tramite SPI (Serial Peripheral Interface). Quindi, la prima cosa da fare è collegare il display ad Arduino usando l’interfaccia SPI.
Collegamenti
Il seguente schema mostra i collegamenti tra il display MAX7219 e un Arduino Uno. Assicurati di collegarti al lato di ingresso (DIN) e non al lato di uscita (DOUT) del display.

Inizia collegando i pin dell’interfaccia SPI: il pin 11 di Arduino va collegato al pin DIN del display. Poi collega il pin 3 a CS e il pin 13 a CLK. Infine, collega 5V a VCC e GND a GND. La tabella seguente mostra nuovamente i collegamenti
| Display | Arduino |
|---|---|
| VCC | 5 V |
| GND | GND |
| DIN | 11 (MOSI) |
| CS | 3 (SS) |
| CLK | 13 (SCK) |
I pin sopra indicati sono per l’SPI hardware, che è più veloce rispetto all’SPI software ma richiede l’uso di pin specifici che variano a seconda della scheda. Per maggiori dettagli vedi la MAX7219 LED dot matrix display Arduino tutorial .
Ora scriviamo ed eseguiamo un po’ di codice di test per assicurarci che i collegamenti siano corretti.
Installa le librerie MD_Parola e MD_MAX72XX
Per prima cosa, devi installare la MD_MAX72XX e la MD_Parola libreria. Apri il Arduino IDE e vai su Tools > Manage Libraries che apre il Library Manager. Cerca “MD_MAX72XX” e verranno elencate le librerie rilevanti. Lo screenshot qui sotto mostra le due librerie (evidenziate in giallo) dopo che sono state installate.

La libreria MD_MAX72XX è essenzialmente il driver software per il display LED MAX7219. La libreria MD_Parola la utilizza per creare animazioni come effetti di testo scorrevole e sprite. Se vuoi saperne di più sulle animazioni disponibili dai un’occhiata alla MAX7219 LED dot matrix display Arduino tutorial .
Codice di test
Il seguente codice è un esempio minimo per testare i collegamenti e il funzionamento del display. Semplicemente fa scorrere il testo “Test” sul display.
#include "MD_Parola.h"
#include "MD_MAX72xx.h"
#include "SPI.h"
#define HARDWARE_TYPE MD_MAX72XX::FC16_HW
#define MAX_DEVICES 4
#define CS_PIN 3
MD_Parola disp = MD_Parola(HARDWARE_TYPE, CS_PIN, MAX_DEVICES);
void setup() {
disp.begin();
disp.setIntensity(0);
disp.displayClear();
disp.displayText("Test", PA_CENTER, 100, 0, PA_SCROLL_LEFT, PA_SCROLL_LEFT);
}
void loop() {
if (disp.displayAnimate()) {
disp.displayReset();
}
}
Se carichi ed esegui questo codice dovresti vedere il seguente output sul tuo display MAX7219:

Per maggiori dettagli su questo codice di test dai un’occhiata al tutorial Control Parola Animations on MAX7219 LED Display , dove usiamo lo stesso codice di test.
Nota: se usi un tipo diverso di display LED MAX7219 o hai un display con più o meno moduli, dovrai modificare le seguenti definizioni:
#define HARDWARE_TYPE MD_MAX72XX::FC16_HW #define MAX_DEVICES 4
Allo stesso modo, se usi l’SPI software invece dell’SPI hardware, dovrai cambiare il modo in cui viene creato l’oggetto display:
MD_Parola disp = MD_Parola(HARDWARE_TYPE, CS_PIN, MAX_DEVICES);
Maggiori informazioni su questo si trovano nella MAX7219 LED dot matrix display Arduino tutorial .
Nelle sezioni seguenti vedremo più da vicino le zone, come crearle e usarle, e soprattutto come coordinare le animazioni che girano in zone diverse.
Cosa sono le zone Parola
La libreria Parola tratta un display LED MAX7219 come una sequenza di moduli LED 8×8, numerati da 0 a n-1 . Una “Zona” è una sezione di moduli consecutivi. Ogni zona può essere controllata indipendentemente dalle altre. L’esempio qui sotto mostra un display LED MAX7219 con quattro moduli (0…3) e due definizioni di zona.

La zona 0 include i moduli 0 e 1, e la zona 1 include i moduli 2 e 3. Nota che la numerazione dei moduli parte da 0 e dal lato di ingresso (lato destro) del display. All’interno di ogni zona puoi eseguire animazioni diverse con font, effetti di testo, stringhe di testo, velocità e persino luminosità differenti.
Le zone non dovrebbero sovrapporsi, possono avere lunghezze diverse e l’indice della zona non deve per forza seguire l’ordine sequenziale dei moduli. Ad esempio, il seguente esempio mostra un’altra definizione di zona valida. Qui abbiamo tre zone (0,1,2) ma sono di lunghezza diversa e gli indici delle zone non sono in ordine sequenziale (0,2,1).

Come definire le zone Parola
Supponiamo di voler definire le seguenti zone del display. La zona 0 va dal modulo 0 al modulo 1, e la zona 1 va dal modulo 2 al modulo 3:

Nella funzione setup, per prima cosa chiama begin() con il numero totale di zone (qui 2). Poi definisci le singole zone chiamando setZone(z, start, end) . Il primo argomento z è l’id della zona (0 o 1, nel nostro caso), poi l’indice del primo e dell’ultimo modulo di quella zona.
#define MAX_ZONES 2
void setup() {
disp.begin(MAX_ZONES);
disp.setZone(0, 0, 1);
disp.setZone(1, 2, 3);
}
Come detto prima, le zone non dovrebbero sovrapporsi, ma possono farlo. Tuttavia, se si sovrappongono, devi assicurarti che le zone sovrapposte non eseguano animazioni contemporaneamente, altrimenti otterrai strani artefatti.
Una volta definite le zone, puoi assegnare animazioni a quelle zone. Ad esempio, la seguente riga di codice definisce un’animazione per la zona 0 che fa scorrere il testo “Left” verso sinistra. Maggiori dettagli nella prossima sezione.
disp.displayZoneText(0, "Left", PA_CENTER, 100, 0, PA_SCROLL_RIGHT, PA_SCROLL_RIGHT);
Come animare le zone Parola
La maggior parte delle funzioni relative alle animazioni Parola esistono in due versioni. Una versione che lavora sull’intero display, e un’altra che lavora su una zona specifica. Dai un’occhiata alla Parola Documentation per una panoramica.
Ad esempio, puoi impostare la luminosità dell’intero display o di una zona specifica chiamando setIntensity senza o con un indice di zona.
setIntensity(intensity); // entire display setIntensity(z, intensity); // zone z
Lo stesso vale per la maggior parte delle altre funzioni per impostare font, effetti di testo, stringhe di testo, velocità e così via. Il seguente estratto di codice mostra come visualizzare e animare testi diversi in due zone. Nella prima zona (0) facciamo scorrere il testo “0” verso destra, e nella seconda zona (1) facciamo scorrere il testo “1” verso sinistra.
#define SPEED 100
void setup() {
disp.begin(MAX_ZONES);
disp.setZone(0, 0, 1);
disp.setZone(1, 2, 3);
disp.displayZoneText(0, "0", PA_CENTER, SPEED, 0, PA_SCROLL_RIGHT, PA_SCROLL_RIGHT);
disp.displayZoneText(1, "1", PA_CENTER, SPEED, 0, PA_SCROLL_LEFT, PA_SCROLL_LEFT);
}
Nella funzione loop, poi, chiamiamo semplicemente la solita displayAnimate() per verificare se una delle due animazioni è terminata e, in tal caso, resettiamo il display per tutte le zone tramite displayReset() .
void loop() {
if (disp.displayAnimate()) {
disp.displayReset();
}
}
Nota che potresti anche resettare le zone singolarmente chiamando displayReset(z) con un identificatore di zona. Lo useremo più avanti.
void loop() {
if (disp.displayAnimate()) {
disp.displayReset(0);
disp.displayReset(1);
}
}
Questo funziona bene, soprattutto perché entrambe le animazioni qui girano alla stessa velocità (100). Ma se le animazioni hanno velocità diverse o testi di lunghezza diversa, allora una animazione finirà prima dell’altra. Come gestire questo caso e specificare cosa deve succedere?
Questa coordinazione tra animazioni su più zone è l’argomento della prossima sezione.
Come coordinare le zone Parola
Come detto sopra, la chiamata di loop predefinita di Parola è displayAnimate() , che restituirà true se una qualsiasi animazione in una qualsiasi zona è terminata. Puoi scoprire quale chiamando getZoneStatus(z) con l’identificatore della zona z .
Ad esempio, se hai due zone, potresti riavviare le animazioni in entrambe le zone quando entrambe sono terminate scrivendo il seguente codice:
void loop() {
if (disp.displayAnimate()) {
if(disp.getZoneStatus(0) && disp.getZoneStatus(1)) {
disp.displayReset();
}
}
}
Questo funziona bene ed è perfetto per questo caso semplice. Ma se hai scenari leggermente più complessi o più zone, rischi di avere codice molto confuso. Ad esempio, supponiamo di avere due animazioni in due zone e una animazione è più veloce dell’altra. Questo porta ad almeno sei diversi possibili scenari di animazione
- Fai girare entrambe le animazioni solo una volta
- Fai girare la prima animazione una volta, ripeti l’altra
- Fai girare la seconda animazione una volta, ripeti l’altra
- Ripeti entrambe le animazioni quando entrambe sono terminate
- Ripeti l’animazione più veloce mentre quella più lenta è ancora in esecuzione
- Riavvia l’animazione più lenta quando quella più veloce è terminata
Nelle sezioni seguenti implementeremo questi sei scenari di animazione. Ma per semplificare le cose, prima definiamo una funzione di supporto getStatus() .
Funzione getStatus
Invece di chiamare getZoneStatus(z) per ogni zona, la seguente funzione getStatus() restituisce un vettore di bit che indica lo stato di tutte le zone contemporaneamente.
byte getStatus() {
static byte status = 0;
disp.displayAnimate();
for (int z = 0; z < MAX_ZONES; z++) {
disp.getZoneStatus(z) ? bitSet(status, z) : bitClear(status, z);
}
return status;
}
Internamente, la funzione itera su tutte le zone e imposta lo stato della zona nel byte status . Questo significa che il numero massimo di zone è limitato a 8, ma di solito è più che sufficiente.
L’immagine seguente mostra un esempio di vettore di bit o di stato che getStatus() restituisce. In questo esempio le animazioni nella zona 1 e nella zona 3 sono terminate:

Se chiami getStatus() nel loop principale puoi facilmente stampare il vettore di stato.
void loop() {
Serial.println(getStatus(), BIN);
}
Nota che la getStatus() internamente chiama anche displayAnimate() , quindi non è più necessario chiamarla nella funzione loop.
Useremo getStatus() nelle prossime sezioni per implementare i sei diversi scenari di animazione menzionati prima.
Esempio di codice con due zone
Iniziamo con un esempio di codice che ha tutto tranne l’implementazione della funzione loop.
#include "MD_Parola.h"
#include "MD_MAX72xx.h"
#include "SPI.h"
#define HARDWARE_TYPE MD_MAX72XX::FC16_HW
#define MAX_DEVICES 4
#define MAX_ZONES 2
#define CS_PIN 3
MD_Parola disp = MD_Parola(HARDWARE_TYPE, CS_PIN, MAX_DEVICES);
byte getStatus() {
static byte status = 0;
disp.displayAnimate();
for (int z = 0; z < MAX_ZONES; z++) {
disp.getZoneStatus(z) ? bitSet(status, z) : bitClear(status, z);
}
return status;
}
void setup() {
disp.begin(MAX_ZONES);
disp.setZone(0, 0, 1);
disp.setZone(1, 2, 3);
disp.setIntensity(0);
disp.displayClear();
disp.displayZoneText(0, "0", PA_CENTER, 100, 0, PA_SCROLL_RIGHT, PA_SCROLL_RIGHT);
disp.displayZoneText(1, "1", PA_CENTER, 200, 0, PA_SCROLL_LEFT, PA_SCROLL_LEFT);
}
void loop() {
// TODO
}
In questo codice, per prima cosa includiamo le librerie necessarie e definiamo le costanti per il display MAX7219. Se hai un tipo di display diverso o un numero diverso di moduli, dovrai modificare queste costanti.
Poi creiamo l’oggetto display disp e implementiamo la funzione getStatus() come mostrato sopra.
Nella funzione setup definiamo due animazioni in due zone. La prima animazione nella zona 0 fa scorrere il testo “0” verso destra. La seconda animazione nella zona 1 fa scorrere il testo “1” verso sinistra. L’immagine qui sotto mostra le due zone con i loro indici.

Nota che la prima animazione nella zona 0 gira più velocemente (speed=100) della seconda animazione (speed=200) nella zona 1. Il breve video qui sotto mostra come appare.

Possiamo lasciare invariato il codice sopra per tutti e sei gli scenari di animazione e dover cambiare solo l’implementazione della funzione loop.
Fai girare entrambe le animazioni solo una volta
Iniziamo con il caso più semplice. Vogliamo che entrambe le animazioni girino solo una volta fino al completamento. In questo caso la funzione loop deve solo chiamare getStatus() e non deve fare altro.
void loop() {
getStatus();
}
Probabilmente non ti servirà spesso, ma se implementi ed esegui questo, il risultato sarà il seguente:

Nota però che, dato che il video è in loop, sembra che l’animazione si riavvii. In realtà non succede: l’animazione gira solo una volta e poi il display rimane vuoto per sempre. Utile magari per un messaggio di avvio che appare solo una volta.
Fai girare la prima animazione una volta, ripeti l’altra
Nel prossimo scenario facciamo girare la prima animazione nella zona 0 solo una volta, ma ripetiamo la seconda. Questo significa che ogni volta che l’animazione nella zona 1 termina, dobbiamo resettare il display per quella zona. Qui sotto trovi il codice per la funzione loop di questo scenario.
void loop() {
if(getStatus() & 0b00000010) {
disp.displayReset(1);
}
}
Otteniamo lo stato e facciamo un’operazione bitwise AND (&) per verificare se l’animazione nella zona 1 è terminata. In tal caso chiamiamo displayReset(1) per questa zona per ripetere l’animazione. Il video seguente mostra l’animazione risultante:

Anche qui, dato che il video è in loop, sembra che la prima animazione si ripeta, ma in realtà non è così.
Fai girare la seconda animazione una volta, ripeti l’altra
Nel prossimo scenario invertiamo i ruoli della prima e della seconda animazione. Facciamo girare la seconda animazione una volta e ripetiamo la prima. Il codice della funzione loop è praticamente lo stesso di prima, cambiamo solo la zona che controlliamo.
void loop() {
if(getStatus() & 0b00000001) {
disp.displayReset(0);
}
}
Il video qui sotto mostra l’animazione corrispondente.

Ripeti entrambe le animazioni quando entrambe sono terminate
Ora, aspettiamo che entrambe le animazioni siano terminate e poi le resettiamo entrambe. È molto semplice. Basta controllare che entrambi i bit di stato siano impostati. Nota che in questo caso non facciamo un AND (&) ma controlliamo l’uguaglianza (=)!
void loop() {
if(getStatus() == 0b00000011) {
disp.displayClear();
disp.displayReset();
}
}
Nel video qui sotto puoi vedere come appare l’animazione in questo caso.

Ripeti l’animazione più veloce mentre quella più lenta è ancora in esecuzione
Dato che le due animazioni girano a velocità diverse, dobbiamo decidere cosa fare quando l’animazione più veloce termina. Vogliamo riavviare subito quella più veloce o aspettare che anche quella più lenta sia finita? Abbiamo già visto il secondo caso nella sezione sopra.
Qui sotto trovi il codice che copre il primo caso. Semplicemente controlliamo per ciascuna zona se è terminata e, in tal caso, riavviamo l’animazione corrispondente.
void loop() {
if(getStatus() & 0b00000001) {
disp.displayReset(0);
}
if(getStatus() & 0b00000010) {
disp.displayReset(1);
}
}
Ecco il video che mostra questo scenario in azione.

Riavvia l’animazione più lenta quando quella più veloce è terminata
L’ultimo scenario di animazione è il caso inverso del precedente. In questo scenario vogliamo riavviare l’animazione più lenta non appena quella più veloce è terminata. Nota che il codice è piuttosto diverso dal precedente.
void loop() {
if(getStatus() & 0b00000001) {
disp.displayReset(1);
disp.displayClear(1);
disp.displayReset(0);
}
}
In particolare, dobbiamo cancellare la zona 1, poiché riavviamo l’animazione nella zona 1 prima che sia terminata. Se non cancelli il display, rimarranno degli artefatti.
Il video qui sotto mostra questa animazione. Nota che lo scorrimento a sinistra della “1” viene interrotto e resettato nel momento in cui lo scorrimento a destra dello “0” è terminato.

Conclusioni
In questo tutorial hai imparato come coordinare le animazioni Parola che girano in più zone. Gli esempi sopra usano solo due zone e sono relativamente semplici. Tuttavia, appena hai più di due zone, la funzione getStatus() semplificherà molto il tuo codice.
Ad esempio, puoi elencare tutti i possibili stati delle zone in uno switch e gestirli di conseguenza. Ecco un esempio per due zone che puoi facilmente estendere a più zone.
void loop() {
switch (getStatus()) {
case 0b00000001:
...
break;
case 0b00000010:
...
break;
case 0b00000011:
...
break;
}
}
Non abbiamo usato questa struttura negli esempi di codice sopra, perché con due zone non semplifica davvero il codice, ma con più zone sì.
Inoltre, se vuoi concatenare più animazioni o controllarle tramite input esterni, dai un’occhiata al tutorial Control Parola Animations on MAX7219 LED Display .
Se vuoi manipolare i singoli pixel della matrice LED, la libreria Parola non è la scelta giusta e ti conviene usare FastLED o qualcosa di simile. Per un esempio, vedi il post Game of Life on a Dot Matrix Display with MAX7219 .
Spero che questo tutorial ti sia stato utile. Sentiti libero di lasciare un commento se hai domande.

