Interrupt-Zeitverhalten AVR

May 26, 2011 12 Replies

Hallo,



ich habe offensichtlich ein Problem mit einer race condition und komme nicht dahinter, wo die her kommt. Also zuerst mal eine so weit wie möglich vereinfachte Beschreibung des Systems.



Es gibt einen Interrupt von Timer2, der immer möglichst zeitnah ausgeführt werden soll. Deswegen kommen die normalen Mechanismen zur Serialisierung der anderen Interrupts nicht in Frage, sie würden die globale Interruptfreigabe zu lange blockieren.



Neben diesem T2-Interrupt gibt es noch zwei weitere Interruptquellen, ADC und UDRE. Deren Handler greifen auf gemeinsame Daten zu. Der jeweils andere Interrupt muß also verhindert werden, wenn einer der Handler ausgeführt wird, außerdem muß auch eine erneute Auslösung eines bereits aktiven Interrupts verhindert werden, solange dessen Handler noch läuft. Außerdem gibt es noch eine Steuerung der Interruptfreigabe für UDRE. Im UDRE-Handler kann bei Eintreten bestimmter Situationen entschieden werden, daß die Auslösung des UDRE-Int nicht mehr länger nötig ist, im ADC-Handler hingegen kann die Entscheidung fallen, daß UDRE-Auslösung doch wieder nötig wird.



Und als wäre das zusammen nicht schon kompliziert genug, ist die ADC nicht freilaufend, sondern wird vom freilaufenden Timer1 (ohne Interrupthilfe, sondern über den Autotrigger-Mechanismus) getriggert und das auch nur dann, wenn der T2-Int in bestimmten Situationen sozusagen eine Freigabe dafür erteilt.



Die Idee zur Realisierung des Systems bestand darin, die vorläufige Interruptsperrung, die normalerweise über SREG_I automatisch bei Interruptauslösung realisiert wird, so schnell wie möglich durch Sperren der einzelnen Interrupt-Enable-Flags zu ersetzen. Die Handler von ADC und UDRE sehen also in etwa so aus (ist ein Device mit MMIO, also nicht über die sts wundern, wo man eigentlich out erwarten würde, außerdem habe ich den ganzen Kram mit dem Retten von Registern und Flags weggelassen, er entspricht dem üblichen Vorgehen)



adc_int: sts UCSR0B,UARTDISABLE ;löscht UDRIE0 sts ADCSRA,ADCDISABLE ;löscht ADIE und ADATE mov ADCCTRL,ADCDISABLE ;setzt diesen Zustand als "default" für die Laufzeit des Handlers. sei ;ab hier müßte meiner Meinung nach immer noch sichergestellt sein, daß ;einerseits weder eine erneute Auslösung von ADC noch eine Auslösung von ;UDRE erfolgen kann, andererseits aber der T2-Int wieder möglich ist. ;------------------------------- ;Spiel mit den gemeinsamen Daten ;------------------------------- ;je nach Sachlage kann dabei die ;Entscheidung fallen, daß die Dienste ;von UDRE wieder benötigt werden, dann ;passiert das (ohne Berücksichtigung ;eines vorigen Zustands) durch: sbr UARTCTRL,1


Also ich hab irgendann ab der Haelfte nicht mehr verstanden was du eigentlich willst.

Das bedeutet vermutlich das du entweder einen Prozessor verwenden solltest der Interruptprioritaeten kennt, oder aber dir eine andere einfachere Loesung fuer dein Problem einfallen lassen solltest. Naja, oder ich bin zu bloed. :-)

Olaf

An der Prioritaet scheitert es im genannten Fall nicht: Der Timer2 hat hier (wenn auch rein zufaellig) hoehere Prioritaet als der ADC und die UARTs. Trotzdem muss in der Standardkonfiguration auch ein Interrupt mit hoeherer Prioritaet warten bis ein anderer zu Ende gelaufen ist. Die Prioritaet entscheidet nur wer zuerst kommt wenn beide "pending" sind.

Was Heiko will ist wohl eher, dass fuer T2 die Interrupt Latenz und der Jitter minimal werden. Deswegen hat er die unwichtigen Interrupt handler auch unterbrechbar gemacht.

Micha

Dann war wohl deine Motivation zum Mitdenken zu gering. Naja, ist ja keine Pflichtveranstaltung hier.

Das wäre eine Möglichkeit.

Leider gibt es zumindest mit den Möglichkeiten der AVR keine einfachere Lösung. Ich bin schon froh, daß ich überhaupt diese gefunden habe. Zu klären ist eben, ob es tatsächlich eine Lösung darstellt. Wenn das von mir als Ursache der Probleme vermutete Zeitverhalten sich tatsächlich so bestätigt, dann ist es nämlich keine.

