Forum: Mikrocontroller und Digitale Elektronik ATMega8 Timer0 verliert Interrupts


von Weinga U. (weinga-unity)


Angehängte Dateien:

Lesenswert?

Hallo,

ich habe ein Problem mit dem Timer0 beim ATMega8. Ich verwende ihn um 
zyklische Interrupts zu generieren und Lade den Zähler immer neu um das 
gewünschte Intervall zu erhalten.

Mein Problem ist, dass manchmal gleich mehrere Interrupts ausgelassen 
werden.

Hat jemand einen Hinweis für mich?

Ich messe und programmiere schon 2 Tage herum und kann das Problem nicht 
weiter isolieren. Hier der Code:



Initialisierung:
1
  TCCR0 = (0<<CS02)|(1<<CS01)|(0<<CS00);              // ck/8
2
  TIFR |= (1<<TOV0);
3
  TIMSK |= (1<<TOIE0);
4
5
  sei();

Interrupt, hier wird ein process() für einen Kommunikationsstack 
zyklisch abgearbeitet und mit sei() erneute interrupts danach erlaubt. 
Zusätzlich werden registrierte callbacks aufgerufen wenn deren Zeit 
abgelaufen ist.

D.h. es passiert dass eine ISR noch mit einem callback beschäftigt ist 
und ein erneuter overflow die ISR ein 2tes mal aufruft um process 
abzuarbeiten.

Siehe BMP im Anhang mit den Signalen zu CH(A) und CH(C) die wie vermerkt 
im Code angesteuert werden.
1
ISR(TIMER0_OVF_vect, ISR_BLOCK){
2
  static volatile uint8_t flag=0;
3
4
  TCNT0-=120; // oder auch TCTN+=136;
5
  CH(C)=1;
6
  process();
7
  CH(C)=0;
8
  sei();
9
10
  if (flag==0)
11
  {
12
    CH(A)=1;
13
    flag=1;
14
15
  // processing of periodic registered callbacks
16
    flag=0;
17
    CH(A)=0;
18
  }
19
}

von isnah (Gast)


Lesenswert?

Das ist immer gefährlich, in einer "Interrupt-Service-Routine" eine 
Funktion abzuarbeiten.;-)

von Peter D. (peda)


Lesenswert?

Weinga Unity schrieb:
> D.h. es passiert dass eine ISR noch mit einem callback beschäftigt ist
> und ein erneuter overflow die ISR ein 2tes mal aufruft um process
> abzuarbeiten.

sei() innerhalb des Interrupts ist schonmal ganz schlecht.
Der 2. Aufruf drängelt sich dann vor, d.h. wird zuerst beendet.

120 * 8 = 960 Zyklen sollten dicke reichen, einen Interrupt regulär 
beenden zu können.

von Axel S. (a-za-z0-9)


Lesenswert?

Weinga Unity schrieb:
> ich habe ein Problem mit dem Timer0 beim ATMega8. Ich verwende ihn um
> zyklische Interrupts zu generieren und Lade den Zähler immer neu um das
> gewünschte Intervall zu erhalten.

Keine gute Idee, wenn das Timing stimmen soll. Die meisten Timer im AVR 
haben extra dafür einen CTC-Modus.

> Mein Problem ist, dass manchmal gleich mehrere Interrupts ausgelassen
> werden.
>
> Hat jemand einen Hinweis für mich?

Die aufgerufene Funktion process() scheint wohl manchmal arg lange zu 
brauchen.

> hier wird ein process() für einen Kommunikationsstack
> zyklisch abgearbeitet und mit sei() erneute interrupts danach erlaubt.

Zweite blöde Idee. In einer ISR sollte man Interrupts nur dann wieder 
erlauben, wenn man sicherstellen kann, daß es nicht der gleiche 
Interrupt ist. Also muß entweder die Laufzeit der ISR garantiert 
kürzer sein als der minimale Abstand zwischen zwei Interruptauslösungen. 
Oder man muß die "eigene" Interruptquelle in der ISR sperren.

Ferner ergibt das in dieser spezifischen Verwendung auch gar keinen 
Sinn. Zwischen dem sei() und dem Ende der ISR passiert praktisch gar 
nichts mehr außer daß ein paar Variablen gelesen bzw. geschrieben 
werden. Wenn man überhaupt nested interrupts erlauben will, dann doch 
bevor man langdauernde Operationen in der ISR ausführt.

