Mit zwei 74HC595 Schieberegistern über nur drei Leitungen eine 8x8-LED-Matrix ansteuern

Das Schieberegister 74HC595 hatten wir ja schon einmal verwendet, um Steuerleitungen einzusparen. Die 595er nehmen über einen Dateneingang Daten an und verteilen Sie auf bis zu 8 Ausgänge. Dazu sind noch zwei zusätzliche Steuerleitungen nötig. So braucht man nur 3 statt 8 Kabel.

In diesem Projekt lernen wir außerdem die 8x8 LED-Matrix 1088BS mit 16 Anschlüssen kennen. Diese 16 Anschlüsse werden wir mit zwei HC595 speisen, so dass wir für ein Bild auf der Matrix jeweils nur 16 Bit einschieben müssen.


Kurz angesprochen hatten wir auch schon, dass man die 595er kaskadieren kann, also miteinander verketten. Der erste 595er schiebt Bits, die beim wieder "herausfallen" über Q7' an einen zweiten 595er weiter, wenn man nmöchte. Dazu braucht man lediglich den Ausgang Q7' des Ersten an den Eingang DS des Nächsten anschließen. Die Clock-Leitungen schaltet man parallel.

So kann man mit zwei 595er (und immer noch drei Kabeln) 16 Bit verwalten, einfach indem man 16 Bit in den ersten hineinschiebt. Dieser schiebt automatisch nach dem 8. Bit alles weiter. Zum Schluss stehen im 595 Nr. 1 die letzten 8 Bits und im 595 Nr.2 die ersten 8Bits.

Man kann aber auch noch mehr 595er verketten, wenn man mehr Eingängen zu beschalten hat. Mit 10 595er kann man z. B. 80 Peripherie-Eingänge beschalten, braucht aber trotzdem immer noch lediglich 3 Kabel.

74HC595er sind Cent-Artikel und den Gewinn an frei gebliebenen, raren Ausgängen an Raspberry Pi und Arduino auf jeden Fall wert. Auch ist es übersichlicher in der Software, ein paar Bits zu verwalten (in diesem Fall 2 Bytes) als zahlreiche GPIO-Ports (in diesem Fall wären es 16).







Die 8x8-LED-Matrix 1088BS














Specifications: Number of LEDs per module: 64 LED diameter: 3 mm Led color: red Operating Voltage: 1.8 ~ 2.3Vcc Brightness: 6mcd to 20mA Wavelength: 660nm at 20mA Operating temperature: -30 ~ 70 ° C Connection strip (2 rows of 8 pins): 2.54mm Protective film PVC: 2µm Dimensions: 32 x 32 mm Weight: 2.7g Die LED-Matrix bietet 8 Zeilen zu je 8 LEDs, also ingesamt 64 rote LEDs mit einem Durchmesser von 3 mm und einem Verbrauch von 20 mA pro LED. Leuchten alle LEDs, so würde das einen Stromstärke von 1.28 Ampere ausmachen. Wir werden aber immer nur eine Zeile gleichzeitig leuchten lassen und bei Bildern multiplexen, was den Stromverbrauch überschaubar macht (ax. 160 mA).

Die Unterseite der Matrix hat ein kleines, erhabenes Dreieck, damit man weiß, wie rum man die Matrix anbauen muss. Und wer jetzt denkt, es wäre so einfach, dass die oberen 8 Pins für die Zeilen und die unteren 8 Pins für die Spalten sind, der irrt.


Wahrscheinlich weil die interne Verdrahtung so kürzer ist, herrscht bei der Zuordnung der Pins augenscheinliches Chaos. Welcher Pin welche Zeile bzw. Spalte anspricht, kann man durch probieren mit einem Multimeter (Wahl: Durchgangs-Summer) ausprobieren, indem man sich notiert, wann welche LED leuchtet (oft leuchten dabei auch gleich viele gleichzeitig) oder man sucht sich das Datenblatt für sein Matrix-Modell und dort den Schaltplan. Der gibt darüber Aufschluss, welcher Pin für welche Zeile (Row) bzw. Spalte (Col) zuständig ist.

Dabei kommt dann die rechts abgebildete Zuordnung heraus (Draufsicht beim Stecken im Breadboard). Ich habe der Einfachheit halber die Anschlüsse an die 8 Positionen der LEDs geschrieben, auch wenn sich die Pins in Wahrheit auf die Breite von nur 6 LEDs quetschen. Die blau beschrifteten Anschlüsse stehen für die Spalten (C) und die schwarz beschrifteten für die Zeilen (R).

Beim Multiplexing müssen wir später eine Zeile auf High setzen und dann alle betreffenden Spalten-LEDs in dieser Zeile auf Low, um durch diese Strom fließen zu lassen. Dann müssen die 8 Vorwiderstände an die blauen Spalten-Ausgänge, damit jede der 8 angesprochene Spalten-LEDs auf der Zeile seinen eigenen Widerstand hat.

