Pfad:
Home ==>
Mikrobeginner ==> 3. LED-Blinker
This page in English (external):
Lektion 3: Eine LED blinkt
Mit dieser Lektion kommt Leben in die Bude: die LED blinkt. Zuerst hektisch, dann
gemütlich.
3.0 Übersicht
- Einführung
- Hardware, Bauteile, Aufbau
- Schnellblinker
- Sekundenblinker
Zum Blinken einer LED muss diese rhythmisch ein- und ausgeschaltet werden. Die Grundlagen zum
Ein- und Ausschalten hatten wir schon in der vorherigen Lektion: cbi PORTB,PORTB0 schaltet
sie ein, sbi PORTB,PORTB0 aus. Das war es.
Dummerweise braucht ein mit 1,2 MHz betriebener Prozessor dafür nur 4 / 1.200.000
Sekunden = 0,000.003.33 Sekunden, was für das menschliche Auge etwas zu schnell ist.
Die Möglichkeiten, den Prozessor gezielt mit etwas anderem zu beschäftigen, werden
hier aufgezeigt.
3.1.1 Ausführung von Instruktionen durch den Prozessor
So bearbeitet ein AVR programmierte Befehlsworte:
- Das nächste Befehlswort wird aus dem Flashspeicher gelesen.
- Das Befehlswort wird dekodiert, das heißt in Schrittfolgen zerlegt.
- Das Befehlswort wird ausgeführt. Währenddessen wird bereits das nächste
Befehswort gelesen und dekodiert ("Pre-Fetch").
Eigentlich dauert die Bearbeitung eines Befehlswortes daher zwei Taktzyklen. Da aber
während der Ausführung schon das nächste Befehlswort geholt und dekodiert wird,
braucht die Instruktion effektiv nur einen Taktzyklus. Das funktioniert meistens, aber dann
nicht, wenn die Instruktion mit einem Sprung zu einer anderen Adresse im Flashspeicher
verbunden ist. In diesem Fall ist das schon gelesene und dekodierte Wort wertlos, da es nicht
zur Ausführung kommt. Daher dauern alle Instruktionen, bei denen gesprungen wird,
mindestens zwei Taktzyklen.
Wegen des Pre-Fetch-Mechanismus sind AVR fast doppelt so schnell wie sie das ohne diesen
wären. Vergleicht man verschiedene Prozessortypen bezüglich ihrer
Ausführungsgeschwindigkeit, reicht es nicht aus, die MHz Takt zu vergleichen. Zu
vergleichen ist, wieviel Taktzyklen je Instruktion anfallen und ob die Ausführung
durch Pre-Fetch beschleunigt wird. So kann aus den gleichen MHz Takt das bis zu vierfache
an Geschwindigkeit herauskommen.
Fast alle Instruktionen des AVR-Prozessors benötigen einen Taktzyklus. Es gibt jedoch
auch Instruktionen, die derer zwei benötigen. Die beiden, die wir in der vorherigen
Lektion verwendet haben, gehören zu dieser eher seltenen Spezies. Das kommt in diesem
Fall daher, dass für das Setzen eines Bits erst der gesamte Port eingelesen, dann das
erste Bit gesetzt und der gesamte Port dann wieder geschrieben wird. Die Ausführungszeit
beträgt mit Pre-Fetch zwei Taktzyklen.
3.1.2 Ausführungszeiten von Instruktionen
Die Ausführungszeiten aller Instruktionen stehen im Device-Handbuch in der Tabelle
"Instruction Set Summary" in der Spalte "Clocks".
Wann immer es auf Ausführungszeiten und Timing ankommt, wie im Falle des Sekundenblinkers,
ist das die erste Anlaufstelle.
Für die Blinker kommt die gleiche Hardware zum Einsatz, wie wir sie schon in Lektion 2
aufgebaut haben.
3.1.1 Der einfachste Schnellblinker
Wie eingangs beschrieben, bräuchten wir nur die Codezeilen
sbi DDRB,DDB0 ; PB0 ist Ausgang, Treiber einschalten
cbi PORTB,PORTB0 ; LED einschalten
sbi PORTB,PORTB0 ; LED ausschalten
zum Ein- und Ausschalten der LED verwenden. Das Ergebnis wäre eine ganz schwach leuchtende
LED.
Die Ursache für diese Schwäche ist, dass die Diode nur für zwei Taktzyklen
eingeschaltet ist, dann aber 511 Takte bei ausgeschalteter LED folgen. Der Prozessor
arbeitet nämlich seinen ganzen Flashspeicher mit NOPs ab, bis er wieder von vorne beginnt.
Abstellen können wir dies, indem wir eine Sprunginstruktion einfügen, die nach dem
Abschalten direkt wieder zum Anschalten verzweigt. Das geht so (
Quellcode hier):
sbi DDRB,DDB0 ; PB0 ist Ausgang, Treiber einschalten
Schleife:
cbi PORTB,PORTB0 ; LED einschalten
sbi PORTB,PORTB0 ; LED ausschalten
rjmp Schleife ; zum Einschalten springen
Schleife: nennt man ein Label, es ist die Sprungsadresse für den Rücksprung. Der
Rücksprung ist die Instruktion "rjmp", in Deutsch "Relativer Sprung".
Die Instruction Set Summary bringt folgende Ausführungszeiten zutage:
sbi DDRB,DDB0 ; PB0 ist Ausgang, Treiber einschalten
Schleife:
cbi PORTB,PORTB0 ; LED einschalten, 2 Takte
sbi PORTB,PORTB0 ; LED ausschalten, 2 Takte
rjmp Schleife ; zum Einschalten springen, 2 Takte
Das bedeutet, die LED ist für zwei Takte angeschaltet und für vier Takte
ausgeschaltet. Das bedeutet 33% Helligkeit 2 / (2 + 4) oder 1 / 3.
Für 50% Helligkeit wäre folgendes nötig:
sbi DDRB,DDB0 ; PB0 ist Ausgang, Treiber einschalten
Schleife:
cbi PORTB,PORTB0 ; LED einschalten, 2 Takte
nop ; Nichts tun, 1 Takt
nop ; Nichts tin, 1 Takt
sbi PORTB,PORTB0 ; LED ausschalten, 2 Takte
rjmp Schleife ; zum Einschalten springen, 2 Takte
Die beiden Instruktionen NOP tun nix, verzögern aber der Programmablauf.
Jetzt ist die LED für vier Takte an und für vier Takte aus. Macht genau 50% duty
cycle. Allerdings beträgt die Blinkfrequenz nunmehr immer noch 1.200.000 / 8 =
150.000 Hz. Also etwas zu viel für das menschliche Auge.
Bei der Simulation mit avr_sim
können die Abläufe sicht- und messbar gemacht werden.
Die erste Instruktion, sbi DDRB,DDB0, wurde ausgeführt. Sie dauert bei
1,2 MHz Takt genau 1,667 µs, wie es das Datenbuch von ATMEL ausweist.
Bit 0 im Richtungsport des Prozessors ist gesetzt, der Pin ist nun Ausgang.
Die nächste Instruktion, cbi PORTB, PORTB0 wurde ausgeführt. Sie
dauert ebensolange wie die Erste.
Und so lange dauert es von der cbi PORTB,PORTB0- bis zur
sbi PORTB,PORTB0-Instruktion, mit den beiden NOP zwischendurch.
Und so lange dauert ein ganzer Durchlauf von Schleife bis Schleife:
genau doppelt so lang. Die erzeugte Rechteckspannung am Ausgang PB0
ist also symmetrisch, die LED zu 50% an und aus.
3.3.2 Verzögerter Schnellblinker, 8-Bit
Um die Blinkfrequenz zu senken, muss noch mehr Verzögerung in den Code. Die folgende
Sequenz ist eine typische Verzögerungsschleife:
.equ cZaehler = 250 ; definiere eine Konstante
ldi R16, cZaehler ; lade Register mit Konstante
Schleife:
dec R16 ; zaehle Register abwaerts
brne Schleife ; verzweige, wenn Null-Flagge nicht gesetzt ist
Hier wird ein Register (R16) verwendet, um abwärts zu zählen. Jeder AVR hat 32
solcher Register, von denen sich aber nur R16 bis R31 in einer einzigen Instruktion mit einem
Festwert beladen lassen. Register sind frei verfügbare Speicherstellen mit je 8 Bits.
Sie können daher Werte zwischen 0 und 255 aufnehmen. Die Konstante cZaehler legt fest, wie oft
die Schleife durchlaufen wird. "LDI" bedeutet Load Immediate,
"DEC" Decrease oder vermindern, "BRNE"
Branch if Not Equal (verzweige wenn nicht Null).
Wiederum fördert das Instruction Set Summary folgende Taktzyklen zutage:
.equ cZaehler = 250 ; (kein Takt)
ldi R16, cZaehler ; 1 Takt
Schleife:
dec R16 ; 1 Takt
brne Schleife ; 2 Takte beim Verzweigen, 1 Takt ohne Verzweigung
Die einzelnen Instruktionen nehmen folgende Takte in Anspruch:
.equ cZaehler = 250 ; (erzeugt keinen Code)
ldi R16, cZaehler ; 1 Takt
Schleife:
dec R16 ; cZaehler Takte
brne Schleife ; 2 * (cZaehler - 1) Takte plus 1 Takt
Der gesamte Durchlauf nimmt also 1 + cZaehler + 2 * cZaehler - 2 + 1 Takte in Anspruch. Das
ergibt 3 * cZaehler oder 750 Takte. Zeitlich dauert die Schleife 750 / 1.200.000 oder
625 µs.
Auch das wieder im Simulator
avr_sim.
Loop:
ldi R16,250
Loop2:
dec R16
brne Loop2
rjmp Loop
ergeben sich folgende Ausführungszeiten:
Der Durchlauf dauert also wie voherberechnet 625 µs, der
Schleifendurchlauf ohne ldi R16,250 ein Takt weniger. Eine
gut vorherberechenbare Sache also.
Verzögerter Schnellblinker, 16 Bit
Das ist immer noch wesentlich zu kurz. Versuchen wir es also mit 16 Bit.
Wir benötigen dafür 16-Bit-Register, der AVR hat davon vier Stück. Das sind
die Registerpaare R25:R24, R27:R26, R29:R28 und R31:R30. Sie lassen sich wie Einzelregister
behandeln, manche Instruktionen wirken sich aber jeweils auf das ganze Paar aus.
Die 16-Bit-Schleife sieht so aus:
.equ cZaehler = 50000 ; 1 bis 65535 (erzeugt keinen Code)
ldi R25, HIGH(cZaehler) ; 1 Takt
ldi R24,LOW(cZaehler) ; 1 Takt
Schleife:
sbiw R24,1 ; abwaerts, 2 Takte, 2 * cZaehler
brne Schleife ; 2 * (cZaehler - 1) Takte plus 1 Takt
Das "SBIW" bewirkt, dass vom Registerpaar R25:R24 immer eins
abgezogen wird, und zwar wortweise (16-Bit). Kommt beim Abziehen Null heraus, wird die
Z-Flagge im Statusregister gesetzt, ansonsten ist sie Null. Die Instruktion
"BRNE" wertet diese Flagge aus, um zur Schleife zurückzukehren, solange
diese noch nicht Null ist.
Nun nimmt der Durchlauf 1 + 1 + 2 * cZaehler + 2 * (cZaehler - 1) + 1 = 4 * cZaehler + 1 Takte
ein. Bei 50.000 Durchläufen sind das 200.001 Takte oder 0,167 Sekunden. Das kommt der
Sekunde schon näher, aber noch nicht ganz.
Auch das wieder im avr_sim.
Auch das stimmt mit den vorausberechneten Zeiten überein.
Die Sekunde kriegen wir daher nur mit einer Kombination einer 8-Bit- mit einer
16-Bit-Verzögerungsschleife hin. Und das geht so (Quellcode hier
):
;
; ***********************************************
; * Eine LED blinken lassen mit einem ATtiny13 *
; * (C)2016 by http://www.gsc-elektronic.net *
; ***********************************************
;
.NOLIST ; Ausgabe im Listing unterdruecken
.INCLUDE "tn13def.inc" ; Port-Definitionen lesen
.LIST ; Ausgabe im Listing einschalten
;
; Register definieren
;
.def ZaehlerA = R16 ; Aeusserer 8-Bit-Zaehler
.def ZaehlerIL = R24 ; Innerer 16-Bit-Zaehler, LSB
.def ZaehlerIH = R25 ; Innerer 16-Bit-Zaehler, MSB
;
; Konstanten definieren
;
.equ cInnen = 2458 ; Zaehler Innere Schleife
.equ cAussen = 61 ; Zaehler Aeussere Schleife
;
; Programmstart
;
sbi DDRB,DDB0 ; Portpin PB0 auf Ausgang
;
; Programmschleife
;
Schleife:
cbi PORTB,PORTB0 ; Portpin PB0 auf Null, Led an, 2 Takte
; Verzoegerungsschleifen
ldi ZaehlerA,cAussen ; Aeusserer 8-Bit-Zaehler setzen, 1 Takt
Schleife1:
ldi ZaehlerIH,HIGH(cInnen) ; Innerer 16-Bit-Zaehler setzen, 1 Takt
ldi ZaehlerIL,LOW(cInnen) ; 1 Takt
Schleife1i:
sbiw ZaehlerIL,1 ; Inneren 16-Bit-Zaehler abwaerts zaehlen, 2 Takte
brne Schleife1i ; wenn noch nicht Null: zurueck, 2 Takte bei Sprung, 1 Takt ohne Sprung
dec ZaehlerA ; aeusseren 8-Bit-Zaehler abwaerts, 1 Takt
brne Schleife1 ; aeussere 8-Bit-Schleife wiederholen, 2 Takte bei Sprung, 1 Takt ohne Sprung
nop ; Verzoegerung, 1 Takt
nop ; Verzoegerung, 1 Takt
;
sbi PORTB,PORTB0 ; Portpin PB0 auf Eins, Led aus, 2 Takte
; Verzoegerungsschleifen
ldi ZaehlerA,cAussen ; aeusserer 8-Bit-Zaehler setzen, 1 Takt
Schleife2:
ldi ZaehlerIH,HIGH(cInnen) ; Innerer 16-Bit-Zaehler setzen, 1 Takt
ldi ZaehlerIL,LOW(cInnen) ; 1 Takt
Schleife2i:
sbiw ZaehlerIL,1 ; Inneren 16-Bit-Zaehler abwaerts zaehlen, 2 Takte
brne Schleife2i ; wenn noch nicht Null: zurueck, 2 Takte bei Sprung, 1 Takt ohne Sprung
dec ZaehlerA ; aeusseren 8-Bit-Zaehler abwaerts, 1 Takt
brne Schleife2 ; aeussere 8-Bit-Schleife wiederholen, 2 Takte bei Sprung, 1 Takt ohne Sprung
; Zyklusende
rjmp Schleife ; von vorne beginnen, 2 Takte
;
; Ende Quellcode
;
Innere 16-Bit- und äussere 8-Bit-Schleife sind zweifach identisch vorhanden.
Die sich ergebende Formel für die Anzahl Takte ist relativ einfach, die Ermittlung der
beiden Konstanten cAussen und cInnen für ein optimales Ergebnis ist weniger trivial. Die
im Quellcode verwendeten Konstanten sind einzigartige Lösungen für diese Aufgabe.
Der Simulator avr_sim sagt
auch, dass nach der halben und der ganzen Sekunde alles korrekt ist.
©2016-2018 by http://www.gsc-elektronic.net