Mit dem 74HC595 Steuerleitungen einsparen

Wenn wir uns das letzte Projekt "eine 7-Segment Anzeige ansteuern" noch einmal anschauen, dann fällt uns auf, dass wir für die 8 LEDs der 7-Segment-Anzeige 8 Steuerleitungen gebraucht haben. Das ist eine ganze Menge. Wenn wir jetzt 4 Anzeigen ansteuern wollten, etwa für eine Digitaluhr, bräuchten wir auf diese Weise 32 Leitungen. Damit kann nicht einmal der Raspberry Pi aufwarten.

In diesem Projekt wollen wir der Steuerleitungsverschwendung Einhalt gebieten, doch wie?



Wir werden dazu die Eigenschaften des IC (steht für integrated circuit, integrierte Schaltung) mit der kryptischen Bezeichnung 74HC595 benutzen, vorzugsweise in der 74HC595N-Version, die in einem Gehäuse daher kommt, die sich gut in unser Breadboard stecken lässt. Es gibt auch noch andere Version, zum SMD-Löten oder als Mini-Variante.

Wollen wir 74HC595N einmal auseinandernehmen: Und was macht der 74HC595 nun? Kurz erklärt: er nimmt am Eingang eine Kette von 8 Bits (sprich: Ein/Aus-Zustände als 1 oder 0) seriell entgegen und schaltet danach die Ausgänge, für die ein Bit geschaltet ist, auf High. Um die Kette zu übertragen brauchen wir 3 Leitungen: Wir kommen also durch diesen seriell -> parallel-Trick mit 3 Leitungen statt mit 8 aus. Das ist doch prima. Das Prinzip sollte mit diesem Schaubild noch einmal deutlicher werden:


An Pin 14 werden die Bits 10101010 (höchstwertiges Bit zuerst) an DS angeliefert. Nach jedem Bit erfolgt ein Shift. Der 74HC595 speichert dadurch das Bit. Nachdem alle 8 Bits angeliefert sind, erfolgt ein Store. Und schon verteilt der HC595 die Bits auf die Ausgänge Q0 bis Q7, schaltet sie also auf High. Hier können wir jetzt über Vorwiderstände unsere 8 LEDs der Segmentanzeige anschließen.

Die Funktionalität, die der '595 abbildet heißt auf englisch "8-bit serial-in/serial or parallel-out shift register with output latches; 3-state", auf gut deutsch und etwas flapsig ausgedrückt würde ich sie als "auf einer Leitung rein, auf 8 Leitungen raus" bezeichnen. "8 Bit Shift Register" ist die kürzere Bezeichnung und meint damit, dass 8 bits nacheinander am Eingang "eingeschoben" ("shift") werden.

Damit die Schaltung funktioniert, müssen wir am '595 noch etwas mehr verkabeln: Die restlichen Pins haben folgende Bedeutung: Der 595 ist übrigens sehr empfindlich und erkennt auch extrem kurze Pulse. Dadurch ist folgendes Phänomen beobachtbar, wenn das Programm nicht läuft und die 3 Steuerleitungen nicht auf Output gesetzt sind und so für Interferenzen anfällig und ggf. undefinierte High/Low-Zustände aufweisen. Wenn ich dann mit dem Finger über die Kabel des Flachbandkabels streiche, die die Datenleitungen ausmachen, erscheint wirres Zeug auf der Segment-Anzeige. Sobald ich aber mit der anderen Hand die Masse des Raspi anfasse, z. B. an der USB-Buchse, dann hört der Spuk auch schon wieder auf, weil ich dann kein Potential mehr aufweise. Im Programmablauf (oder wenn man das GPIO.cleanup() weglässt) sind die Steuerleitungen immer in einem definierten Zustand und der HC595 arbeitet stabil.

Gegenüber dem letzten Projekt ist der HC595 in der Mitte hinzugekommen. Von der GPIO-Steckerleister geht die Daten- und die zwei Clock-Leitungen an die entsprechenden Pins des 595. An den 8 Ausgängen hängt jeweils ein Vorwiderstand, der zu den LEDs der Segmente der Anzeige führt. Insgesamt sieht die Schaltung jetzt komplizierter aus, aber wenn man bedenkt, dass das wertvollste die Leitungen am Pi sind, dann ist es den Aufwand wert, wenn man mal mehr als nur eine Segmentanzeige ansteuern muss.



