LED Matrix Display ansteuern

Über den Beitrag

In diesem Beitrag möchte ich euch Methoden vorstellen, wie ihr ein LED Matrix Display (engl.: Dot Matrix Display) ansteuern könnt. Das ist natürlich nicht der erste Beitrag zu diesem Thema im Netz, aber vielleicht gibt es doch noch das eine oder andere Neue für euch zu entdecken. Im Einzelnen behandele ich die folgenden Themen: 

Als Appetizer hier schon mal ein kleines Video:

Laufschriften für ein 8x8x4 LED Matrix Display

Was ist ein LED Matrix Display und wie funktioniert es?

8x8 LED Matrix Display mit Reihen-Eingang und Spalten-Ausgang, Rot: LED (0/0)
8×8 LED Matrix Display mit Reihen-Eingang und Spalten-Ausgang, Rot: LED (0/0)

Ein LED Matrix Display ist ein LED Dioden Array. Jeweils ein Eingangs- bzw. Ausgangspin verbindet die LEDs einer Reihe (Row) bzw. einer Spalte (Column). Ob die Reihen oder die Spalten die Ein- oder Ausgänge sind, variiert von Display zu Display. 

Durch Anlegen einer Spannung an einen Eingangspin und durch Verbinden eines Ausgangspins mit Ground bringt ihr genau eine LED am Schnittpunkt zum Leuchten.

Wenn ihr keinen Ansteuerungs-IC wie den MAX7219 oder MAX7221 verwendet, dann müsst ihr Vorwiderstände einsetzen. Die benötigte Größe hängt von den technischen Daten eurer Displays ab. Meistens liegt ihr aber bei 5 Volt mit 330 Ohm schon richtig. 

Noch eine Anmerkung zum Schema oben: normalerweise findet man in Datenblättern die Reihen- und Spaltennummern von 1 bis 8. Gewöhnt euch lieber die Nummerierung 0 bis 7 an. Denn dann denkt ihr gleich „richtig“ in Bits und Bytes und das ist bei der Ansteuerung hilfreich.

Und noch etwas: wenn ich eine LED Position (einen Dot) in diesem Beitrag als Wertepaar (a/b) angebe, dann ist a die Reihe und b die Spalte. Also nicht wie x und y im Koordinatensystem. 

Woher bekommt Ihr LED Matrix Displays?

LED Matrix Displays gibt es in vielen verschiedenen Ausfertigungen. Dabei variiert die Anzahl der LEDs (Dots), die Größe der Displays und die Farbe der LEDs. Darüber hinaus gibt es sie mit integriertem Ansteuerungs-IC und teilweise auch im Verbund, z.B. als 8 x 8 x 4. Sucht in dem Online-Shop eures Vertrauens nach „LED Matrix Display“ oder „Dot Matrix“. Die Preise variieren recht stark mit der Herkunft und der Menge. 

Direkte Ansteuerung per Microcontroller oder Arduino

Ich verwende als Beispiel das oben schematisch abgebildete 8×8 LED Matrix Display mit Reihen-Eingang und Spalten-Ausgang. Vorab: die Verdrahtung macht keinen Spaß, da acht Eingänge, acht Ausgänge und acht Widerstände verbunden werden müssen. Außerdem sind die Reihen und Spalten den Pins ohne erkennbares System zugeordnet. Da müsst ihr ziemlich viel suchen. Teilweise ist auch nicht offensichtlich welches der Pin 1 ist. In diesem Fall müsst ihr erstmal ein wenig ausprobieren. 

Der ganze erste Abschnitt dient denn auch mehr der Didaktik als der Praxis. Ansteuerungs-ICs wie der MAX7219 machen das Leben leichter. Und noch komfortabler sind die Displays mit integrierter Ansteuerung. Da komme ich dann später zu. Trotzdem sollte man erstmal das Prinzip verstanden haben.

Beschaltung

Bei 16 benötigten Ein- und Ausgängen müssen auf dem Arduino auch die analogen Pins mit einbezogen werden. Oder ihr verwendet eine Portweiterung wie den MCP23017

So sieht die Beschaltung schematisch und praktisch aus:

Beschaltung für ein 8x8 LED Matrix Display
Beschaltung (schematisch) für ein 8×8 LED Matrix Display…
Verkabelung in der Praxis - eine ziemliche "Wurschtelei"
…und so sieht es in der Praxis aus – eine ziemliche „Wurschtelei“

Ein kleiner Beispielsketch

Im folgenden Beispielsketch werden zunächst die Arduino Pins für die Reihen und Spalten definiert. Die Eingänge (Reihen) werden auf LOW gesetzt, die Ausgänge (Spalten) auf HIGH. Damit sind alle LEDs aus. Eine LED an der Stelle „r/c“ wird angeschaltet, wenn die Reihe „r“ auf HIGH gesetzt wird und die Spalte „c“ auf LOW.

Als erste kleine Spielerei gibt es ein Lauflicht. Dabei geht jede LED einmal kurz an. Dann wird die Diagonale von (0/0) bis (7/7) zum Leuchten gebracht.

#define r0 2 // pin for row 0
#define r1 3 // pin for row 1
#define r2 4 // ...
#define r3 5
#define r4 6
#define r5 7
#define r6 8
#define r7 9
#define c0 10 // pin for column 0
#define c1 11 // pin for column 1
#define c2 12 // ...
#define c3 13
#define c4 A0
#define c5 A1
#define c6 A2
#define c7 A3

int row[] = {r0, r1, r2, r3, r4, r5, r6, r7};
int column[] = {c0, c1, c2, c3, c4, c5, c6, c7};
               
void setup() {
  for(int i = 0; i<=7; i++){
    pinMode(row[i], OUTPUT);
  }
  for(int i = 0; i<=7; i++){
    pinMode(column[i], OUTPUT);
  }
  clearDisplay();
}

void loop() {
  for(int i=0; i<=7; i++){
    for(int j=0; j<=7; j++){
      switchLED(row[i],column[j],1);
      delay(100);
      switchLED(row[i],column[j],0);
    }
  }
}

void clearDisplay(){
   for(int i = 0; i<=7; i++){
      digitalWrite(row[i],LOW); 
  }
  for(int i = 0; i<=7; i++){
    digitalWrite(column[i], HIGH);
  } 
}

void switchLED(int r, int c, bool ON){
  if(ON){
    digitalWrite(r, HIGH);
    digitalWrite(c, LOW);
  }
  else{
    digitalWrite(r, LOW);
    digitalWrite(c, HIGH);
  }
}

 

Unerwünschte Querbeeinflussung

Ersetzt im letzten Sketch einmal die Sketch Hauptschleife durch die folgende:

void loop() {
  switchLED(row[0],column[0],1);
  delay(1000);
  switchLED(row[0],column[1],1);
  delay(1000);
  switchLED(row[1],column[0],1);
  delay(1000);
  clearDisplay();
}

 

Eigentlich soll der Sketch die Dots (0/0), (0/1) und (1/0) anschalten. Das tut er zwar auch, aber mit dem Dot (1/0) wird auch (1/1) angeschaltet. Falls euch nicht klar sein sollte, warum das so ist, schaut dazu noch einmal in das Schema des 8×8 Displays. Dann sollte die Problematik offensichtlich sein. 

Die Lösung: Multiplexing

Ersetzt nun noch einmal die Hauptschleife durch die folgende:

void loop() {
  switchLED(row[0],column[0],1);
  delay(1);
  switchLED(row[0],column[0],0);
  switchLED(row[0],column[1],1);
  delay(1);
  switchLED(row[0],column[1],0);
  switchLED(row[1],column[0],1);
  delay(1);
  switchLED(row[1],column[0],0);
}

 

In diesem Fall leuchten tatsächlich nur die Dots (0/0), (0/1) und (1/0), da jeder Dot separat für kurze Zeit angeschaltet wird. Ein Flackern sieht man nicht, dafür ist der Wechsel zu schnell.

Die Grenzen des einfachen Multiplexings

Zunehmendes Multiplexing in dieser Form führt zu einer Abnahme der Intensität. Im folgenden Beispiel ist der Dot (7,7) immer an, der Rest der Diagonale ist „gemultiplext“. Dass der Rest von Reihe und Spalte 7 auch leuchten, ist ein Nebeneffekt.

Zu beachten: Die Sketchzeilen 1 und 2 gehören noch ins Setup. 

  switchLED(row[7],column[7], 1);
}

void loop() {
  for(int i=0; i<=6; i++){
    switchLED(row[i],column[i],1);
    delay(1);
    switchLED(row[i],column[i],0);  
  }
}

 

Auf dem Foto erkennt man die Unterschiede nicht so gut wie in der Realität. LEDs sind nicht einfach zu fotografieren. Dennoch könnt ihr vielleicht erahnen, dass der Dot (7,7) heller ist. 

"Immer an" vs. "gemultiplext"
„Immer an“ vs. „gemultiplext“

Auch für dieses Problem gibt es eine Lösung. Über Pulsweitenmodulation (PWM, siehe mein Beitrag über Timer) lässt sich die Intensität je nach Anzahl der „gemultiplexten“ LEDs gewichten. Darauf gehe ich aber jetzt nicht näher ein und möchte die bequemere und allgemein übliche Lösung vorstellen.

Ansteuerung mit dem MAX7219 / MAX7221

Alle eben genannten Probleme lassen sich mit den Ansteuerungs-ICs MAX7219 oder MAX7221 lösen. Da beide fast identisch sind, nenne ich ab jetzt nur den MAX7219, meine aber beide.

