Libreria per matrice led 32x8 MAX7219

Buonasera
ispirato da questo post, ho cercato di capire meglio come gestire i moduli a matrice di led 32x8 (4 MAX7219).
Purtroppo, ingenuamente, avevo pensato di ruotare i caratteri nella matrice di byte, ma questo oltre a non permettere lo scorrimento corretto, fa in modo che i caratteri occupino sempre 8x8 bit, aumentando lo spazio tra i caratteri di una parola; l'unico modo corretto (a mio parere) è ruotare ogni carattere prima di stamparlo.
Naturalmente ho trovato un'ottima libreria (MD_MAX72XX), ma onestamente è un mattone!
Quindi ho deciso di studiare e provare a realizzare una semplice libreria (minimalista)!
Sono partito dalla libreria MaxMatrix, ma solo come spunto, in quanto non c'era niente di utilizzabile!

Naturalmente (per me) non è cosi banale, in quanto bisogna agire sui singoli bit e conoscete i miei limiti, ma sono testardo quindi ho preso un bel foglio a quadretti e ho iniziato a studiare.

Obiettivi:

  • ruotare la matrice di bit che rappresenta il carattere
  • testo scorrevole a sinistra, ed eventualmente a destra
  • possibilità di usare caratteri estesi (in particolare le accentate)

Mi sono procurato una matrice dei caratteri (copiata dalla libreria MD_MAX72XX), che rappresenta i caratteri in questo modo (per comodità i byte sono scritti nella forma binaria, un bit corrisponde ad un led)

3, B01000010, B01111111, B01000000, B00000000, B00000000, // 49 - '1'

dove il 3 indica il numero di bit occupati una volta ruotato, cioè

010
110
010
010
010
010
111
000

e quindi permette l'avvicinamento del carattere successivo.

Per la rotazione, dopo molti fogli a quadretti, ho risolto in questo modo, naturalmente grazie a vari articoli sulle operazioni bit a bit :frowning:

 for (uint8_t i = 0; i < sprite[0]; i++) {
  for (uint8_t row = 0; row < 8; row++) {
   //porta a sinistra il bit che mi interessa
   uint8_t bit_value = sprite[i + 1] << (7 - row);
   //estrae il bit più a sinistra (ottavo)
   bit_value = bit_value & 0x80;
   uint8_t max_num = (col / 8);
   uint8_t bit_col = 7 - (col % 8);
   bitWrite(_matrix[row][max_num], bit_col, bit_value);
  }
  col++;
 }

dove sprite è la matrice che rappresenta il carattere (vedi sopra), col è la colonna (bit) corrente, e _matrix è una matrice bidimensionale (8 righe per 80 colonne) che è una rappresentazione virtuale di 10 matrici 8x8.

A questo punto, una volta riempita la matrice virtuale, per lo scorrimento avrei dovuto far scorrere i singoli bit, il che implica spostare un bit da un byte al precedente o al successivo!!
Anche qui dopo vari tentativi, ho risolto grazie alle operazioni bit a bit.

Scorrimento a sinistra

 for (uint8_t row = 0; row < 8; row++) {
  for (uint8_t i = 0; i < MAX_DIGITS - 1; i++) {
   unsigned char b = 0;
   if ( i < MAX_DIGITS - 1) {
    //Legge il valore del bit più a sinistra
    b = bitRead(_matrix[row][i + 1], 7);
   }
   //Sposta di una posizione verso sinistra
   _matrix[row][i] <<= 1;
   //Imposta il valore del bit più a destra
   _matrix[row][i] |= b;
  }
 }

Scorrimento a destra

 for (uint8_t row = 0; row < 8; row++) {
  for (uint8_t i = MAX_DIGITS; i > 0; i--) {
   unsigned char b = 0;
   if ((i - 1) > 0) {
    //Legge il valore del bit più a destra
    b = bitRead(_matrix[row][i - 1 - 1], 0);
   }
   //Sposta di una posizione verso destra
   _matrix[row][i - 1] >>= 1;
   //Imposta il valore del bit più a sinistra
   _matrix[row][i - 1] |= (b * 0x80);
  }
 }

A questo punto sembrava risolto, sparo la matrice virtuale sui moduli e il gioco è fatto!
Invece no, o almeno, non per lo scorrimento a destra, in quanto verso sinistra, i caratteri escono dalla matrice virtuale in colonna zero, ma verso destra dalla colonna zero devono entrare!

Dopo vari tentativi, ho avuto una illuminazione ho risolto in questo modo:

 for (uint8_t row = 0; row < 8; row++) {
  digitalWrite(_load, LOW);
  for (uint8_t i = start; i < start + _num; i++) {
   shiftOut(_data, _clock, MSBFIRST, B00000001 + row);
   shiftOut(_data, _clock, MSBFIRST, _matrix[row][i]);
  }
  digitalWrite(_load, HIGH);
 }