Der GPIO-Port sieht jetzt wesentlich aufgeräumter aus.

Es sind jetzt nur noch 3 Steuerleitungen, GND und +3.3V belegt.





Der HC595 stellt das neue Zentrum der Schaltung dar.

An ihm laufen alle Leitungen zusammen.

Da muss man schon geschickt stecken, damit sich die ganzen Widerstände nicht gegenseitig im Weg stehen.
















Der 7-Segment-Anzeige ist es egal, ob sie direkt oder über einen HC595 angesteuert wird.

Der Verkabeleungsaufwand bleibt hier gleich.

Hauptsache, die Vorwiderstände passen, dann fühlt sie sich wohl.








Da wir jetzt nicht mehr die LEDs der Anzeige direkt ansteuern, sondern den HC595 muss der Code natürlich auch angepasst werden. Wie immer: zuerst der Code, dann zusätzliche Erklärungen... # -*- 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=10 pinClockStore=9 pinClockShift=11 GPIO.setup(pinData, GPIO.OUT) GPIO.setup(pinClockStore, GPIO.OUT) GPIO.setup(pinClockShift, GPIO.OUT) clockPulseLen=.00001 # als HighSpeed-Baustein verträgt der HC595 auch kurzer Impulse # Dictionary: welche Ziffer/Buchstabe -> welche Segmente sind an # Dictionary: welche Ziffer/Buchstabe -> welche Segmente sind an chars = {" ":"", "0":"ABCDEF", "1":"BC", "2":"ABDEG", "3":"ABCDG", "4":"BCFG", "5":"ACDFG", "6":"ACDEFG", "7":"ABC", "8":"ABCDEFG", "9":"ABCDFG", "A":"ABCEFG", "B":"CDEFG", "C":"ADEF", "D":"BCDEG", "E":"ADEFG", "F":"AEFG", "G":"ACDEF", "H":"BCEFG", "I":"AE", "J":"ACD", "K":"ACEFG", "L":"DEF", "M":"ACEG", "N":"ABCEF", "O":"CDEG", "P":"ABEFG", "Q":"ABCFG", "R":"EG", "S":"ACDF", "T":"DEFG", "U":"BCDEF", "V":"BEFG", "W":"BDFG", "X":"CE", "Y":"BCDFG", "Z":"ABDE"} 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 showSegs (onSegs, dotState): # schaltet die als Buchstaben angegebenen Segmente ein # angeschaltete Segmente vom höchstwertigen Bit abwärts pulsen for seg in "GFEDCBA": if onSegs.find(seg) > -1: GPIO.output(pinData, GPIO.HIGH) else: GPIO.output(pinData, GPIO.LOW) shiftTick() # nächstes Bit # Der Punkt liegt auf Q0 des HC595, darum den immer zuerst pulsen if dotState: GPIO.output(pinData, GPIO.HIGH) else: GPIO.output(pinData, GPIO.LOW) shiftTick() # nächstes Bit storeTick() # alle Bits fertig. Speichern def show(char, dotState): # das übergebene Zeichen auf der 7-Segment-Anzeige anzeigen # segmente für char suchen try: onSegs = chars[char] except KeyError: print "Das Zeichen " + char + " ist nicht definiert." return showSegs (onSegs, dotState) def say(word): # mehrere Zeichen wiedergeben for char in word.upper(): show (char,0) sleep (.5) show (" ",0) # alles wieder aus sleep (.1) def test1(): # Test, ob alle Segmente funktionieren showSegs ("ABCDEFG",1) sleep (1) show (" ",0) def test2(): # alle Segmente der Reihe nach testen - Reihenfolge richtig? A-G und DP for segment in "ABCDEFG": showSegs (segment,0) sleep (.5) show (" ",1) sleep (1) show (" ",0) def waitAnim(wdhl): # Round and round it goes.. Wdhl = wie oft for wd in range(0,wdhl): for segment in "ABCDEF": showSegs (segment,0) sleep (.1) show (" ",0) def waitAnim2(secs): # lässt den Punkt im Sekundentakt blinken for i in range(0,secs): show (" ",1) sleep (.5) show (" ",0) sleep (.5) def testZiffern(): for ziffer in "0123456789 ": show (ziffer,1) sleep (.5) show (" ",0) def testBuchstaben(): for bst in "ABCDEFGHIJKLMNOPQRSTUVWXYZ ": show (bst,0) sleep (.5) # --- Ende Funktionen --- Beginn Hauptprogramm ------------------------------------------- test1() sleep (1) test2() sleep (1) waitAnim(5) sleep (1) waitAnim2(5) sleep (1) testZiffern() sleep (1) testBuchstaben() sleep (1) say ("Hallo Welt ") sleep (1) GPIO.cleanup() # Programm sauber verlassen und Ressourcen wieder freigeben Statt der 8 direkten Steuerleitungen gibt es jetzt 3: pinData, pinClockStore und pinClockShift. Die sind für das zuständig, was der Name schon sagt. Wie genau die anzusprechen werden später in der entsprechenden Funktion. clockPulseLen gibt die Länge eines Pulses für das Timing (Clocking an). Hier verträgt der HC595 auch sehr kleine Werte. Ich habe fast das Gefühl, kleiner als das der Raspi den Befehl sleep() ausführen kann.