Primär ist der MAX7219 für die Ansteuerung von 7-Segment Displays konzipiert. Er lässt sich aber auch für die Ansteuerung von LED Matrix Displays oder losen LED Arrangements verwenden. Das ist auch nicht weiter verwunderlich, da die Ansteuerung mehrerer 7-Segment Anzeigen im Grunde dieselbe Herausforderung wie die Ansteuerung eines LED Matrix Displays darstellt.

Pinout und technische Eigenschaften des MAX7219

Pinout des MAX7219 / MAX7221; CS gilt nur für den MAX7221
Pinout des MAX7219 / MAX7221; CS gilt nur für den MAX7221

Der MAX7219 besitzt 24 Pins:

  • Die Pins SEG X liefern die positive Spannungsversorgung
    • Ob diese mit den Reihen- oder Spaltenpins verbunden werden, hängt davon ab, wie herum die LEDs im Display verbaut sind. 
  • Die Pins DIG X schalten nach GND.
  • Der MAX7219 wird per SPI angesteuert:
    • DIN ist der Data Input, also MOSI (Master Out, Slave In).
    • Ein MISO (Master In, Slave Out) gibt es nicht; es ist also eine One-Way Kommunikation.
    • CLK: Clock Pin.
    • LOAD / CS: Chip Select Pin.
  • Wenn ihr mehrere Displays verwendet, wird DOUT mit dem DIN des nächsten Displays verbunden.
  • An VCC dürfen 4 bis 5.5 Volt verwendet werden
  • An ISET wird die Höhe des Stroms für die LEDs eingestellt. Ich habe 10 kOhm verwendet. Im Datenblatt des MAX7219 gibt es eine grafische Darstellung, welcher Strom mit welchem Widerstand bei welcher Spannung bereitgestellt wird. 

Verwendung der Bibliothek LedControl

Zur Ansteuerung des MAX7219 verwende ich die Bibliothek LedControl von Eberhard Fahle, die ihr von Github oder direkt über die Bibliotheksverwaltung der Arduino IDE installieren könnt. Eine sehr gute Einführung gibt es hier.

Die Bibliothek erwartet eigentlich den Spannungseingang an den Spalten und den Spannungsausgang an den Reihen. Das ist bei dem von mir verwendeten Modell anders herum. Man kann einfach entsprechend andersherum verkabeln (Segx an die Reihenpins, Digx an die Spaltenpins), muss dann aber nachher noch etwas umdenken.

Diese LED Matrix Display Struktur erwartet die Bibliothek LedControl
Diese LED Matrix Display Struktur erwartet die Bibliothek LedControl

Der MAX7219 besitzt nicht sonderlich viele Register. Deshalb lässt er sich auch ohne all zu viel Aufwand ohne Bibliothek ansteuern. Wie das geht, zeige ich am Ende dieses Beitrages. 

Was die Komplexität der Verkabelung des LED Matrix Displays angeht, bringt der MAX7219 noch keinen großen Vorteil, aber immerhin braucht ihr die Vorwiderstände nicht:

Verkabelung eines 8x8 LED Matrix Displays mit dem MAX7219
Verkabelung eines 8×8 LED Matrix Displays mit dem MAX7219
8x8 LED Matrix Display mit MAX7219 am Arduino - Fritzing Schema
Verkabelung LED Matrix Display – MAX7219, schematisch

Zu beachten ist, dass die oben abgebildete Verkabelung spezifisch für das von mir verwendete LED Matrix Display ist. Vielleicht sind die Anschlüsse eures Displays gleich, vielleicht aber auch nicht.

Ein kleiner Beispielsketch

#include <LedControl.h>
//12= data pin(DIn), 11= CLK Pin, 10= Load/CS Pin, 1 = num of devices
LedControl lc88=LedControl(12,11,10,1); 

void setup(){
  lc88.shutdown(0,false); // Wake up! 0= index of first device;
  lc88.setIntensity(0,2);
  lc88.clearDisplay(0);
  delay(500);
}

void loop(){
  for(int row=0; row<=7; row++){
    lc88.setLed(0,row,0,true);
    delay(250); 
  }
  for(int col=0; col<=7; col++){
    lc88.setLed(0,0,col,true);
    delay(250); 
  }
  delay(500);
  lc88.clearDisplay(0);
  delay(2000); 
}

 

An diesem Beispiel sollten die meisten Funktionen der Bibliothek schon klar werden:

  • zunächst wird ein Display Objekt erzeugt und dabei die Anschluss Pins und die Zahl der Einzeldisplays festgelegt
  • shutdown(display, false) weckt das Display; mit true geht es schlafen
  • setIntensity(display, value) legt die Helligkeit der Dots fest mit 0 <= value < 16
  • clearDisplay(display) schaltet alle Dots eines Displays aus
  • setLed(display, row, col, true/false) schaltet genau einen Dot in Reihe row und Spalte col an oder aus.  

Weitere nützliche Funktionen, die ihr selber ausprobieren könnt, sind:

  • setRow(display, row, val) – die Reihe row eines Displays wird mit dem Wert val belegt, z.B. 0b11110000 -> die ersten vier LEDs sind an, die letzten vier aus 
  • setColumn(display, col, val) – wie setRow, nur eben zum Setzen von Spalten
    • diese Funktion ist erheblich langsamer als die setRow-Funktion, da sie die setLed-Funktion (achtmal pro setColumn) verwendet. 

Da ich bei meinem LED Matrix Display die SEG und die DIG Anschlüsse vertauschen musste, sind auch Reihen und Spalten vertauscht. Eigentlich sollte der Sketch erst die Spalte 0 und dann die Reihe 0 füllen. Offensichtlich ist es anders herum: 

LedControl_Max7219_test.ino: Spalten und Zeilen sind an diesem Display vertauscht
LedControl_Max7219_test.ino: Spalten und Zeilen sind an diesem Display vertauscht

Auf dem Bild oben seht ihr, dass die obere Reihe schon komplett leuchtet und nun die linke Spalte vervollständigt wird.

Eine Lösung lautet, das Display einfach um 90° gegen den Uhrzeigersinn zu drehen. Dann ist der Dot (0/0) unten links (wie in einem Koordinatenkreuz) und nicht oben links. Ihr müsst dann bei der Zeilennummerierung umdenken.

Verwendung von Displays mit integriertem MAX7219

8x8x1 LED Matrix Display

Wer es bequem haben möchte, dem rate ich zur Verwendung von Displays mit integriertem MAX7219. Die Verkabelung solcher Teile sieht schon deutlich angenehmer aus:

Verkabelung von Displays mit integriertem MAX7219 - auch das Kaskadieren ist einfach
Verkabelung von Displays mit integriertem MAX7219 – auch das Kaskadieren ist einfach

Wo der Nullpunkt (0/0) liegt, kann unter Umständen überraschend sein. Probiert es aus. Dieses LED Matrix Display macht es wie erwartet:

8x8 LED Matrix Display mit integriertem MAX7219
8×8 LED Matrix Display mit integriertem MAX7219

Hinweis: wenn ihr alle LEDs eines 8×8 Matrix Displays gleichzeitig leuchten lasst, dann kann der Strombedarf  mehrere 100 mA pro Display betragen. Bei Verwendung mehrerer Displays könnt ihr dann sehr locker die Limits des Arduinos überschreiten. Mehr als 500 mA solltet ihr einem Arduino UNO bzw. eurem USB Anschluss nicht zumuten. Setzt ggf. eine separate Stromquelle ein.  

8x8x4 LED Matrix Display

Wollt ihr mehrere Displays aneinanderhängen, kommt für euch vielleicht ein Verbunddisplay infrage. Vor allem sind 8x8x4 Displays weit verbreitet. Auch hier seid ihr unter Umständen überrascht, wo sich der Dot (0,0) befindet. Zum Testen habe ich den folgenden kurzen Sketch verwendet:

#include "LedControl.h"
LedControl lc884=LedControl(12,11,10,4);

unsigned long delaytime=3000;

void setup() {
  for(int i=0;i<4;i++){
    lc884.shutdown(i,false);
    lc884.setIntensity(i,8);
    lc884.clearDisplay(i);
  }
}

void loop() { 
  lc884.setRow(0,0,B01111111);
  lc884.setRow(1,1,B00111111);
  lc884.setRow(2,2,B00011111);
  lc884.setRow(3,3,B00001111);  
}

 

Eigentlich hätte ich gerne das Display 0 links und das Display 4 rechts. Außerdem hätte ich gerne den Nullpunkt oben links und eine Reihenabbildung, die der Reihenfolge der Bits entspricht. D.h. ein 0b00001111 soll vier Dots einer Reihe rechts leuchten lassen und nicht links. Ich habe zwei Displays aus unterschiedlichen Quellen getestet und mit beiden konnte ich nicht alle Wünsche erfüllen. Mit einer umgekehrten Displayreihenfolge konnte ich am ehesten leben: 

8x8x4 LED Matrix Display: meine Ausrichtung
8x8x4 LED Matrix Display: meine Ausrichtung

Display 0 ist hier rechts, dafür ist aber der Rest so wie ich es will. Diese Anordnung verwende ich im Folgenden für die Entwicklung der Laufschrift. Wenn euer Display die Dinge anders abbildet, dann müsst ihr die Sketche entsprechend anpassen.

Programmierung einer Laufschrift (Banner)

Vorbereitung: Kreieren und Darstellen von Buchstaben

