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?
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
voidmeineFunktion(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
intmain()
4
{
5
inti;
6
7
for(i=0;i<100;++i)
8
printf("Ich soll den Stack in C nicht manipulieren\n");
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.
> 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?
>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.
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
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.