Die kanonische Lösung besteht darin, process() nicht innerhalb der ISR, 
sondern außerhalb in der obligatorischen main() Endlosschleife 
auszuführen. Und in der ISR nur ein Flag zu setzen, daß process() mal 
wieder dran ist.


XL

von H.Joachim S. (crazyhorse)


Lesenswert?

Naja, wenn man sich das hier

>CH(C)=1;
>process();
>CH(C)=0;

und das Bild von CH(C) anschaut, scheint die Laufzeit von process() 
nicht das eigentliche Problem zu sein.

Ja, sicher, könnte und sollte man ausserhalb der ISR aufrufen. Je 
nachdem, was sonst noch so passiert kann das auch so passen.

Problem liegt also eher am sei();
Gefährlich ist es sowieso, und unnötig offensichtlich auch.
CTC-Mode wurde schon genannt. Der Mega8 ist nicht gerade das aktuellste 
Modell, beim Mega88 sieht es besser aus mit den Timern. In deinem Fall: 
nimm den Timer2.

von oldmax (Gast)


Lesenswert?

Hi
Der Timer0 im Atmega hat kein CTC Mode. Aber vielleicht ginge auch der 
Timer2, der kann das.
im Übrigen bin ich auch der Meinung, das vieles mit Jobflags besser 
gelöst ist. Ein SEI ist die ungünstigste Variante. Zu schnell findet man 
sich in einer unnötigen Fehlersuche wieder. Mag ja sein, das es Briefe 
gibt, die man direkt im Beisein des Briefträgers liest, aber die Regel 
ist doch: Es klingelt (Interrupt) du gehst hin und erledigst das 
Türgeschäft (ISR) kommst zu deiner Arbeit zurück und baust die neue 
Information in das Tagesgeschäft.
(Haftbefehle hingegen sollte man schon lesen, bevor man mitgeht.... ach 
ja, und die einzige SEI, die mir da einfällt ist FLUCHT)
Gruß oldmax

von npn (Gast)


Lesenswert?

oldmax schrieb:
> (Haftbefehle hingegen sollte man schon lesen, bevor man mitgeht.... ach
> ja, und die einzige SEI, die mir da einfällt ist FLUCHT)

Du hast eine sehr blumige Ausdrucksweise, um die Arbeitsweise eines µC 
zu beschreiben. :-))

von lagerwiese (Gast)


Lesenswert?

oldmax schrieb:
> sich in einer unnötigen Fehlersuche wieder. Mag ja sein, das es Briefe
> gibt, die man direkt im Beisein des Briefträgers liest, aber die Regel
> ist doch: Es klingelt (Interrupt) du gehst hin und erledigst das
> Türgeschäft (ISR) kommst zu deiner Arbeit zurück und baust die neue
> Information in das Tagesgeschäft.
> (Haftbefehle hingegen sollte man schon lesen, bevor man mitgeht.... ach
> ja, und die einzige SEI, die mir da einfällt ist FLUCHT)

Wie süß die zurechtgepfriemelten Analogien aus dem echten Leben lol

von Georg (Gast)


Lesenswert?

Weinga Unity schrieb:
> D.h. es passiert dass eine ISR noch mit einem callback beschäftigt ist
> und ein erneuter overflow die ISR ein 2tes mal aufruft um process
> abzuarbeiten.

Wenn es tatsächlich so ist, dass die Verarbeitungszeit im Schnitt länger 
ist als die Zeit zwischen 2 Interrupts, dann gibt es keine Lösung, dann 
ist eben der Prozessor nicht leistungsfähig genug.

Kommt das bloss gelegentlich vor, kannst du z.B. in der ISR nur eine 
Variable hochzählen und die so gezählten Ereignisse in der Hauptschleife 
abarbeiten. Aber auch das funktioniert natürlich nur, wenn die 
Bearbeitung Schritt halten kann.

Ich habe auch schon manchmal die ISR gegen einen 2.Aufruf gesperrt, aber 
das ist nur sinnvoll, wenn man den Interrupt tatsächlich ignorieren 
kann. Das geht in manchen Fällen, z.B. wenn ein Messwert ständig gelesen 
und angezeigt werden soll, da kann man auch einen auslassen.

Georg

von Weinga U. (weinga-unity)


Lesenswert?

Danke für die Antworten.

Die Anwendung sieht so aus, dass es 3 prioritäten von zyklischen Tasks 
gibt.

Level 1 ist die process() Routine im Interrupt