Deswegen hoffe ich ja stark darauf, daß jemand irgendeinen Denkfehler in meiner Implementierung findet, denn das könnte das Projekt noch retten. Wenn's aber wirklich das Verhalten der Hardware ist, dann muß ich es beerdigen, jedenfalls in der gegenwärtig geplanten Form.

AFAIK nein. Das Mask Bit verhindert, dass der Interrupt ausgefuehrt wird auch wenn das Flag gesetzt ist.

Laut Datenblatt sollte das eigentlich auch dort nicht so sein.

---------------------------------------------------------------------- udre_int: sts UCSR0B,UARTDISABLE ;löscht UDRIE0 sts ADCSRA,ADCDISABLE ;löscht ADIE und ADATE ^^^^^^^^^^^^^^^^^^^^^ sei

---------------------------------------------------------------------- Zumindest UART0 hat hoehere Prioritaet als der ADC, es kann im USART0 handler also das ADC Flag gesetzt sein. Hier koenntest du daher in ADCSRA mal noch eine 1 auf ADIF schreiben um das potentiell gesetzte ADC Interrupt flag definitiv zu loeschen.

Was auch prinzipiell gefaehrlich ist: Die ADC und UDRE Interrupts werden in deinen jeweiligen Handlern vor dem ret wieder demaskiert waehrend das globale I-Flag gesetzt ist. Das koennte potentiell zum Stack overflow fuehren weil die Verschachtelungstiefe nicht sicher begrenzt ist. Du koenntest stattdessen am Ende sowas machen:

---------------------------------------------------------------------- ... cli

reti

---------------------------------------------------------------------- Dann koennen sich die Interrupt handler nicht mehr selbst unterbrechen. Und die Zusatz-Latenz die sich der T2 Interrupt damit einfangen kann ist nicht laenger als die durch deine Mimik am Anfang, es sollte sich also an dieser Front nichts verschlechtern.

Micha

Richtig erkannt, so isses.

Übrigens ergänzend noch:

Eine denkbare Variante wäre T2-Int als einziger Int und Polling der anderen Interruptquellen im T2-Handler. Das fällt aber leider aus, weil es zuviel Rechenzeit kosten würde, der T2-Handler wird nämlich sehr häufig durchlaufen und es gibt im vollständigen System noch drei weitere Interuptquellen. Auch Reduktion der Pollingfrequenz dadurch, daß nur in jedem x-ten T2-Int gepollt wird, geht nicht. Die anderen Quellen werden im fertigen System zwar nur relativ selten ausgelöst und machen abgesehen vom ADC-Int auch nur sehr wenig, müssen aber ebenfalls möglichst zeitnah behandelt werden.

Klar, der kleine Atmel spielt hier an den Grenzen seiner Möglichkeiten. Ich hoffe halt, daß er es noch packt. Und wenn nicht dieses verflixte, bisher ungeklärte Problem dazwischen gekommen wäre, dann würde er es auch packen können.

Die Frage ist jetzt halt: Habe ich irgendwas nicht bedacht oder nicht richtig umgesetzt und knallt es deshalb, oder gibt es tatsächlich das vermutete Hardwareproblem mit Int-Auslösung trotz Disable bei gleichzeitigen Ints. Mit der Entscheidung zwischen diesen beiden Möglichkeiten steht und fällt das Projekt. Zwar wäre theoretisch auch gegen das vermutete Hardwareproblem ein work-around möglich, aber der würde soviel Rechenzeit vor dem SEI kosten, daß der T2-Jitter dann zu groß wird.

Ich werde morgen wohl erstmal ein Testprogramm basteln, was gezielt die vermutete Störbedingung herstellt. Mal sehen, was dabei rauskommt.

Sieht eigendlich nicht schlecht aus. Hast du schon versucht die Interuptflags und Interruptenableflags im Fehlerfall über Ports auszugeben? Dann hättest du einen Ansatzpunkt wo es hängt. Ich bin nicht sicher ob ein nachträhliches setzten den Interruptenableflags den Interrupt immer auslöst wenn das interruptflag davor gesetzt war.

MFG Gernot

Nö, ich will und darf den möglichen Int ja nicht unter den Tisch fallen lassen. Er soll dann später abgearbeitet werden, halt wenn der dre-Handler fertig ist und den ADC-Int wieder freigibt. Das Problem scheint aber gerade zu sein, daß eben das nicht so wie geplant passiert, sondern der ADC-Int in dieser Situation sofort nach dem SEI ausgelöst wird, trotz des zwischenzeitlich disableten ADIE.

