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?
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.
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".
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).
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.
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.
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!
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...
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!!!
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.
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....
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.
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...
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.
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 ...
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.
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.
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...?!
Moby A. schrieb:> insgesamt mehr Komplexität
Und?
> einfach benutzbar
Ist der 8051 und ein ARM (den man sinnvollerweise in Hochsparen
programmiert) auch.
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.
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.
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 ;-)
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.
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.
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.
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.