mikrocontroller.net

Forum: Mikrocontroller und Digitale Elektronik In Interruptroutine einen aufruf/sprung machen


Autor: Oliver D. (smasher)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
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?

Autor: Matthias Lipinsky (lippy)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Was?

Poste doch mal das Stück COde.

Autor: Oliver D. (smasher)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
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.

Autor: Johannes M. (johnny-m)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
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!

Autor: spess53 (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
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

Autor: Johannes M. (johnny-m)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
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!

Autor: Oliver D. (smasher)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Oh gott,

assembler ist wohl duch um einiges komplizierter als C ;)

Autor: Matthias Lipinsky (lippy)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
>assembler ist wohl duch um einiges komplizierter als C ;)

Nein. Nur die Befehle heißen anders.

Autor: Johannes M. (johnny-m)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
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...

Autor: spess53 (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
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

Autor: Oliver D. (smasher)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
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 ;)

Autor: Johannes M. (johnny-m)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
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)?

Autor: spess53 (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
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

Autor: Oliver D. (smasher)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
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?

Autor: Johannes M. (johnny-m)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
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

Autor: Matthias Lipinsky (lippy)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
>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,...)

Autor: Oliver D. (smasher)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
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.

Autor: Johannes M. (johnny-m)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
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.

Autor: spess53 (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Hi

>Sodass ich quasi hinter dem breq befehl lande.

Dann halt so:

       cpi ...
       brne abcd
       call Programm

abcd:  reti

MfG Spess

Autor: Oliver D. (smasher)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
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 :)

Autor: Matthias Lipinsky (lippy)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
>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:
loop:
  cpi status, 50
  brne loop
  call gleich
  rjmp loop

gleich:
      out PORTC, led
      com led
      ldi status,0b00000000
      ret

timer0_overflow:
  inc status
  reti

Das macht aber dasselbe:
Machs lieber so:
loop:
 rjmp loop


timer0_overflow:
  inc status
  cpi status, 50
  brne ungleich
  out PORTC, led
  com led
  ldi status,0b00000000
ungleich:
  reti

Autor: Johannes M. (johnny-m)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
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.

Autor: Oliver D. (smasher)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Ok.

Kommt das mit der Zeit, dass man erkennt, was "vernünftig" programmiert 
ist und was nicht?

Autor: Matthias Lipinsky (lippy)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
>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.

Autor: Oliver D. (smasher)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
:)


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.

Autor: spess53 (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
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

Autor: Hannes Lux (hannes)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
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
loop:
 rjmp loop


timer0_overflow:
  inc status
  cpi status, 50
  brne ungleich
  out PORTC, led
  com led
  ldi status,0b00000000
ungleich:
  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:
loop:                 ;Hauptschleife
 rjmp loop                ;nochmal dasselbe...


timer0_overflow:      ;ISR, Timer0-Überlauf, alle xxx ms
;  in srsk,sreg            ;SREG sichern (hier nicht erforderlich)
  dec status              ;Zähler runterzählen
  brne ungleich           ;schon unten? - nein...
  ldi status,50           ;ja, Zähler auf Startwert setzen,
  out PORTC, led          ;Bitmuster ausgeben
  com led                 ;und für nächste Ausgabe invertieren
ungleich:
;  out sreg,srsk           ;SREG wiederherstellen (hier nicht nötig)
  reti                    ;fertig und zurück...

Einen solchen Quelltext kann auch Jemand lesen und verstehen, der keine 
große ASM-Erfahrung hat.

...

Antwort schreiben

Die Angabe einer E-Mail-Adresse ist freiwillig. Wenn Sie automatisch per E-Mail über Antworten auf Ihren Beitrag informiert werden möchten, melden Sie sich bitte an.

Wichtige Regeln - erst lesen, dann posten!

  • Groß- und Kleinschreibung verwenden
  • Längeren Sourcecode nicht im Text einfügen, sondern als Dateianhang

Formatierung (mehr Informationen...)

  • [c]C-Code[/c]
  • [avrasm]AVR-Assembler-Code[/avrasm]
  • [code]Code in anderen Sprachen, ASCII-Zeichnungen[/code]
  • [math]Formel in LaTeX-Syntax[/math]
  • [[Titel]] - Link zu Artikel
  • Verweis auf anderen Beitrag einfügen: Rechtsklick auf Beitragstitel,
    "Adresse kopieren", und in den Text einfügen




Bild automatisch verkleinern, falls nötig
Bitte das JPG-Format nur für Fotos und Scans verwenden!
Zeichnungen und Screenshots im PNG- oder
GIF-Format hochladen. Siehe Bildformate.
Hinweis: der ursprüngliche Beitrag ist mehr als 6 Monate alt.
Bitte hier nur auf die ursprüngliche Frage antworten,
für neue Fragen einen neuen Beitrag erstellen.

Mit dem Abschicken bestätigst du, die Nutzungsbedingungen anzuerkennen.