Buchstaben, Zahlen und Zeichen sind auf einem Blatt kariertem Papier schnell entwickelt. Dann schreibt ihr das Ergebnis zeilenweise im Byteformat in Arrays. Im folgenden Sketch seht ihr Beispiele für A,r,d,u,i,n und o. Ich habe die Buchstaben recht schmal gemacht, da ich sie später noch „zusammenschieben“ möchte, sodass der ganze Arduino Schriftzug auf einmal auf das Display passt.

Die Buchstaben werden zunächst nacheinander auf dem Display 0 dargestellt. Dann machen wir den ersten Schritt in Richtung Laufschrift, indem wir die Buchstaben Display-weise von rechts nach links durchlaufen lassen. Mein Bezugsdisplay ist dabei das nicht sichtbare, virtuelle Display -1. 

#include "LedControl.h"
LedControl lc884=LedControl(12,11,10,4);

unsigned long delayTime=500;

byte a[8]={B00000000,B01100000,B10010000,B10010000,B10010000,B11110000,B10010000,B10010000};
byte r[8]={B00000000,B00000000,B00000000,B10100000,B11010000,B10000000,B10000000,B10000000};
byte d[8]={B00000000,B00010000,B00010000,B00010000,B01110000,B10010000,B10010000,B01110000};
byte u[8]={B00000000,B00000000,B00000000,B10010000,B10010000,B10010000,B10010000,B01110000};
byte i[8]={B00000000,B00000000,B01000000,B00000000,B01000000,B01000000,B01000000,B01000000};
byte n[8]={B00000000,B00000000,B00000000,B10100000,B11010000,B10010000,B10010000,B10010000};
byte o[8]={B00000000,B00000000,B00000000,B01100000,B10010000,B10010000,B10010000,B01100000};

void setup() {
  for(int i=0;i<4;i++){
    lc884.shutdown(i,false);
    lc884.setIntensity(i,8);
    lc884.clearDisplay(i);
  }
}

void loop() { 
  oneMatrix();
  fourMatrices();
  delay(1000);
}

void oneMatrix(){
  displayCharAndWait(a,0);
  displayCharAndWait(r,0);
  displayCharAndWait(d,0);
  displayCharAndWait(u,0);
  displayCharAndWait(i,0);
  displayCharAndWait(n,0);
  displayCharAndWait(o,0);
}

void displayCharAndWait(byte* x, byte displayNumber){
  lc884.clearDisplay(displayNumber);
  for(int j=0; j<=7;j++){
    lc884.setRow(displayNumber,j,x[j]);
  }
  delay(delayTime);
  lc884.clearDisplay(displayNumber); 
}


void fourMatrices(){
  for(int j=0; j<=10; j++){
    int currentMatrix = -1;
    if( ((currentMatrix+j)>=0) && ((currentMatrix+j)<4) ){
      displayChar(a,currentMatrix+j);
    }
    currentMatrix--;
    if( ((currentMatrix+j)>=0) && ((currentMatrix+j)<4) ){
      displayChar(r,currentMatrix+j);
    }
    currentMatrix--;
    if( ((currentMatrix+j)>=0) && ((currentMatrix+j)<4) ){
      displayChar(d,currentMatrix+j);
    }
    currentMatrix--;
    if( ((currentMatrix+j)>=0) && ((currentMatrix+j)<4) ){
      displayChar(u,currentMatrix+j);
    }
    currentMatrix--;
    if( ((currentMatrix+j)>=0) && ((currentMatrix+j)<4) ){
      displayChar(i,currentMatrix+j);
    }
    currentMatrix--;
    if( ((currentMatrix+j)>=0) && ((currentMatrix+j)<4) ){
      displayChar(n,currentMatrix+j);
    }
    currentMatrix--;
    if( ((currentMatrix+j)>=0) && ((currentMatrix+j)<4) ){
      displayChar(o,currentMatrix+j);
    }
   
    delay(delayTime);
    for(int i=0; i<=4; i++){
      lc884.clearDisplay(i);
    }
  }  
}

void displayChar(byte *x, int displayNumber){
  lc884.clearDisplay(displayNumber);
  for(int j=0; j<=7;j++){
    lc884.setRow(displayNumber,j,x[j]);
  }
}

 

Wie das im Ergebnis aussieht, könnt ihr in dem Video zu Beginn des Beitrages sehen. Hier ein Ausschnitt: 

Ausschnitt der Laufschrift
Ausschnitt der Laufschrift

Ein Zwischenschritt: statische Darstellung als unsigned long Array

Das Durchlaufen „Display für Display“ war einfach. Aber es ist auch noch nicht besonders schick. Schöner wäre ein Verschieben „Dot für Dot“. Zugegebenermaßen habe ich mich damit schwerer getan als ich dachte. Ich zeige ein paar Lösungen. Vielleicht denke ich auch zu kompliziert – wenn ihr einfachere Methoden habt, immer her damit!

Für meinen Lösungsweg Nr. 1 kommt hier erst einmal ein Zwischenschritt. Zunächst habe ich die Buchstaben so zusammengefasst, dass sie auf das Viererdisplay passen. Dazu habe ich die Zwischenräume auf ein Mindestmaß reduziert und alle 0ten, 1sten, 2ten….7ten Reihen in jeweils einem unsigned long Wert zusammengefasst. Da eine unsigned long Variable 32 bits hat, entspricht das der Breite des 4er Displays. Bei der späteren Darstellung auf dem Display muss man den Wert dann allerdings wieder in bytes „aufdröseln“. Das macht die Funktion displayBanner().

#include "LedControl.h"
LedControl lc884=LedControl(12,11,10,4);

long delayTime=500;

unsigned long banner[8]={0b00000000000000000000000000000000,
                         0b01100000000001000000000000000000,
                         0b10010000000001000000100000000000,
                         0b10010101000001010010001010001100,
                         0b10010110100111010010101101010010,
                         0b11110100001001010010101001010010,
                         0b10010100001001010010101001010010,
                         0b10010100000111001110101001001100};
               
void setup() {
  for(int i=0;i<4;i++){
    lc884.shutdown(i,false);
    lc884.setIntensity(i,8);
    lc884.clearDisplay(i);
  }
}

void loop() { 
  displayBanner(); delay(500);
}

void displayBanner(){
  byte currentMatrix[8];
  for(int j=0; j<4; j++){  
    for(int i=0; i<8; i++){
      currentMatrix[i] = (((banner[i])>>(j*8)) & 0b11111111);
    }
    displayMatrix(currentMatrix,j);
  }
}

void displayMatrix(byte *matrix, int matrixNumber){
  for(int i=0; i<=7;i++){
    lc884.setRow(matrixNumber,i,matrix[i]);
  }
}

 

Und so sieht das Ergebnis aus: 

Der "kondensierte" Arduino Schriftzug
Der „kondensierte“ Arduino Schriftzug

Das unsigned long Array dotweise laufen lassen

Der Vorteil der Darstellung als unsigned long array ist, dass die Verschiebung wesentlich leichter über Binäroperationen zu programmieren ist als Byte für Byte. Das Banner wird schrittweise nach links verschoben und auf die Einzeldisplays aufgeteilt. Beim Nachvollziehen des Sketches vergesst nicht, dass sich das Display 0 rechts befindet.

Als kleines Feature habe ich noch eingebaut, dass das Banner kurz stoppt, wenn es vollständig auf dem Display ist. Es wird kurz heller, dann wieder dunkler und läuft dann aus dem Display. Das Ergebnis seht ihr im Video zu Beginn des Beitrages. 

#include "LedControl.h"
LedControl lc884=LedControl(12,11,10,4);

long delayTime=150;

unsigned long banner[8]={0b00000000000000000000000000000000,
                         0b01100000000001000000000000000000,
                         0b10010000000001000000100000000000,
                         0b10010101000001010010001010001100,
                         0b10010110100111010010101101010010,
                         0b11110100001001010010101001010010,
                         0b10010100001001010010101001010010,
                         0b10010100000111001110101001001100};
               
void setup() {
  for(int i=0;i<4;i++){
    lc884.shutdown(i,false);
    lc884.setIntensity(i,8);
    lc884.clearDisplay(i);
  }
}

void loop() { 
  calcCurrentBanner(); delay(500);
}

void calcCurrentBanner(){
  unsigned long currentBanner[8];
  for(int i=32; i>=0; i--){
    for(int j=0; j<8; j++){
      currentBanner[j] = (banner[j])>>i;
    }
    displayBanner(currentBanner);
    delay(delayTime);
  }
  stopAndHighlight();
  for(int i=0; i<33; i++){
    for(int j=0; j<8; j++){
      currentBanner[j] = (banner[j])<<i;
    }
    displayBanner(currentBanner);
    delay(delayTime);  
  }
}

void displayBanner(unsigned long *cb){
  byte currentMatrix[8];
  for(int j=0; j<4; j++){  
    for(int i=0; i<8; i++){
      currentMatrix[i] = (((cb[i])>>(j*8)) & 0b11111111);
    }
    displayMatrix(currentMatrix,j);
  }
}

void displayMatrix(byte *matrix, int matrixNumber){
  for(int i=0; i<=7;i++){
    lc884.setRow(matrixNumber,i,matrix[i]);
  }
}

void stopAndHighlight(){
  for(int i=8; i<16; i++){
    for(int j=0;j<4;j++){
      lc884.setIntensity(j,i);
      delay(20);
    }
  }
  delay(1000);
  for(int i=15; i>=8; i--){
    for(int j=0;j<4;j++){
      lc884.setIntensity(j,i);
      delay(20);
    }
  }
}

 

