Forum: Mikrocontroller und Digitale Elektronik Arbeitsregister retten


von Bestromer (Gast)


Lesenswert?

Hallo,

Um Arbeitsregister in einer Interruptroutine zu retten kann ja z.B. in 
Assembler prima push und zur Wiederherstellung pop benutzt werden, um 
diese zwischenzeitlich auf dem Stack auszulagern.
Da dies öfter mal mit den selben Registern in untersch. Int.-Routinen 
passiert liegt es nahe dies in ein Unterprogramm "push_all" "pop_all" zu 
packen....dummerweise geht das nicht,da meine Rücksprungadresse 
ebenfalls
auf dem Stack liegt und ich ja mit den push's diese zustable....

Man kann natürlich ein Macro schreiben,damit es übersichtlicher 
wird,aber so richtig befriedigend ist das auch nicht.

Spricht irgend etwas dagegen die Register statt i auf dem Stack selber 
im Ram abzulegen, also statt push-->sts und pop-->lds zu nutzen?
Der Stack liegt ja auch nur im Ram, gibts da bei den zwei Varianten 
unterschiede in der Zugriffszeit?

Wie macht ihr so etwas?

von P. K. (pek)


Lesenswert?

Das bequeme am Stack ist ja, dass Du Dich nicht um die Adressierung zu 
kümmern brauchst, wenn die Interrupt-Routine fertig abgelaufen ist, 
lädst Du einfach wieder Deine Register vom Stack.

Wenn Du die Register ins Memory schreibst, musst Du Dir immer noch 
merken, wo Du sie hingeschrieben hast, d.h. Du brauchst ein 
Adress-Management.

Die Zugriffszeit hängt davon ab, was für Speicher Du hast. SRAM dürfte 
relativ schnell sein, DDR im Random Access unter Umständen etwas lästig.

von Lothar M. (Firma: Titel) (lkmiller) (Moderator) Benutzerseite


Lesenswert?

Bestromer schrieb:
> dummerweise geht das nicht,da meine Rücksprungadresse ebenfalls
> auf dem Stack liegt und ich ja mit den push's diese zustable....
Warum "stabelst" du den da irgendwas zu? Weißt du, wie der Stack 
funktioniert? Etwas, das schon drauf liegt, wird natürlich wieder 
abgestapelt. Der Trick dabei ist: vor dem Rücksprung muss(!!) der Stapel 
einfach wieder abgestapelt werden.

> Spricht irgend etwas dagegen die Register statt i auf dem Stack selber
> im Ram abzulegen, also statt push-->sts und pop-->lds zu nutzen?
Ja. Denk mal nach, warum der Stack keine absoluten Adressen hat, 
sondern eben gestapelt wird: dadurch ist jede Information auf dem 
Stapel einmalig. Es kann z.B. 20 mal das Register R1 auf dem Stapel 
geben. Wenn du eine einzige RAM-Adresse für das R1 hast, dann geht das 
schon beim zweiten Aufruf dieser Funktion schief...
Das Stichwort dazu ist "reentrante Funktion".

von Peter D. (peda)


Lesenswert?

Bestromer schrieb:
> Wie macht ihr so etwas?

1. garnicht, macht alles der C-Compiler.

2. früher zu Assemblerzeiten habe ich einfach von den 32 Registern 
einige den Interrupts reserviert, da der AVR ja keine nested Interrupts 
kann.
D.h. die Interrupts sind fast immer ohne Push/Pop ausgekommen.
z.B.:
für Interrupts: R10..R17, Y-Pointer
fürs Main: R0..R9, R18..R25, X-, Z-Pointer

Und wenn der Interrupt mal LPM-Zugriffe brauchte, hat er einfach mit 
"MOVW R10, R30" den Z-Pointer gesichert (nur 1 statt 4 Zyklen).

von Karl H. (kbuchegg)


Lesenswert?

