Forum: Compiler & IDEs tückische Timer


von Gast-01 (Gast)


Lesenswert?

Hallo
bei der Suche, weshalb mein Programm nicht das tut, was es soll, bin ich 
auf ein eigenartiges Verhaltens eines 16-bit Timmers gestoßen:

Ich starte den Timer, er soll so lange zählen, bis ca 400 ms vergangen 
sind und dann einen Overflow-Interrupt auslösen.
Ich hatte erwartet, daß es nach Ablauf der Zeit einen Overflow-Interrupt 
gibt, egal, wie das Programm die Zwischenzeit verbracht hat.

Das scheint aber nicht der Fall zu sein. Schreibt man
  timerstart();   // enthält Timer-, TIMSK-, Interrupteinstellungen usw.
  _delay_ms(800); // e i n e  Art, auf den Interrupt zu warten
  if (flag == 1)  // in der ISR wird flag=1 gesetzt
  { .......};

dann kann man das flag abfragen, das man in der ISR gesetzt hat und 
alles ist gut.

Schreibt man hingegen
       timerstart();
       while(1);      // eine andere Art, auf den Interrupt zu warten
       if (flag == 1)
       { .......};

dann wird entweder die ISR nicht angesprungen oder sonst etwas 
Unerklärliches getan. Jedenfalls bleibt das Programm hängen.

Geht das nicht so, wie mir das gedacht habe?

Gruß
Gast-01

von Rolf Magnus (Gast)


Lesenswert?

>       timerstart();
>       while(1);      // eine andere Art, auf den Interrupt zu warten
>       if (flag == 1)
>       { .......};
>
> dann wird entweder die ISR nicht angesprungen oder sonst etwas
> Unerklärliches getan. Jedenfalls bleibt das Programm hängen.

Das verwundert nicht. Du hast schließlich eine Endlosschleife in deinem 
Programm. Was läßt dich glauben, daß nach dem Interrupt die 
while-Schleife auf einmal aufhört, endlos zu sein?

von Stefan E. (sternst)


Lesenswert?

1
while(1);
Das ist eine Endlosschleife. Das "if (flag == 1)" wird nie erreicht. Der 
Interrupt unterbricht die Schleife nur für kurze Zeit, er bricht sie 
nicht ab.
Was du wahrscheinlich haben möchtest:
1
while(flag == 0);

PS: Denke aber an das "volatile" für "flag".

von Ahem (Gast)


Lesenswert?

1
       while(1);      // eine andere Art, auf den Interrupt zu warten

Das ist eher eine Art auf den jüngsten Tag zu warten, wenn man denn 
daran glaubt.

Was genau soll denn den uC veranlassen die nachfolgende Zeile
1
       if (flag == 1)
auszuführen?
Der uC ist ja munter dabei while(1) zu bearbeiten, wenn der Interrupt 
auftritt. Dann wird zwar die Interruptroutine abgearbeitet, aber danach 
macht er wieder mit while(1) weiter. Was anderes steht ja auch nicht da.
1
_delay_ms(800); // e i n e  Art, auf den Interrupt zu warten
wartet auch nicht auf den Interrupt, sonderen darauf, das 800ms 
vergangen sind. Der Interrupt wird zwar abgearbeitet, wenn er 
zwischenzeitlich auftritt (und verlängert auch das delay) aber er 
beendet das delay nicht vorzeitig.

Interrupts an sich ändern überhaupt nichts an der Abarbeitung des 
Programmteils, der gerade läuft, wenn der Int auftritt. Danach geht es 
wie gehabt weiter. Deswegen bemutzt man ja die volatile flag variable, 
damit das Hauptprogramm mitbekommt, das ein Int aufgetreten ist.

Deswegen also, z.B. sowas wie:
1
while(flag == 0);

von Gast-01 (Gast)


Lesenswert?

>Das verwundert nicht. Du hast schließlich eine Endlosschleife in deinem
>Programm. Was läßt dich glauben, daß nach dem Interrupt die
>while-Schleife auf einmal aufhört, endlos zu sein?

Nun ja, ich hatte mir schon gedacht, daß ich nicht so ohne weiteres aus 
der Schleife herauskomme, um den flag abzufragen und daß ich es 
innerhalb der Schleife tun sollte. Also werde ich, wie ihr schreibt, 
while (flag == 0) in die Schleife integrieren.

Gäbe es denn überhaupt eine Möglichkeit, durch einen Sprungbefehl in der 
ISR  aus der Schleife herauszukommen? Return würde ja wohl nicht helfen, 
da es auch nur zurück in die Schleife führt. (Ist nur eine Frage 
neugierdehalber, es ginge bei mir ohnehin nicht, weil ich die ISR nicht 
nur an einer Stelle benutzen möchte).

mfg
Gast-01

von Karl H. (kbuchegg)


Lesenswert?

Gast-01 schrieb:

> Gäbe es denn überhaupt eine Möglichkeit, durch einen Sprungbefehl in der
> ISR  aus der Schleife herauszukommen? Return würde ja wohl nicht helfen,
> da es auch nur zurück in die Schleife führt. (Ist nur eine Frage
> neugierdehalber, es ginge bei mir ohnehin nicht, weil ich die ISR nicht
> nur an einer Stelle benutzen möchte).

Im Prinzip gäbe es eine Möglichkeit  (setjmp/longjmp)
Aber das willst nicht wirklich benutzen. Vergiss es gleich wieder.