Oder aber man spricht die Spalten auf High an und die betreffenden Zeilen-LEDs in dieser Spalte auf Low. Dann müssen die Vorwiderstände an die schwarzen Zeilen Ausgänge, damit jede Zeilen-LED ihren eigenen Vorwiderstand hat.

Schließt man die Vorwiederstände an die falsche Partie an, dann teilen sich alle Spalten- bzw. Zeilen-LED einen Widerstand und Zeilen (bzw. Spalten) mit mehr als eine LED leuchten weniger hell als solche mit nur einer. Das sieht unschön aus.

Ich habe mich für die zweite Variante entschieden, nämlich spaltenweise zu schreiben. Das hat den Vorteil, dass das Multiplexing von links nach rechts und nicht von oben nach unten erfolgt. Das sieht beim Scrolling von Texten von rechts nach links geschmeidiger aus - zumindest gefühlt. Als Vorwiderstand habe ich 94 Ω errechnet und 100 Ohm genommen. Dabei sind die LEDs auch bei Multiplexing noch hell genug.

Die Schaltung



Ich nutze die GPIO-Ports BCM 13, 19 und 26 für die Ansteuerung des 1. HC595. Mehr ist auch nicht nötig an GPIO-Pins, also natürlich +3.3V und Masse.





Die restlichen Pins an beiden HC595 werden wieder wie gewohnt verkabelt: Der zweite 595er nutzt dabei die Store und Shift-Leitung mit, wird also einfach parallel an die GPIO-Port angeschlossen. Während der 1. 595er sein Data vom GPIO-Port bekommt, bekommt der 2. 595er sein Data von 1. 595er und damit alles, was ihm dieser reinschiebt.


Da 16x geshiftet wird (also 16x ein Signal auf der Shift-Leitung gesendet) und dann erst ein Store-Signal gesendet wird, stehen dann jeweils 8 der 16 geshifteten Bits in jedem der 595er.

Von dem 1. (linken) 595er gehen jetzt die Leitungen zu den acht Row-Pins der LED-Matrix (über die Vorwiderstände), also Q0 an Zeile 1, Q1 an Zeile 2 etc. Der 2. (rechte) 595er übernimmt die Spalten, also geht hier Q0 an Spalte 1, Q1 an Spalte 2 usw.

Hier sollte man darauf achten, die richtige Spalten bzw. Zeilennummer nach obigen Schaltplan bzw. Anschlussplan zu erwischen, sonst geht später etwas durcheinander und die falsche LED (oder keine) leuchtet.

Damit sind alle Pins der Matrix versorgt und können jetzt ganz bequem über die Software angesprochen werden, indem diese jeweils 16 Bit in den 1. 595er einschiebt.

Fehlt nur noch die Software. Die soll folgendes können: Und so sieht das Ergebnis aus:




Der Source-Code