Die Funktionen storeTick() und shiftTick() sind neu. Sie machen nichts anderes, als einen High-Puls und dann einen Low-Pulse der Länge clockPulseLen auf die benannte Leitung zu schicken. _ | |_ Auch die Funktion showSegs (onSegs, dotState) ist neu. Sie ist die Kernfunktion und schaltet die als Buchstaben angegebenen Segmente ein und ggf. auch den Dezimalpunkt (dotState=1). Dazu wird in onSegs ein String übergeben mit den einzuschaltenden Segmenten.

Der HC595 erwartet das höchstwertige Bit zuerst, also das für Q7 bzw. das Segment G. Also wird in der Reihenfolge "GFEDCBA" abgefragt. Kommt der abgefragte Buchstabe im String vor, dann soll das Segment eingeschaltet werden und es wird auf der Datenleitung ein High gesetzt (aus entsprechen Low). Während sich die Datenleitung noch auf High befindet wird ein Puls auf der Shift-Leitung (shiftTick()) gesendet.

Das zuletzt zu sendende Bit ist das für den Dezimalpunkt, der Bit 0, also das niederwertigste Bit darstellt. Auch hier die Datenleitung auf High (Punkt an) oder Low (Punkt aus) und ein Shift gepulst (shiftTick()).

Dann sind alle Bits durch und es wird einmal auf der Store-Leitung (storeTick())gepulst als Zeichen, dass wir fertig sind und der HC595 nun das Zeichen darstellen soll.

Am Beispiel der Ziffer 7 wollen wir die Signale, die an den 595 geschickt werden, einmal durchgehen. Bei der 7 sind die drei Segment A, B und C an. Folgendes kommt beim HC595 an: ... G H F E D C B A ... _ _ _ _ _ _ _ _ _______| |_| |_| |_| |_| |_| |_| |_| |____________ Shift-Clock _ _ _ ___________________________| |_| |_| |____________ Data _ _______________________________________| |________ Store-Clock Das weiß der HC595 zu interpretieren und schaltet die entsprechenden Ausgänge auf High, sobald er das Store-Signal empfangen hat.

Der Rest der schon bekannten Funktionen wurde an das neue Verhalten angepasst. Es ist jetzt auch nicht mehr möglich, den Dezimalpunkt gesondert anzusprechen. Entweder man spendiert ihm doch noch eine extra Leitung, die dann direkt verbunden wird oder man behilft sich, indem man das bereits dargestellte Zeichen noch einmal per show(char, dotState) sendet, aber dabei den dotState entsprechend setzt.

Es gibt übrigens auch Module, die bereits vier 7-Segment-Anzeigen in sich vereinen. Diese haben nur 12 Anschluss-Pins. Wie man diese anspricht, im nächsten Projekt.