AVR-Tutorial: 7-Segment-Anzeige
Die Ausgabe von Zahlenwerten auf ein Text-LCD ist sicherlich das Nonplusultra, aber manchmal liegen die Dinge sehr viel einfacher. Um beispielsweise eine Temperatur anzuzeigen ist ein LCD etwas Overkill. In solchen Fällen kann die Ausgabe auf ein paar 7-Segment-Anzeigen gemacht werden. Außerdem haben 7-Segment-Anzeigen einen ganz besonderen Charme :-)
Typen von 7-Segment-Anzeigen
Eine einzelne 7-Segment-Anzeige besteht aus sieben (mit Dezimalpunkt acht) einzelnen LEDs in einem gemeinsamen Gehäuse. Aus praktischen Gründen wird einer der beiden Anschlüsse jeder LED mit den gleichen Anschlüssen der anderen LEDs verbunden und gemeinsam aus dem Gehäuse herausgeführt. Das spart Pins am Gehäuse und später bei der Ansteuerung. Dementsprechend spricht man von Anzeigen mit gemeinsamer Anode (engl. common anode) bzw. gemeinsamer Kathode (engl. common cathode).
Eine einzelne 7-Segment-Anzeige
Schaltung
Eine einzelne 7-Segment-Anzeige wird nach dem folgenden Schema am Port D des ATmega8 angeschlossen. Port D wurde deshalb gewählt, da er am ATmega8 als einziger Port aus den vollen 8 Bit besteht. Die 7-Segment-Anzeige hat neben den Segmenten a bis g eine gemeinsame Anode CA sowie einen Dezimalpunkt dp (siehe folgende Abbildung).
Welcher Pin an der Anzeige welchem Segment (a…g) bzw. dem Dezimalpunkt entspricht, wird am besten dem Datenblatt zur Anzeige entnommen. Hat man kein Datenblatt, dann kann man auch empirisch die Pinbelegung feststellen, indem man mit einer 5-Volt-Quelle und einem 1-kΩ-Widerstand einfach probeweise alle Pins „abklappert“. 1 kΩ deswegen, damit man im Zweifelsfall Low-Current-LEDs (für 2 mA) nicht überfordert. Eine 7-Segment-Anzeige, die auf die üblichen 10 bis 20 mA ausgelegt ist, wird damit immer noch sichtbar leuchten, wenn auch schwach. Aber mehr braucht es ja auch nicht, um die Pinbelegung feststellen zu können.
Im Folgenden wird von oben abgebildeter Segmentbelegung ausgegangen. Wird eine andere Belegung genutzt, dann ist das problemlos möglich, jedoch müsste das in der Programmierung (→Codetabelle) berücksichtigt werden.
Da eine 7-Segment-Anzeige konzeptionell sieben einzelnen LEDs entspricht, ergibt sich im Prinzip keine Änderung in der Ansteuerung einer derartigen Anzeige im Vergleich zur LED-Ansteuerung, wie sie im Kapitel IO-Grundlagen gezeigt wird. Genau wie bei den einzelnen LEDs wird eine davon eingeschaltet, indem der zugehörige Port-Pin auf 0 gesetzt wird. Aber anders als bei einzelnen LEDs möchte man mit einer derartigen Anzeige eine Ziffernanzeige erhalten. Dazu ist es lediglich notwendig, für eine bestimmte Ziffer die richtigen LEDs einzuschalten.
Codetabelle
Die Umkodierung von einzelnen Ziffern in ein bestimmtes Ausgabemuster kann über eine sogenannte Codetabelle geschehen: Die auszugebende Ziffer wird als Offset zum Anfang dieser Tabelle aufgefasst und aus der Tabelle erhält man ein Byte (Code), welches direkt auf den Port ausgegeben werden kann und das entsprechende Bitmuster enthält, sodass die für diese Ziffer notwendigen LEDs ein- bzw. ausgeschaltet sind.
- Beispiel
- Um die Ziffer 3 anzuzeigen, müssen auf der Anzeige die Segmente a, b, c, d und g aufleuchten. Alle anderen Segmente sollen dunkel sein.
Aus dem Anschlußschema ergibt sich, dass die dazu notwendige Ausgabe am Port binär 10110000 lauten muss. Untersucht man dies für alle Ziffern, so ergibt sich folgende Tabelle:
.db 0b11000000 ; 0: a, b, c, d, e, f
.db 0b11111001 ; 1: b, c
.db 0b10100100 ; 2: a, b, d, e, g
.db 0b10110000 ; 3: a, b, c, d, g
.db 0b10011001 ; 4: b, c, f, g
.db 0b10010010 ; 5: a, c, d, f, g
.db 0b10000010 ; 6: a, c, d, e, f, g
.db 0b11111000 ; 7: a, b, c
.db 0b10000000 ; 8: a, b, c, d, e, f, g
.db 0b10010000 ; 9: a, b, c, d, f, g
Programm
Das Testprogramm stellt nacheinander die Ziffern 0 bis 9 auf der 7-Segment-Anzeige dar. Die jeweils auszugebende Zahl steht im Register count
und wird innerhalb der Schleife um jeweils 1 erhöht. Hat das Register den Wert 10 erreicht, so wird es wieder auf 0 zurückgesetzt. Nach der Erhöhung folgt eine Warteschleife, welche dafür sorgt, dass bis zur nächsten Ausgabe eine gewisse Zeit vergeht. Normalerweise benutzt man keine derartig langen Warteschleifen, aber hier geht es ja nicht ums Warten, sondern um die Ansteuerung einer 7-Segment-Anzeige. Einen Timer dafür zu benutzen wäre zunächst zuviel Aufwand.
Die eigentliche Ausgabe und damit der in diesem Artikel interessante Teil findet jedoch direkt nach dem Label loop
statt. Die bereits bekannte Codetabelle wird mittels .db
-Direktive (define byte) in den Flash-Speicher gelegt. Der Zugriff darauf erfolgt über den Z-Pointer und den Befehl lpm
. Zusätzlich wird vor dem Zugriff noch der Wert des Registers count
und damit der aktuelle Zählerwert zum Z-Pointer addiert.
Beachtet werden muss nur, dass der Zählerwert verdoppelt werden muss. Dies hat folgenden Grund: Wird die Tabelle so wie hier gezeigt mittels einzelnen .db
-Anweisungen aufgebaut, so fügt der Assembler sogenannte Padding-Bytes zwischen die einzelnen Bytes ein, damit jede .db
-Anweisung auf einer geraden Speicheradresse liegt. Dies ist eine direkte Folge der Tatsache, dass der Flash-Speicher wortweise (16 Bit) und nicht byteweise (8 Bit) organisiert ist. Da aber somit von einem .db
in der Tabelle zum nächsten .db
eine Differenz von 2 Bytes vorliegt, muss dies in der Berechnung berücksichtigt werden. Im zweiten Beispiel auf dieser Seite wird dies anders gemacht. Dort wird gezeigt, wie man durch eine andere Schreibweise der Tabelle das Erzeugen der Padding-Bytes durch den Assembler verhindern kann.
Aus dem gleichen Grund wird auch der Z-Pointer mit dem 2-fachen der Startadresse der Tabelle geladen: Die Startadresse wird vom Assembler in wortweiser Adressierung eingesetzt, lpm
möchte die Zugriffsadresse aber als Byteadresse angegeben haben.
Interessant ist auch, dass in der Berechnung ein Register benötigt wird, welches den Wert 0 enthält. Dies deshalb, da es im AVR keinen Befehl gibt, der eine Konstante mit gleichzeitiger Berücksichtigung des Carry-Bits addieren kann. Daher muss diese Konstante zunächst in ein Register geladen werden und erst dann kann die Addition mithilfe dieses Registers vorgenommen werden. Das Interessante daran ist nun, dass dieser Umstand in sehr vielen Programmen vorkommt und es sich bei der Konstanten in der überwiegenden Mehrzahl der Fälle um die Konstante 0 handelt. Viele Programmierer reservieren daher von vorne herein ein Register für diesen Zweck und nennen es das Zero-Register. Sinnvollerweise legt man dieses Register in den Bereich r0…r15, da diese Register etwas zweitklassig sind (ldi
, cpi
etc. funktionieren nicht damit).
.include "m8def.inc"
.def zero = r1
.def count = r16
.def temp1 = r17
.org 0x0000
rjmp main ; Reset Handler
main:
ldi temp1, HIGH(RAMEND) ; Stackpointer initialisieren
out SPH, temp1
ldi temp1, LOW(RAMEND)
out SPL, temp1
ldi temp1, $FF ; die Anzeige hängt am Port D
out DDRD, temp1 ; alle Pins auf Ausgang
ldi count, 0 ; und den Zähler initialisieren
mov zero, count
loop:
ldi ZL, LOW(Codes*2) ; die Startadresse der Tabelle in den
ldi ZH, HIGH(Codes*2) ; Z-Pointer laden
mov temp1, count ; die wortweise Adressierung der Tabelle
add temp1, count ; berücksichtigen
add ZL, temp1 ; und ausgehend vom Tabellenanfang
adc ZH, zero ; die Adresse des Code-Bytes berechnen
lpm ; dieses Code-Byte in das Register r0 laden
out PORTD, r0 ; und an die Anzeige ausgeben
inc count ; den Zähler erhöhen, wobei der Zähler
cpi count, 10 ; immer nur von 0 bis 9 zählen soll
brne wait
ldi count, 0
wait: ldi r17, 10 ; und etwas warten, damit die Ziffer auf
wait0: ldi r18, 0 ; der Anzeige auch lesbar ist, bevor die
wait1: ldi r19, 0 ; nächste Ziffer gezeigt wird
wait2: dec r19
brne wait2
dec r18
brne wait1
dec r17
brne wait0
rjmp loop ; auf zur nächsten Ausgabe
Codes: ; Die Codetabelle für die Ziffern 0 bis 9:
; Sie regelt, welche Segmente für eine bestimmte
; Ziffer eingeschaltet werden müssen.
;
.db 0b11000000 ; 0: a, b, c, d, e, f
.db 0b11111001 ; 1: b, c
.db 0b10100100 ; 2: a, b, d, e, g
.db 0b10110000 ; 3: a, b, c, d, g
.db 0b10011001 ; 4: b, c, f, g
.db 0b10010010 ; 5: a, c, d, f, g
.db 0b10000010 ; 6: a, c, d, e, f, g
.db 0b11111000 ; 7: a, b, c
.db 0b10000000 ; 8: a, b, c, d, e, f, g
.db 0b10010000 ; 9: a, b, c, d, f, g
Mehrere 7-Segment-Anzeigen (Multiplexen)
Mit dem bisherigen Vorwissen könnte man sich jetzt daran machen, auch einmal drei oder vier Anzeigen mit dem ATmega8 anzusteuern. Leider gibt es da ein Problem, denn für eine Anzeige sind acht Portpins notwendig – vier Anzeigen würden demnach 32 Portpins benötigen. Die hat der ATmega8 aber nicht. Dafür gibt es aber mehrere Auswege. Schieberegister sind bereits in einem vorhergehenden Kapitel beschrieben worden. Damit könnte man sich ganz leicht die benötigten 32 Ausgangsleitungen mit nur 3 Portpins erzeugen. Das Prinzip der Ansteuerung unterscheidet sich in nichts von der Ansteuerung einer einzelnen 7-Segment-Anzeige, lediglich die Art und Weise, wie die „Ausgangspins“ zu ihren Werten kommen ist anders und durch die Verwendung von Schieberegistern vorgegeben. An dieser Stelle soll aber eine andere Variante der Ansteuerung gezeigt werden. Im Folgenden werden wir uns daher das Multiplexen einmal näher ansehen.
Multiplexen bedeutet, dass nicht alle vier Anzeigen gleichzeitig eingeschaltet sind, sondern immer nur eine für eine kurze Zeit. Geschieht der Wechsel zwischen den Anzeigen schneller als wir Menschen das wahrnehmen können, so erscheinen uns alle vier Anzeigen gleichzeitig in Betrieb zu sein, obwohl immer nur eine kurzzeitig aufleuchtet. Dabei handelt es sich praktisch um einen Sonderfall einer LED-Matrix mit nur einer Zeile. Die vier Anzeigen können sich dadurch die einzelnen Segmentleitungen teilen und alles, was benötigt wird, sind 4 zusätzliche Steuerleitungen für die 4 Anzeigen, mit denen jeweils eine Anzeige eingeschaltet wird. Dieses Ein-/Ausschalten wird mit einem pnp-Transistor in der Versorgungsspannung jeder Anzeige realisiert, die vom ATmega8 am Port C angesteuert werden.
Ein Aspekt dieser Ansteuerungsart ist die Multiplexfrequenz, also ein kompletter Zyklus des Weiterschaltens von einer Anzeige zur nächsten. Sie muss hoch genug sein, um ein Flimmern der Anzeige zu vermeiden. Das menschliche Auge ist träge, im Kino reichen 24 Bilder pro Sekunde, beim Fernseher sind es 50. Um auf der sicheren Seite zu sein, dass auch Standbilder ruhig wirken, soll jedes Segment mit mindestens 100 Hz angesteuert werden, es also höchstens alle 10 ms angeschaltet ist. In Ausnahmefällen können aber selbst 100 Hz noch flimmern, z. B. wenn die Anzeige schnell bewegt wird oder wenn es zu Interferenzerscheinungen mit künstlichen Lichtquellen kommt, die mit Wechselstrom betrieben werden.
Bei genauerer Betrachtung fällt auch auf, dass die vier Anzeigen nicht mehr ganz so hell leuchten wie die eine einzelne Anzeige ohne Multiplexen. Bei wenigen Anzeigen ist dies praktisch kaum sichtbar; erst bei höherer Anzahl von Anzeigen wird es deutlich. Um dem entgegenzuwirken, lässt man pro Segment einfach mehr Strom fließen; bei LEDs dürfen dann 20 mA überschritten werden. Als Faustregel gilt, dass der n-fache Strom für die (1/n)-fache Zeit fließen darf. Details finden sich im Datenblatt unter den Punkten Peak Current (Spitzenstrom) und Duty Cycle (Tastverhältnis).
Allerdings gibt es noch ein anderes Problem, wenn insgesamt zu viele Anzeigen gemultiplext werden. Die Pulsströme durch die LEDs werden einfach zu hoch. Die meisten LEDs kann man bis 8:1 multiplexen, manchmal auch bis 16:1. Hier fließt aber schon ein Pulsstrom von 320 mA (16 × 20 mA), was nicht mehr ganz ungefährlich ist. Energie lässt sich durch Multiplexen nicht sparen, denn die verbrauchte Leistung ändert sich beim n-fachen Strom für 1/n der Zeit nicht. Kritisch wird es aber, wenn das Multiplexen deaktiviert wird (Ausfall der Ansteuerung durch Hardware- oder Softwarefehler) und der n-fache Strom dauerhaft durch eine Segment-LED fließt. Bei 320 mA werden die meisten LEDs innerhalb von Sekundenbruchteilen zerstört. Hier muss sichergestellt werden, dass sowohl Programm (Breakpoint im Debugger) als auch Schaltung (Reset, Power-On, Watchdog, siehe Forumsthread [1]) diesen Fall verhindern. Prinzipiell sollte man immer den Pulsstrom und die Multiplexfrequenz einmal überschlagen, bevor der Lötkolben angeworfen wird.
Sollten die Anzeigen zu schwach leuchten, wie bereits beschrieben, können die Ströme durch die Anzeigen erhöht werden. Dazu werden die 330-Ω-Widerstände kleiner gemacht. Da hier 4 Anzeigen gemultiplext werden, würden sich Widerstände in der Größenordnung von 100 Ω anbieten. Auch muss dann der Basiswiderstand der Transistoren verkleinert werden. Zudem muss berücksichtigt werden, dass der ATmega8 in Summe an seinen Portpins und an den Versorgungsleitungen nicht beliebig viel Strom liefern oder abführen kann. Auch hier ist daher wieder ein Blick ins Datenblatt angebracht und gegebenenfalls muss wieder ein Transistor als Verstärker eingesetzt werden (oder eben fertige Treiberstufen in IC-Form).
Programm
Das folgende Programm zeigt eine Möglichkeit zum Multiplexen. Dazu wird ein Timer benutzt, der in regelmäßigen Zeitabständen einen Overflow-Interrupt auslöst. Innerhalb der Overflow-Interrupt-Routine wird
- die momentan erleuchtete Anzeige abgeschaltet,
- das Muster für die nächste Anzeige am Port D ausgegeben und
- die nächste Anzeige durch eine entsprechende Ausgabe am Port C eingeschaltet.
Da Interruptfunktionen kurz sein sollten, holt die Interrupt-Routine das auszugebende Muster für jede Stelle direkt aus dem SRAM, wo sie die Ausgabefunktion hinterlassen hat. Dies hat 2 Vorteile:
- Zum einen braucht die Interrupt-Routine die Umrechnung einer Ziffer in das entsprechende Bitmuster nicht selbst machen.
- Zum anderen ist die Anzeigefunktion dadurch unabhängig von dem, was angezeigt wird. Die Interrupt-Routine gibt das Bitmuster so aus, wie sie es aus dem SRAM liest. Werden die SRAM-Zellen mit geeigneten Bitmustern gefüllt, können so auch einige Buchstaben oder Balkengrafik oder auch kleine Balken-Animationen abgespielt werden. Insbesondere letzteres sieht man manchmal bei Consumer-Geräten kurz nach dem Einschalten des Gerätes, um eine Art Defektkontrolle zu ermöglichen oder einfach nur als optischer Aufputz.
Die Funktion out_number
ist in einer ähnlichen Form auch schon an anderer Stelle vorgekommen: Sie verwendet die Technik der fortgesetzten Subtraktionen, um eine Zahl in einzelne Ziffern zu zerlegen. Sobald jede Stelle feststeht, wird über die Codetabelle das Bitmuster aufgesucht, welches für die Interrupt-Funktion an der entsprechenden Stelle im SRAM abgelegt wird.
Achtung: Anders als bei der weiter oben gezeigten Variante wurde die Codetabelle ohne Padding-Bytes angelegt. Dadurch ist es auch nicht notwendig, derartige Padding-Bytes in der Programmierung zu berücksichtigen.
Der Rest ist wieder die übliche Portinitialisierung, Timerinitialisierung und eine einfache Anwendung, indem ein 16-Bit-Zähler laufend erhöht und über die Funktion out_number
ausgegeben wird. Wie schon im ersten Beispiel wurde auch hier kein Aufwand getrieben: Zähler um 1 erhöhen und mit Warteschleifen eine gewisse Verzögerungszeit einhalten. In einer realen Applikation wird man das natürlich nicht so machen, sondern ebenfalls einen Timer für diesen Teilaspekt der Aufgabenstellung einsetzen.
Weiterhin ist auch noch interessant: Die Overflow-Interrupt-Funktion ist wieder so ausgelegt, dass sie völlig transparent zum restlichen Programm ablaufen kann. Dies bedeutet, dass alle verwendeten Register beim Aufruf der Interrupt-Funktion gesichert und beim Verlassen wiederhergestellt werden. Dadurch ist man auf der absolut sicheren Seite, hat aber den Nachteil, etwas Rechenzeit für manchmal unnötige Sicherungs- und Aufräumarbeiten zu „verschwenden“. Stehen genug freie Register zur Verfügung, dann wird man natürlich diesen Aufwand nicht treiben, sondern ein paar Register ausschließlich für die Zwecke der Behandlung der 7-Segment-Anzeige abstellen und sich damit den Aufwand der Registersicherung sparen (mit Ausnahme von SREG natürlich!).
.include "m8def.inc"
.def temp = r16
.def temp1 = r17
.def temp2 = r18
.org 0x0000
rjmp main ; Reset Handler
.org OVF0addr
rjmp multiplex
;
;********************************************************************
; Die Multiplexfunktion
;
; Aufgabe dieser Funktion ist es, bei jedem Durchlauf eine andere Stelle
; der 7-Segment-Anzeige zu aktivieren und das dort vorgesehene Muster
; auszugeben.
; Die Funktion wird regelmäßig in einem Timer-Interrupt aufgerufen.
;
; Verwendet werden 6 Bytes im SRAM (siehe .DSEG weiter unten im Programm)
; NextDigit Bitmuster für die Aktivierung des nächsten Segments
; NextSegment Nummer des nächsten aktiven Segments
; Segment0 Ausgabemuster für Segment 0
; Segment1 Ausgabemuster für Segment 1
; Segment2 Ausgabemuster für Segment 2
; Segment3 Ausgabemuster für Segment 3
;
; NextSegment ist einfach nur ein Zähler, der bei jedem Aufruf der Funktion
; um 1 weitergezählt wird und bei 4 wieder auf 0 zurückgestellt wird.
; Er wird benutzt, um ausgehend von der Adresse von Segment0 auf das
; jeweils als nächstes auszugebende Muster aus Segement0, Segment1,
; Segment2 oder Segment3 zuzugreifen. Die Adresse von Segment0 wird
; in den Z-Pointer geladen und NextSegment addiert.
;
; NextDigit enthält das Bitmuster, welches direkt an den Port C ausgegeben
; wird und den jeweils nächsten Transistor durchschaltet.
; Dazu enthält NextDigit am Anfang das Bitmuster 0b11111110, welches
; bei jedem Aufruf um 1 Stelle nach links verschoben wird. Beim nächsten
; Aufruf findet sich dann 0b11111101 in NextDigit, dann 0b11111011 und
; zu guter Letzt 0b11110111. Wird beim nächsten Schiebevorgang 0b11101111
; erkannt, dann wird NextDigit auf 0b11111110 zurückgesetzt (und NextSegment
; auf 0) und das ganze Spiel beginnt beim nächsten Funktionsaufruf wieder
; von vorne.
;
; Segment0 .. 3 enthalten die auszugebenden Bitmuster für die Einzel-LEDs
; der jeweiligen 7-Segment-Anzeigen. Diese Muster werden so wie sie sind
; einfach ausgegeben. Soll eine der Anzeigen etwas bestimmtes anzeigen
; (z. B. eine Ziffer), so obliegt es dem Code, der Werte in diese SRAM-
; Zellen schreibt, das dafür zuständige Bitmuster dort zu hinterlassen.
; Die Multiplexroutine kümmert sich nicht darum, dass diese Bitmuster
; in irgendeiner Art und Weise sinnvoll (oder was man dafür halten könnte)
; sind.
;
; veränderte CPU-Register: keine
;
multiplex:
push temp ; Alle verwendeten Register sichern
push temp1
in temp, SREG
push temp
push ZL
push ZH
ldi temp1, 0 ; Die 7 Segment ausschalten
out PORTC, temp1
; Das Muster für die nächste Stelle ausgeben
; Dazu zunächst mal berechnen, welches Segment als
; nächstes ausgegeben werden muss
ldi ZL, LOW( Segment0 )
ldi ZH, HIGH( Segment0 )
lds temp, NextSegment
add ZL, temp
adc ZH, temp1
ld temp, Z ; das entsprechende Muster holen und ausgeben
out PORTD, temp
lds temp1, NextDigit ; Und die betreffende Stelle einschalten
out PORTC, temp1
lds temp, NextSegment
inc temp
sec
rol temp1 ; beim nächsten Interrupt kommt reihum die
cpi temp1, 0b11101111 ; nächste Stelle dran.
brne multi1
ldi temp, 0
ldi temp1, 0b11111110
multi1:
sts NextSegment, temp
sts NextDigit, temp1
pop ZH ; die gesicherten Register wiederherstellen
pop ZL
pop temp
out SREG, temp
pop temp1
pop temp
reti
;
;************************************************************************
; 16-Bit-Zahl aus dem Registerpaar temp (=low), temp1 (=high) ausgeben
; Die Zahl muss kleiner als 10000 sein, da die Zehntausenderstelle
; nicht berücksichtigt wird.
; Werden mehr als vier 7-Segment-Anzeigen eingesetzt, dann muss dies
; natürlich auch hier berücksichtigt werden.
;
out_number:
push temp
push temp1
ldi temp2, -1 ; Die Tausenderstelle bestimmen
_out_tausend:
inc temp2
subi temp, low(1000) ; -1000
sbci temp1, high(1000)
brcc _out_tausend
ldi ZL, low(2*Codes) ; für diese Ziffer das Codemuster für
ldi ZH, high(2*Codes) ; die Anzeige in der Codetabelle nachschlagen
add ZL, temp2
lpm
sts Segment3, r0 ; und dieses Muster im SRAM ablegen
; die OvI-Routine sorgt dann für die Anzeige
ldi temp2, 10
_out_hundert: ; die Hunderterstelle bestimmen
dec temp2
subi temp, low(-100) ; +100
sbci temp1, high(-100)
brcs _out_hundert
ldi ZL, low(2*Codes) ; wieder in der Codetabelle das entsprechende
ldi ZH, high(2*Codes) ; Muster nachschlagen
add ZL, temp2
lpm
sts Segment2, r0 ; und im SRAM hinterlassen
ldi temp2, -1
_out_zehn: ; die Zehnerstelle bestimmen
inc temp2
subi temp, low(10) ; -10
sbci temp1, high(10)
brcc _out_zehn
ldi ZL, low(2*Codes) ; wie gehabt: Die Ziffer in der Codetabelle
ldi ZH, high(2*Codes) ; aufsuchen
add ZL, temp2
lpm
sts Segment1, r0 ; und entsprechend im SRAM ablegen
_out_einer: ; bleiben noch die Einer
subi temp, low(-10) ; +10
sbci temp1, high(-10)
ldi ZL, low(2*Codes) ; ... Codetabelle
ldi ZH, high(2*Codes)
add ZL, temp
lpm
sts Segment0, r0 ; und ans SRAM ausgeben
pop temp1
pop temp
ret
;
;**************************************************************************
;
main:
ldi temp, HIGH(RAMEND)
out SPH, temp
ldi temp, LOW(RAMEND) ; Stackpointer initialisieren
out SPL, temp
; die Segmenttreiber initialisieren
ldi temp, $FF
out DDRD, temp
; die Treiber für die einzelnen Stellen
ldi temp, $0F
out DDRC, temp
; Initialisieren der Steuerung für die
; Interrupt-Routine
ldi temp, 0b11111110
sts NextDigit, temp
ldi temp, 0
sts NextSegment, temp
ldi temp, (1 << CS01) | (1 << CS00)
out TCCR0, temp
ldi temp, 1 << TOIE0
out TIMSK, temp
sei
ldi temp, 0
ldi temp1, 0
loop:
inc temp
brne _loop
inc temp1
_loop:
rcall out_number
cpi temp, low(4000)
brne wait
cpi temp1, high(4000)
brne wait
ldi temp, 0
ldi temp1, 0
wait: ldi r21, 1
wait0: ldi r22, 0
wait1: ldi r23, 0
wait2: dec r23
brne wait2
dec r22
brne wait1
dec r21
brne wait0
rjmp loop
Codes:
.db 0b11000000, 0b11111001 ; 0: a, b, c, d, e, f
; 1: b, c
.db 0b10100100, 0b10110000 ; 2: a, b, d, e, g
; 3: a, b, c, d, g
.db 0b10011001, 0b10010010 ; 4: b, c, f, g
; 5: a, c, d, f, g
.db 0b10000010, 0b11111000 ; 6: a, c, d, e, f, g
; 7: a, b, c
.db 0b10000000, 0b10010000 ; 8: a, b, c, d, e, f, g
; 9: a, b, c, d, f, g
.DSEG
NextDigit: .byte 1 ; Bitmuster für die Aktivierung des nächsten Segments
NextSegment: .byte 1 ; Nummer des nächsten aktiven Segments
Segment0: .byte 1 ; Ausgabemuster für Segment 0
Segment1: .byte 1 ; Ausgabemuster für Segment 1
Segment2: .byte 1 ; Ausgabemuster für Segment 2
Segment3: .byte 1 ; Ausgabemuster für Segment 3
Forenbeiträge
- Geisterleuchten bei 7-Segment Anzeige
- Re: Zehn 7Segmente im Multiplexbetrieb : Hilfe ! – Ausführliche Erklärung, wie und warum man entweder die Stellen oder die Segmente multiplext.
- 7 Segment Multiplex, Sicherheit, watchdog?? – Hardware-Watchdog mit 74HC123