Forum: Mikrocontroller und Digitale Elektronik AVR ATMega324 Interrupt-Programmierung, Prioritäten


von Peter M. (peter0)


Lesenswert?

Hallo zusammen,

ich fange gerade an, mich mit Interruptprogrammierung beim ATMega324 zu 
beschäftigen. In dem Zusammenhang bin ich etwas verunsichert, weil ich 
immer wieder von Interruptprioritäten lese.

Wenn ich das, was im Datenblatt steht bzw. nicht steht, richtig 
zusammenzähle, denke ich, dass der eigentliche Prozessorkern keine 
Interruptprioritäten im klassichen Sinne kennt. Mit klassisch meine ich 
wie z.B. beim 80C166, wo der Prozessorkern schon allein durch Annahme 
eines Interrupts von z.B. Priorität 5 (ohne weitere Beeinflussung durch 
die Software) dafür sorgt, dass keine anderen Interrupts der Priorität 5 
oder niedriger angenommen werden. Höher priorisierte Interrupts aber 
schon. Dort gibt es nicht ein I-Flag im Status, sondern IMHO vier, um 
den Level der gerade erlaubten Interrups abzubilden.

Dem Datenblatt entnehme ich, dass bei Annahme des Interrupts beim AVR 
lediglich das I-Bit gelöscht wird.
Die Prioritäten hier sind nur dafür wichtig, welcher Interrupt als 
erstes bedient wird, sofern mehrere gleichzeitig anstehen. Wenn ich in 
der ISR das I-Flag nicht beeinflusse, sind während der Ausführung alle 
weiteren Interrupts gesperrt, egal wie hoch deren Priorität im Vergleich 
zu der "meiner" gerade laufenden Routine ist.

Sehe ich das richtig?

Danke und schönen Gruß – Peter

von Oliver S. (oliverso)


Lesenswert?

Richtig

Oliver

von Stefan (Gast)


Lesenswert?

Ja, das siehst Du richtig.
Das Einzige, was Du machen kannst: das I-Bit in Interrupt-Routinen zu 
löschen, die von anderen Interrupts unterbrochen werden sollen. Mit 
allen negativen Konsequenzen.

Gruß, Stefan

von c-hater (Gast)


Lesenswert?

Stefan schrieb:

> Das Einzige, was Du machen kannst: das I-Bit in Interrupt-Routinen zu
> löschen, die von anderen Interrupts unterbrochen werden sollen. Mit
> allen negativen Konsequenzen.

Es gibt eigentlich nur eine wirklich negative Konsequenz und das ist die 
Gefahr eines Stacküberlaufs. Und die kann man sehr leicht dadurch 
ausschließen, daß man möglichst schnell in der ISR den exklusiv zu 
erledigenden Kram abarbeitet, um dann von der globalen Interruptsperre 
mit der Holzhammermethode via I-Flag auf die lokale via *IE-Bit des 
Kontrollregisters der entsprechenden interruptauslösenden Einheit zu 
wechseln.

Damit hat man eigentlich nur noch die üblichen Probleme (und Vorteile!) 
"paraller" Programmierung.

Natürlich: Wenn die Last insgesamt zu hoch ist, wird's nicht mehr 
richtig funktionieren. Aber das ist bei jeder anderen Methode der 
Ereignisbehandlung ganz genauso der Fall, bis herunter zu den Systemen 
ganz ohne Interrupt, die alles nur in main() pollen.

Das Problem beim Umswitchen auf lokale Interruptsperren ist vor allem 
der zusätzliche Overhead, der dadurch eingeführt wird. Man muß hier 
abwägen, wo es sinnvoll (im Sinne des Gesamtsystems) ist und wo nicht. 
Auf jeden Fall hat man damit erstmal eine einfache und gut beherrschbare 
Möglichkeit, zwei Prioritätsebenen für Interrupts zu schaffen.

Das besonders blöde bei größeren AVRs ist, daß hier leider längst nicht 
mehr alle Register mit *IE-Flags per sbi/cbi erreichbar sind, was dazu 
zwingt, den exklusiven Teil der ISRs durch das Retten von Registern und 
Flags noch signifikant weiter zu verlängern. Da ist es dann sehr 
nützlich, wenn man eine Sprache/Compiler verwendet, wo es möglich ist, 
zwei Register extra für solche Zwecke in ISRs zu reservieren. Die stehen 
dann allen ISRs zur Verfügung und verkürzen für alle die Zeit, in denen 
sie exklusiv laufen müssen und senken damit auch die Latenzen für alle 
ISRs, die oft viel wichtiger sind als die Laufzeit an sich.

von Peter M. (peter0)


Lesenswert?

Danke für die Bestätigung.

Ich möchte gar nicht wirklich eine Priorisierung. Andere Interrupts 
sollen aber kommen können; es soll halt nur nicht die eigene IRQ-Quelle 
nochmal kommen können.

In meinem Beispiel geht es Timer0, Overflow.

