Hallo, ich wollte mal fragen, ob es "erlaubt" ist, in einer Interruptroutine, die von einem Timer aufgerufen wurde, einen vergleich zu starten und per breq zu springen? Nach beendigung des aufgerufenen per breq, nutze ich ret . Danach sollte ich doch wieder in den Interrupt springen, oder? Per breq wird auf jeden fall angesprungen, aber anscheinend komme ich nicht mehr zurrück :( Woran liegt das?
ldi led,0b11111111 ldi status,0b00000000 loop: rjmp loop programm: out PORTC, led com led ldi status,0b00000000 ret timer0_overflow: inc status cpi status, 150 breq programm reti Wenn der Interrupt stattfindet, soll status incrementiert werden. Wenn status = 150 ist, soll programm angesprungen werden. Dort werden meine LEDs beschrieben, der Wert negiert, status zurückgesetzt und dann per ret zurückgesprungen.
Oliver D. wrote: > Hallo, > > ich wollte mal fragen, ob es "erlaubt" ist, in einer Interruptroutine, > die von einem Timer aufgerufen wurde, einen vergleich zu starten und per > breq zu springen? > Nach beendigung des aufgerufenen per breq, nutze ich ret . Die Branch-Befehle und ret haben nichts miteinander zu tun! Bei breq und ähnlichen wird nur verzweigt, aber keine Rücksprungadresse auf dem Stack abgelegt! Das geht nur mit call bzw. rcall und Anverwandtem. Wenn Du ein ret einbaust, ohne dass es vorher einen entsprechenden call gegeben hat, ruinierst Du den Stack (d.h. Du nimmst eine Adresse vom Stack, die vorher für einen anderen Zweck da abgelegt wurde) und es gibt mit großer Wahrscheinlichkeit (eher mit Sicherheit) Bruch. > Danach sollte ich doch wieder in den Interrupt springen, oder? > > Per breq wird auf jeden fall angesprungen, aber anscheinend komme ich > nicht mehr zurrück :( Siehe oben... Merke: Zu jedem call bzw. rcall oder icall gehört genau ein ret . Alle Sprung- und Verzweigungsbefehle wie (r, i)jmp und brXX legen keine Adresse auf den Stack, weshalb man dann auch keine Adresse vom Stack nehmen darf!
Hi Natürlich kannst du Sprünge in einer Interruptroutine machen. Du musst nur gewährleisten, das alle Wege zum 'ret' führen. MfG Spess
spess53 wrote: > Natürlich kannst du Sprünge in einer Interruptroutine machen. Du musst > nur gewährleisten, das alle Wege zum 'ret' führen. Aber nicht mit breq oder so!
Oh gott, assembler ist wohl duch um einiges komplizierter als C ;)
>assembler ist wohl duch um einiges komplizierter als C ;)
Nein. Nur die Befehle heißen anders.
Matthias Lipinsky wrote: >>assembler ist wohl duch um einiges komplizierter als C ;) > > Nein. Nur die Befehle heißen anders. In C gibt es gar keine Befehle...
Hi Wieso nicht mit 'breq'? Übrigens soll das 'ret' natürlich 'reti' heissen. Nur der Konstrukt : breq... ret funktioniert nicht. Weil kein 'call'. MfG Spess
Alles klar, wenn ich nun das reti hier einfüge: programm: out PORTC, led com led ldi status,0b00000000 reti funktioniert es natürlich. Ich denke mal, dass mich dafür einige Köpfen, weil guter Stil natürlich was anderes ist ;)
Oliver D. wrote: > Oh gott, Du darfst mich Johannes nennen... > assembler ist wohl duch um einiges komplizierter als C ;) In gewisser Weise ist das tatsächlich der Fall, aber genaugenommen kann man beides nicht wirklich vergleichen. Wenn C schwerer wäre als Assembler, würde dann noch jemand in C programmieren (abgesehen von Freaks, die das als Herausforderung sehen)?
Hi
>assembler ist wohl duch um einiges komplizierter als C ;)
Nein. Nur muss man sich um einiges mehr selbst kümmern. Dafür geht
einiges einfacher. Und der Assembler will auch nicht klüger als der
Programmierer sein.
Übrigens die paar Befehle, die du da anspringst kannst du bedenkenlos im
Interrupt ausführen.
MfG Spess
Hmm ja, das hast du wohl recht ;) Ist es denn möglich auf den Stack eine neue Adresse zu legen? Also wenn ich nun per BREQ ein label anspringe, damit ich per RET wieder hinter BREQ komme. Ist das irgendwie möglich?
Oliver D. wrote: > Ist es denn möglich auf den Stack eine neue Adresse zu legen? Ja, mit call bzw. rcall (habe ich das nicht deutlich genug geschrieben?) AVR-Tutorial
>Also wenn ich nun per BREQ ein label anspringe, damit ich per RET wieder >hinter BREQ komme. Das ist nicht ratsam. SO entsteht Spaghetti-Code. Der Programmierer sollte mit dem Stapelzeiger direkt nur EINS tun: Ihn initialisieren. Alles weitere sollte man den entsprechenden Befehlen überlassen (push,pop, call,ret,"isr", reti,...)
Jaja, das verstehe ich schon Nur ist es irgendwie "von hand" möglich, eine adresse anzutragen, die dann nach ret angesprungen wird? Sodass ich quasi hinter dem breq befehl lande.
Oliver D. wrote: > Nur ist es irgendwie "von hand" möglich, > eine adresse anzutragen, die dann nach ret angesprungen wird? Warum zum Geier willst Du das von Hand machen? Theoretisch wäre es zwar möglich, aber das macht keiner, weil es gefährlich ist und es genügend Möglichkeiten gibt, es ordentlich zu machen! > Sodass ich quasi hinter dem breq befehl lande. Entweder Du springst mit nem jmp oder rjmp zurück, oder Du verwendest (wie schon mehrfach angesprochen) einen call mit ret (was aber Overhead mit sich bringt) oder Du lässt das ganze Gespringe komplett sein und kopierst die 3 Zeilen da hin, wo sie ausgeführt werden sollen. Ein echter Unterprogrammaufruf wird grundsätzlich mit einem call oder rcall oder icall ausgeführt, und nur dann wird auch mit einem ret an die aufrufende Stelle zurückgesprungen.
Hi
>Sodass ich quasi hinter dem breq befehl lande.
Dann halt so:
cpi ...
brne abcd
call Programm
abcd: reti
MfG Spess
Ok, habe es jetzt mal so gemacht: loop: cpi status, 50 breq programm zurueck: rjmp loop programm: out PORTC, led com led ldi status,0b00000000 rjmp zurueck timer0_overflow: inc status reti Funktioniert prima und ist für den anfänger wohl gut verständlich :)
>Funktioniert prima und ist für den anfänger wohl gut verständlich :)
Finde ich nicht. Sollte man sich nicht einprägen.
Das ganze wird eine Wollknäul-Programmierung:
Machs lieber so:
1 | loop: |
2 | cpi status, 50 |
3 | brne loop |
4 | call gleich |
5 | rjmp loop |
6 | |
7 | gleich: |
8 | out PORTC, led |
9 | com led |
10 | ldi status,0b00000000 |
11 | ret |
12 | |
13 | timer0_overflow: |
14 | inc status |
15 | reti |
Das macht aber dasselbe: Machs lieber so:
1 | loop: |
2 | rjmp loop |
3 | |
4 | |
5 | timer0_overflow: |
6 | inc status |
7 | cpi status, 50 |
8 | brne ungleich |
9 | out PORTC, led |
10 | com led |
11 | ldi status,0b00000000 |
12 | ungleich: |
13 | reti |
Die Methoden haben alle eines gemeinsam: Das ganze willenlose hin- und hergehüpfe verbrät mehr Rechenzeit als die drei Zeilen, die ausgeführt werden sollen. Die Methode mit call ist da noch die schlechteste... EDIT: Matthias' zweite Variante ist in diesem Fall die einzig sinnvolle.
Ok. Kommt das mit der Zeit, dass man erkennt, was "vernünftig" programmiert ist und was nicht?
>Kommt das mit der Zeit, dass man erkennt, was "vernünftig" programmiert >ist und was nicht? Wenn du Ratschläe von hier annimmst und selbststänig dazu Erfahrungen sammelst, ja.
:) Na dann hoffe ich doch mal, dass ich das so mache. Schade, dass die geschichte mit den Sprüngen+Rücksprungadressen im AVR Tutorial nicht sooo genau angesprochen wird. Zumindest habe ich das nicht herausgelesen.
Hi >Kommt das mit der Zeit, dass man erkennt, was "vernünftig" programmiert >ist und was nicht? Was vernünftig ist entscheidet der Programmierer selbst. Es muss natürlich richtig sein. Für ein Problem gibt es in der Regel mehrere richtige Lösungen. Da muss man sich letztendlich für eine entscheiden. Entscheidungskriterien können z.B. Codegrösse/Laufzeit sein. Lass dich nicht von Kommentaren wie 'das macht man nicht' irritieren. Allerdings solltest du dir solche Sachen, wie das Sichern und Zurückschreiben des SREG in einer Interruptroutine ganz schnell angewöhnen. MfG Spess
Was in einem Fall vernünftig sein kann, kann im anderen Fall schon wieder Unfug sein, es kommt immer auf den Einzelfall an. Obige Routine
1 | loop: |
2 | rjmp loop |
3 | |
4 | |
5 | timer0_overflow: |
6 | inc status |
7 | cpi status, 50 |
8 | brne ungleich |
9 | out PORTC, led |
10 | com led |
11 | ldi status,0b00000000 |
12 | ungleich: |
13 | reti |
ist z.B. recht vernünftig. Es ist allerdings nicht unbesehen als Vorlage für weitere Programme geeignet, denn sobald in "loop" noch etwas Programmcode dazu kommt, sollte in der ISR unbedingt das SREG gesichert werden. Dazu kommt noch die Frage des Stils bzw. der Lesbarkeit. Man sollte sich angewöhnen, Programme mit Kommentaren zu ergänzen, in denen nachgelesen werden kann, warum man das gerade so und nicht anders realisiert. Dazu gehört auch, dass man bei der Angabe von Zahlen das Format wählt, das die Zahl am treffendsten bezeichnet. "ldi status,0b00000000" sagt mir, dass alle Bits dieses Registers auf L gesetzt werden (halbwegs aussagekräftig, wenn jedes Bit eine andere Bedeutung hat). "status" ist aber ein stinknormaler Zähler, da reicht es, wenn man ihn auf 0 setzt. Die Zeile ldi status,0 ;Zähler löschen hat demnach eine bedeutend bessere Lesbarkeit. Klar, das Programm arbeitet dadurch auch nicht besser, aber es ist besser lesbar und wartbar. Ohne Änderung der Funktion könnte der Quellcode auch so aussehen:
1 | loop: ;Hauptschleife |
2 | rjmp loop ;nochmal dasselbe... |
3 | |
4 | |
5 | timer0_overflow: ;ISR, Timer0-Überlauf, alle xxx ms |
6 | ; in srsk,sreg ;SREG sichern (hier nicht erforderlich) |
7 | dec status ;Zähler runterzählen |
8 | brne ungleich ;schon unten? - nein... |
9 | ldi status,50 ;ja, Zähler auf Startwert setzen, |
10 | out PORTC, led ;Bitmuster ausgeben |
11 | com led ;und für nächste Ausgabe invertieren |
12 | ungleich: |
13 | ; out sreg,srsk ;SREG wiederherstellen (hier nicht nötig) |
14 | reti ;fertig und zurück... |
Einen solchen Quelltext kann auch Jemand lesen und verstehen, der keine große ASM-Erfahrung hat. ...
Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.