216
Eine Möglichkeit Zeitverzögerungen zu erreichen ist den Prozessor mit Zählen zu beschäftigen:
ldi temp,0 ;Register mit 0 initialisieren loop: dec temp ;temp um 1 erniedrigen brne loop ;solange ungleich 0 springen weiter... |
Wie lange hat er damit zu tun? Nehmen wir eine Taktfrequenz von 1 MHz an: Nach der Scheife wird temp wieder den Wert 0 haben, somit wird dec temp 256 mal ausgeführt macht 256 Maschinenzyklen. 255 mal wird brne loop zu loop: springen a 2 Maschinenzyklen macht 2 * 255 = 510. Summe: 1 (init) + 256 + 510 + 1 (letzer brne) = 768 Zyklen a 1 µs = 0,768 ms Zeit sind um. |
Es gibt einen Befehl nop "No Operation" der nix tut ausser 1 Zylus Zeit kosten und 1 Word Speicher verbrauchen:
ldi temp,0 ;Register mit 0 initialisieren loop: dec temp ;temp um 1 erniedrigen nop ;tue nichts brne loop ;solange ungleich 0 springen weiter... |
Summe der Zeit ist nun 1 + 256 + 256 (nop) + 510 + 1 = 1024 somit warten wir 1,024 ms. |
Durch Verschachteln von zwei Schleifen kann erheblich länger gewartet werden:
ldi aussen,0 ;Register auf 0 initialisieren ldi innen,0 ;Register auf 0 initialisieren loop: dec innen ;innen um 1 erniedrigen brne loop ;solange ungleich 0 springen dec aussen ;aussen um 1 erniedrigen brne loop ;solange ungleich 0 springen weiter... |
Die innere Schleife benötigt 256 + 510 + 1 = 767 Zyklen und wird 256 mal durchlaufen macht 196352 Zyklen. Dazu kommen noch Initialisierung und dec aussen 256 mal und brne loop somit ergibt sich: 2 + 196352 + 256 + 510 + 1 = 197121 Zyklen also ca. 0,2 s warten. |
Nun zur hohen Kunst der Warteschleifen, wie kann man es bei 1 MHz genau 1 ms warten lassen? Sieht nach einem Fall mit einer Schleife und nop aus: 1000 / (1+1+2) = 250 somit folgendes Programm:
wait1ms: ldi temp,250 ;initialisieren loop: dec temp nop brne loop weiter.. |
Analyse: 1 + 250 *(1+1) + 249 * 2 + 1 = 1000 Zyklen! Dieses Code-Beispiel lässt sich also mühelos einstellen von 1 * 4 µs bis 256 * 4 µs. |
Dazu kann man einfach die 1 ms Verzögerung umverpacken:
wait10ms: ldi aussen,10 loop_aussen: ldi innen,250 ;initialisieren loop_innen: dec innen nop brne loop_innen dec aussen brne loop_aussen weiter.. |
Leider ist es nicht ganz genau, denn die äussere Schleife kostet zusätzlich Zeit: 1+ 10 + 2 * 9 + 1 = 30 Zyklen. Man könnte nun bei der inneren Schleife etwas einsparen, |
wait10ms: ldi aussen,10 loop_aussen: ldi innen,249 ;initialisieren loop_innen: dec innen nop brne loop_innen nop dec aussen brne loop_aussen weiter.. |
Man könnte nun in der inneren Schleife auf die nops verzichten und das Ganze noch mal neu durchrechnen usw.. Viel Spass!
Es muss nicht immer so genau sein etwa 100 ms bei 1 Mhz Takt warten:
wait100ms: ldi aussen,130 loop_aussen: ldi innen,0 ;Register auf 0 loop_innen: dec innen brne loop_innen dec aussen brne loop_aussen ret ; Rücksprung |
Die innere Schleife benötigt 1+255*(1+2)+1+1 = 256 * 3 = 768 Zyklen = 0,768 ms
Mit der äusseren Schleife ergibt sich 3 (rcall) + 1 (ldi) + 130 * 768 + 130 * 1 (dec) + 129 * 2 (brne) + 1 (brne) + 4 (ret) = |
Sobald man Timer und Interrupts verwendet kann es mit der Genauigkeit vorbei sein. Der Ablauf wird unterbrochen und zur Interrupt-Routine verzweigt..
Der µC sei mit 4 MHz getaktet, erstellen Sie Quellcode für eine 1 ms Warteschleife!
Der µC sei mit 6 MHz getaktet, erstellen Sie Quellcode für eine 5 ms Warteschleife!
Lutz Lißeck schrieb mir eine sehr interessante Mail:
..ich bin auf Ihrer Webseite über die Warteschleifenprogrammierung beim AVR gestolpert. Hier wird eine Verzögerung nach klassischem Beispiel realisiert, in dem in verschachtelten Schleifen Rechenzeit verbraten wird. Die Verschachtelung der Schleifen macht aber die Berechnung von Wartezeiten (unnötig) kompliziert.
Der AVR kann auch mit Zahlen größer als ein Byte rechnen, dazu wurdem dem Controller spezielle Flags spendiert (dazu gibt es auch ein gutes App-Note von Atmel). Wird damit einen Zähler mit 16, 24, 32 oder mehr Bits programmiert, so läßt sich die Verzögerungszeit viel einfacher berechnen. Wie das gemacht wird, hatte ich vor einiger Zeit bereits in einem Forum auf Mikrocontroller.net gepostet (http://www.mikrocontroller.net/forum/read-4-11426.html#11426).
Auf den Mailtext folgend ist diese Routine bereits als Makro verpackt, das lnop-Makro muss allerdings evtl. direkt in das Verzögerungsmakro geschrieben werden (alternativ kann auch 2x nop verwendet werden), da AVRASM Makros in Makros (noch?) nicht unterstützt.
;*************************************************************************** ;* lnop ;*************************************************************************** ;* Typ : Macro, Öffentlich ;* Kurzbeschreibung : Quasi ein NOP-Befehl, der aber 2 Zyklen benötigt ;* Eingabe : keine ;* Ausgabe : keine ;* Benutzte Register : keine ;* Zyklen : 2 ;* PrgMem-Verbrauch : 1 Words ;*************************************************************************** ; Langer NOP ; Braucht 2 Zyklen zur Verarbeitung, aber nur ein Word Code. .macro lnop ; rjmp $ + 1 ; Für AvrTerse (Muss im listfile zu c000 kompiliert werden) rjmp PC + 1 ; Für AVRASM (Muss im listfile zu c000 kompiliert werden) .endmacro ;*************************************************************************** ;* delay_flex3Kern ;*************************************************************************** ;* Typ : Makro, Öffentlich ;* Kurzbeschreibung : FlexiWait mit 3 freiwählbaren Registern, ;* die mit der Verzögerungszeit initialisiert sein ;* müssen. Verzögerungsbereich: 10 - 167,7M Zyklen ;* Eingabe : @0: Lowbyte, @1: Midbyte, @2: Highbyte ;* @0-@2: Alles initialisierte Register ab R16 ;* Zugelassener 24-Bit-Wertebereich: ;* $1 - $ffffff ($0 entspr. $1000000) ;* Ausgabe : keine ;* Benutzte Register : Status, @0-@2 ;* Zyklen : 24-Bit-Wert * 10 ;*************************************************************************** ; Flexible Waitroutine zum abwarten von bel. Zyklenanzahlen ; Eingabe: 24-Bit in drei Registern aufgeteilt. ; Verzögerung: (24-Bit-Wert * 10) = Zyklen ; Variablen: @0 Low-Byte, @1 Mid-Byte, @2 High-Byte ; (alles Reg. oder Reg.Def's von R16-R31) ; Beispiel: ; ; Register mit Verzögerung initialisieren ; ; .set delayzyklen = 1000000 ; ; ldi r16, low(delayzyklen) ; 1 ; ldi r17, byte2(delayzyklen) ; 1 ; ldi r18, byte3(delayzyklen) ; 1 ; ; delay_flex3Kern r16, r17, 18 ; ; Gesammtverzögerung bis hier: 3 + 10*delayzyklen .macro delay_flex3Kern ; Schleifenkern: ; Großen Zähler decrementieren delay_f3K_W1: subi @0, 1 ; 1 sbci @1, 0 ; 1 sbci @2, 0 ; 1 lnop ; 2 (LNOP) nop ; 1 lnop ; 2 (LNOP) brne delay_f3K_W1 ; 2 (1) nop .endmacro
© Oliver Mezger