Gößere Banner laufen lassen

Nun passt das, was ihr vielleicht als Banner über das Display laufen lassen wollt, nicht unbedingt auf das Display bzw. in ein unsigned long Array. Aber das geht auch. Als Beispiel nehme ich den ungekürzten Arduino Schriftzug, der sich über sieben Einzeldisplays erstreckt. Diesen verteile ich in ein zweidimensionales unsigned long Array „bannerPart[2][8]“. Dabei verschwende ich natürlich ein wenig Speicherplatz, da ich das Array nicht ganz nutze.  

Die Übernahme der einzelnen Buchstaben in das Array bannerPart habe ich diesmal automatisiert. Das geschieht im Setup in den ersten beiden for-Schleifen. In calcCurrentBanner() finden sich drei for-Schleifen, die für drei Phasen stehen. In der ersten Phase wird das erste Bannerteil hineingeschoben. Während der zweiten Phase geht das erste Teil hinaus und die frei werdenden Bereiche werden mit dem zweiten Bannerteil aufgefüllt. In der dritten Phase wird das zweite Bannerteil hinausgeschoben. 

#include "LedControl.h"
LedControl lc884=LedControl(12,11,10,4);

long delayTime=150;
byte banner[7][8]={{B00000000,B01100000,B10010000,B10010000,B10010000,B11110000,B10010000,B10010000},
                   {B00000000,B00000000,B00000000,B10100000,B11010000,B10000000,B10000000,B10000000},
                   {B00000000,B00010000,B00010000,B00010000,B01110000,B10010000,B10010000,B01110000},
                   {B00000000,B00000000,B00000000,B10010000,B10010000,B10010000,B10010000,B01110000},
                   {B00000000,B00000000,B01000000,B00000000,B01000000,B01000000,B01000000,B01000000},
                   {B00000000,B00000000,B00000000,B10100000,B11010000,B10010000,B10010000,B10010000},
                   {B00000000,B00000000,B00000000,B01100000,B10010000,B10010000,B10010000,B01100000}};

unsigned long bannerPart[2][8];

void setup(){
  unsigned long b1, b2, b3;
  for(int i=0; i<8; i++){
    unsigned long b1, b2, b3;
    b1 = ((unsigned long)(banner[0][i]))<<24;
    b2 = ((unsigned long)(banner[1][i]))<<16;
    b3 = ((unsigned long)(banner[2][i]))<<8;
    bannerPart[0][i] = b1 + b2 + b3 + banner[3][i];
  }
   for(int i=0; i<8; i++){
    unsigned long b1, b2, b3;
    b1 = ((unsigned long)(banner[4][i]))<<24;
    b2 = ((unsigned long)(banner[5][i]))<<16;
    b3 = ((unsigned long)(banner[6][i]))<<8;
    bannerPart[1][i] = b1 + b2 + b3;
  }
 
  for(int i=0;i<4;i++){
    lc884.shutdown(i,false);
    lc884.setIntensity(i,8);
    lc884.clearDisplay(i);
  }
}

void loop() { 
  calcCurrentBanner(); 
  delay(500);
}

void calcCurrentBanner(){
  unsigned long currentBanner[8];
  for(int i=32; i>=0; i--){
    for(int j=0; j<8; j++){
      currentBanner[j] = (bannerPart[0][j])>>i;
    }
    displayBanner(currentBanner);
    delay(delayTime);
  }
  for(int i=1; i<=32; i++){
    for(int j=0; j<8; j++){
      currentBanner[j] = ((bannerPart[0][j])<<i) + ((bannerPart[1][j])>>(32-i));
    }
    displayBanner(currentBanner);
    delay(delayTime);
  }
  for(int i=1; i<=24; i++){
    for(int j=0; j<8; j++){
      currentBanner[j] = (bannerPart[1][j])<<i;
    }
    displayBanner(currentBanner);
    delay(delayTime);
  }
}

void displayBanner(unsigned long *cb){
  byte currentMatrix[8];
  for(int j=0; j<4; j++){  
    for(int i=0; i<8; i++){
      currentMatrix[i] = (((cb[i])>>(j*8)) & 0b11111111);
    }
    displayMatrix(currentMatrix,j);
  }
}

void displayMatrix(byte *matrix, int matrixNumber){
  for(int i=0; i<=7;i++){
    lc884.setRow(matrixNumber,i,matrix[i]);
  }
}

 

Das Ergebnis findet ihr wieder im Video.

Wenn ihr eigene Banner mit einer anderen Breite kreiert, dann müsst ihr einige Werte im Sketch anpassen. Ich hatte offengestanden keine Lust mehr den Sketch zu verallgemeinern.

Weitere Methoden

Das Verschieben mittels Byte Arrays ist etwas komplizierter als man zunächst meinen sollte. Zumindest habe ich mich damit ein wenig schwergetan. Ihr findet den Sketch am Schluss des Beitrages.

Die mit Abstand einfachste Methode wäre ein bool Array, in dem jeder Dot eine separate Variable ist. In dieses Array könnte man auch die vier leeren Displays zu Beginn und die vier leeren Displays am Ende integrieren. Macht 4 + 7 (für das Arduino Banner) + 4 = 15 Einzeldisplays. Daraus würde ein 120 x 8 Array mit 960 bool Werten entstehen. Das Durchlaufen des Banners wäre so sehr einfach zu programmieren. Aber leider belegt jede bool Variable ein ganzes Byte. Diese Verschwendung von Speicherplatz ist mir dann doch zu groß.

LED Matrix Display Ansteuerung ohne MAX7219 Bibliothek

Wie schon erwähnt, hat der MAX7219 nicht übermäßig viele Register und kann deswegen recht leicht auch ohne Bibliothek angesteuert werden. Im nächsten Sketch könnt ihr sehen wie das geht.

Ich möchte den Sketch aber auch nicht im Detail erläutern, da der Beitrag sowieso schon so lang ist. Wenn ihr euch das Datenblatt danebenlegt, sollte er nicht so schwer zu verstehen sein. Wenn ihr trotzdem Fragen habt, fragt! Nur ein paar Anmerkungen:

  • Der Sketch verwendet die Standard SPI Anschlüsse für MOSI und CLK (beim UNO Pin 11 bzw. 13). Ihr müsst also ein wenig umstöpseln.
  • MISO gibt es nicht, der MAX7219 ist nicht gesprächig.
  • Da es kein MISO gibt, lässt sich der Zustand der Dots nicht abfragen.
  • Da man bei der setLed Funktion nur einzelne Dots ändern möchte, aber nur ganze Reihen Bytes schreiben kann, muss der Sketch sozusagen Buch führen. Das macht er über das Array dots.
#include <SPI.h>

const int slaveSelectPin = 10;
const int totalDevs = 4;
const int colsPerDev = 8;
const int rowsPerDev = 8;
byte dots[totalDevs][rowsPerDev] = {0}; // in diesem Array wird der Zustand der Dots gespeichert

byte a[8]={B00000000,B01100000,B10010000,B10010000,B10010000,B11110000,B10010000,B10010000};

void setup(){
  SPI.begin();
  pinMode(slaveSelectPin, OUTPUT);
  digitalWrite(slaveSelectPin, HIGH);
  for(int device=0; device<totalDevs; device++){
    wakeUp(device);                  // Normal Operation (shutdown beenden)
    displayTest(device,500);
    writeRegister(device,0x09,0x00); // Decode: 00 = Einzeldots, kein decoding
    setIntensity(device, 6);
    writeRegister(device,0x0B,0x07); // alle Reihen werden angezeigt
    clearDevice(device);
  }
  delay(500);
}

void loop(){
  setRow(0,0,B11111000);
  delay(1000);
  setLED(0,0,5,0);
  delay(1000);
  setLED(0,0,7,0);
  delay(1000);
  displayChar(1, a);
}  


void clearDevice(int device){
  for(int row=0; row<=rowsPerDev; row++){  //alles ausschalten
    writeRegister(device,byte(row),0x00);
  }
}

void setIntensity(int device, int intensity){
  writeRegister(device,0x0A,intensity);
}

void displayTest(int device, int testTime){
  writeRegister(device,0x0F,0x01); //Display Test ein
  delay(testTime);
  writeRegister(device,0x0F,0x00); //Display Test aus
}

void wakeUp(int device){
  writeRegister(device,0x0C,0x01);
}

void shutDown(int device){
  writeRegister(device,0x0C,0x00);
}

void setRow(int device, byte row, byte data){
  dots[device][row] = data;
  writeRegister(device, row+1, data); 
}

void setLED(int device, byte row, byte col, bool on){
  byte reg = row; 
  byte dataToSend;
  byte mask;
  if(on){
    mask = (1<<(col));
    dots[device][row] |= mask;
  }
  else{
    mask = ~(1<<(col));
    dots[device][row] &= mask;
  }
  dataToSend = dots[device][row];
  writeRegister(device, reg+1, dataToSend);
}

void displayChar(int device, byte *character){
  for(int i=0; i<=7; i++){
    dots[device][i] = character[i];
    writeRegister(device,i+1,character[i]);
  }
}


