Eine 4-fach-7-Segment Anzeige (3641AS) mit dem Raspberry Pi ansteuern

In meinem letzten Projekt "Mit dem 74HC595 Steuerleitungen einsparen" haben wir ja schon eine 7-Segment-Anzeige mit einem HC595, drei Signal-Leitungen und acht Widerständen angesteuert. Wollen wir jetzt z. B. eine Uhr mit vier Stellen realisieren, dann bräuchten wir mit diesem Prinzip Ohne die HC595 bräuchten wir sogar 32 Steuerleitungen.

Das Bauteil 3641AS











Aber es gibt auch einen Baustein mit der Bezeichnung 3641AS, der schon 4 Sieben-Segment-Anzeigen nebeneinander integriert und mit nur 12 Anschlusspins daherkommt.



Die teilen sich folgendermaßen auf: 8 Pins für die Segmente A-G/P (rechts rot dargestellt).

Hier noch einmal zur Erinnerung die Nummerierung der Segment-LEDs.



... Und 4 gemeinsame Kathoden (rechts blau dargestellt), jeweils für das 1., 2., 3., und 4., Segment.

Verbindet man etwa GND mit Pin 12 (Minus 1.) und +3.3V (über Vorwiderstand) an Pin 3 (P, Dezimalpunkt), dann wird der erste Dezimalpunkt leuchten. Wechselt man die GND-Leitung dann auf Pin 9 (Minus 2.), dann erlischt der 1. Dezimalpunkt und der 2. leuchtet.

Multiplexing

Die Anschlüssel 1. bis 4. bestimmen also, welche Anzeige benutzt wird und die Segment-Leitungen, welches Zeichen auf dieser Anzeige dargestellt wird. Das spart natürlich Steuerleitungen, Pins (und uns auch LED-Vorwiderstände), hat aber einen großen Nachteil: es kann immer nur eine einzige der vier Anzeigen auf einmal leuchten.

Wollen wir alle vier benutzen, dann müssen wir erst etwas auf der ersten Anzeige darstellen, die ausschalten, dann die 2. Anzeige bedienen, ausschalten, 3. Anzeige, wieder aus und schließlich die 4. Anzeige und aus. Dieses Verfahren nennt sich "Multiplexing".

Wenn wir das schnell genug hintereinander machen, dann erkennt das "träge" menschliche Auge (oder ist es nicht eigentlich das Gehirn? - egal) nicht mehr, welche der Anzeige gerade leuchtet, sondern es nimmt wahr, dass alle vier zusammen leuchten, allerdings jedes für sich schwächer.

Dieses Video zeigt das eindrucksvoll. Zuerst werden die Anzeigen, jeweils mit 0. bis 3., langsam durchgeschaltet. Die Anzeigen erscheinen sehr hell. Bei der 2. Sequenz geschieht das schon schneller. Da die Kamera noch träger als das menschliche Auge ist, entsteht schon bei Pause der Eindruck, das zwei Ziffern gleichzeitig leuchten. Bei der 3. Sequenz sieht man nur noch ein Flackern und bei der 4. schließlich nur noch ein flimmern. Die 5. Sequenz ist dann ruhig und stabil, allerdings auch bedeutend schwächer leuchtend.

Danach folgen übrigens wechselnd die Uhrzeit, das Datum und die CPU-Temperatur des Raspis, damit unsere Schaltung auch eine halbwegs sinnvolle Aufgabe hat.



Ich habe wieder 220 Ω als Wert für die Vorwiderstände gewählt. Doch durch das Multiplexing ist die Anzeige jetzt fast ein bisschen kontrastarm. Da die LEDs durch das Multiplexing jetzt nicht mehr auf Dauerbetrieb laufen, also nicht ständig Strom durch sie fließt, darf man auch ein bisschen mehr durch sie durchschicken, ohne dass sie Schaden nehmen. Ich glaube, 100 Ω wäre jetzt ein guter Wert. Dann sollte man allerdings nicht in der Versuchung kommen, eine einzige Anzeige länger leuchten zu lassen.