Lothar M. schrieb:
> Bestromer schrieb:
>> dummerweise geht das nicht,da meine Rücksprungadresse ebenfalls
>> auf dem Stack liegt und ich ja mit den push's diese zustable....
> Warum "stabelst" du den da irgendwas zu? Weißt du, wie der Stack
> funktioniert? Etwas, das schon drauf liegt, wird natürlich wieder
> abgestapelt. Der Trick dabei ist: vor dem Rücksprung muss(!!) der Stapel
> einfach wieder abgestapelt werden.

Ich hab auch eine Weile gebraucht, aber er hat recht.

Er kann nicht einen CALL zu einer push_all machen, wenn die so aussieht
1
push_all:  push r16
2
           push r17
3
           push r18
4
           ret
5
6
pop_all:   pop r18
7
           pop r17
8
           pop r16
9
           ret

man könnte natürlich mit dem Stackpointer tricksen und sich die 
Returnadresse aus dem Stack raussuchen und den Stack in diesen 
Funktionen entsprechend manipulieren, so dass beim ret aus push_all 
alles in der richtigen Reihenfolge auf dem Stack liegt.
Ist aber Aufwand.

von Karl H. (kbuchegg)


Lesenswert?

Bestromer schrieb:

> Man kann natürlich ein Macro schreiben,damit es übersichtlicher
> wird,aber so richtig befriedigend ist das auch nicht.
>
> Spricht irgend etwas dagegen die Register statt i auf dem Stack selber
> im Ram abzulegen, also statt push-->sts und pop-->lds zu nutzen?

Aha.
Und das ist befriedigender?

Egal wie du es drehst und wendest. Du musst am Anfang deiner ISR etwas 
tun. Ob das jetzt ein CALL auf eine Funktion ist, die SRAM sichert, oder 
die Benutzung eines Makros, ist doch für dich als Programmierer Jacke 
wie Hose. Nur das so ein Makro viel einfacher zu schreiben ist.

von Bestromer (Gast)


Lesenswert?

Peter D. schrieb:
> 2. früher zu Assemblerzeiten habe ich einfach von den 32 Registern
> einige den Interrupts reserviert, da der AVR ja keine nested Interrupts
> kann.
> D.h. die Interrupts sind fast immer ohne Push/Pop ausgekommen.
> z.B.:
> für Interrupts: R10..R17, Y-Pointer
> fürs Main: R0..R9, R18..R25, X-, Z-Pointer

Hallo Peter,
interessante herangehensweise,ist ganz nach meinem Geschmack...vielen 
Dank für diesen Denkanstoß :)

Lothar M. schrieb:
> Warum "stabelst" du den da irgendwas zu? Weißt du, wie der Stack
> funktioniert?
Hallo Lothar,
klar weis ich wie ein Stack funktioniert ;-)
Wie Du schon sagtest, man muss auch alles wieder ordentlich vom Stapel 
herunter nehmen, bevor man das UP verlässt.
"Push_all" als Unterprogramm sagt ja schon aus,das dies nicht geschehen 
wird...also so in etwa:
1
call push_all     ;alle register retten
2
....              ;Anweisungen für die Interruptroutine
3
....
4
....
5
call pop_all      ;alle register wieder herstellen

Rufe ich mein Unterprogramm mittels "call push_all" auf, wird zuerst 
meine
Rücksprungadresse auf dem Stack gelegt, dann meine 
Pushanweisungen....wenn ich jetzt mittels Return das Unterprogramm 
verlassen wollte um z.B. die Interruptroutine abzuarbeiten würde ich 
mich wundern was der Controller so alles kann wenn er von der Leine 
gelassen wird :)
Deshalb macht man sowas ja auch nicht und nun war ich auf der Suche nach 
Alternativen.
Möglich wäre jetzt an Stelle des Stacks einfach alle Register selber in 
den Ram zu sichern, in etwa so...
1
.dseg
2
  rescue:   .byte = 16
3
.cseg
4
5
push_all:
6
  sts r16,rescue
7
  sts r17,rescue+1
8
  ....
9
  sts r(n),rescue+(n)
10
  ret
11
12
13
pop_all:
14
  lds rescue,r16
