Ich arbeite mit einem Atmega168 und programmiere in C. Ich möchte in einem Interrupt den Stackpointer so manipulieren, das nach dem beendigen des Interrupts ein Unterprogramm eingeschoben wird. Es soll also nicht zum eigentlichen Programmablauf zurückgekehrt werden, sondern erst ein Unterprogramm ausgeführt werden und dann zum Ursprünglichen Programmteil zurückgekehrt werden. Nun meine Fragen: Ist so etwas nur mit Assembler möglich? Kann mir jemand Auskunft über die Stack Verwaltung geben? Wie kann ich zum Beispiel ein „push“ oder „pop“ in C durchführen? Legt C immer als letztes die Rücksprungadresse auf den Stack?
Warum packst Du das nicht direkt (eventl. als Funktionsaufruf) in die entsprechende ISR? Nebenbei, wozu das ganze? -->http://www.mikrocontroller.net/articles/AVR-GCC-Tutorial#Programmieren_mit_Interrupts (--> http://www.mikrocontroller.net/articles/AVR-Tutorial:_Interrupts in ASM)
Du schreibst jetzt 100 mal: "Ich soll den Stack in C nicht manipulieren" Die Verwaltung des Stacks, des Speichers obliegt einzig und alleine dem Compiler. Wenn du dich da einmischt, wird alles nur noch schlimmer. Wenn du am Ende einer ISR eine Funktion aufgerufen haben möchtest, dann ruf sie doch in Gottes Namen auf. Wenn die Absicht darin besteht, dass du Interrupts zugelassen haben willst, während die Funktion arbeitet, dann gib halt in Allahs Namen die Interrupts vor dem Aufruf der Funktion frei.
Warum sollte man das so tun? Warum nicht das "Unterprogramm" (Du meinst vermutlich eine Funktion) aus der Interruptroutine heraus aufrufen?
1 | void meineFunktion(void) |
2 | {
|
3 | // tut irgendwas
|
4 | }
|
5 | |
6 | __interrupt__ meineInterruptFunktion(void) |
7 | {
|
8 | // tut irgendwas wichtiges
|
9 | |
10 | // hier als letztes aus der Interruptroutine die wichtige
|
11 | // Funktion aufrufen
|
12 | meineFunktion(); |
13 | }
|
Davon abgesehen gäbe die Vorgehensweise Probleme. Zwar ist es möglich (mit Assembler-Mitteln), die auf dem Stack liegende Rücksprungadresse zu manipulieren, aber dann wird beim Verlassen des Interrupts bereits der Prozessorzustand (Registerinhalte etc.) in den Zustand versetzt, in dem er bei Eintreffen des Interrupts war - Dein "Unterprogramm" kann damit gar nichts anfangen, und außerdem müsste das "Unterprogramm" dann auch dafür sorgen, daß nichts von diesem Zustand verändert wird.
Heiko wrote:
> Legt C immer als letztes die Rücksprungadresse auf den Stack?
Nein, natürlich als erstes. Und dann werden einige Register gesichert.
Du hast also sehr schlechte Karten, herauszufinden, wo die Retunradresse
liegt.
Ist aber auch garnicht nötig, schreib einfach "sei();" und dahinter den
Funktionsaufruf.
Peter
Na gut, dann werde ich den aufgerufenen Interrupt sperren, den globalen Interrupt wieder freigeben und dann die Funktion aufrufen. Ich dachte nur, ich könnte das evtl. auch anders erledigen! Mein Fehler! @Karl Heinz: "Ich soll den Stack in C nicht manipulieren" "Ich soll den Stack in C nicht manipulieren" "Ich soll den Stack in C nicht manipulieren" ....
Heiko wrote: > @Karl Heinz: > "Ich soll den Stack in C nicht manipulieren" > "Ich soll den Stack in C nicht manipulieren" > "Ich soll den Stack in C nicht manipulieren" > .... Das ist die falsche Einstellung :-) Die Richtige wäre gewesen
1 | #include <stdio.h> |
2 | |
3 | int main() |
4 | {
|
5 | int i; |
6 | |
7 | for( i = 0; i < 100; ++i ) |
8 | printf( "Ich soll den Stack in C nicht manipulieren\n" ); |
9 | }
|
:-) :-)
Heiko wrote: > Na gut, dann werde ich den aufgerufenen Interrupt sperren, den globalen > Interrupt wieder freigeben und dann die Funktion aufrufen. Das bewirkt dann aber etwas völlig anderes, als die Stackmanipulation! Mit Stackmanipulation würde der Interrupt nicht gesperrt und er könnte wärend des Unterprogramms wieder eintreten. Es könnten also 2 (oder mehr) Instanzen des Unterprogramms gleichzeitig laufen. Peter
>> Legt C immer als letztes die Rücksprungadresse auf den Stack? > Nein, natürlich als erstes. Und dann werden einige Register gesichert. Nur um pingelig zu sein : das "Auf den Stack legen" der Rücksprungadresse, bzw. Statusregister bei einem Interrupt oder Funktionsaufruf macht der Prozessor, also die Hardware, und hat mit der Programmiersprache nichts zu tun.
@Peter: ja.. @Klaus: der Prozessor führt die Befehle aus, die ein Programm zum Inhalt hat, was wiederum ein Produkt des Compiler und Linker ist.
> Der Prozessor führt die Befehle aus, die ein Programm zum Inhalt > hat, was wiederum ein Produkt des Compiler und Linker ist. Ääähh, jetzt wirds philosophisch, aber der Prozessor führt den CALL Befehl aus, welcher die Rücksprungadresse (= aktueller Programmzähler + x) als Low-Byte und High-Byte) auf den Stack legt, und die Adresse der angesprungenen Funktion in den Programmzähler lädt. Das ist EIN Befehl, und schaut immer gleich aus, egal ob Assembler, C oder Pascal. Dasselbe gilt für die Interrupts, bei dem die HARDWARE den aktuellen Pogrammzähler und bei manchen Prozessoren auch das Statusregister auf den Stack legt. Die Reihenfolge, in der dies auf den Stack gelegt wird, ist nur vom Prozessor bestimmt, und nicht von der verwendeten Programmiersprache.
Der Ansatz von Heiko ist so dumm nicht, er erinnert an top half / bottom half interrupt handlers komplexerer Interrupt-Systeme. Aber in C kann man das vergessen, das geht nicht. Dazu kommt, dass die Routine, die dabei eingeschoben werden soll, ähnlichen Regeln unterliegt wie ein Interrupt-Handler. Denn sie muss alle Register sichern. Fragt sich aber, wie eilig es mit dem Aufruf dieses bottom half handlers ist. Wenn der nicht zwingend direkt danach erfolgen muss, gibt es andere Methoden.
Andreas Kaiser wrote: > Der Ansatz von Heiko ist so dumm nicht, er erinnert an top half / bottom > half interrupt handlers komplexerer Interrupt-Systeme. Aber in C kann > man das vergessen, das geht nicht. > > Dazu kommt, dass die Routine, die dabei eingeschoben werden soll, > ähnlichen Regeln unterliegt wie ein Interrupt-Handler. Denn sie muss > alle Register sichern. > > Fragt sich aber, wie eilig es mit dem Aufruf dieses bottom half handlers > ist. Wenn der nicht zwingend direkt danach erfolgen muss, gibt es andere > Methoden. Also mir ist nicht ganz klar, was mit dem top-half bzw. bottom-half handler gemeint ist. Ich ahne aber, dass sich die Frage des Ursprungsposters auf ein ähnliches Problem wie das meine bezieht. Nehmen wir mal abstrakt folgendes an: Der Controller soll drei völlig voneinander unabhängige Aufgaben A, B, C erfüllen können. Dafür gibt es auch drei eigene Programmteile, oder genauer drei eigenständige Programme, die auch völlig unabhängig voneinander funktionieren. Eine weitere Funktion ermöglicht beim Start des Controllers die Auswahl welche der drei Aufgaben erfüllt werden soll. Soweit werden keinerlei Interrupts benutzt. Nun möchte ich aber durch einen Eingang (Taster) zwischen den drei "Aufgaben" wechseln, ohne dass ich dazu den Sourcecode der drei Programme selbst modifizieren kann oder will. Ich kann dort also nicht den Taster abfragen. Das wäre per Interrupt natürlich sehr einfach zu machen. Bislang habe ich die Kommunikation zwischen ISR und Hauptprogramm immer mittels volatile Variablen gelöst. Aber das bringt mich hier nicht weiter, weil ich diese ja dann auch permanent prüfen müsste bzw. den Sourcecode der drei Programme modifizieren müsste. Einfacher wäre es, wenn ich nach Ende der ISR zum jeweiligen Programm springen könnte. Wenn ich hier einfach sei(); A/B/C(); aufrufen würde, läuft aber doch irgendwann der Stack voll, oder? Wie macht man sowas "richtig"? Viele Grüße, Klaus
Hinweis: der Originalbeitrag ist mehr als 6 Monate alt. >Der Controller soll drei völlig voneinander unabhängige Aufgaben A, B, C >erfüllen können. Dafür würde ich Zustandsautomaten ('state machine') verwenden, die abhängig von eine Variablen aktiviert oder stillgelegt werden. Im Interrupt wird dabei aber nicht mehr herumgewuschtelt. Wenn Kosten-Nutzen stimmen, kann man auch ein Betriebssystem dafür verwenden ....
Gast wrote: > Hinweis: der Originalbeitrag ist mehr als 6 Monate alt. > >>Der Controller soll drei völlig voneinander unabhängige Aufgaben A, B, C >>erfüllen können. > > Dafür würde ich Zustandsautomaten ('state machine') verwenden, die > abhängig von eine Variablen aktiviert oder stillgelegt werden. Im > Interrupt wird dabei aber nicht mehr herumgewuschtelt. > > Wenn Kosten-Nutzen stimmen, kann man auch ein Betriebssystem dafür > verwenden .... Naja, nur wie gesagt, ist das ja alles schon fertig. Ich möchte das Programm einfach gerne "von außen" per Interrupt unterbrechen ohne dass das Programm selbst etwas davon merkt.
Klaus W. wrote: > Naja, nur wie gesagt, ist das ja alles schon fertig. Ich möchte das > Programm einfach gerne "von außen" per Interrupt unterbrechen ohne dass > das Programm selbst etwas davon merkt. Äh, also zur Ergänzung: Eher abbrechen statt unterbrechen! Es müssen dabei keine Zustände (Stack, Speicher, Register o.ä.) gesichert werden. Jeder Programmteil startet nach dem Interrupt komplett neu mit Initialisierung usw...
Bin gerade auf den Thread gestossen und kann mit eine kleine Stapelei nicht verkneifen. #define STAPEL 0 void fun1(void); void fun2(int i); int main(void) { fun1(); puts("zurück aus fun1- oder wars doch fun2?"); return(0); } void fun1(void) { int i; fun2(i); puts("in fun1"); } void fun2(int i) { int *stapel; puts("in fun2"); #if STAPEL stapel=&i-1; *stapel=*stapel+4; #endif } ;-)
Gerhardt O. wrote: > Bin gerade auf den Thread gestossen und kann mit eine kleine Stapelei > nicht verkneifen. hm... klaus@hermes:~/work$ gcc -o test test.c klaus@hermes:~/work$ ./test in fun2 hæž@\žpî·PÏò· zurück aus fun1- oder wars doch fun2?
Sachtma, gabs für so Viecher nich kleine RTOS..? Also quasi kleine "Kernel", die das alles erledigen?
>Eher abbrechen statt unterbrechen!
Wenn man eine Unterbrechung nicht fortsetzt, ist es ein Abbruch. Wenn
man eine 'state machine' z.B. mit Zustand INIT neu startet, kann man
auch alles neu initialisieren lassen.
Diese Aktionen direkt aus einer Interruptroutine zu veranlassen, würde
ich nicht (besser nie) machen. Der Umfang Deiner Funktionen ist mir
nicht bekannt, scheint aber eher "bescheiden" zu sein ;-)
Betriebssystem -> RTOS; muß aber nicht unbedingt sein.
Um eine laufendes Programm zu unterbrechen, und völlig von vorne anzufangen, haben alle Mikroprozessoren einen speziellen Pin - genannt Reset. Was danach passieren soll, kannst du selber per Programm bstimmen. Wo also ist das Problem? Oliver
@texmex Ich will natürlich keinen zweifelhaften Code in die Welt setzen, das Programmfragment ist leider fehlerhaft. Im Anhang der korrigierte Code. Sollte sich mit jedem Ansi-C Compiler compilieren lassen, allerdings darf der zugewiesene Adressraum nicht größer als 64k sein. Die Stapelmanipulation funktioniert in diesem Fall nicht mehr. mfg
Gerhardt O. wrote: > @texmex > Ich will natürlich keinen zweifelhaften Code in die Welt setzen, das Naja, ein bischen zweifelhaft ist er schon :-). Scheint auch nicht so ganz ausgereift:
1 | klaus@hermes:~/work$ gcc -o stapel stapel.c |
2 | klaus@hermes:~/work$ ./stapel |
3 | in fun2 |
4 | Ungültiger Maschinenbefehl |
5 | klaus@hermes:~/work$ |
Naja, aber ernsthaft: Klar kann man den Stack vielleicht auch in C irgendwie zurechtbiegen, aber gerade solche dirty tricks wollte ich ja eigentlich vermeiden. Die Frage ist eher, ob es eine vernünftige Lösung gibt. In Assembler wäre das mit ein paar Zeilen zu machen...
Oliver wrote: > Um eine laufendes Programm zu unterbrechen, und völlig von vorne > anzufangen, haben alle Mikroprozessoren einen speziellen Pin - genannt > Reset. Auf den Hinweis habe ich ja gewartet :-). Grundsätzlich hast du natürlich recht. Wenn der Controller aber nach dem Reset unterschiedliche Dinge tun soll, die von Bedingungen VOR dem Reset abhängen, müsste man schon allerlei drumherum basteln. Und wie ich das dem C-Compiler beibringe, ist mir schon eher schleierhaft.
Gast wrote: >>Eher abbrechen statt unterbrechen! > > Wenn man eine Unterbrechung nicht fortsetzt, ist es ein Abbruch. Wenn > man eine 'state machine' z.B. mit Zustand INIT neu startet, kann man > auch alles neu initialisieren lassen. Genau so ist es. > Diese Aktionen direkt aus einer Interruptroutine zu veranlassen, würde > ich nicht (besser nie) machen. Der Umfang Deiner Funktionen ist mir > nicht bekannt, scheint aber eher "bescheiden" zu sein ;-) Naja, "bescheiden" ist relativ. Es handelt sich im Grunde um eigene Programme, mit einer eigenen main() funktion. Der Punkt ist aber vor allem der, dass ich daran nichts modifizieren möchte. > Betriebssystem -> RTOS; muß aber nicht unbedingt sein. Kaum. Da ich ja nie nach einem Abbruch in das andere Programm zurückkehren muss.
>In Assembler wäre das mit ein paar Zeilen zu machen...
Dann zeige uns bitte diese Zeilen!
Gast wrote: >>In Assembler wäre das mit ein paar Zeilen zu machen... > > Dann zeige uns bitte diese Zeilen! Naja, ich muss doch nur den Stackpointer zurücksetzen und die Rücksprungadresse auf die Startadresse des neuen Programms setzen.
Klaus W. wrote: > Naja, "bescheiden" ist relativ. Es handelt sich im Grunde um eigene > Programme, mit einer eigenen main() funktion. Der Punkt ist aber vor > allem der, dass ich daran nichts modifizieren möchte. Wenn es immer noch um den AVR geht, dann geht das nicht. Der kann keine virtuelle CPU emulieren. Es darf nur ein main geben, basta. Du mußt die Programme z.B. in main1, main2, main3 usw. umbenennnen und im richtigen main machst Du dann einfach die Auswahl. Und Du mußt natürlich alles zusammen linken. Peter
>Es handelt sich im Grunde um eigene Programme, mit einer eigenen main() >funktion. Na, dann dürfte das erste Problem, welches du angehen solltest, sein, einen Weg zu finden, wie du die drei Programme zusammengebastelt bekommst. Über den normalen C-Compiler/Linker wird das wohl nicht gehen, für drei globale Funktionen mit dem schönen Namen main() in einem Programm braucht es schon Java. Da geht das. Oliver
Peter Dannegger wrote: > Klaus W. wrote: > >> Naja, "bescheiden" ist relativ. Es handelt sich im Grunde um eigene >> Programme, mit einer eigenen main() funktion. Der Punkt ist aber vor >> allem der, dass ich daran nichts modifizieren möchte. > > Wenn es immer noch um den AVR geht, dann geht das nicht. > Der kann keine virtuelle CPU emulieren. > > Es darf nur ein main geben, basta. > > Du mußt die Programme z.B. in main1, main2, main3 usw. umbenennnen und > im richtigen main machst Du dann einfach die Auswahl. > > Und Du mußt natürlich alles zusammen linken. Ja, das ist völlig klar. Ich habe mir zwar schon überlegt, ob es nicht möglich wäre, die Programme getrennt zu compilieren und dem Linker irgendwie einen Offset beizubringen, so dass ich sie hintereinander ins flash legen kann. Dann evtl. noch eine kleine resetroutine, welche das jeweilige Programm startet. Aber das ist jetzt abgesehen von der Stack/Interrupt Geschichte schon wieder eine andere Baustelle.
Oliver wrote: >>Es handelt sich im Grunde um eigene Programme, mit einer eigenen main() >>funktion. > > Na, dann dürfte das erste Problem, welches du angehen solltest, sein, > einen Weg zu finden, wie du die drei Programme zusammengebastelt > bekommst. Über den normalen C-Compiler/Linker wird das wohl nicht gehen, > für drei globale Funktionen mit dem schönen Namen main() in einem > Programm braucht es schon Java. Da geht das. > > Oliver Naja, siehe oben. Natürlich kann ich einfach main1(), main2(), main3() machen. Dann dürfen sich nur andere Symbole (globale Variablen, Funktionen usw.) nicht ins Gehege kommen. Deshalb wäre getrennt compilieren natürlich schon ganz nett.
Da war der Peter wieder schneller... Egal, mit drei Funktionen mit angepassten mainx()-Namen geht es. Dazu eine ISR, die in Abhängigkeit der gedrückten Taste einen Code für die nächste aufzurufende mainx()-Aktion ins EEPROM schreibt, und dann einen Software-Reset ausführt (oder den Watchdog startet, und wartet, bis der zuschlägt). Dazu eine main()-Funktion, die anhand es EEPROM-Codes in die richtige Funktion verzweigt. Oliver
Oliver wrote: > Da war der Peter wieder schneller... > > Egal, mit drei Funktionen mit angepassten mainx()-Namen geht es. Dazu > eine ISR, die in Abhängigkeit der gedrückten Taste einen Code für die > nächste aufzurufende mainx()-Aktion ins EEPROM schreibt, und dann einen > Software-Reset ausführt (oder den Watchdog startet, und wartet, bis der > zuschlägt). Dazu eine main()-Funktion, die anhand es EEPROM-Codes in die > richtige Funktion verzweigt. Ja, sowas ist mir schon in den Sinn gekommen. Erschien mir dann aber auch irgendwie overkill, wenn ich mir vorstelle, dass ich EIGENTLICH nur die richtige Adresse in den Stack und den Stackpointer auf die richtige Adresse setzen müsste. Aber so funktioniert es natürlich.
Gerhardt O. wrote: > @texmex > Ich will natürlich keinen zweifelhaften Code in die Welt setzen Dann lass es bitte. Dein angehängtes Programm ist ein Hack der aller-übelsten Sorte. Da kriege ich Kopfschmerzen und Augenschmerzen zugleich, wenn ich mir das so anschaue.
Simon K. wrote: > Dann lass es bitte. Dein angehängtes Programm ist ein Hack der > aller-übelsten Sorte. Na und? Solange man es weiss, darf er doch mal etwas spielen :-). (Abgesehen davon dass es bei mir nicht funktioniert, aber das haben solche Programme wohl so an sich :-). Mal was anderes: Was passiert eigentlich, wenn ich innerhalb einer interrupt routine exit() aufrufe? Gab es da nicht mal ein atexit() wo sich Funktionen bei exit() registrieren lassen? Oder geht das nur bei "echtem" Programmende (ohne exit())? Viele Grüße, Klaus
>Gab es da nicht mal ein atexit() ...
Stimmt, da ga es auch ein change_task(). Damit kann man zwischen
verschiedenen main() umschalten.
Dann nimm doch lieber gleich exec(). Das zugehörige linux musst du halt auch noch mal eben auf deinen AVR portieren - hoffentlich ist es kein tiny :-) Oliver
Simon K. wrote: > Gerhardt O. wrote: >> @texmex >> Ich will natürlich keinen zweifelhaften Code in die Welt setzen > > Dann lass es bitte. Dein angehängtes Programm ist ein Hack der > aller-übelsten Sorte. Da kriege ich Kopfschmerzen und Augenschmerzen > zugleich, wenn ich mir das so anschaue. In einem solchen Fall empfehle ich Aspirin und eine Sehhilfe. Ab hier wieder konstruktiv. Der Programmschnipsel zeigt eine, wie ich meine, legale Stapelmanipulation, die, und da liegt wohl der Hund begraben, auf einer x86-Maschine einwandfrei läuft, wenn unter MSC oder BorlandC compiliert wurde. Das Programm bewirkt lediglich, dass bei aktivierter Stapelmanipulation aus der zuletzt aufgerufenen Funktion direkt zu main() zurückgekehrt wird. Diese Technik wurde, in anderer Form, in dem Buch "C-Programmierung..." von H.Schildt, erschienen 1990 bei McGraw-Hill, beschrieben. Mir scheint, irgendwie bin ich im falschen Forum gelandet. :-(
Gerhardt O. wrote: > Ab hier wieder konstruktiv. > Der Programmschnipsel zeigt eine, wie ich meine, legale > Stapelmanipulation, die, und da liegt wohl der Hund begraben, auf einer > x86-Maschine einwandfrei läuft, wenn unter MSC oder BorlandC compiliert > wurde. Siehst du. Das Problem bei der Sache ist, dass du dich in Compilerspezifische Sachen einmischst. Und sowas macht man einfach nicht. Parameter müssen nicht zwangsläufig über den Stack übergeben werden. Für mich ist das schlicht und einfach Missbrauch von Pointern zur Lösung eines Problems, dass man viel einfacher und sauberer lösen könnte. Vermutlich funktioniert das Beispiel auch nur unter bestimmten Compilerversionen bzw. mit bestimmten Compilerparametern.
Hm, also der Abschnitt Non-Local Exits Im glibc manual klingt doch recht viel versprechend: Sometimes when your program detects an unusual situation inside a deeply nested set of function calls, you would like to be able to immediately return to an outer level of control. This section describes how you can do such non-local exits using the setjmp and longjmp functions.
Gerhardt O. wrote: > Bin gerade auf den Thread gestossen und kann mit eine kleine Stapelei > nicht verkneifen. Bitte reduzieren Sie die Anzahl der Zitatzeilen > void fun2(int i) > { int *stapel; > > puts("in fun2"); > #if STAPEL > stapel=&i-1; > *stapel=*stapel+4; > #endif > } Ist ein Hack. Du kannst nicht auf allen Maschinen garantieren, dass i überhaupt auf dem Stack übergeben wird und dass stapel auf dem Stack liegt. Beide Variablen können auch in Registern liegen. Ganz abgesehen von der magischen 4.
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.