Die Schaltung

Der HC595 steuert wie gehabt (siehe letztes Projekt) über Vorwiderstände die 8 Segmente der Anzeige an. Welche Anzeige gerade gemeint ist, wird über 4 zusätzliche Steuerleitungen (an BCM 26, 19, 13 und 6) geschaltet, von der jeweils eine auf GND geschaltet wird.


Ganz links GND, ganz rechts +3.3V, die mit den jeweiligen (+) und (-) - Schienen des Breadboards verbunden sind.

Dann neben dem linken GND die 4 Steuerleitungen für die Anzeigennr. (grün, gelb, gelb, rot) an BCM 26, 19, 13, 6.

In der Mitte wie gehabt unsere HC595-Ansteuerung (grün, lila, gelb) an BCM 10, 9, 11 für Data, Store und Shift.



HC595 und 4-fach-7-Segment-Anzeige sind ein bisschen zusammengerückt, damit man sie gut mit kurzen Kabeln verbinden kann.

Die Anschlusspins der 4-fach-Anezige liegen in der Mitte, also auf den Breaboard-Spalten 40-35 unten und 20-25 oben. Von den Segment-Anschlüssel führen jeweils Vorwiderstände mit 220 Ohm (evtl. geringer für mehr Helligkeit wählen) nach außen, um dort mit den Ausgänge Q0 bis Q7 des HC595 verbunden zu werden. Beim 595er die Anschlüsse für GND und 3.3V nicht vergessen anzuschließen.

An den entsprechenden Pins der Anzeige (12, 9, 8, 6) sind dann noch die Steuerleitungen für die Anzeigenwahl eingesteckt, die für eine aktive Anzeige auf LOW und für eine inaktive auf HIGH geschaltet werden.



Das Programm

Unser Programm soll zuerst zur Demonstration die einzelnen Anzeigen (mit 0 bis 3 nummeriert) nacheinander ansprechen und danach immer schneller werden, um den Multiplexing-Effekt zu verdeutlichen. Danach geht sie in dern Endlos-Modus, der Uhrzeit, Datum, und CPU-Temperatur anzeigt. Die Endlosschleife kann nur durch CTRL+C auf der Konsole unterbrochen werden. Geschieht das, zeigt das Programm noch kurz CIAO zum Abschied an, räumt auf und beendet sich dann.

Der Code ist wie immer mit vielen Kommentaren versehen. Im Anschluss werde ich dann noch auf Besonderheiten oder neue Befehle eingeben.