void writeRegister(int device, byte reg, byte data){
  digitalWrite(slaveSelectPin, LOW);
  for(int i=0; i<(totalDevs-device); i++){  
    SPI.transfer(0x00);
    SPI.transfer(0x00);
  }
  SPI.transfer(reg);
  SPI.transfer(data);
  for(int i=0; i<device; i++){
    SPI.transfer(0x00);
    SPI.transfer(0x00);
  }
  digitalWrite(slaveSelectPin, HIGH);
}

 

Zu guter Letzt…

…noch der Banner Sketch auf Basis der Byte Arrays. Hier habe ich als Denkmodell ein virtuelles Display verwendet, welches das physikalische Display quasi von rechts nach links durchwandert (siehe Schema unten). Ein bisschen wie ein Filmstreifen, der im Projektor hinter dem Objektiv entlang läuft.

Grau: Virtuelles Display, Blau: physikalisch vorhandenes Display
Grau: Virtuelles Display, Blau: physikalisch vorhandenes Display

Ich habe den Sketch so verfasst, dass ihr ihn leicht auf andere Displaygrößen anpassen könnt. Ich habe ihn z.B. auf zwei hintereinandergeschalteten 8x8x4 Matrix Display laufen lassen (dazu musste lediglich die numberOfPhysicalDisplays von vier auf acht geändert werden:

Anwendung auf ein 2x8x8x4 Display
Anwendung auf ein 2x8x8x4 Display

Der Sketch ist recht kompakt, aber einigermaßen schwer „verdaulich“. Ich habe versucht, ihn durch Kommentare halbwegs verständlich zu gestalten. Viel Spaß beim Nachvollziehen meiner Logik!

#include "LedControl.h"

const unsigned int delayTime = 150;
const int numberOfCharacters = 7;   // = A,r,d,u,i,n,o
const int numberOfPhysicalDisplays = 4;  // 4 Matrix Displays
const int displayWidth = 8;  // 8x8 Format
const int displayHeight = 8;
int numberOfSteps;            // Anzahl Verschiebeschritte
int numberOfVirtualDisplays;  // Anzahl virtuelle Displays 

LedControl lc884=LedControl(12,11,10,numberOfPhysicalDisplays);

byte banner[7][8]={{B00000000,B01100000,B10010000,B10010000,B10010000,B11110000,B10010000,B10010000},
                   {B00000000,B00000000,B00000000,B10100000,B11010000,B10000000,B10000000,B10000000},
                   {B00000000,B00010000,B00010000,B00010000,B01110000,B10010000,B10010000,B01110000},
                   {B00000000,B00000000,B00000000,B10010000,B10010000,B10010000,B10010000,B01110000},
                   {B00000000,B00000000,B01000000,B00000000,B01000000,B01000000,B01000000,B01000000},
                   {B00000000,B00000000,B00000000,B10100000,B11010000,B10010000,B10010000,B10010000},
                   {B00000000,B00000000,B00000000,B01100000,B10010000,B10010000,B10010000,B01100000}};

void setup(){
  numberOfVirtualDisplays = numberOfPhysicalDisplays + numberOfCharacters;  // das virtuelle Gesamtdisplay besteht aus 4 leeren Displays + Anzahl der Zeichen
  numberOfSteps = numberOfVirtualDisplays * displayWidth;
  for(int i=0;i<numberOfPhysicalDisplays; i++){
    lc884.shutdown(i,false);
    lc884.setIntensity(i,8);
    lc884.clearDisplay(i);
  }
}

void loop(){
  for(int i=0; i<= numberOfSteps; i++){
    calcVisibleBannerPart(i);
  }
}

void calcVisibleBannerPart(int step){
  int currentFirstVirtualDisplay = step/displayWidth; // Erstes virtuelles Display, dass auf dem physikalischen Display abgebildet wird
  int bitPosition = step % displayWidth; // Bit-Position, an der die virtuellen Displays auf zwei benachbarte physikalische Displays verteilt werden
  byte matrixToDisplay[8]; // abzubildende Matrix

  for(int currentPhysicalDisplay=0; currentPhysicalDisplay<numberOfPhysicalDisplays; currentPhysicalDisplay++){ // Gehe die physikalischen Displays durch

    if( (currentFirstVirtualDisplay + currentPhysicalDisplay) == (numberOfPhysicalDisplays - 1)){
      for(int i=0; i < displayHeight; i++){
         matrixToDisplay[i] = banner[0][i] >> (8 - bitPosition);
      }
      displayMatrix(matrixToDisplay,(numberOfPhysicalDisplays-1 - currentPhysicalDisplay));  // mein 0tes Display ist rechts!
    }


    if( ((currentFirstVirtualDisplay + currentPhysicalDisplay) > (numberOfPhysicalDisplays - 1)) && (currentFirstVirtualDisplay + currentPhysicalDisplay) < (numberOfCharacters + numberOfPhysicalDisplays - 1) ){
      for(int i=0; i < displayHeight; i++){
         matrixToDisplay[i] = (((banner[currentFirstVirtualDisplay + currentPhysicalDisplay - numberOfPhysicalDisplays][i]) << bitPosition) + ((banner[currentFirstVirtualDisplay + currentPhysicalDisplay - numberOfPhysicalDisplays+1][i]) >> (8- bitPosition))) ;
      }
      displayMatrix(matrixToDisplay,(numberOfPhysicalDisplays-1 - currentPhysicalDisplay));
    }
    
    
    if( (currentFirstVirtualDisplay + currentPhysicalDisplay) == (numberOfCharacters + numberOfPhysicalDisplays-1)){
      for(int i=0; i < displayHeight; i++){
         matrixToDisplay[i] = banner[numberOfCharacters-1][i] << bitPosition;
      }
      displayMatrix(matrixToDisplay,(numberOfPhysicalDisplays-1 - currentPhysicalDisplay));
    }
  }
  delay(delayTime);
}
  
void displayMatrix(byte *matrix, int matrixNumber){
  for(int i=0; i<=7;i++){
    lc884.setRow(matrixNumber,i,matrix[i]);
  }
}

 

Anhang: Ansteuerung eines zweifarbigen TA6932 basierten Displays

Es gibt auch zweifarbige Displays mit Dots, die rot oder grün leuchten können. Diese werden intern über einen TA6932 Chip gesteuert. Eine gleichnamige Bibliothek gibt es hier auf GitHub oder ihr installiert sie über die Arduino Bibliotheksverwaltung.

Zweifarbiges Matrix-Display

Mit der Bibliothek ist die Ansteuerung sehr einfach. Mit displayCache(row) = value legt ihr fest, welche Dots einer Reihe (row) leuchten sollen. Die roten Dots sind als Reihen 0 bis 7 definiert. Die grünen Dots sprecht ihr als Reihen 8 bis 15 an. Das sollte durch den folgenden Sketch klarer werden.

#include <TA6932.h>

#define PIN_TA6932_STB   8
#define PIN_TA6932_CLK   9
#define PIN_TA6932_DIN  10

TA6932 tm(PIN_TA6932_STB, PIN_TA6932_CLK, PIN_TA6932_DIN);

void setup() {
    tm.begin();
  
    tm.displayCache[0] = 0b00000001; // red dot at 0/0
    tm.updateDisplay();
    delay(2000);
    tm.displayCache[0] = 0b00000011; // red dots at 0/0 and 1/0
    tm.updateDisplay();
    delay(2000);
    tm.displayCache[0] = 0b00011011; // red dots at 0/0,1/0,3/0,4/0 
    tm.updateDisplay();
    delay(2000);
    tm.displayCache[1] = 0b00000001; // red dot at 0/1;
    tm.updateDisplay();
    delay(2000);
    tm.displayCache[1] = 0b11110000; // red dots at 4/1,5/1,6/1,7/1
    tm.updateDisplay();
    delay(2000);
    tm.displayCache[3] = 0b11110000; // red dots at 4/3,5/3,6/3,7/3
    tm.updateDisplay();
    delay(2000);
    tm.displayCache[11] = 0b11110000; // green dots at 4/3,5/3,6/3,7/3 (in addition!)
    tm.updateDisplay();
    delay(2000);
    tm.displayCache[3] = 0b01010101; // red dots at 6/3,4/3,2/3,0/3
    tm.displayCache[11] = 0b10101010; // green dots at 7/3,5/3,3/3,1/3
    tm.updateDisplay();
    delay(2000);

    byte value = 0b10101010;
    for(int i=0; i<8; i++){
      tm.displayCache[i] = value;
      value = ~value; // inverts all bits
      tm.displayCache[i+8] = value; 
    }
    tm.updateDisplay();
}

void loop() {
}

 

44 thoughts on “LED Matrix Display ansteuern

  1. Hallo Herr Ewald,

    Ich habe mir das Programm (unter der Überschrift größere Banner laufen lassen) heruntergeladen und es funktioniert alles super ! Danke für das Programm. Allerdings würde Ich gerne zufällig zwischen drei Texten wählen. Haben Sie Vielleicht eine Idee wie man das umsetzen könnte ?

    1. Hallo Herr Hilt,

      im Prinzip kein Problem aber eine Menge Fleißarbeit. Der Sketch im Beitrag erzeugt zunächst ein Array bannerPart[Teil][Zeile]. Wenn Sie so fortfahren wollen, wie ich es begonnen habe, dann müssten Sie das Array zu einem dreidimensionalen Array erweitern, also bannerPart[Nummer][Teil][Zeile] mit Nummer = 0 bis 2. Da die Arrays ziemlich groß sind, würde ich hier nicht so verfahren, dass bannerPart aus dem Array banner erzeugt wird, sondern das Array bannerPart direkt eingeben.
      Dann würde ich in die Hauptschleife (loop) eine Zufallsfunktion einbauen:
      int bannerNummer=random(3);
      und dann calcCurrentBanner mit der Zufallszahl aufrufen, also:
      calcCurrentBanner(bannerNummer).
      Die Funktion muss natürlich entsprechend erweitert werden,
      void calcCurrentNumber(int num){……}
      Und dann müssten sie nur noch überall in der Funktion bannerPart[x][j] durch bannerPart[num][x][j] ersetzen und dann sollte es funktionieren. Alternativ könnten sie auch den Sketch unter „Zu guter Letzt“ modifizieren, das ist aber etwas komplexer.
      Viel Glück!
      VG, Wolfgang Ewald

      1. Hallo Herr Ewald,

        Danke für ihre Hilfe !! Es hat alles super geklappt allerdings bringt mir das Arduino Programm eine Fehlermeldung siehe Bild

        void loop() {
        int bannerNummer=random(3);
        calcCurrentBanner(bannerNummer);
        delay(500);
        }

        void calcCurrentNumber(int num){
        unsigned long currentBanner[8];
        for(int i=32; i>=0; i–){
        for(int j=0; j>i;
        }
        displayBanner(currentBanner);
        delay(delayTime);
        }
        for(int i=1; i<=32; i++){
        for(int j=0; j<8; j++){
        currentBanner[j] = ((bannerPart[num][0][j])<>(32-i));
        }
        displayBanner(currentBanner);
        delay(delayTime);
        }
        for(int i=1; i<=24; i++){
        for(int j=0; j<8; j++){
        currentBanner[j] = (bannerPart[num][1][j])<<i;
        }
        displayBanner(currentBanner);
        delay(delayTime);
        }
        }
        Viele Grüße, Marco Hilt

          1. Hallo Herr Ewald,

            Vielen Dank für ihre Hilfe . Es funktioniert alles super außer, dass an der 4. Stelle auf der LED-Matrix immer der gleiche Buchstabe ausgeben wird. Ich habe einiges schon ausprobiert aber bisher noch keine Lösung gefunden.

            Viele Grüße,
            Marco Hilt

            1. Hallo,

              bin froh, dass das der einzige Fehler ist. Ist eine gewisse Herausforderung einen Sketch zu schreiben, wenn man nicht parallel testet. Den Fehler habe ich gefunden und eine neue Version ist per e-mail unterwegs.

              VG, Wolfgang Ewald

  2. Hallo Wolle,
    erstmal vielen Dank für die super Erklärung!!!

    Ich habe hier ein „Bicolor dot matrix display 8×8“. Da sind 64 rote und 64 grüne LEDs drin verbaut (sieht aber erstmal genauso aus wie Dein Matrix Display, nur hat es halt 24 Pins: 8 Kathoden für die roten LEDs, 8 Kathoden für die grünen LEDs, 8 gemeinsame Anoden, also COMMON ANODE).

    In meiner unendlichen Naivität habe ich jetzt 2 Tage lang versucht die roten und grünen LED Matrizen mit 2 hintereinander geschalteten MAX7219 anzusteuern (ähnlich wie in diesem Schaltbild: http://wayoda.github.io/LedControl/images/MAX72XX_Schematic.jpg)

    Anders als in dem Schaltbild, wo es zwei komplett unabhängige Displays sind, hängen in dem Bicolor Dot Matrix Display die roten 8×8 und die grünen 8×8 LEDS aber über die Common Anode zusammen. D.h. wenn beide MAX7219 jeweils „ihre“ 8×8 Matrix multiplexen kommen sie sich gegenseitig ins „Gehege“ (und man bekommt ein wildes Geflackere aus beiden).

    Etwas Internetrecherche ergab, dass die Lösung wohl darin besteht abwechselnd einen der beiden MAX7219 mit shutdown schlafen zu legen (und dann schnell zwischen den beiden MAX7129 hin und her zu schalten).

    Leider geht das aber anscheinend nur mit COMMON KATHODE. Bei meinen COMMON ANODE Displays funktioniert das nicht, da ein shutdown eines MAX7129 bewirkt, dass dieser die (gemeinsame!) Anode auf GND legt und damit der andere MAX7129 auch nichts mehr multiplexen kann.

    Was ist da die Lösung? Zusatzelektronik, damit der ausgeschaltete Max7129 von der common Anode getrennt wird? Wie müsste die aussehen? … oder gleich auf die MAX7129 verzichten und selbst was zum Multiplexen bauen (mit Shiftregistern etc.)? Oder gibts noch andere Möglichkeiten? Bin da komplett ratlos und im Netz gibts auch nichts…

    Das schreit förmlich nach einer Fortsetzung Deines Artikels 🙂 … die Bicolor Dot Matrix Displays sind sehr weit verbreitet und billig. Leider gibts im Internet aber nur Tutorials für die Single Color 8×8 Matrix Displays.

    Viele Grüße, Christian

    1. Hallo Christian,
      ich muss mich da auch erst einmal wieder reindenken. Und das gelingt mir am besten, wenn ich so ein Teil vor mir liegen habe und herumprobiere. Wo hast du deins gekauft? Ich habe ein bisschen gesucht und nur diese Quelle gefunden:
      https://www.ebay.de/itm/151189255660?hash=item233394f5ec:g:M7QAAOxydlFSrNWr
      Das Display selbst ist günstig, aber fast 25 Euro Versandkosten aus den USA. Auch bei Ali Express habe ich nichts gefunden.
      VG, WOlfgang

      1. Hallo Wolfgang,
        Danke für die Antwort. Ich hab vor einiger Zeit mal 10 Stück bei Aliexpress gekauft (ca. 8-12 EUR inkl. Versand) … Kannst gerne eins davon haben!

        Ich habe jetzt auf die Schnelle z.B. diese hier gefunden: https://www.aliexpress.com/item/1005003133257404.html (24 bits redgreen, common anode, USD 5,50 inkl. Versand für 5 Stück)

        Wobei noch interessanter ist das hier (das gabs damals nicht):
        https://www.aliexpress.com/item/1005001835762802.html (allerdings 14 USD für 1 Stück)
        Hier wird die Bicolor Matrix mit dem Spezial-Chip TA6932 angesteuert. Den gibt’s auch bei Aliexpress für 1-2 USD. Leider hab ich aber nur chinesische Datasheets gefunden. Vielleicht probier ich das einfach mal aus und lass mir den TA6932 schicken.

        Bis dahin versuch ich es mit einem Shift-Register 74HC595, so wie hier beschrieben: https://www.instructables.com/Multiplexing-with-Arduino-and-the-74HC595/

        Ich kann Dir gern kostenlos eins meiner Bicolor Matrix-Displays schicken, wenn Du auch mitspielen willst 🙂 An die Adresse im Impressum?

        LG, Christian

        1. Hallo Christian,

          ich habe mir das erstgenannte rot-grüne Teil bestellt – vielen Dank für den Link! In ca. drei Wochen soll es ankommen. Danke für das Angebot, mir eines von deinen zu senden. Aber wahrscheinlich komme ich vor dem Eintreffen der Bestellung auch nicht dazu. Ich nehme mir immer zu viel vor. Der Tag müsste 48 h haben!

          Auch das andere Display mit dem TA6932 habe ich bestellt – einfach mal probieren, ein kleines Abenteuer. Bin mal gespannt.

          VG, Wolfgang

          1. Wow, cool! Lass mich wissen, wie’s ausgeht. Ich probiere es gerade mit Schieberegistern und händischem Multiplexen (ohne MAX7219).
            Und den TA6932 habe ich auch bestellt (nur den IC … nicht das Display). Bin mal gespannt, ob ich aus dem chinesischen Datasheet schlau werde. Auf dem Foto in Aliexpress sind auf der Platine noch ein paar andere Komponenten drauf. Vielleicht kannst Du, sobald Du das Display hast, rausbekommen, wie der TA6932 verwendet werden muss.
            Bis dahin!
            Grüße
            Christian

            1. Hallo Christian,

              ich hab’s! Der TA6932 ist im Prinzip ein Schieberegister. Man kann 16 Bytes hineinschieben. Das erste Byte ist Reihe 0, rot. Das zweite Byte ist Reihe 1, rot, usw.
              Das achte Byte ist dann wieder Reihe 0, aber grün. Es gibt eine Bibliothek in der Arduino IDE namens TA6932, damit ist das ganz einfach. Nimm den Demosketch und ändere ihn ab. Z.B:
              tm.displayCache[3] = 0b00001111;
              tm.displayCache[11] = 0b11110000;
              tm.updateDisplay();
              Das lässt die ersten vier Dots in Reihe 3 grün und die letzten vier Dots in Reihe 3 rot erscheinen. Ich schicke dir mal einen Sketch per mail.
              Viel Spaß! VG, Wolfgang

              1. Hallo Wolfgang,

                super! Du hast ja die LED Matrix mit dem integrierten TA6932 von Aliexpress. Ich hab ja damals die nackte LED Matrix gekauft und seit vorgestern auch eine handvoll TA6932. Ich habe das chinesische Datasheet (https://datasheetspdf.com/pdf-file/789446/TitanMicro/TA6932/1) und die Beschreibung/Skizze von https://github.com/allenchak/TA6932.

                Meine Frage ist nun, auf der Platine, die Du bei Aliexpress gekauft hast, sieht man 4 weitere kleine Bauteile (Kondensatoren? Widerstände?). Weißt Du was das ist und mit welchen Pins die verbunden sind? Weder bei allenchak noch in dem Datasheet gibts ein Schaltbild, wo man die 4 Bauteile sehen würde.

                Grüße, Christian

                1. Es sind drei 10kOhm Pull-Up Widerstände für die Datenleitungen drauf. Wie bei I2C – ein Signal ist LOW. Dann gibt es einen 100 µF Elko und einen 0.1 µF Keramikkondensator, die sind parallel zwischen VCC und GND. Das würde ich mal so zusammenstecken. Dann würde ich die TA6932 Bibliothek nehmen und systematisch durchprobieren. Die Grid-Anschlüsse dürften für die Reihen zuständig sein und die Seg-Ausgänge für die Spalten. Je nachdem, wie das Display aufgebaut ist, wird, um eine LED zum Leuchten zu bringen, entweder ein Grid Pin auf Spannung gesetzt und der Segment Pin schaltet nach GND durch oder umgekehrt. Aber das kann man ja probieren. Einfach mal ein tm.displayCache(0) = 0b00000001; nehmen und gucken was an welchen Pins passiert. VG, Wolfgang

                  1. Hallo Wolfgang,
                    vielen Dank!! Ich hab meinen TA6932 auf einen Breadboard-Adapter gelötet und etwas mit der Verkabelung rumprobiert und jetzt funktionierts!! Sieht so aus: https://ibb.co/wBgnT9x
                    Als nächstes wird der Arduino Uno durch einen ESP8266 ersetzt und die Stromversorgung auf Lipo umgestellt, dann kommt das ganze auf ne Platine und bekommt ein kleines Gehäuse und dann meld‘ ich mich mit dem Endergebnis wieder 😉
                    Vielen Dank für Deine Hilfe!
                    LG, Christian

                    1. Naja, überhaupt mal den TA6932 entdeckt zu haben war schon ein Highlight. Die Bicolor-Displays waren damals als ich sie gekauft hatte alle „nackt“ (ohne Ansteuerungs-IC) und ich habe Tage damit verbracht die roten und die grünen LEDs jeweils mit einem MAX7219 anzusteuern. Was einzeln klappt, aber zusammen natürlich nicht, da sich 2 MAX7219 gleichzeitig an der gleichen Anode natürlich beim Multiplexen in die Quere kommen. Mit dem TA6932 und der Bibliothek ists jetzt ein Kinderspiel!

  3. Hallo,

    ich habe das Programm für die beiden Streifen in einen MEGA geladen (brauche einen sehr großen Speicher). Nach dem Start liefen beide Streifen parallel und nicht hintereinander.
    Wahrscheinlich lag das an der Zeile:

    const int numberOfPhysicalDisplays = 4 // 4 Matrix Displays

    Nach der Änderung auf:

    const int numberOfPhysicalDisplays = 8; // 4 Matrix Displays

    Dann war die Anzeige in Reihe.

    Das ist aber eigentlich nicht mein Problem. Ich möchte einzelne Punkte auf 4 Streifen setzen und wieder Löschen.
    Falls jemand mal ein Beispiel in der Schublade hat . . .

    M.f.G.

    Hans-Ulrich

    1. Hallo Ulrich, einzelne Punkte sind doch eigentlich nicht besonders schwierig. Mit setLed(display, row, col, true/false) schaltest du sie an oder aus. Oder habe ich das Problem noch nicht verstanden? Sonst müsstest du vielleicht nochmal genauer beschreiben was du vor hast. VG, Wolfgang

  4. Hallo Wolle,

    ich bin ein absoluter Anfänger welches dieses Thema betrifft; Deine Anleitung ist wirklich klasse und sehr gut verständlich.

    MfG

    Patrick

      1. Moin moin Wolfgang und Alle in der Runde.

        Ich habe ein paar BLAUE 4 in 1 8x 32 Max7219 (1088AB) Module in die Hände bekommen.

        Betreibe ein rotes 3 in 1 Modul, das an einem Arduino Nano mit BME-P280 hängt und mir Druck, Temper…… anzeigt.

        Nun habe ich das Rote mit dem Blauen 4 in 1 Modul ausgetauscht. Doch das Ergebnis ist nicht berauschend.

        Die Anzeige scrollt die Werte / Text nach links, wie es soll.
        Nicht Kopf stehen und nicht gespiegelt ;-))))

        Doch der Durchlauf ist irgend wie falsch,
        Als wenn er etwas verzögert dem Beginn auf der 1.’n auf der 2.’n auch von vorn anfäng und danach auf dem 3, auch und auf dem 4.

        Stecke ich die rote 4 in 1 an läuft alles wieder, Tadel los.

        Ich bin nicht der Spezi und wende mich nach erfolgloser Suche zwecks einer Lösung zur „Blauen“ LED-Matrix 8×8 mit Maxim 7219 (1088AB) an Dich und die Runde..

        Nun hoffe ich, das Ihr mir weiter helfen könnt.

        Mehr als den Sketch anfügen, der die rote DotMatrix einwand frei antreibt, kann ich leider nicht dazu beitragen.

        Grüße
        Paul Preuß

        Und das ist der (für die rote Matrix) funktionierende Sketch:

        //8×32 Dot Matrix Max7219 4 x 1088AS
        #include
        #include
        #include
        #include

        #define HARDWARE_TYPE MD_MAX72XX::FC16_HW

        #define DATA_PIN 11
        #define CLK_PIN 13
        #define CS_PIN 10
        #define MAX_DEVICES 4

        MD_Parola disp = MD_Parola(HARDWARE_TYPE, DATA_PIN, CLK_PIN, CS_PIN, MAX_DEVICES);

        Adafruit_BME280 bme;

        int temp, hum, pres; String tempString, humString, presString;

        void setup()
        {

        disp.begin(); bme.begin(0x76, &Wire);

        }

        void loop()
        {

        disp.displayClear();
        disp.displayText(„Temperatur“, PA_CENTER, 100, 100, PA_SCROLL_LEFT, PA_SCROLL_LEFT);
        while (!disp.displayAnimate());
        delay(500);
        for (int i = 1; i <= 1; i++) //i=1; i<=1; i++)

        temp = bme.readTemperature();
        tempString = " " + String(temp) + " 'C"; // "*C " oder "~C"
        disp.print(tempString);
        delay(4000);

        disp.displayClear();
        disp.displayText("Luftfeuchte", PA_LEFT, 100, 100, PA_SCROLL_LEFT, PA_SCROLL_LEFT);
        while (!disp.displayAnimate());
        delay(500);
        for (int i = 1; i <= 1; i++) //i=1; i<=1; i++)

        hum = bme.readHumidity();
        humString = " " + String(hum) + " %";
        disp.print(humString);
        delay(4000);

        disp.displayClear();
        disp.displayText("Luftdruck", PA_LEFT, 100, 100, PA_SCROLL_LEFT, PA_SCROLL_LEFT);
        while (!disp.displayAnimate());
        delay(500);
        for (int i = 1; i <= 1; i++) //i=1; i<=1; i++)
        {
        pres = bme.seaLevelForAltitude(45, bme.readPressure()) / 100;
        presString = "" + String(pres) + " hp";
        disp.print(presString);
        delay(4000);
        }
        }

        1. Bin noch einmal und noch einmal alles durch gegangen und antworte mir mal selbst;-)
          Den ersten Hinweis fand ich auf dem PCB unter einem 1088AB und den Hinweis auf die Lösung in der MD_MAX72xx.h

          Die Zeile:
          #define HARDWARE_TYPE MD_MAX72XX::FC16_HW habe ich deaktiviert
          und dafür
          #define HARDWARE_TYPE MD_MAX72XX::ICSTATION_HW gesetzt.

          Nun funzen die Blauenauch, wie sie sollen.

          Sorry an Alle die schon angesetzt haben, aber vielleicht interessiert oder hilftes es ja sogar Jemanden, der diese Module verwenden möchte.

          Grüße
          Paul

      2. Hallo Wolfgang,

        Ich soll eine Teststellung für Fundino One und die komplexen Module des dazugehörigen Sets anbauen. Wir verleihen die Sets und es soll bei Rückgabe eine Kontrolle auf Funktionsfähigkeit erfolgen. Außerdem soll es möglich sein, an der Testplatine selbst programmierte Sketche im Rahmen eines Projektes im ersten Semester zu testen. Das sind meist Anfänger ohne Programmierkenntnisse. Für diesen Teil habe ich mir ein Selbstbau-Kit ohne eingelöteter Matrix und dem MAX7219 bestellt.

        Bis auf die Ansteuerung der 8×8- Matrix habe ich alles fertig. In einem passiven Testmodus, der über einen Schalter die dazu gehörigen Bustreiber aktiviert, werden die Anzeigen mit einem Spannungsregler (ohne Arduino) an einem USB- Eingang betrieben.
        Es werden da immer alle Segmente oder sollen die 64 LEDs der Matrix gleichzeitig angesteuert werden.

        Für die Ansteuerung der 8×8- Matrix mit dem Arduino wollte ich erst das Lauflicht von Dir nutzen,. Als das nicht die gewünschten Ergebnisse zeigte, wollte ich jeweils eine Reihe anschalten und wie bei einer Balkenanzeige die Spalten nacheinander aktivieren.

        Allerdings leuchtet bei aktivierter Reihe 0 die Reihe 4 dunkler mit, obwohl bei abgezogener Matrix am Arduino die Belegungen von r0 = D2 = High und c0 = D10 = LOW korrekt anliegen. Stecke ich die Anzeige an, wechselt r4 auf High.

        Das Verhalten ist mir nicht erklärlich, denn wenn ich direkt 5V und GND anlege, leuchtet nur die LED (0;0).

        Hast Du eine Erklärung?

        Mit freundlichen Grüßen
        Wolf

        1. Hallo Wolf, die kurze und ehrliche Antwort lautet schlicht: Nein, keine Ahnung! Merkwürdig, dass ausgerechnet diese eine Reihe mitleuchtet. Schwierig auf die Entfernung eine Lösung zu finden. Ich müsste die ganze Schaltung sehen und den Sketch dazu. Wenn du magst, kannst du mir mehr Informationen an Wolfgang. Ewald @wolles-elektronikkiste.de senden. Ansonsten probiere ich bei solchen Phänomenen einfach systematisch aus. Z.B. Was passiert bei Aktivierung anderer Reihen? Leuchtet dann auch immer Reihe 4 mit? Oder andere Reihen? Gibt es da Muster? VG, Wolfgang

          1. Hallo Wolfgang,

            Danke für die schnelle Antwort! Es leuchten auch eine andere LED mit, wenn es ich beispielsweise die r4 zwangsweise auf GND lege. Ob es ein Exemplarfehler ist, teste ich am Dienstag . Software habe ich 1:1 von Dir kopiert, um das zu testen. Beschäftige mich erst seit 2 Tagen mit der Matrix.

            Für meinen Testplatz soll die Ansteuerung mit dem MAX7219 bzw. auch ohne Software erfolgen.Auf der Platine verbinde ich die Spalten mit GND und schalte vielleicht mit einem Drehschalter die Reihen einzeln zu, um nicht den Strombedarf zu hoch werden zu lassen. Ich dachte nur, dass Dir bei Deinen Tests so etwas vielleicht schon mal untergekommen ist.

            Falls ich doch noch die Ursache ermittle, melde ich mich noch einmal.

            Viele Güße
            Wolf

              1. Hallo Wolfgang,

                wie versprochen, meine Rückmeldung. Die LED- Matrix war defekt, da wahrscheinlich hei der Ausleihe von den Studenten etwas falsch gemacht wurde. Dummerweise funktionierte auch die 2. Matrix nicht, aber mit einem anderen Fehler. Jetzt läuft alles und natürlich auch das Lauflicht. Trotzdem nochmals Danke!

                Viele Grüße!
                Wolf

                1. Schön, das freut mich! Danke für die Rückmeldung. Es ist immer hilfreich zu erfahren, wenn ein Problem am Ende gelöst wurde.

  5. Hallo Wolfgang
    toller Beitrag- finde ich echt spitze!
    Funktioniert tadellos – leider habe ich dann ein Problem wenn ich zwei LED 8x8x4 hintereinander schalte beginnt die Laufschrift zu flackern. Habe alle Bauteile getauscht – selbes Problem wieder.
    Hast du ne Idee woran das liegen könnte?
    Verwednde keinen Uno sondern einen ESP8266…
    LG
    Ewald

    1. Merkwürdig, ich habe es ja auch mit 2x(8x8x4) probiert, da ging es ohne Flackern. Und eigentlich ist ein ESP8266 ja schneller als ein Arduino. Also sollte zumindest auf der Seite nichts haken. Vielleicht eine Frage der Stromversorgung? Wenn du die Schrift ganz langsam laufen lässt, flackert es dann auch? Oder wenn du mal die Helleigkeit herunterregelst?

  6. Hallo Wolle, es gibt auch Display mit 4 x 8x8x4 DOT. Diese sind übereinander angeordnet. Also Matrix 4 x 4 Stück in 8×8 DOT Ausführung. Wie könnte man dort die Ansteuerung realisieren? ich will auf diesem Display Symbole die ich vorher fest erstelle, zur Anzeige bringen. In Summe sind das dann 32 x 32 DOT. Die 4 fach Anzeigen sind fertig verdrahtet und montiert. Bei Amazon heißen die 32×32 16 in 1 DOT Matrix…

    1. Hallo Camillo, das Prinzip ist immer dasselbe. Am besten nimmst du dir kariertes Papier und malst dort die Zeichen in 32 x 32 Kästchen auf. Dann teilst du das in die 16 8×8 Displays auf. Viel Schreibarbeit.

      Wie die Displays angeordnet sind, also wo sich Display 0 und wo sich Display 31 befindet, musst du schlicht ausprobieren. Auch, wo in einzelnen der Displays der Punkt 0,0 bzw. 8,8 ist, musst du schlicht mal systematisch ausprobieren.

      1. Danke für deine Antwort. Prinzipell kann ich die einzelnen Bytes als DOT Grafik festlegen. Leider werden diese aber nicht richtig angezeit. Nur 8 Display. Danach wiederholt sich das Grafik Muster.
        Habe den Sketch „Display _ static _short “ von dir verwendet. Welche Konfiguration muss ich noch bezüglich der Anzahl der Elemente machen? Habe LedControl lc884=LedControl(12,11,10,4); in 12,11,10,16
        geändert sowie unsigned long banner[8] in 32. Danach die 32 Stück 8Bit Datenfelder (Arrays) fest definiert.Ich brauche dann nur noch die Hilfe zur Konfiguration der Schleife die abgearbeitet wird. Welchen Schleifenzähler muss ich wie ändern?

        Gruß Camillo

        1. Hallo, das ist ein bisschen schwer für mich aus der Ferne zu beurteilen. Ich kenne dein Display ja nicht. Zumindest werde es 16 Displays sein, die du ansteuern musst. Aber die können ja z.B. so organsiert sein:
          0-1-2-3
          4-5-6-7
          8-9-10-11
          12-13-14-15

          Oder so:
          0-4-8-12
          1-5-9-13
          2-6-10-14
          3-7-11-15

          Oder sonst irgendwie. Das kann ich ja nicht wissen. Deswegen schrieb ich, dass du erstmal systematisch herausbekommen solltest, wo welches Display ist.

          Wenn du Glück hat und ich vermute, das wird so sein, dann sind die Displays wie im ersten meiner Optionen angeordnet. Dann nimmst du deine 32 x 32 Zeichen und teilst sie in 4 8×32 banner auf. Nenne sie z.b. banner0, banner1, banner2, banner3.

          Und dann machst du 4 mal das, was im Sketch 884_display_static_short.ino steht. Der Zähler j ist die Matrix. D.h. für banner0 durchläufst du j=0 bis j=3, für banner1 j=4 bis j=7, usw.

          Du bis bei j=0 bis j=3 geblieben, also 4 Displays und hast versucht 32 Zeilen hineinzuschreiben. Aber nach 8 Zeilen ist Schluss. Die 9. Zeile gehört zu anderen Displays.

          Schwer zu erklären und wahrscheinlich schwer zu verstehen. Ich hoffe, du kannst mir ungefähr folgen…

  7. …es klappt hervorragen. Ich weiß nicht, wozu man dann noch eine Bibliothek benötigt.

    Noch mal vielen Dank für Deine Arbeit. Wenn meine Wetterstation fertig ist, schicke ich mal ein Foto. (Der Code ist bestimmt nicht so überwältigend)

    Gruß Harald!

  8. Hallo Wolfgang
    das hat mir sehr geholfen. Ich habe gerade einen ESP32 angeschlossen. Es geht wirklich super.

    Eine Frage habe ich aber noch, warum musste ich die PIN’s für SPI nicht definieren. Wurden die Standard-Pin’s der Bibliothek SPI.h genommen?

    SS habe ich auf PIN 5 gesetzt weil bei mir CS auf 5 war.

    1. Hallo Harald,

      wie hast du denn das LedControl Objekt erzeugt? Eigentlich übergibst du da die SPI Pins, also z.B.:

      LedControl lc884=LedControl(12,11,10,4);

      VG, Wolfgang

      1. Hallo Wolfgang,
        ich habe ja gar kein Objekt erzeugt, sondern Dein Beispiel ohne Bibliothek genommen. Da am ESP32 der PIN 5 SS ist, klappt es eigentlich sehr gut. Ich habe mit Variablen für die Zeichen erzeugt und schreibe mit der Funktion displayChar(int device, byte *character).

        Leider bekommen ich keine Anzeige, wenn ich die Funktion setLED(int device, byte row, byte col, bool on) nutzen will. Ich wollte damit eine Art Kurve für den Verlauf des Luftdruckes zeigen.

        In meinem Beispiel sieht der Befehl so aus:

        setLED(0, B0001000, B0001000, 1);

        Habe ich da einen Denkfehler?

        Vielen Dank und Gruß Harald!

        1. Ach so, wir reden also über den Sketch „884_display_MAX7219_without_library.ino“. Das war mir nicht so klar. Um auf die erste Frage zurückzukommen: wenn man SPI initialisiert und keine Pins übergibt, dann werden die vordefinierten SPI Pins genommen.

          Zur zweiten Frage: die setLED() Funktion spricht nur eine einzelne LEDs an. Z.B. würde setLED(0,3,6,1) die LED in der Reihe 3, Spalte 6 am Display 0 einschalten. Bei einem 8×8 Display gehen die LEDs von 0,0 bis 7,7. Wenn du B0001000, B0001000 übergibst, dann ist das die LED 8,8 – und die gibt es nicht!

          1. Ah, alles klar. Mein Fehler war, dass ich in bit gedacht haben und nicht in Byte!!

            Probiere es gleich aus!

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht.