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.
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)
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 |
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
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.