15
  lds rescue+1,r17
16
  ....
17
  lds rescue+(n),r(n)
18
  ret

Das war auch nur ein Ansatz von mir...momentan möchte ich ein wenig in 
Assembler "reinriechen" und mal alle Fallstricke beleuchten.
Man müsste jetzt mal die Taktzyklen anschauen sts/lds vs. push/pop 
...aber wie gesagt, es war ganz interessant zu erfahren wie ihr das 
macht, wobei mir Peters Ansatz sehr gut gefällt.

Danke Dir Lothar!

von Lothar M. (Firma: Titel) (lkmiller) (Moderator) Benutzerseite


Lesenswert?

Karl H. schrieb:
> Er kann nicht einen CALL zu einer push_all machen, wenn die so aussieht
Dieses pauschale Pushen ALLER Register kostet doch unnötig Zeit, die 
gerade in einer ISR eh' schon knapp ist.

Und für eine "normale" Funktion ist so eine nicht reentrante Lösung der 
manuellen Sicherung im RAM unnütz, denn so könnte z.B. in keinem 
Interrupt ein solche "optimierte" Funktion aufgerufen werden, weil es ja 
sein kann dass genau diese Funktion unterbrochen wurde.

Nein, das Stack-Konzept funktioniert schon ganz gut, und nur deshalb hat 
es denSprung in die allermeisten Prozessoren und Controller geschafft. 
Es würde mich sehr wunder, wenn da einer so vom Fleck weg etwas 
Effizienteres und ähnlich erfinden würde...

von Bestromer (Gast)


Lesenswert?

Karl H. schrieb:
> Aha.
> Und das ist befriedigender?
>
> Egal wie du es drehst und wendest. Du musst am Anfang deiner ISR etwas
> tun. Ob das jetzt ein CALL auf eine Funktion ist, die SRAM sichert, oder
> die Benutzung eines Makros, ist doch für dich als Programmierer Jacke
> wie Hose. Nur das so ein Makro viel einfacher zu schreiben ist.

:D ....Naja befriedigender nicht wirklich ...
Es sind ein paar Bytes weniger im Flash, aber das fällt kaum ins 
Gewicht...es ist schön Eure Praxis zu der Sache kennenzulernen.
Man sucht ja immer den effektivsten Weg und das ist oft eine 
Kratwanderung.
Oftmals gibt es schon tolle raffinierte Code-Konstrukte wie man sie bei 
Peter antrifft,im ersten Moment fragt man sich "warum macht er das 
so",dann kommt die Erleuchtung, man fängt an zu schmunzeln und merkt da 
steckt Zeit und Erfahrung drin.
Also, in dem Sinn auch noch mal ein dickes Dankeschön an Euch, das ihr 
Eure Erfahrung teilt....ich hoffe irgendwann auch mal etwas zurück geben 
zu können!!!

von c-hater (Gast)


Lesenswert?

Peter D. schrieb:

> 2. früher zu Assemblerzeiten habe ich einfach von den 32 Registern
> einige den Interrupts reserviert, da der AVR ja keine nested Interrupts
> kann.

Zwar ist die Reservierung von Registern für ISRs sehr oft überaus 
nützlich, aber die Begründung dafür ist falsch. Natürlich kann der AVR 
nested interrupts, denn es ist problemlos möglich, in einer ISR "sei" zu 
benutzen, was effektiv genau das ermöglicht: nested interrupts.

Das einzige Problem ist, zu verhindern, dass ein Interrupt sich selbst 
"nested" und die Lösung ist absolut trivial (nur leider relativ 
ineffizient): man schaltet die spezifische Interruptquelle temporär ab, 
bevor man "sei" ausführt, also den anfänglichen exclusiven Teil der ISR 
verläßt und wieder ein, bevor man aus dem abschließenden exclusiven Teil 
der ISR wieder zurückkehrt.