# -*- encoding: utf-8 -*- # sudo apt-get install screen # installiert screen, damit kann man sitzung auch nach SSH-Logoff weiterlaufen lassen # (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 import time # die anderen time Funktionen müssen wir über vollen Namen ansprechen import datetime from sys import exit # um das Programm ggf. vorzeitg zu beenden from subprocess import PIPE, Popen GPIO.setmode(GPIO.BCM) # die GPIO-Pins im BCM-Modus ansprechen pinData=10 # Steuerleitungen für den HC595 pinClockStore=9 pinClockShift=11 clockPulseLen=.000001 # als HighSpeed-Baustein verträgt der HC595 auch kurzer Impulse digits=[26,19,13,6] # Steuerleitung für die vier 7-Segment-Anzeigen GPIO.setup(pinData, GPIO.OUT) GPIO.setup(pinClockStore, GPIO.OUT) GPIO.setup(pinClockShift, GPIO.OUT) for digit in digits: GPIO.setup(digit, GPIO.OUT) # 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", "°":"ABGF"} 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 (digit, onSegs, dotState): # schaltet die als Buchstaben angegebenen Segmente ein for d in range(0,4): if d == digit: GPIO.output(digits[d], GPIO.LOW) else: GPIO.output(digits[d], GPIO.HIGH) # 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(digit, char, dotState): # das übergebene Zeichen auf der x. 7-Segment-Anzeige anzeigen # segmente für char suchen try: onSegs = chars[char] except KeyError: print "Das Zeichen " + char + " ist nicht definiert." return showSegs (digit, onSegs, dotState) def say(word, dur, dotsOn): # Vier Zeichen für vier Digits, dur = wieviele Sek. anzeigen word=word.upper() anz=int(dur / cycleTime) for w in range(0,anz): digit=0 for char in word: dotOn=(dotsOn.find(str(digit)))+1 show (digit,char, dotOn) sleep (.00001) show (digit," ", 0) digit +=1 allOff() def allOff(): for d in range(0,4): GPIO.output(digits[d], GPIO.HIGH) def measureCycleTime(): # Mittelwert bestimmen, wie lange show für 4 Segmente dauert startTime = time.time() for d in range (0,4): show (d," ",0) usedTimeForShow = (time.time() - startTime) return usedTimeForShow def getCpuTemp(): process = Popen(['vcgencmd', 'measure_temp'], stdout=PIPE) output, _error = process.communicate() return float(output[output.index('=') + 1:output.rindex("'")]) # --- Ende Funktionen --- Beginn Hauptprogramm ------------------------------------------- cycleTime=measureCycleTime() # wie lange dauert eine Darstellung der 4 Segmente? pausen=[.5,.1,.01,.005] for p in pausen: for i in range(0, int(.5/p) ): # jeweils 2 Sekunden for i in range(0,4): show (i,str(i),1) sleep (p) show (i," ",0) allOff() sleep (1) say ("0123",2,"0123") sleep (1) # Uhr try: while 1: ts = datetime.datetime.now() datum = ts.strftime("%d%m") zeit= ts.strftime("%H%M") for i in range (0,6): # Zeit anzeigen say(zeit,.5,"1") say(zeit,.5,"") say(datum,3,"13") # zwischendrin auch mal das Datum t=str(getCpuTemp()) # print (t) temp = t[0]+t[1]+t[3] + "°" say(temp,3,"1") # zwischendrin CPU-Temperatur anzeigen except KeyboardInterrupt: # wenn in der Konsole CTRL+C gedrückt, Schleife beenden allOff() say ("C1A0",1,"") allOff() GPIO.cleanup() # Programm sauber verlassen und Ressourcen wieder freigeben Bei den Imports sind datetime, die wir für Datum, Zeit und Zeitmessung benötigen und subprocess hinzugekommen, dass wir für die Abfrage der CPU-Temperatur brauchen.

Die Steuerleitungen verwalte ich im Array digits. Dem Dictionary chars für die Segmente habe ich "°":"ABGF" hinzugefügt, da mir ein Grad-Symbol fehlte.

Die Funktion showSegs (digit, onSegs, dotState) hat einen Parameter digit dazu bekommen, der angibt, welche der vier Anzeigen (nummeriert von links nach rechts mit 0 bis 3) angesprochen werden soll. Dazu wird die entsprechende Steuerleitung auf LOW (=GND) gesetzt: GPIO.output(digits[d], GPIO.LOW) Der Rest der Leitungen geht auf High. Ansonsten ist die Funktion unverändert zum Vorgänger aus dem letzten Projekt.

Die Funktion show(digit, char, dotState) braucht dann natürlich auch den Parameter digit, um zu wissen, auf welchem der vier Displays das Zeichen dargestellt werden soll.

Und say(word, dur, dotsOn) fasst die Funktionalität für alle vier Anzeigen zusammen. Übergeben werden jetzt 4 Zeichen, die auf die 4 Displays verteilt werden. dur gibt dabei an, wie lange word auf den Anzeigen bleiben soll, bevor sie wieder dunkel werden. Da das Clocking des HC595 nicht unwesentlich Zeit in Anspruch nimmt, wird vorher einmal im Hauptprogramm measureCycleTime() aufgerufen, dass die Zeit für eine Darstellung misst und zurückgibt. Damit können wir dann errechnen, wie oft die Anzeige-Schleife durchlaufen werden muss, um auf die angegebene Anzeigedauer dur zu kommen. In der Schleife werden einfach der Reihe nach die Anzeigesteuerleitungen aktiviert und dann ein Zeichen (auf die jeweilige Anzeige) ausgegeben. Das geschieht so schnell, dass kein Flimmern mehr auftritt, ein paar hundert mal pro Sekunde.

