MezData-Logo 216 Creative Commons License Lösungen Button :AVR: Warteschleifen

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.

Länger warten mit nop

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.

Länger warten mit zwei Schleifen

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.

Genau 1 ms 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.

Genau 10 ms warten

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,
ein Durchlauf weniger bringt 4 Zyklen mal 10 macht 40 sind also 10 zu wenig,
die müssen nun mit nops wieder ausgelichen werden:

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!

Unterprogramm ca. 100 ms warten

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) =
4 + 130 * 769 + 129 *2 + 5 = 100237 Zyklen = 100,237 ms.

Nix genaues mehr mit Interrupts

Sobald man Timer und Interrupts verwendet kann es mit der Genauigkeit vorbei sein. Der Ablauf wird unterbrochen und zur Interrupt-Routine verzweigt..

Aufgaben

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!

Nachschlag: Lange warten mit nur einer Schleife

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 22.04.2008 MezData.de Den Kontakt herstellen...