Hallo Leute!!
Ich möchte eine Zeiverzögerung von einer knappen Sekunde (800ms) in mein
Programm einbauen. Desweiteren soll das Programm auch erst dann mit dem
Ablauf weiter machen, wenn die Flanke von High auf Low am Eingang
(Taster) gewechselt hat. Verwirklichen möchte ich mit meinem Programm
ein D-Flip-Flop. Taster drücken, LED an, Taster wieder drücken, LED
wieder aus.
Ich bitte um leicht verständliche Antworten. Dies ist mein erstes
Programm und ich verstehe noch nicht sonderbar viel von Assambler, weil
ich mich erst seit ca. 5 Tagen intensiv mit dem Thema AVRs und Assembler
befasse und außer in der Spate Elektronik, quasi keine Vorkenntnisse
habe.
Danke!!
Hier der Code: (Im Anhang noch mal der Code mit Kommentaren)
Wo ist die Frage?
Wie bekommst du einen 800ms Delay hin?
http://electronics-lab.com/downloads/mcu/003/index.html
Da kannst du dir einen netten Warteschleifen Generator runterladen.
Ist aber nicht wirklich effizient, falls du noch Erweiterungen einplanen
solltest, da der Prozessor während der 800ms nur blöde die Zeit
verplempert. Mt einem Timer wärs etwas effizienter.
Falls es im Batteriebetrieb laufen soll, könnte man den Taster an einen
INT0 oder 1 hängen und solange die LED aus ist und kein Taster gedrückt
ist den µC in den Sleep-Modus versetzen. Der wacht dann bei Tasterdruck
wieder auf.
Das kannst du dir sparen, da niemals im Programmablauf erreicht.
Jörg S. schrieb:> ;-----------------------------------------------------------------------> ; Ende> ;----------------------------------------------------------------------->> Ende:
Danke INGA,
das funktioniert schon mal, aber die Zeit, die nun vergeht, liegt bei
ca. 3 Sekunden, bis das Programm weiter läuft. Wie kann ich die Zeit auf
800ms oder von mir aus auch 1 sekunde anpassen??
Grüße, Jörg
Dann wird wohl der ATmega8 nicht mit 4, sondern mit 1 MHz laufen. Also
die Fuses entsprechend einstellen oder die Konstanten der Zeitschleife
passend berechnen.
Habe es jetzt hinbekommen, indem ich ein wenig mit dem "delay loop
generator" rumgespielt habe und die Werte veränderte. Ich verstehe zwar
in keiner Weise was der Generator macht und wodurch genau der Code die
Verzögerung verursacht, aber es funktioniert.
Dass das Programm erst weiterläuft, sobald der Taster nach der
Zeitverzögerung losgelassen wird, habe ich auch verwirklichen können.
Der vollständigkeisthalber lege ich noch mal den fertigen Code als Datei
bei.
Danke, Jörg
> Ich verstehe zwar in keiner Weise ...
Ihr Controller läuft mit 1 MHz, also benötigt 1 Takt die Zeit von 1 µs.
Der Befehl dec benötigt 1, brne 2 Takte.
Jörg S. schrieb:> Habe es jetzt hinbekommen, indem ich ein wenig mit dem "delay loop> generator" rumgespielt habe und die Werte veränderte. Ich verstehe zwar> in keiner Weise was der Generator macht und wodurch genau der Code die> Verzögerung verursacht, aber es funktioniert.
Sind drei verschachtelte Schleifen. Die innerste zählt 0x92 ($ =
Hexadezimalwert folgt) mal r19 runter, die mittlere zählt dabei r18
runter (6 Mal) und die äußerste wird 0x97 mal ausgeführt und zählt dabei
r17 runter. Ist r17 null hört der Kram auf.
Die Zahl der Zyklen entsteht dadurch dass dec+brne (wenn der Sprung
genommen wird, was bei fast allen Durchläufen der Fall ist) drei Zyklen
braucht.
((0x92*3 + 3) * 0x06 + 3) * 0x97 = 3999999
Die +3 sind dabei die drei Zyklen vom dec+brne der jeweils nächstäußeren
Schleife, die dann noch draufkommen.
Mal auf die schnelle... Aber ungetestet und du müsstest es noch auf den
µC und auf den Takt anpassen (Fuses lernen!!! Sei vorsichtig). Sollte
aber funktionieren.
Die Delay-Routine ist nicht 100% da ich vergessen habe die Takte für
rcall und ret abzuziehen.
Aber im groben und ganzen sind das mit Toleranz 800ms. ;-)
Ach ja. Wenn du einen anderen Takt als 8MHz hast musst du natürlich auch
die delay-Routine anpassen, da die in Takten rechnet und nicht nach
Zeit.Ist aber nur Copy`n`Paste... ;) Das Programm hast ja jetzt. Ich
sollte mich aber Inga anschließen und dir die Verwendung eines Timers in
Verbindung mit dem Sleep-Mode nahelegen. Einmal aus Lern- und auch aus
Energiespargründen.
Pardon, aber...:
> ldi TMP1, (0<<LED)> out PORTB, TMP1 ; Setz Ausgang auf low. Die LED müsste leuchten> ldi TMP1, (0<<LED)> out PORTB, TMP1 ; Setz Ausgang auf High- Die LED müsste erlöschen
??
Und jeweils direkt im Anschluss §delay aufzurufen, die von TMP1 abhängig
ist?
Ja. Na und?
Das Register kann doch ruhig überschrieben werden, da die Werte nicht
mehr benötigt werden. Werden doch nach Ablauf der Zeitschleife neu
geladen.
Die Zeitverzögerung in delay hängt von TMP1 ab, ist also eigentlich
undefiniert. Das fällt spätestens dann auf, wenn die jetzt falsche
Ausgabensteuerung korrigiert wird.
>> ldi TMP1, (0<<LED)>> out PORTB, TMP1 ; Setz Ausgang auf low. Die LED müsste leuchten>>> ldi TMP1, (0<<LED)>> out PORTB, TMP1 ; Setz Ausgang auf High- Die LED müsste erlöschen
Also für auf high setzen würde ich auch nicht 0<<LED in den Port
schieben.
Aber was, wenn an einem anderen Pin von PORTB noch was dranhängt, was
nicht geändert werden soll? Ich würde das auf sbi/cbi ändern. Dann wird
wirklich nur das eine Bit geändert und der Rest bleibt unangetastet.
Irgendwann wird was an einen anderen Pin von PORTB gehangen und wenns
erst im nächsten Projekt ist. Aber dann hat man sich bis dahin schon
gemerkt, dass man Bits einzeln ändern kann, und nicht das komplette
Portregister überschreiben muss.
foo schrieb:> Aber was, wenn an einem anderen Pin von PORTB noch was dranhängt, was> nicht geändert werden soll? Ich würde das auf sbi/cbi ändern. Dann wird> wirklich nur das eine Bit geändert und der Rest bleibt unangetastet.
Stimmt. Hast du Recht. Mein Codebeispiel sollte aber auch nur als
Beispiel dienen.
foo schrieb:> Irgendwann wird was an einen anderen Pin von PORTB gehangen und wenns> erst im nächsten Projekt ist. Aber dann hat man sich bis dahin schon> gemerkt, dass man Bits einzeln ändern kann, und nicht das komplette> Portregister überschreiben muss.
s.o.
foo schrieb:> Also für auf high setzen würde ich auch nicht 0<<LED in den Port> schieben.
Siehe ersten Beitrag mit Code. Da kam es mir invertiert vor...
S. Landolt schrieb:> Die Zeitverzögerung in delay hängt von TMP1 ab, ist also eigentlich> undefiniert.
Oooops. Den Fehler seh ich auch erst jetzt. Dankeschön. Eigentlich
sollte die Schleife so lauten:
Was jetzt noch offen wäre ist... Was ist wenn man den Taster länger als
800ms drückt, bzw. gedrückt hält. dann würde der Ausgang mit 800ms
toggeln.
Also doch der Vorschlag von Inga oder eine weitere Abfrage welche
abfragt ob der Taster losgelassen wurde und erst danach bei loop3
weitermacht. Dabei würde aber auch keine Taster-Entprellung
softwareseitig benutzt...
Schau dir mal das AVR-Tutorial an. Da wird so etwas hervorragend
erklärt.
Ich finde die hier verwendete Zeitroutine wirklich SEHR unintuitiv. Das
heisst nicht, dass sie nicht funktioniert. Nur arbeiten würde ich so
nicht wollen. Hier ist meine eigene, die ich für meine Projekte
verwende:
1
.macro delayms ms //Benötigt InsertDelayMs. 6 Bytes pro Benutzung. Maximal 65535ms.
2
ldi r24,lo8(\ms)
3
ldi r25,hi8(\ms)
4
rcall delayms
5
.endm
6
7
.macro InsertDelayMs khz //Da push/pop/ret nicht beachtet werden, ist die Routine immer 10-11 Ticks länger als gewünscht.
8
delayms:
9
push XL
10
push XH
11
delayanotherms:
12
ldi XL,lo8(\khz / 4 - 2) ;Max 256 MHz CPU.
13
ldi XH,hi8(\khz / 4 - 2)
14
delayonems:;
15
sbiw XL,1
16
brne delayonems
17
rjmp .+0
18
sbiw r24,1
19
brne delayanotherms
20
pop XH
21
pop XL
22
ret
23
.endm
Wenn ich in meinem Projekt eine 1 MHZ MCU verwende, muss ich die
"Funktion" nur einmal einbinden und kann kann mit dem zweiten Macro
beliebig viele Delays in den Code einbauen, wobei jeder Delay 6 Bytes
Code belegt.
1
KHZ = 1000
2
3
mainloop:
4
blabla code
5
delayms 800
6
mehr code
7
delayms 100
8
rjmp mainloop
9
10
InsertDelayMs KHZ
Die Funktion ist jedoch immer +-10 Ticks länger, da ret und push nicht
abgezogen werden. Warum "10-11"? Je nach MCU braucht die eine mehr oder
weniger Ticks für ret, push oder pop. Ich möchte aber nicht für jede MCU
eine eigene delayms bauen, da die gegebene Genauigkeit für eine delayms
Funktion ausreichend ist. Für taktgenaue Funktionen habe ich andere
Macros.
Wer mag, kann beim ersten Macro noch die Register r24 und r25 sichern.
Ich arbeite jedoch so, dass die Register 24 und 25 generell immer als
"unsicher" gelten und ich immer davon ausgehe, dass diese bei
Macroaufrufen zerstört werden.
Hi
Nun, du solltest dir abgewöhnen, mit Delays die Zykluszeit
hochzuschrauben. Statt dessen befasse dich mit dem Timer und leite dir
einen ms, 10 ms oder 1s Signal ab. Das ist nicht besonders schwer. Das
Signal ist eigentlich nichts anderes wie en Bit. Ist es gesetzt, dann
rufst du eine Routine auf, die das Ereignis bearbeitet und das Bit
zurücksetzt. So reagiert dein Zyklus immer nur auf dieses Ereignisbit.
Angenommen, im Byte "TimeFlag" setzt du im Timerinterrupt das Bit 0 jede
ms, das Bit 2 alle 10 ms, das Bit 2 alle 100ms und das Bit 3 jede
Sekunde. Dafür benötigst du entsprechend Zähler, die du in der ISR dann
prüfst. Diese gesetzten Bits sind dann deine Ereignisflags. In einem
Beitrag der AVR-Praxis-Gemeinde steht das ziemlich genau beschrieben.
Such mal nach "keine Angst vor Assembler" im Forum von AVR_Praxis unter
FAQ. Dort solltest du weitere Infos finden, was du hier in den Tutorials
vielleicht vermisst.
Gruß oldmax
oldmax schrieb:> Nun, du solltest dir abgewöhnen, mit Delays die Zykluszeit> hochzuschrauben. Statt dessen befasse dich mit dem Timer
Das kann man nur dick unterstreichen.
Alles andere ist reichlich laienhaft, ineffizient + wenig universell
verwendbar- und führt früher oder später zu Problemen.
Für alle Problemchen dieser Art und noch sehr viel mehr sollte man /in
allen Programmen/ ganz grundsätzlich einen festen Timerinterupt
installieren, z.B. alle 100ms. Damit und darin lassen sich nicht nur
Verzögerungen elegant umsetzen, sondern noch Tausend andere Aufgaben
mehr, mit denen sich ein Hauptprogramm dann nicht mehr rumzuschalgen
hat. Zum Beispiel Tasterabfragen samt Entprellung oder
Ausgaben/Initialisierungen aller Art. Das Hauptprogramm fragt nur noch
Flags oder auch Datenbytes ab oder setzt diese- Datenfelder die damit
das Interface zum "Mini-Betriebssystem" Timerinterrupt darstellen.
So strukturiert man, so vereinfacht man, so optimiert man
Ressourcenverbrauch, so schafft man Übersicht!
Oliver H. schrieb:> ldi TMP1, $F1> delay_0: ldi TMP2, $35 ;Hexadezimale schreibweise> delay_1: ldi TMP3, $A6
Ich verstehe nicht, wozu man sich kryptische Hex-Werte antut. Eine
Änderung und sie müssen neu berechnet werden.
Der Assembler kann Konstanten doch selber ausrechnen.
Allerdings kann er kein float.
Hi
Moby schrieb:> Alles andere ist reichlich laienhaft, ineffizient + wenig universell> verwendbar- und führt früher oder später zu Problemen.
Na ja, ganz so hart würd ich es nicht sagen, obwohl es stimmt. Aber Jörg
ist noch Anfänger und diesen Fehler machen wohl alle. Es ist auch gar
nicht so einfach, sich eine Verzögerung vorzustellen, auf die man nicht
warten muss oder anders herum gesagt, das eine Verzögerung Auch
verhindert, das der Rst vom Programm bearbeitet wird. Ergebis sind dann
völlig überflüssige Abfragen von Tastern in einem Interrupt, weil ja zu
befürchten ist, das ein Tastendruck sonst nicht wahr genommen wird. Der
trick, eine kurze Zykluszeit zu bekommen und hier rede ich von µs, ist
in der Ereignisbearbeitung zu finden. Selbst ausgereifte und
umfangreiche Programme lassen sich mit Job- oder Eventbits schnell und
effektiv ausführen. Und auch ohne die Übersichtlichkeit zu verlieren. In
dem angegebenen Beitrag hab ich dieses Problem ziemlich ausführlich
beschrieben, so das ich hier auf weitere Programmcodes verzichte.
Gruß oldmax
Hi
@Peter Dannegger
Ich nehme an, dein Beispiel bezieht sich auf das Setzen von Werten in
Variablen und ist kein ernst gemeinter Beitrag zu Wartezeiten. Denn
genau dieses Konstrukt legt den Controller für die Schleifendurchläufe
auf Eis und dann sind Ereignisse, wie gedrückte Taster wiederum nur
sicher zu erfassen, wenn das mit einer ISR bearbeitet wird.
Gruß oldmax