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


von Oliver D. (smasher)


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?

von Matthias L. (Gast)


Lesenswert?

Was?

Poste doch mal das Stück COde.

von Oliver D. (smasher)


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.

von Johannes M. (johnny-m)


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!

von spess53 (Gast)


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

von Johannes M. (johnny-m)


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!

von Oliver D. (smasher)


Lesenswert?

Oh gott,

assembler ist wohl duch um einiges komplizierter als C ;)

von Matthias L. (Gast)


Lesenswert?

>assembler ist wohl duch um einiges komplizierter als C ;)

Nein. Nur die Befehle heißen anders.

von Johannes M. (johnny-m)


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...

von spess53 (Gast)


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

von Oliver D. (smasher)


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 ;)

von Johannes M. (johnny-m)


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)?

von spess53 (Gast)


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

von Oliver D. (smasher)


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?

von Johannes M. (johnny-m)


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

von Matthias L. (Gast)


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,...)

von Oliver D. (smasher)


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.

von Johannes M. (johnny-m)


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.

von spess53 (Gast)


Lesenswert?

Hi

>Sodass ich quasi hinter dem breq befehl lande.

Dann halt so:

       cpi ...
       brne abcd
       call Programm

abcd:  reti

MfG Spess

von Oliver D. (smasher)


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 :)

von Matthias L. (Gast)


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:
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

von Johannes M. (johnny-m)


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.

von Oliver D. (smasher)


Lesenswert?

Ok.

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

von Matthias L. (Gast)


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.

von Oliver D. (smasher)


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.

von spess53 (Gast)


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

von Hannes L. (hannes)


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
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
Noch kein Account? Hier anmelden.