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.
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.
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
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.
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
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. :-))
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
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
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.
@ 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.
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.
@ 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?
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.
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.