Ja, sehr gute Idee. Das kann mit Sicherheit nicht schaden und kostet auch nur einen lumpigen Extra-Takt. Werde ich also morgen sinngemäß so einbauen.

Das löst aber leider höchstwahrscheinlich mein Problem nicht.

Jepp.

So unmöglich ist das nicht. Man könnte nachdem die T2 aufgaben durchgelaufen sind die Interrupts wider freigeben und danach mit einem vorteiler ADC und Uart pollen. Der erste Teil muß so programmiert sein dass er alle im zweiten Teil verwendeten Variablen sichert. Der T2 Interrupt kann also den ersten Teil nochmal ausführen während das Polling noch läuft. Wie das gemeint ist kannst du im SMC3 Servocontroller von Chan (ELM) anschauen.

MFG Gernot

Hier fällt mir nochwas auf. Irgendwo stellst du ja das Statusregister wider her. Darin ist das Interruptflag noch gelöscht. Da du aber nicht reti verwendest musst du es setzen. Machst du das?

MFG Gernot

Nein. Sinn des Konstruktes war ja die Minimierung der Latenz für den T2-Int. Jeder Takt vor dem SEI in den konkurrierenden Handlern bedeutet ein Takt mehr Latenz für T2. Also wird nur das absolut Notwendigste vor dem SEI erledigt. Superteurer Luxus wie das Retten von Registern und Flags gehört nicht dazu. Das passiert erst nach dem SEI. Damit ist natürlich das I-Flag in den geretteten Flags bereits wieder gesetzt.

Der Teil vor dem SEI muß für diesen Ansatz natürlich so konstruiert sein, daß er selber keinerlei Flags verändert. Das ist er.

OK, ich hatte wohl nicht richtig verstanden was du machen willst.

Im Datasheet des ATmega1281 gibt es ein huebsches Schaltbild des ADC (Figure 25-1), da ist deutlich zu erkennen, dass am ADCSRA Register an den Bits ADIE und ADIF ein AND-Gatter haengt dessen Ausgang zum Interrupt controller geht. Der Interrupt controller darf also den ADC IRQ nur dann "sehen", wenn *beide* Bits gesetzt sind, so steht es auch im Text.

Vielleicht ist der Arbiter des Interrupt controllers synchron implementiert und braucht einen Takt um sein Ergebnis bereit zu stellen, dann kaeme das SEI einen Takt zu frueh. Das Datenblatt sagt aber, dass nach dem SEI immer noch ein Befehl ausgefuehrt wird vor einem potentiellen pending interrupt (genau wie das ja auch zwischen 2 Interrupts der Fall ist). Der Interrupt controller hat also 2 Takte Zeit zu merken, dass der ADC maskiert wurde.

Du kannst ja trotzdem mal ein NOP probieren, also:

---------------------------------------------------------------------- udre_int:

nop sei ...

---------------------------------------------------------------------- Sollte es tatsaechlich daran liegen aber diese Loesung zu viel Latenz erzeugen, kannst du ja mal schauen ob ein kleinerer AVR in Frage kommt wo ADC und UART auch I/O mapped sind. Ein ATmega8 haette das z.B. so implementiert und dadurch 2 Takte weniger Latenz (d.h. er waere selbst mit zwei NOPs noch genauso schnell).

Micha

Jepp.

Nachdem es mir endlich gelungen war, den "sichtbaren Fehler" (zumindest in abgeschwächter Ausprägung) auch im Simulator so bei Takt 970.000 mal zum Erscheinen zu bringen, konnte ich dann dank der Reproduzierbarkeit der Ereignisse im Simulator so ungefähr im 100sten Anlauf die eigentliche Ursache des Problems so ca. 24.000 Takte vorher lokalisieren. HURRA, es ist nicht der Atmel, der tut, wie im Datenblatt geschrieben steht.

Es war schon, wie vermutet, eine race condition. Aber zum Glück eine, die durch meine Blödheit entsteht und nicht durch einen Fehler im Silizium. D.h.: mit Logik und ohne meine Betriebsblindheit hätte man den Bug in dem symbolischen Programm aus dem OP durchaus finden können. Wenn ich mir das mit dem neuen Wissen jetzt erneut ankucke, dann habe ich jedenfalls das Gefühl, daß der Bug mir förmlich in die Nase beißt. ;o)

Join the Discussion

Have something to add? Share your thoughts — no account required.

Didn't find your answer?

Ask the community — no account required