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- 3 Steuerleitungen
- 4 74HC595
- 32 Vorwiderstände
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.
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.