Ich schreibe also am Anfang von meiner ISR ein sei hin. Weil aber beim 
TIFR0 (Timer/Counter 0 Interrupt Flag Register) steht: "TOV0 is cleared 
by hardware when executing the corresponding interrupt handling vector" 
(verstehe ich so, dass in dem Moment, wo meine ISR betreten wird, es 
schon gelöscht ist), bleibt mir nichts anderes übrig, als vor dem sei 
noch ein TOIE0=0 (TIMSK0-Register) zu veranlassen. Am Ende der ISR setze 
ich das Bit wieder. Damit ein ggf. vorher aufgelaufener wiederholter 
Timer-Überlauf nun nicht reinhackt, kommt vorher noch ein cli.

Also:
1
ISR(TIMER0_OVF_vect, ISR_BLOCK)
2
{
3
  TIMSK0 &= ~(1<<TOIE0)  // Weitere Timer-IRQs verhindern
4
  sei();                 // Andere IRQs erlauben
5
  MeinCode();
6
  cli();                 // Neuer Timer-IRQ soll nicht reingrätschen
7
  TIMSK0 |= (1<<TOIE0)   // Timer-IRQ wieder erlauben
8
}

Was passiert, wenn MeinCode so lange braucht, dass währenddessen ein 
neuer Timer-Überlauf stattfindet?
Ich hoffe/verstehe das so: Bit in TIFR0 wird gesetzt, es passiert 
erstmal nichts, weil ich Bit in TIMSK0 gelöscht habe. Erst nach reti 
sind I-Flag und Bit in TIMSK0 beide gesetzt. D.h. dann wird sofort 
erneut die ISR aufgerufen, ohne Stacküberlauftendenzen.

Die angesprochenen Laufzeitoptimierungen durch exklusive Register habe 
ich erstmal hintenangestellt.

Ist das in anderen Worten das, was c-hater (Gast) meinte? Oder habe ich 
noch was übersehen?

Gruß – Peter

von Peter M. (peter0)


Lesenswert?

Gerade gefunden: 
http://www.mikrocontroller.net/articles/AVR-GCC-Tutorial#Unterbrechbare_Interruptroutinen

Meine Vorschläge scheinen dem zu entsprechen.

von Frank M. (ukw) (Moderator) Benutzerseite


Lesenswert?

Peter M. schrieb:
> Also:
>
1
ISR(TIMER0_OVF_vect, ISR_BLOCK)
2
> {
3
>   TIMSK0 &= ~(1<<TOIE0)  // Weitere Timer-IRQs verhindern
4
>   sei();                 // Andere IRQs erlauben
5
>   MeinCode();
6
>   cli();                 // Neuer Timer-IRQ soll nicht reingrätschen
7
>   TIMSK0 |= (1<<TOIE0)   // Timer-IRQ wieder erlauben
8
> }

Würde ich nur im Notfall machen. Du erhebst doch lediglich den Anspruch, 
dass ein anderer Interrupt auf jeden Fall auch ausgeführt werden soll. 
Das passiert doch auch ohne sei() und cli() in der ISR. Wenn während 
Ausführung der ISR "A" ein weiterer Interrupt für ISR "B" eintrifft, 
wird das mittels Flag gespeichert. Sobald sich ISR "A" beendet, wird 
dann ISR "B" angesprungen. Es geht also nichts verloren - außer wenn die 
ISR "A" so lahm ist, dass während der Ausführung mehr als ein Interrupt 
für ISR "B" eintrifft. Dann wird die ISR "B" nur einmal statt zum 
Beispiel 3x ausgeführt. Aber das halte ich eher für einen Vorteil als 
Nachteil.

Bei diesem Fall müsste man den Code für ISR "A" halt nachbessern.

> Was passiert, wenn MeinCode so lange braucht, dass währenddessen ein
> neuer Timer-Überlauf stattfindet?

Wird als Flag gespeichert. Nach Beendigung wird direkt wieder die ISR 
angesprungen. Damit hast Du dann den µC zu 100% dichtgemacht. ;-)

von Oliver S. (oliverso)


Lesenswert?

Peter M. schrieb:
> ich fange gerade an, mich mit Interruptprogrammierung beim ATMega324 zu
> beschäftigen.

In den allermeisten Fällen gibt es bei typischen AVR-Programmen keinen 
Grund, ISRs unterbrechbar zu machen, wenn man den ebenso AVR-typischen 
Ratschlägen folgt, ISRs kurz zu halten, und Datenbearbeitung in der 
main-Loop durchzuführen. Die verfügbare Rechenleistung wird ja durch 
Einsatz von ISRs nicht größer. Mehr Zyklen, als der Prozessor pro 
Zeiteinheit ausführen kann, kann er nicht ausführen.

In den ganz seltenen und wenigen Ausnahmefällen, bei denen es auf 
zyklengenaue und extrem schnelle Reaktion auf (externe) Ereignisse 
ankommt, mögen unterbrechbare ISRs erforderlich sein. Dann weiß man aber 
sehr genau, warum das nötig ist.

Oliver

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.