allOff() schaltet alle vier Anzeigen aus, indem es die entsprechenden Steuerleitungen auf High setzt. In measureCycleTime() wird ein Timestamp über time.time() in StartZeit gespeichert. Dann wird ein show()-Vorgang gestartet und die Zeit nocheinmal genommen. Der Unterschied, also die Laufzeit für show() wird dann zurückgegeben und für die Berechnung der Anzahl der Schleifendurchgänge in say(...) benutzt.

Um die CPU-Temperatur zu erhalten, wird in der Funktion getCpuTemp() ein Sub-Prozess mit process = Popen(['vcgencmd', 'measure_temp'], stdout=PIPE) gestartet. Das ist so, als ob man vcgencmd measure_temp in der Konsole eingibt. Das Ergebnis, dass in Form temp=37.9'C daherkommt nehmen wir mit output, _error = process.communicate() entgegen. Dann schneiden wird alles nach dem = bis hin zum ' aus, konvertieren es in eine Fließkommazahl (dann also 37.9) und geben den Wert zurück.

Im Hauptprogramm wird als erstes mit cycleTime=measureCycleTime() die Zeit gemessen, die ein Show-Zyklus braucht und dies in der globalen Variablen cycleTime gespeichert. Darauf wird in say(...) Bezug genommen. Danach werden die Anzeige einzeln angesteuert mit den Pausenwerten von einer halben Sekunde bis zu einer zweihunderstel Sekunde, um das Multiplexing-Flackern und die abfallende Helligkeit zu demonstrieren. Dann wird einmal durch say ("0123",2,"0123") die normale Anzeige demonstiert.

Danach geht es in die Uhr-Endlosschleife (while 1): Es wird bei jeder Runde aktuell Zeit und Datum geholt. Dann wird die für 6 Sekunden die Zeit angezeigt, und zwar abwechseln alle halbe Sekunden mit und dann wieder ohne Punkt. Das ergibt eine schöne Sekunden-Animation und zeigt bem Benutzer an: das ist die Zeit, nicht das Datum, denn das hat zwei Punkte. Das folgt dann auch für 3 Sekunden. Und dann folgt abermals für 3 Sekunden die CPU-Temperatur.

Falls der Benutzer jetzt auf der Konsole STRG+C drückt, um das Programm zu beenden, wir die Exception KeyboardInterrupt an das Programm übermittelt und es landet in der Zeile nach except KeyboardInterrupt:. Dann werden alle Anzeigen ausgeschaltet, um einen definierten Anzeigezustand zu haben und dann kurz CIAO (geschrieben als "C1A0", um nur Großbuchstaben zu haben) angezeigt als Bestätigung an den User, dass sich das Progrann geordnet beendet hat. Anschließend wird die Endlosschleife verlassen. Und noch ein alloff() und ein GPIO.cleanup() ausgeführt, bevor das Programm sauber endet.



Falls man die Uhr auch nach Schließen des SSH-Fenster weiterlaufen lassen will: Mit sudo apt-get install screen kann man das Tool Screen installieren. Dann kann man durch Aufruf von screen eine Screen-Sitzung starten, welche man nachfolgend mit STRG+A D detachen / abkoppeln kann. Dann kann man die SSH-Sitzung beenden und das Programm läuft trotzdem noch weiter. Dann kann man den stromfressenden PC ausschalten und den Raspi alleine weitermachen lassen.

Will man dann später die Sitzung weiterführen, startet man wieder putty und gibt als ersten Befehl screen -r ein. Dann ist der alte Bildschirm wieder da und man kann das Programm immer noch mit STRG+C beenden.

Screen kann natürlich noch viel mehr. Mit man screen in der Konsole kann man sich satt lesen oder man ruft mit STRG+A ? die Kurzhilfe auf.