8x8x-Matrix.c (klicken, um diesen Abschnitt aufzuklappen)
# -*- encoding: utf-8 -*- # (C) 2018 by Oliver Kuhlemann # Bei Verwendung freue ich mich über Namensnennung, # Quellenangabe und Verlinkung # Quelle: http://cool-web.de/raspberry/ import RPi.GPIO as GPIO # Funktionen für die GPIO-Ansteuerung laden from time import sleep # damit müssen wir nur noch sleep() statt time.sleep schreiben from sys import exit # um das Programm ggf. vorzeitg zu beenden GPIO.setmode(GPIO.BCM) # die GPIO-Pins im BCM-Modus ansprechen PinData=13 PinClockStore=19 PinClockShift=26 GPIO.setup(PinData, GPIO.OUT) GPIO.setup(PinClockStore, GPIO.OUT) GPIO.setup(PinClockShift, GPIO.OUT) ClockPulseLen=.0000001 # als HighSpeed-Baustein verträgt der HC595 auch kürzere Impulse def delayMicroseconds(usecs): sleep (usecs/1000000); def delay(msecs): sleep (msecs/1000); def storeTick(): # einen Puls auf die ClockStore-Leitung schicken GPIO.output(PinClockStore, GPIO.HIGH) sleep (ClockPulseLen) GPIO.output(PinClockStore, GPIO.LOW) sleep (ClockPulseLen) def shiftTick(): # einen Puls auf die ClockShift-Leitung schicken GPIO.output(PinClockShift, GPIO.HIGH) sleep (ClockPulseLen) GPIO.output(PinClockShift, GPIO.LOW) sleep (ClockPulseLen) def clearDots(): # alle Punkte löschen for i in range(0,16): GPIO.output(PinData, GPIO.LOW) shiftTick() # nächstes Bit storeTick() # alle Bits fertig. Speichern def lightCol(col, colByte): # Vorwiderstände hängen an den Row-Ausgängen, sonst keine gleichmäßige Helligkeit # zuerst das rowByte einschieben for b in range (0,8): w = (colByte & 1) GPIO.output(PinData,w) # w = 0 | 1 shiftTick() # nächstes Bit colByte = colByte >> 1 # dann das colByte invertiert for b in range (0,8): w = (b == (7-col)) w^=1 # invertieren, weil wird müssen auf Low und damit zu GND durchschalten GPIO.output(PinData,w) # w = 0 | 1 shiftTick() # nächstes Bit storeTick() # alle Bits fertig. Speichern def lightDot(x, y): # eine einzelne LED leuchten lassen lightCol(x, 1<<(7-y)) def lightMatrix( rowArray, msecs): # eine 8x8 Matrix leuchten lassen colArray = [0, 0, 0, 0, 0, 0, 0, 0] # übergeben wurden 8 Zeilen, diese auf 8 Spalten ummünzen, weil wir spaltenorientiert sind for col in range (0,8): for row in range (0,8): colArray[col] += (rowArray[7-row] & 128)/128 * (1 << row) rowArray[7-row] = rowArray[7-row]<<1 # nun die Spalten anzeigen for i in range (0,msecs/7): for col in range (0,8): lightCol(col, colArray[col]) delayMicroseconds(50) clearDots() def scrollShow (colArray, verzoegerung): # einen Scroll-Frame für eine bestimme Zeit anzeigen for i in range(0,verzoegerung/7): for col in range (0,8): lightCol(col, colArray[col]) delayMicroseconds(500) clearDots() def scroll (text, cols, verzoegerung): # cols=Anzahl von Spalten gesamt - Scroller anzeigen colArray = [0, 0, 0, 0, 0, 0, 0, 0 ] #ersten Screen reinschieben for c in range (0,7): for i in range (0,7): colArray[i]=colArray[i+1] colByte=0 for row in range(0,8): if (text[row][c] == '#'): colByte += 1<<(7-row) colArray[7]=colByte scrollShow(colArray,verzoegerung) # gesamten Text durchscrollen for col in range ( 0,cols-8): for c in range (col,col+8): # 8 ZeilenBytes holen colByte=0 for row in range (0,8): if (text[row][c] == '#'): colByte += 1<<(7-row) colArray[c-col] = colByte scrollShow(colArray,verzoegerung) # letzten Screen auch noch rausschieben for c in range (0,8): for i in range (0,7): colArray[i]=colArray[i+1] colArray[7]=0 scrollShow(colArray,verzoegerung) # --- Ende Funktionen --- Beginn Hauptprogramm ------------------------------------------- # 1. 2. 3. 4. 5. 6. 7. 8. # # Anodes: 9, 14, 8, 12, 1, 7, 2, 5 ROW # Cathodes: 13, 3, 4, 10, 6, 11, 15, 16 COL # 74HC595 74HC595 # # .--- 7 6 5 4 3 2 1 0 <--- Shift F E D C B A 9 8 <---. Bits # `---------------------------------------------------------´ try: clearDots(); ###### die Dots der Reihe nach durchschalten ##### for i in range (0,64): x=i%8 y=i/8 lightDot(x,y) sleep (.025) clearDots(); delay (500); ###### kleine Warte-Animation ##### # 0 1 2 3 4 5 6 7 # . . # # # # . . 0 # . # . . . . # . 8 # # . . . . . . # 16 # # . . . . . . # 24 # # . . . . . . # 32 # # . . . . . . # 40 # . # . . . . # . 48 # . . # # # # . . 56 # dots = [2,3,4,5, 14, 23, 31, 39, 47, 54, 61,60,59,58, 49, 40, 32, 24, 16, 9] for w in range (0,10): for i in range (0,20): x=dots[i]%8 y=dots[i]/8 lightDot(x,y) sleep(.010) clearDots(); delay(500); ###### ein 8x8-Matrix-Bild anzeigen ##### pic = [0b01100110, 0b10011001, 0b10000001, 0b10000001, 0b10000001, 0b01000010, 0b00100100, 0b00011000] lightMatrix (pic,2000) clearDots() delay (500) ###### einen Scroll-Text durchlaufen lassen ##### scrollText = [ "###...##.##....####....####....####...#..............#...#...#####..####.......####....#####", ".#...#..#..#..#....#..#....#..#....#..#..............#...#...#......#...#......#...#...#....", ".#...#.....#..#.......#....#..#....#..#..............#...#...#......#...#......#....#..#....", ".#...#.....#..#.......#....#..#....#..#......#####...#...#...####...####.......#....#..####.", ".#...#.....#..#.......#....#..#....#..#..............#...#...#......#...#......#....#..#....", ".#....#...#...#.......#....#..#....#..#..............#.#.#...#......#...#......#....#..#....", ".#.....#.#....#....#..#....#..#....#..#..............##.##...#......#...#..##..#...#...#....", "###.....#......####....####....####...#####..........#...#...#####..####...##..####....#####" ] scroll (scrollText,92,100) delay (500); clearDots(); except KeyboardInterrupt: pass clearDots() GPIO.cleanup() # Programm sauber verlassen und Ressourcen wieder freigeben



Das Projekt habe ich übrigens im Anschluss auf den Arduino portiert und dort auf ein Prototype Shield gelötet.