Level 2 sind die Callbacks, die im Takt vom Interrupt auf deren Timeout 
warten und die Level 3 tasks unterbrechen können, jedoch vom Level 1 
unterbrochen werden kann.

Level 3 überprüft mehrere die von euch erwähnten "Hochzähl Variablen" 
und arbeitet die zugehörigen Routinen in der while(1) ab.



Ich habe folgende Ansatzvermutungen:
  - Timer0 mit manuellem Reload tut nicht so gut
  - sei(); in der ISR ist an einer ungünstigen Stelle platziert
  - Ein Stack-Problem (Überlauf) wenn die ISR ein 2tes mal aufgerufen 
wird.

Ich bin offen für weitere Kommentare und werde weiter daran arbeiten. 
Wenn jemand eine Lösung hat, wie der Timer-ISR beendet werden kann und 
gleichzeitig aber eine andere Routine anstößt die, die while(1) 
unterbricht, dann her damit.

von Falk B. (falk)


Lesenswert?

@ Weinga Unity (weinga-unity)

>Wenn jemand eine Lösung hat, wie der Timer-ISR beendet werden kann und
>gleichzeitig aber eine andere Routine anstößt die, die while(1)
>unterbricht, dann her damit.

Mit Multitasking und einer gescheiten Statemachine.

von Weinga U. (weinga-unity)


Lesenswert?

Meine unterschiedlichen Tasks sind lauter State-Machines. Nur die 
Priorisierung macht das ganze interessant....

Dennoch sollte der Timer periodisch auslösen. und nicht 2-3 Zyklen 
aussetzen.

von Falk B. (falk)


Lesenswert?

@ Weinga Unity (weinga-unity)

>Meine unterschiedlichen Tasks sind lauter State-Machines.

Dann sind ggf. deine States zu lang oder warten unzulässig.

>Nur die
>Priorisierung macht das ganze interessant....

Hmm.

>Dennoch sollte der Timer periodisch auslösen.

Das tut er.

> und nicht 2-3 Zyklen
>aussetzen.

Das tut er nicht, es sei denn, DU macht einen Fehler.

Über welche Zeiten reden wir denn ? 1ms? 10ms?

von Weinga U. (weinga-unity)


Lesenswert?

Aaaahhh, Fehler gefunden.

Im meiner IO API habe verwende ich Pointer für PIN, PORT, DDRD für die 
einzelnen IO-Pins meiner Steuerung.

Die Debug-Pins habe ich schlamping ausgehebelt, jedoch ist noch auf die 
somit nicht initialisierten Pointer geschrieben worden. Dabei dürfte der 
Timer irgendwie beeinflusst werden.


Jetzt tuts......


Zur Fehlerfindung habe ich mit den Debug-Pins einzelne Code-Segmente in 
der Zeitachse anzeigen lassen und habe so nun schnell das Code-Segment 
während der Lücke mit den fehlenden Interrupts isoliert. Dort befand 
sich auch die IO API.

Danke für euren Support.

von c-hater (Gast)


Lesenswert?

Weinga Unity schrieb:

> Level 1 ist die process() Routine im Interrupt
>
> Level 2 sind die Callbacks, die im Takt vom Interrupt auf deren Timeout
> warten und die Level 3 tasks unterbrechen können, jedoch vom Level 1
> unterbrochen werden kann.
>
> Level 3 überprüft mehrere die von euch erwähnten "Hochzähl Variablen"
> und arbeitet die zugehörigen Routinen in der while(1) ab.

Nunja, das ist immerhin ein Konzept, welches (mit einer kleinen 
Erweiterung) zuverlässig funktionieren kann, wenn man es fehlerfrei 
umsetzt.

Es ist übrigens schon recht ähnlich dem bei richtigen OS' verwendeten 
Konzepten.

>   - Timer0 mit manuellem Reload tut nicht so gut

Da das im exklusiven Teil der ISR geschieht, sollte es problemlos sein, 
ganz sicher, solange es keine konkurrierenden Interrupts gibt.
Natürlich stellt sich trotzdem die Frage, warum mit manuellem Reload 
gearbeitet wird, denn sinnvoll oder gar zwingend notwendig ist das fast 
niemals.

>   - sei(); in der ISR ist an einer ungünstigen Stelle platziert

