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.
- Data: BCM 13 --> HC595 DS
- Store: BCM 19 --> HC595 ST-CP
- Shift: BCM 26 --> HC595 SH-CP
Die restlichen Pins an beiden HC595 werden wieder wie gewohnt verkabelt:
- VCC bekommt +3.3V (selbstredend)
- GND geht auf Masse (auch klar)
- MR (Master Reset) verbinden wir mit +3.3V. Hier wird ein HIGH erwartet als Normalzustand und ein LOW für einen Reset. Vergessen wir die Leitung, befindet sich der 595er im Dauer-Reset und tut rein gar nichts
- OE (Output Enable) verbinden wir mit Masse, dadurch erreichen wir ein definiertes LOW. Denn nur, wenn hier LOW anliegt, ist die Ausgabe aktiviert.
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:
- Funktionstest: alle LEDs der Reihe nach durchschalten
- eine kleine Warteanimation, wieder ist nur eine LED an
- ein 8x8 Pixel großes Bild per Multiplexing auf dem Display darstellen, möglichst flackerfrei
- einen beliebigen Text (oder auch eine Grafik) von rechts nach links durchscrollen lassen
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.