Man kann das sogar mit einer speziellen Behandlung für den Fall 
kombinieren, der zu einem "self-nesting" geführt hätte. In diesem Fall 
kann man nämlich die ISR zu einer Schleife (mit hoffentlich nur zwei 
Durchläufen...) schließen und damit den Interrupt-Overhead für die 
zweite Auslösung größtenteils einsparen. Mindestens fünf Takte mehr, um 
dem System vielleicht den Verbleib in der Echtzeit zu ermöglichen, das 
ist schon was...

Nunja, wirklich sinnvoll ist das Ganze natürlich nur für Interrupts, die 
aus Sicht des Systems "unvorhersehbar" auftreten (im Allgmeinen selten, 
aber gelegentlich auch als Burst) und die relativ länglichen Code 
triggern.

von Bestromer (Gast)


Lesenswert?

Hallo c-hater,
....nested Interrupts,hatte mir vorgenommen heute Abend mal 
nachzuschauen was der Peter damit meint, da ich den Begriff noch nicht 
kannte.
Nun hab ich ja schon mal einen Einblick !!!
Ist ja auch eine nette Spielwiese, mal sehen wo man das verbauen kann :)
Danke Dir und einen schönen Abend noch....

von Possetitjel (Gast)


Lesenswert?

Karl H. schrieb:

> man könnte natürlich mit dem Stackpointer
> tricksen [...]
> Ist aber Aufwand.

Nicht viel. Prinzip:

  push r1
  call push_almost_all
  .
  .
  .
push_almost_all:
  pop r1
  push r2
  push r3
  ...
  push r99
  push r1
  ret

Sind genau zwei Befehle mehr; einmal pop r1, einmal push r1.
Beim Rückladen dasselbe Prinzip, aber umgekehrte Reihenfolge.

Allerdings erschließt sich mir der Sinn nicht. Übermäßige
Stackfrickelei ist ein sicherer Weg in die Orthopädie.

von Moby A. (moby-project) Benutzerseite


Lesenswert?

Bestromer schrieb:
>"push_all" "pop_all"

kann man natürlich machen da die AVRs meist immer noch genügend Leistung 
bieten.
Besser ist es aber, sich genaue Gedanken zur Registerverwendung auch 
anhand ihrer spezifischen Eigenschaften zu machen. Die Pointerregister 
nehme dank ihrer Universalität gern für Interrupts her und diese sind 
dann eben auch (nur) zu sichern. Soll es ganz schnell gehen dann die 
Pointer- über Movw in andere Registerpärchen, z.B. ab R10. Ein weiteres 
Register ist dann noch zur Statusflag-Sicherung reserviert. Öfter als 
man denkt lässt sich die gesamte Programmfunktionalität aber auch in nur 
einem (meist Timer-) Interrupt verpacken - mit schlafendem, leeren 
Hauptprogramm. Dann ist gar nichts mehr explizit zu sichern...

von Moby A. (moby-project) Benutzerseite


Lesenswert?

Possetitjel schrieb:
> Übermäßige Stackfrickelei
> ist ein sicherer Weg in die Orthopädie.

Du meintest wohl Psychiatrie :-)
Zuweilen bringt das Drehen am Stackpointer platzsparende Vorteile, wenn 
zum Beispiel mit einem einfachen Ret bedingungsgesteuert über eine oder 
mehrere Unterprogrammebenen hinweg zurückgesprungen werden kann/soll.

von Lothar (Gast)


Lesenswert?

Moby A. schrieb:
> kann man natürlich machen da die AVRs meist immer noch genügend Leistung
> bieten

Ein 8051 oder ARM wechselt halt einfach beim Eintritt in die ISR die 
Registerbank, muss gar nichts gestackt werden ...

von Moby A. (moby-project) Benutzerseite


Lesenswert?

Lothar schrieb:
> Ein 8051 oder ARM wechselt halt einfach beim Eintritt in die ISR die
> Registerbank, muss gar nichts gestackt werden ...