Dove start indica la colonna (byte) da dove cominciare a leggere la matrice virtuale, che nel caso di scorrimento a sx, è uguale a zero, ma nel caso di scorrimento a dx è uguale alla posizione dell'ultimo led acceso (ultimo carattere scritto).

Per finire, grazie a @docdoc e questo post, ho inserito questa funzioncina

 void Max7219_Matrix::unicodeFilter(char *string) {
  for (uint16_t i = 0; i < strlen(string); i++) {
   if ((uint8_t)string[i] == 0xC3) {
    string[i] = string[i + 1] + 0x40;
    memcpy(string + i + 1, string + i + 2, strlen(string) - i);
   }
  }
 }

che processa la stringa sostituendo i due byte del carattere Unicode nel relativo ascii code esteso (128-255)

In definitiva, ho scritto una libreria minimalista che sembra funzionare correttamente (video) e che condivido con piacere.

Al solito:

  • se ho scritto idiozie
  • se trovate errori
  • se pensate possa essere ottimizzato/migliorato
    vi prego di dirmelo.

Grazie
Federico

PS
Utile lettura:
capire il MAX7219
operatori bit a bit sintetico, ma riassuntivo

UPDATE 10/12/2019
Versione 2.0. Informazioni qui (#67)

UPDATE 20/12/2019
Versione 2.1. Informazioni qui (#116)

Max7219_Matrix_v2.1.zip (7.88 KB)

Grazie, Federico! :slight_smile: :slight_smile: :slight_smile:
Appena posso faccio qualche prova, anche se abbiamo cominciato a lavorare di sera in parrocchia per il presepe, quindi sarò un po' impegnato.

Avevo provato la MD_max, ma mi sono fermato perché non capisco che problema c'è: rimane tutto spento o si accende tutto, ma non appaiono caratteri né sgorbi. Sembra un problema di comunicazione, ma i collegamenti sono esatti... Proprio ora mi viene il sospetto di aver caricato il programma con l'IDE impostato a 16MHz, mentre lì uso l'oscillatore interno a 8MHz! :slight_smile:

All'ora di pranzo, mi son fatto prendere la mano e ho aggiunto:

  • inversione testo (negativo)
  • testo scorrevole verso l'alto
  • testo scorrevole verso il basso

File aggiornati nel primo post.

Federico

Datman:
Avevo provato la MD_max, ma mi sono fermato perché non capisco che problema c'è: ...

Hai visto che devi impostare l'hardware corretto.

//nel mio caso, ma immagino anche il tuo :)

#define HARDWARE_TYPE MD_MAX72XX::FC16_HW

Federico

Era un problema dei driver dell'USBASP sul computer che stavo usando.

Funziona! :slight_smile:
Per integrarlo, però, temo che dovrò sforzarmici un po' e con calma, perché durante lo scorrimento avevo messo la lettura dell'encoder, proprio dentro alla funzione...

void printStringWithShift(unsigned char*s, int tempo_scorr, byte visual_ver)
{ //                                            da 1 a 10.
while(*s!=0)
  {
  if(!digitalRead(6) && !visual_ver) {selezione(); while(!digitalRead(6)); delay(200); Riavvia();}
  printCharWithShift(*s, tempo_scorr);
  s++;
  }
if(*s==0)
  {
  t0=millis();
  while(millis()-t0<pausa*1000)
    {   // Se visual_ver==1, significa che è stata chiamata dal setup() per visualizzare la versione,
        // perciò, pur essendo ancora premuto il pulsante, non deve saltare a selezione().
    if(!digitalRead(6) && !visual_ver) {selezione(); while(!digitalRead(6)); delay(200); Riavvia();}
    printCharWithShift(' ', tempo_scorr);
    }
  }
}

Scritte_scorrevoli_v1.3_p6.zip (11.9 KB)

Datman:
Funziona! :slight_smile:
Per integrarlo, però, temo che dovrò sforzarmici un po' e con calma, perché durante lo scorrimento avevo messo la lettura dell'encoder, proprio dentro alla funzione...

Io ho eliminato la possibilità di stampare un char, nel senso che questa è una funzione privata della libreria per una mia scelta, ma in effetti a te è comodo interrompere lo scorrimento prima di stampare il carattere e non aspettare tutta la stringa e in generale potrebbe essere comodo.

Ci lavoro nel fine settimana e vedo cosa riesco a fare.

Federico

Grazie!!! :slight_smile:

Datman:
Grazie!!! :slight_smile:

Ho aggiornato la libreria.
Nel tuo caso dovresti provare cosi, ma sono scettico :frowning:

void printStringWithShift(char* string, uint16_t scroll_speed) {
  m.unicodeFilter(string);
  uint16_t col = 8 * MAX_NUM;
  for (uint16_t i = 0; i < strlen(string); i++) {
    //fai quello che vuoi
    printChar(&col, string[i]);
  }
  for (uint16_t i = 0; i < col; i++) {
    delay(scroll_speed);
    m.scrollLeft();
  } 
}

void printChar(uint16_t *col, char scroll_char) {
  if (scroll_char < 32 || scroll_char > 255) return;
  uint8_t b = scroll_char - 32;
  uint8_t buf[7];
  memcpy_P(buf, CH + 6 * b, 6);
  m.writeChar(*col, buf);
  *col += buf[0] + 1;
}

Grazie, Federico! Credo che proverò domani :slight_smile:

Ciao, Federico
E' da un po' che sto provando, ma non riesco a integrare la tua libreria. Credevo che avessi fatto delle funzioni con gli stessi nomi...
Ho modificato l'inizio, ho tolto la definizione dei caratteri, ma non va... Puoi aiutarmi? :slight_smile:

Scritte_scorrevoli_v2.2_p2_non_funziona.zip (11 KB)

Datman:
Credevo che avessi fatto delle funzioni con gli stessi nomi...

Non credo che il problema siano i nomi, ma la diversa logica di "scrittura del testo" tra le due librerie.

Ho dato un'occhiata al tuo programma, ma faccio fatica a seguirlo, quindi colgo l'occasione per prendere un encoder, e provo :wink:
A proposito, va bene uno di questi qui?

Federico

Sì, va benissimo.
Grazie! :slight_smile:
Ho faticato parecchio, anche con molto vostro aiuto, per arrivare a quel punto. Da solo non ce l'avrei fatta!

Datman:
Sì, va benissimo.

Preso, mi arriva tra qualche giorno :wink:

Nel frattempo avevo qualche minuto e ho provato a fare modifiche alla cieca ::slight_smile:

Prova con quella in allegato e fammi sapere.
Per mia comodità ho messo la libreria nella stessa cartella.
Ho commentato le funzioni che ho sostituito.
Compila correttamente, ma non so se funziona :wink:

A proposito di compilazione, ma tu hai disabilitato o non hai mai abilitato i warning? ::slight_smile:

Federico

Scritte_scorrevoli_v2.2_p2_federico.zip (14.6 KB)

Tana! :slight_smile:
Ieri, infatti, su un computer su cui avevo sostituito l'hard disk e reinstallato tutto mi apparivano, inspiegabilmente, un sacco di warning! Non capivo dove avevo sbagliato... Quella versione funzionava con il display singolo... Ho disabilitato i warning e ora non ho più messaggi! :slight_smile:

Più tardi provo. Grazie

Datman:
Tana! :slight_smile:

... e ma questo è barare :wink: :smiley:

alcuni li ho corretti, nella versione che ti ho inviato, non mi piace vedere rosso, poi mi sono fermato, visto che è comunque una versione temporanea!

Federico

FUNZIONA!!! :slight_smile:
... quasi :slight_smile:

  • C'è un problema con l'anteprima del messaggio 2, sembra: si mischiano i caratteri e vengono fuori cose strane. Però mi sembra che vada a scrivere sequenzialmente tutte le lettere del messaggio, mentre dovrebbe scriverne contemporaneamente un numero pari a MAX_NUM.

  • Lo scorrimento va un po' a scatti, mentre prima era regolare

Gli darei un'occhiata, ma devo mangiare e andare a continuare a fare il presepe. Siamo in 3, senza un'idea precisa per riempire 5 mq salvo cambiarlo rispetto a quello riciclato degli ultimi anni, con difficoltà nel combinare le dimensioni di ciò che abbiamo con la prospettiva e con l'obiettivo di completarlo entro l'8 dicembre... :frowning:

Grazie, Federico
Gianluca

Datman:
FUNZIONA!!! :slight_smile:

Bene, son molto contento :slight_smile:

Sicuro che funzioni tutto come dovrebbe?

Federico

Ps
Appena mi arriva encoder, ti chiederò in merito ad alcune tue scelte che non mi sono chiare; ad esempio il riavvio dopo selezione!

Come leggi da ciò che ho aggiunto, qualche difettuccio c'è! :slight_smile:
Il riavvio l'ho usato per cancellare le scritte, ma credo che bastasse semplicemente fare un clear scansionando i display. Ora non ricordo più esattamente. Non essendo tutto il codice mio, non ho la padronanza su tutto...

Datman:
Come leggi da ciò che ho aggiunto, qualche difettuccio c'è! :slight_smile:

A ecco, sembrava troppo bello :frowning:

Federico