Wenn man sei() in eines ISR benutzt, muß man einfach wissen, was man 
tut. Die korrekte Position des sei() ergibt sich dann von selbst, 
nämlich genau zwischen dem Teil, der exklusiv abgearbeitet werden muß 
und dem Teil, der unterbrochen werden kann. So einfach ist das.

>   - Ein Stack-Problem (Überlauf) wenn die ISR ein 2tes mal aufgerufen
> wird.

Wenn die ISR ein zweites Mal aufgerufen wird, kommt es in aller Regel 
noch nicht sofort zum Stackunterlauf. Erst, wenn das etliche Male 
hintereinander passiert, dann knallt es.

Was aber schon beim der ersten Wiedereintritt passiert, ist eine Art 
kausaler Inversion für den nichtexklusiven Teil der ISR. Es wird nämlich 
die später ausgelöste Instanz zuerst vollständig abgearbeitet und dann 
die unterbrochene Instanz fortgesetzt, bei mehr als zwei 
Unterbrechungsebenen gilt das rekursiv.

Hier zeigt sich dann die Erfahrung in paralleler Programmierung. Das 
funktioniert nämlich nur dann, wenn es um parallel abarbeitbare Probleme 
geht. In jedem anderen Fall muß die Kausalität erhalten bleiben, was 
ohne Synchronisierung der Instanzen nicht geht. Und eine 
Synchronisierung zwischen diesen Softwareinstanzen ist im Unterschied zu 
einem echten Tasks eines MT-OS nicht möglich.

Also: Entweder das Problem läßt sich vollständig parallelisieren. Dann 
mußt du's einfach machen. Oder es geht nicht (was leider meist der Fall 
ist), dann mußt du verhindern, daß es zu dieser kausalen Inversion 
kommt. Und das macht man einfach dadurch, daß man das sei() und alles 
danach kommt, einfach nicht macht, wenn schon eine Instanz von dem Zeug 
läuft. Damit erschlägt man dann auch gleich das Problem des potentiell 
drohenden Stackunterlaufs, das kann dann nicht mehr passieren, weil dann 
die zweite Instanz der ISR auch die letzte ist. Erst wenn die beendet 
ist, kann eine neue entstehen, die dann aber auch bloß wieder die 
Ordnungszahl zwei trägt (oder eins, wenn die ursprünglich erste 
inzwischen auch zu ihrem Ende gelangt ist).

Das ist eigentlich total simpel. Kostet nur ein Bit als Merker. Man muß 
aber natürlich das Prüfen und Setzen des Bits im exklusiven Teil der ISR 
erledigen.

Was mit einem weiteren Merker-Bit zusätzlich auch möglich ist: Erfassen 
der Situation, daß die Erzeugung einer weiteren Instanz des 
nichtexklusiven Teils der ISR nicht möglich war und im Gegenzug am Ende 
eben dieses Teils prüfen, ob diese Situation aufgetreten ist und den 
laufenden nichtexklusiven Teil nicht etwa beenden, sondern im Gegenteil 
erneut abzuarbeiten, um sozusagen die "liegen gebliebene" Arbeit 
nachzuholen, nun aber mit korrekter Kausalität. Das erfordert dann 
allerdings wiederum einen FIFO für die Arbeitsaufgaben (typischerweise 
gefüllt vom exklusiven Teil der ISR). Damit droht dann statt des 
Stackunterlaufs allerdings der Heapüberlauf, was letztlich dieselbe 
Scheiße produziert.

Die einzige Abhilfe dagegen ist eine fixe Beschränkung der FIFO-Größe. 
Und ein drittes Bit, wo wiederum die Situation aufgezeichnet wird, daß 
diese Beschränkung erreicht wurde und ein dritter Zweig der ISR, der 
dafür sorgt, daß der Schaden in dieser (Überlast-) Situation möglichst 
gering gehalten wird und die Situation so schnell als möglich bereinigt 
wird.

Das alles zusammen ist machbar. Aber in C ein furchtbarer Krampf. Wenn 
du sowas unbedingt sowas machen willst: Tue dir selbst einen Gefallen 
und nimm eine für das Problem geeignete Sprache. Asm!

Ach ja: bezüglich der Gesamteffizienz ist so eine Lösung immer ziemlich 
schlecht, ganz unabhängig von der Sprache (also auch dann, wenn echte 
Programmierer sie bestmöglich in Asm implementieren).

Trotzdem kann so eine Lösung es in bestimmten Fällen nützlich sein. Ist 
aber eher die extreme Ausnahme als die Regel.

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.