Ja- für diesen Fall sehr effizient, aber letztlich zum Preis von 
insgesamt mehr Komplexität. Und das Schöne am AVR ist doch, daß ihn die 
Mischung seiner Eigenschaften- und insbesondere gerade der Verzicht auf 
so manches Detail so attraktiv sprich einfach benutzbar macht, was 
gerade dem Asm-Programmierer zugute kommt.

von MD (Gast)


Lesenswert?

Karl H. schrieb:
> Lothar M. schrieb:

>
> Ich hab auch eine Weile gebraucht, aber er hat recht.
>
> Er kann nicht einen CALL zu einer push_all machen, wenn die so
> aussiehtpush_all:  push r16
>            push r17
>            push r18
>            ret
>
> pop_all:   pop r18
>            pop r17
>            pop r16
>            ret


Er müsste sich ein Register frei halten, wo der die Rücksprungadresse 
zunächst vom Stack holt, dann alles auf den Stack wirft und anschließend 
wieder die Rücksprungadresse draufpackt.

von Possetitjel (Gast)


Lesenswert?

MD schrieb:

> Er müsste sich ein Register frei halten, wo der die
> Rücksprungadresse zunächst vom Stack holt, dann alles
> auf den Stack wirft und anschließend wieder die
> Rücksprungadresse draufpackt.

<Staun> Nicht möglich!

Was habe ich sechs Beiträge zuvor geschrieben...?!

von Hannes (Gast)


Lesenswert?

Moby A. schrieb:
> insgesamt mehr Komplexität
Und?
> einfach benutzbar
Ist der 8051 und ein ARM (den man sinnvollerweise in Hochsparen 
programmiert) auch.

von m.n. (Gast)


Lesenswert?

Karl H. schrieb:
> Ich hab auch eine Weile gebraucht, aber er hat recht.
>
> Er kann nicht einen CALL zu einer push_all machen, wenn die so
> aussieht
> ......
> man könnte natürlich mit dem Stackpointer tricksen und sich die
> Returnadresse aus dem Stack raussuchen und den Stack in diesen
> Funktionen entsprechend manipulieren, so dass beim ret aus push_all
> alles in der richtigen Reihenfolge auf dem Stack liegt.
> Ist aber Aufwand.

Wenn man Platz sparen möchte, kann man sich einen Datenstack einrichten, 
der beim AVR z.B. über das Y-Register angesprochen wird.

push_all:
     st -Y,r10
     st -Y,r11
     ...
     st -Y,r24
     st -Y,r25
     ret

pop_all:
     ld r25,Y+
     ld r24,Y+
     ...
     ld r11,Y+
     ld r10,Y+
     ret

Bei modernen µCs kann man irgendein Adressregister verwenden, wobei die 
Zugriffe aufs RAM genauso schnell sein können.

von Axel S. (a-za-z0-9)


Lesenswert?

Der IMHO wesentliche Punkt ist, daß man praktisch niemals alle 
Register sichern muß. Und deswegen will man das auch nicht. Jedes 
zusätzliche PUSH/POP Paar frißt Zeit, Flash und Stackverbrauch.

Wenn man dieses Argument akzeptiert hat, dann ist klar daß jede Funktion 
eine spezifische Menge an Registern hat, die gesichert werden müssen. Ob 
man das jetzt per manueller PUSH/POP Orgie macht oder sich ein Makro 
dafür schreibt, ist Geschmackssache. Wenn man es nicht sowieso den 
Compiler erledigen läßt.

Wenn man garantieren kann, daß die Funktion nicht reentrant ist (bei ISR 
ist das zumindest gewünscht, garantieren muß man das natürlich selber) 
dann kann man auch mit fest reservierten Registern oder fest 
reserviertem Platz im RAM arbeiten.

Register sind schneller, allerdings kann man nicht bei allen Compilern 
Register fest reservieren. Oder man will das nicht, weil es den 
Compiler zwingt ineffizienten Code zu erzeugen.

von m.n. (Gast)


Lesenswert?

Axel S. schrieb:
> Jedes
> zusätzliche PUSH/POP Paar frißt Zeit, Flash und Stackverbrauch.

