Hi, wie kann ich ne pause in AVR studio machen, mit einer variable?
mann kann ja einfach "delay50ms:" schreiben, ich möchte aber, das die
pause so viele millisekunden lang ist, wie es in z.B. r16 steht.
help please! thx
> mann kann ja einfach "delay50ms:" schreiben,...
Türlich kann Mann das. Ich glaub Frau kanns u.U. sogar auch;-) Ob es
das tut, was Du willst, weiß ich nicht.
Da musst Du Dir halt ne Zählschleife programmieren, die bei dem Takt,
mir dem Dein µC läuft, genau eine ms braucht und dann Deine Variable
runterzählen. Zu dem Thema ('wie berechne ich ne Zählschleife') gibt
hier ca. 1357,4 Threads. Kannst ja mal suchen. Aber vielleicht bringt
es mehr, wenn Du selber überlegst, wie es geht. Schau mal ins
Instruction Set Manual. Da steht drin, wie viele Taktzyklen die
einzelnen Befehle brauchen (für Dich kämen da am eheseten dekrement-
und Sprungbefehle in Frage...). Dann alles zusammenpuzzeln und
irgendwann läufts vielleicht sogar richtig.
...
.def pause_counter = r25
...
rcall pause
...
;********Unterprogramme**********
pause:
ldi pause_counter, 0xFF
pause_loop:
dec pause_counter
nop
brne pause_loop
ret
Je nach MHz und dem Wert im counter, ändert sich die Verweildauer.
Also nachschauen wieviel Zyklen jeder einzelne Befehl benötigt, dann
zusammenaddieren. Der Sprung kostet nochmals 3 Zyklen.
Näheres gibt's hier:
http://www.avr-asm-tutorial.net/avr_de/beginner/sprung.html#Timing
im myAVRWorkpad PLUS hab ich für sowas nen CodeWizard ich muss sagen
wier schnell mein AVR läuft und dann baut mir der Wizard die
warteroutine... hier bei 3,6864 MHz:
;--------------------------------------------------------------------
; myWait_us - Warte-Routine für x-Mikrosekunden einschließlich des
Aufrufes
; berechnete Abweichung: 8.5%
; PE: r16 = Anzahl der zu wartenden Mikrosekunden
; es wird mindestens 3µs gewartet, auch wenn r16 kleiner als 3
; PA: r16 = 0
;--------------------------------------------------------------------
; created by myAVR-CodeWizard
;--------------------------------------------------------------------
myWait_us:
nop ; wait
subi r16,3
brcs myWait_us_1
breq myWait_us_1
myWait_us_2:
nop ; wait
dec r16
brne myWait_us_2
myWait_us_1:
ret
;--------------------------------------------------------------------
gruß Jahn
hier noch mal ne millisekunden routine bei 3,6864 MHz aus dem CodeWiz
;--------------------------------------------------------------------
; myWait_ms - Warte-Routine für x-Millisekunden
; ein Millisekundenzyklus dauert 1,052 ms
; PE: r16 = Anzahl der zu wartenden Milisekunden
; PA: r16 = 0
;--------------------------------------------------------------------
; created by myAVR-CodeWizard
;-----------------------------------------------------------------------
--
myWait_ms:
push r16
ldi r16,1
myWait_ms_3:
push r16
ldi r16,5
myWait_ms_2:
push r16
ldi r16,255
myWait_ms_1:
dec r16
brne myWait_ms_1
pop r16
dec r16
brne myWait_ms_2
pop r16
dec r16
brne myWait_ms_3
pop r16
dec r16
brne myWait_ms
ret
;-----------------------------------------------------------------------
--
Gruß Jahn
Man braucht dazu nicht extra nen Codewizard zu bemühen.
Der Assembler-Präprozessor kann doch selber 32Bit Berechnungen
durchführen.
Einfach bei XTAL die gewünschte Quarzfrequenz eintragen:
also, ich haääte vielleicht dazusagen müssen, das ich inzwischen nun
schon das kapitel 1,2und 4 des avr tutorials auf dieser page durch
habe und gut verstanden habe. un das mit den takzyklen hört sich ganz
gut an.
ich habe den AT Mega 8, der hat 16 Mhz. Kann mir jemand ganz simple
erklären, was ein takt zyklus ist? Und was die Mhz aussgaen, je mehr
Mhz, desto mehr zyklen pro sekunde ??!?
danke!
Ein µProzessor hat einen Grundtakt. Der Grundtakt
ist so was wie der Mann der auf einer Galeere: Der
Schlägt auf eine Pauke und gibt so den Takt vor in
dem gerudert wird.
In deinem Prozessor wird zwar nicht gerudert, aber
verschiedene Einheiten müssen da zusammenarbeiten.
Wann welche Einheit was zu tun hat, gibt der Systemtakt
vor.
Dein Mega8 wird mit 16Mhz betrieben. Das heisst der Quarz
schwingt 16 Millionen mal in der Sekunde. Folgerichtig
'schlägt der kleine Japaner im Chip' 16 Millionen mal
in der Sekunde auf die Pauke und es passieren in einer
Sekunde 16 Millionen Arbeitsschritte.
Die Zeit von einem Schlag zum nächsten ist der Taktzyklus.
Viele (die meisten) Befehle können auf deinem Mega8 in
einem Taktzyklus abgearbeitet werden. D.h. dein Mega8
kann dann im Extremfall 16 Millionen Befehle in der
Sekunde abarbeiten.
Da du 16MHz bebutzt, braucht dein Atmel für einen Arbeitstakt
1/16000000 Sekunden. (62,5 Nanosekunden)
Für die Pause von 1 ms mußt du also 16000 Takte "verbraten".
Wenn du eine simple Schleife nutzen willst, mußt du entweder eine
16bit
Variable benutzen oder eine innere und eine äußere Schleife verwenden.
Ok, nu check ich das, danke für die gute beschreibung! Gibt es denn
einen einfachen befehl um eine bestimmte anzahl von zyklen "nichts"
zu tun?
Was sind denn "us" die bei der simulation unter prozessor/stopwatch
gezählt werden?
Ich glaube, das mit den Zählschleifen hatten wir oben schon mal... Du
scheinst zu vergessen, dass Du in Assembler programmierst. Da ist halt
alles ein bisschen einfacher gehalten (ein Befehl macht genau eine
Elementar-Operation, in den meisten Fällen bezogen auf einen einzigen
Taktzyklus). Man muss dann eben ne Menge selber machen, lernt dadurch
aber die Hardware besser kennen, als wenn man gleich mit C (oder gar
Basic) anfängt. Musst Dir da halt was einfallen lassen.
Du kannst eine Schleife drum herum legen.
So nach dem Muster:
1 Register mit einem Wert laden
2 nichts tun
3 Register um 1 vermindern
4 0 erreicht?
5 wenn nein, gehe zu 2
Hinweis: In einem Register kannst du nur Zahlen
bis 256 laden. D.h. du kannst nicht in einem
Rutsch mit nur einem Register von 16000 bis
auf 0 herunter zählen.
Es hindert dich aber niemand daran immer wieder
von 255 bis 0 zu zählen und das 62 mal :-)
> ok, kann ich "nop" auch irgendwie multiplizieren? Ich mächte ja> nicht 16000 mal nop schreiben
Für relativ kurze Wartezeiten (Zeitschleifen) nutze ich gern mal rcall
/ ret, das vertrödelt schonmal 7 Takte pro Aufruf.
Aber solch "lange Verzögerungen" realisiert man besser mittels Timer.
Dabei sollte man sich mit einem ständig laufenden Timer Interrups im
Zeitabstand von 0,1ms..20ms (je nach sonstigen Aufgaben des Timers)
erzeugen, in denen man den Rest per "Software-Timer" machen kann.
Den Timer-Int braucht man sowiso zum Entprellen von Tasten, Generieren
von Blinktakten und anderen immer wieder gebrauchten Dingen, da kann
man sich also schon von Anfang an mit beschäftigen.
Zuvor solltest du aber das Prinzip der Warteschleife verstanden haben.
Das sind ineinander verschachtelte Zählschleifen, die erst verlassen
werden, wenn die gewünschte (voreingestellte) Anzahl "Runden"
absolviert wurde. Da ein MC während einer Warteschleife nichts anderes
(sinnvolleres) machen kann als eben Wartezeit abzählen, sollte man
Warteschleifen nur für sehr kurze Verzögeringen nutzen und ansonsten
meiden.
...
Eine einfache 16bit-Warteschleife sieht so aus:
FOO:
subi r16, low(1) ; 1
sbci r17, high(1) ; 1
brne FOO ; 2 bei Sprung, sonst 1
; 4n - 1
Ich habe hinter jede Answeisung als Kommentar die Zahl an Taktzyklen
geschrieben, die sie braucht. Die Schleife braucht pro Durchlauf 4
Taktzyklen, man muß also die gewünschte Zahl an Taktzyklen durch 4
teilen (zwei Bits nach rechts schieben), um den Wert zu bekommen, mit
dem der Schleifenzähler initialisiert werden muß. Dann schreibt man die
unteren 8 bit nach r16, die oberen nach r17.
Nagut, dann will ich auch mal ein Warteschleifen-Beispiel posten. Es
diente zum Erzeugen der Wartezeiten beim Initialisieren eines LCDs. Zu
diesem Zeitpunkt (während der Reset-Routine) ist der Timer noch nicht
aktiviert, deshalb diese Warteschleifen.
;====================================================================
; Interne Routinen, werden nur von lcd_init und lcd_clear aufgerufen
;--------------------------------------------------------------------
wait1ms: ;wartet etwa 1 Millisekunden
push wl ;Variablen beschaffen
push wh
ldi wl,low(clock/1000/25) ;Startwert setzen
ldi wh,high(clock/1000/25) ;(AVR-Takt/1000Hz/25 Takte je Runde)
rjmp wait1 ;weiter...
wait5ms: ;wartet etwa 5 Millisekunden
push wl ;Variablen beschaffen
push wh
ldi wl,low(clock/200/25) ;Startwert setzen
ldi wh,high(clock/200/25) ;(AVR-Takt/200Hz/25 Takte je Runde)
rjmp wait1 ;weiter...
wait10ms: ;wartet etwa 10 Millisekunden
push wl ;Variablen beschaffen
push wh
ldi wl,low(clock/100/25) ;Startwert setzen
ldi wh,high(clock/100/25) ;(AVR-Takt/100Hz/25 Takte je Runde)
wait1:
rcall waitend ;7 Takte trödeln
rcall waitend ;7 Takte trödeln
rcall waitend ;7 Takte trödeln
subi wl,1 ;Low-Byte vermindern
sbc wh,null ;High-Byte um Übertrag (Carry) vermindern
brcc wait1 ;nochmal, solange $0000 -> $ffff kein
;Carry setzt
pop wh ;Variablen
pop wl ;entsorgen
waitend:
ret ;fertig bzw.zurück...
Die Register "wl" und "wh" sind meist r24 und r25, das Register
"null" ist meist r3 und enthält immer 0. Die Konstante "clock"
enthält die Taktfrequenz des AVRs. Die veränderten Register werden
gesichert und wiederhergestellt.
...
So, wenn du keinen Timer benutzen möchtest:
Wenn's nicht genau eine ms ist, NOPs einfügen bzw, Zählerwerte
verringern
pause:
ldi counter1,0xFF ;innerer Zähler (255)
ldi counter2,0x3E ;äußerer Zähler (62)
pause_loop_aussen:
dec counter2
pause_loop_innen:
dec counter1
brne pause_loop_innen
brne pause_loop_aussen
ret
Wegen der Taktberechnung ist dass nicht immer genau hinzubekommen, da
einige Befehle auch 2 oder 3 Takte benötigen. Die Rechnerei ist sehr
mühsam, da Sprungbefehle in Abhängigkeit der Bedingung unterschiedliche
Taktanzahlen benötigen. Ich lasse mir meine Warteroutinen vom CodeWizard
im myAVR Workpad genau auf den Wert machen den ich brauche. Hie eine
ziehmlich genaue 1ms Routine (gerechnet incl. Aufruf).
tori
;--------------------------------------------------------------
; Titel : Warteroutinen Beispiel
;--------------------------------------------------------------
; Funktion : WarteRoutinen
; Schaltung :
;--------------------------------------------------------------
; Prozessor : ATmega8
; Takt : 16000000 Hz
; Sprache : Assembler
; Datum : 19.7.2006
; Version : 1.0
; Autor : tori
; Programmer:
; Port :
;--------------------------------------------------------------
; created by myAVR-CodeWizard
;--------------------------------------------------------------
.include "avr.h"
...
;--------------------------------------------------------------------
; Warte-Routine für 1 ms
; die Routine wartet inclusive Aufruf 1,014 ms
;--------------------------------------------------------------------
myWait_1ms:
push r16
ldi r16,1
myWait_1ms_3:
push r16
ldi r16,21
myWait_1ms_2:
push r16
ldi r16,255
myWait_1ms_1:
dec r16
brne myWait_1ms_1
pop r16
dec r16
brne myWait_1ms_2
pop r16
dec r16
brne myWait_1ms_3
pop r16
ret
@tori,
"Wegen der Taktberechnung ist dass nicht immer genau hinzubekommen,
da
einige Befehle auch 2 oder 3 Takte benötigen."
Das kann man aber auch ausnutzen.
Schau Dir mal mein obiges Macro an. Dem kann man jeden Wert von 8 ...
65543 Zyklen übergeben und die werden haargenau eingehalten.
Peter
P.S.:
Habe ich schon gesagt, daß Delayschleifen ohne Timer nur dann stimmen
können, wenn absolut kein einziger Interrupt verwendet wird.
@Tori:
Wer sich von irgendwelchen Codewizzards oder Berechnungsprogrammen
abhängig macht, verlernt im allgemeinen das logische Denken. Dass ein
Verzweigungsbefehl mal mehr und mal weniger Takte braucht, ist
überhaupt kein Problem, denn man sieht ja, welcher Fall eintritt.
...
Normalerweise würde man sagen:
Hol dir von Atmel das 'Instruction Set'
Da sind zumindest alle Befehle drinn, die der
Atmel kennt.
Nur ist das halt nur die halbe Miete. Die Befehle
alleine machens nicht. Erst die Kombination
der Befehle bringt Leben in die Bude.
Schau dich mal hier um
http://www.avr-asm-tutorial.net/avr_en/index.html
Da gibts einige Tutorials die dir die Grundlagen
beibringen.
Alternativ kannst du auch:
Von Atmel das 'Instruction Set Manual' downloaden
(Merkst du was? Ohne dieses Dokument kommst du nicht weit)
und dann einfach mal die bisher geposteten Lösungen
anylsieren. Alle Befehle die du nicht kennst schlägst
du im ISM nach.
Das AVR-Studio hat auch eine Online-Hilfe. Wenn man den Editorcursor auf
einen ASM-Befehl setzt und dann die F1-Taste drückt, dann erfährt man
etwas über den betreffenden Befehl. Ist fast wie beim guten alten
QBASIC...
...