Das Wesen einer ISR ist es, dass das Hauptprogramm exakt dort weiter 
macht, wo es unterbrochen wurde. Wenn die ISR dem Hauptprogramm etwas 
mitzuteilen hat, dann tut sie das, in dem globale Variablen manipuliert 
werden, die vom Hauptprogramm an günstiger Stelle abgefragt werden.

Alles andere führt über kurz oder lang nur zu Ärger. Wenn man aus diesem 
Korsett ausbricht, muss man schon sehr genau wissen was man tut und auch 
die Internals des Compilers gut kennen, denn schliesslich mischt man 
sich damit in einige Bereiche ein, die eigentlich unter alleiniger 
Kontrolle des Compilers stehen sollten (Returnstack, Stack Frames, etc)

von Ste N. (steno)


Lesenswert?

Hallo Gast-01,

warum willst Du überhaupt mit einer while() Schleife Rechenzeit 
verschwenden, wie wäre es denn mit

void main(void)
{
    timerstart();
    while(1)      // Endlosschleife in Main
    {
        if (flag == 1)
        {
            .......
            flag = 0;
        };

        // tue andere wichtige Sachen

    }
};

So muß man auf den Interrupt nicht warten und kann nebenbei das 
Hauptprogramm weiter abarbeiten.

Viele Grüße,
Steffen

von Ahem (Gast)


Lesenswert?

Ich finde die Frage interessant. Wir hatten schon so oft hier die Frage 
wie man die Rücksprung-Adresse von einem Interrupt ändern kann uswusf.
Leider war mir nie ganz klar, wie die Fragesteller darauf kamen.

Nimmt man aber diese Frage, so scheint es, zumindest bei einem Teil der 
Frager, das Problem zu geben, das Interrupts nicht verstanden werden.
Insbesondere die Tatsache, das ein Interrupt, den Ablauf des 
unterbrochenen Teils nicht ändert. D.h. also, ein solcher Frager würde 
wahrscheinlich gerne irgendwas wie:
1
ISR () {
2
   mach_was ();
3
   goto nachher; // eigentlich eher manipulation des stackinhalts
4
}
5
6
main () {
7
   timerinit ();
8
   while (1);
9
nachher:
10
   mach_was_anderes ();
11
}
schreiben.
Das ist natürlich nur ein pseudocode.

>Nun ja, ich hatte mir schon gedacht, daß ich nicht so ohne weiteres aus
>der Schleife herauskomme, um den flag abzufragen und daß ich es
>innerhalb der Schleife tun sollte. Also werde ich, wie ihr schreibt,
>while (flag == 0) in die Schleife integrieren.
>Gäbe es denn überhaupt eine Möglichkeit, durch einen Sprungbefehl in der
>ISR  aus der Schleife herauszukommen? Return würde ja wohl nicht helfen,
>da es auch nur zurück in die Schleife führt. (Ist nur eine Frage
>neugierdehalber, es ginge bei mir ohnehin nicht, weil ich die ISR nicht
>nur an einer Stelle benutzen möchte)

Das ist kein workaround, was wir da vorschlagen. Das ist das normale 
, korrekte Verfahren.
Im Gegenteil: das mit dem Sprungbefehl wäre furchtbar ungeschickt, 
auch wenn es geht. Auch die Manipulation der Adresse auf dem Stack wäre 
möglich, ist gilt aber als unsauber.
Du kommst auf diese Problem (und die Sprunglösung), weil Du nicht 
verstanden hast, wie Interrupt-Routine und Hauptprogramm in Beziehung 
stehen.
Das mit der Variablen IST die korrekte Lösung um die das auftreten des 
Interrupts an das Hauptprogramm zu melden.

Die Gründe dafür sind nicht ganz leicht zu erklären:
1. Nach der Verwendung: Beim Interrupt wird die momentan abgearbeitete 
Adresse (eigentlich die folgende) auf dem Stack abgelegt. Das würde 
nicht der Fall sein, wenn die Manipulation der Rücksprungadresse normal 
wäre.

2. Wegen der möglichen Nebenwirkungen: Wenn die Rücksprungadresse 
manipuliert würde, so wären auch Maßnahmen nötig um die Effekte von noch 
nicht oder nur teilweise ausgeführten Schritten im Hauptprogramm zu 
kompensieren.

3. Übersicht: So wie gotos sind auch longjmps bzw. geänderte 
Rücksprungadressen verpönt, weil sie den Ablauf unübersichtlich, den 
Test und die Codeanalyse schwieriger wenn nicht unmöglich machen.

Da könnte man glatt mal einen Wiki-Artikel drüber schreiben.

von Ahem (Gast)


Lesenswert?

Wenn man das Problem der Fragesteller noch etwas genauer betrachtet, so 
haben sie zwar erkannt, das der Kontrollfluss des Hauptprogrammes 
geändert werden soll, aber sie sind in dem Moment zu sehr auf das 
Ausführungsmodell fixiert.

Also etwas so:

Da ist der Prozessor, setzt den Instruction Pointer, führt den befehl 
aus, setzt den IP, nächster Befehl.... Ooops, ein Interrupt. Also IP auf 
den Stack, interrupt routine ausführen (IP, Befehl....). Und nun? Wieder 
zurück zum Hauptprogramm. Jaaaa, aber ich will ja jetzt was ganz anderes 
machen, wo der Interrupt aufgetreten ist! Also der IP ist jetzt 
"falsch".

Was hier glaube ich nützlich wäre, wäre die Sichtweise, das man auch 
über Daten den Kontrollfluss ändern kann. Das "IF", "SWITCH" usw. den 
Kontrollfluss ändern. Das das Ergebnis eines Interrupts auch geänderte 
Daten sein können bzw. in gewissen Fällen müssen.

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.