Nicht jede Anwendung muß auf höchste Geschwindigkeit getrimmt sein. Für 
einen Tiny mit begrenztem Flash, der zudem noch in Assembler 
programmiert wird, kann sich eine separate Routine zum 
Sichern/Wiederherstellen vieler (oder fast aller) Register schon lohnen. 
Notfalls läßt man den µC mit 2 MHz statt mit 1 MHz laufen ;-)
Bei C-Programmen muß der Compiler diese Optimierung können, sonst hat 
man nichts davon.

Axel S. schrieb:
> Register sind schneller, allerdings kann man nicht bei allen Compilern
> Register fest reservieren. Oder man will das nicht, weil es den
> Compiler zwingt ineffizienten Code zu erzeugen.

Beim IAR-Compiler für AVR kann man bis zu 12 Register reservieren.
Daß dadurch der Code ineffizient würde, ist ein Gerücht ;-)

von Nase (Gast)


Lesenswert?

Im Prinzip müsstest du die obersten beiden Bytes auf dem Stack mit dem 
Z-Register vertauschen. Dann wäre das Z-Register gerettet (wurde ja 
gerade auf den Stack getauscht) und du könntest mit ijmp zurück in 
deine ISR springen.

von Sam .. (sam1994)


Lesenswert?

Wenn es wirklich nur um den Speicherplatz geht, dann sollte man auch 
keine 64 Befehle (=128 Byte) für push und pop verwenden. Man kann diese 
auch in einer Schleife sichern. Dafür wird die Ausführungszeit nochmal 
deutlich länger.
1
  push ZL
2
  push ZH
3
4
  ldi ZL, 30
5
  ldi ZH, 0
6
loop:
7
  ld r29, -Z
8
  push r29
9
  cpse ZL, ZH
10
  rjmp loop
11
12
  ldd ZL, Y+32
13
  ldd ZH, Y+33
14
  ijmp
 (ungetestet)

von Thomas (kosmos)


Lesenswert?

soweit es geht vergebe ich den INT-Routinen exclusive Register, die kein 
Programmteil anfasst. Wenn es nicht geht achte ich drauf in der 
INT-Routine möglichst nur ein Register zu verwenden um nur eins sichern 
zu müssen, den restlichen Programmteilen reserviere ich im SRAM 
entsprechend Platz und lege dort die Ergebnisse ab. Kostet halt ein paar 
Takte, wenn man nicht direkt mit den Registern arbeitet, finde ich aber 
sehr bequem wenn ich sehr viele Variablen nutze und die Register nicht 
ausreichen.

1
.DSEG  ;Reserve jeweils 1 Byte / Variable im SRAM
2
Steuerbyte1:        .byte 1
3
Steuerbyte2:        .byte 1
4
Drahtvorschub:      .byte 1
5
Gasvorstroemzeit:   .byte 1
6
Gasnachstroemzeit:  .byte 1
7
Impulszeit:         .byte 1
8
9
....
10
ADC-Wandlung...
11
12
sts Drahtvorschub, temp    ;Speichere Drahtvorschubgeschwindigkeit im SRAM
13
14
....
15
lds temp, Drahtvorschub
16
out OCR0, temp

von Lurchi (Gast)


Lesenswert?

In der Regel muss man nicht alle Register sichern sondern nur einige 
wenige. Da lohnt es eher nicht für 2 oder 3 ISRs da ein paar Bytes Flash 
sparen zu wollen. Beim Zurückhohlen der Register vom Stack kann man auch 
so schon gemeinsamen Code nuten. Etwa so:

pop R21
pop R22
pop R23
RETI

Wenn man in einer ISR weniger registser gerettet hat, kann man auch 
weiter unten einsprigen. Das spart zwar etwas Flash, aber daruf kommt es 
nur selten an. Das ist also mehr so eine Idee um ein Programm doch noch 
in den bestenden kleinen Chip zu quetchen. ASM prgrammiert man ja 
ohnehin eher bei kleinen Chips, und in Zeiten von Tinys mit bKytes ist 
es da nur selten knapp.

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.