Forum: Mikrocontroller und Digitale Elektronik Interrupt-kritische Bereiche


von Florian Bantner (Gast)


Lesenswert?

Hallo,

wie schon hier: http://www.mikrocontroller.net/forum/read-1-163645.html
angekündigt, beschäftige ich mich aus akuten Anlass gerade mit den
Tücken der Interrupt-Programmierung. Nachdem ich da (für größere
Projekte ohne OS) noch keine sinnvolle Literatur gefunden habe, suche
ich jetzt einfach 'mal hier im Forum. Und zwar:

Bei der Arbeit mit Interrupts tritt immer wieder das Problem auf, das
der Interrupt quasi zur 'unzeit' auftritt. Es gibt also eine Reihe
von Kommandos / Konstrukten wo Interrupts temporär mit cli/sei (evtl.
unterstützt durch Sichern des i-flags aus sreg) ausgeschaltet werden
sollten. Dieses Problem tritt vor allem, wenn auf Resourcen sowohl
innerhalb der Interrupts als auch von ausserhalb (main) darauf
zugegriffen wird.

Ich sammele hier und jetzt Fälle, wo solches auftritt. Ich denke, dass
dies ein wichtiger Punkt in der uC-Programmierung ist und es nicht
schaden kann, einmal eine Liste der möglichen Probleme
zusammenzustellen. Ich gehe hirbei von Programmierung in C mit dem GCC
aus, auch wenn diese Probleme nicht ausschlißlich darauf Beschränkt
sind. Aber es ist nunmal im Moment das einzige, womit ich mich leidlich
auskenne. Bestimmte Probleme treten wohl auch erst in ASM offensichtlich
auf, sind aber auch in C vorhanden. Problemfälle die mir gerade
einfallen sind:

  * Zugriff auf globale Variablen. Hierbei modifiziert ein Int
Variablen, wärend sie gerade von main bearbeitet werden. Dieses Problem
ist offensichtlich z.B. bei Konstrukten wie
<pre>if( glob == 0 ) glob++;</pre> Hier kann eine Unterbrechung nach
dem if auch das an dieser Stelle unerwünschte Ergebnis glob == 2
entstehen. Können sich hier Ints main unterbrechen, müssen alle
nicht-atomaren Operationen durch Ausschalten der Interrupts gesichert
werden. Die Eigenschaft 'atomar' ist in C alles andere als trivial zu
bestimmen. PORTC=0 ist z.B. atomar. (ein Befehl in ASM). PORTC = PORTC+1
nicht (Laden in R, inkrementieren, speichern). PORTC |= 1 dagen schon
(sbi PORTC,1). Konstukte, die auf Konditionen auf globalen Variablen
bauen sind imho generell als nicht atomar zu sehen.
Als global sind hier auch per Zeiger zurückgegebenen lokale (_myvar)
Variablen zu sehen.

  * Paging und Paging-ähnliche Mechanismen. Da ich den aktuellen Fall
gerade habe: im at90can128 wird das aktuelle CAN-MOb durch CANPAGE
ausgewähl. Danach kann z.B. mit CANMSG auf den Inhalt des gerade
selektierten MObs zugegriffen werden. Kommen sich hier nun zwei MOb
Zugriffe in die Quere, wird womöglich auf den Falschen MOb zugegriffen.
Einzige Abhilfe: cli/sei um den gesammten MOb-Zugriff.

Ich bin mir sicher, das hier andere Leute mit mehr erfahrung in
uC-Probrammierung noch einige zusätzliche Probleme / Beispiele bringen
können. Sollte sich eine erschöpfende Liste ergeben, bin ich gerne
bereit, das ganze für die FAQ zusammenzufassen.

Schoene Gruesse,

Florian

von Christof Krüger (Gast)


Lesenswert?

Wenn es um synchronisierung und somit um pseudoparallele Abarbeitung
geht, dann gibt es da recht viel Literatur zu. Das was du beschreibst,
nennt man oft "race conditions". Ein gutes Buch über Betriebssysteme
allgemein kann da helfen (z.B. Modern Operating Systems von
Tanenbaum).

Den Zugriff auf eine geteilte (im Sinne von shared) Ressourcen hast
du ja bereits erwähnt.

Ansonsten gibt es beim AVR mehrere Operationen, die kurz aufeinander
folgen müssen, das Schreiben ins EEPROM gehört glaube ich auch dazu,
bei SPM im Bootloader ist das ähnlich, da muss etwas innerhalb von z.B.
4 Taktzyklen passieren, da sind Interrupts dann nicht willkommen.

Ein absolutes No-No ist ansonsten natürlich Busy-Waiting im Interrupt.
Mehr fällt mir momentan nicht ein.

von Florian Bantner (Gast)


Lesenswert?

@Christof

Den Tannenbaum hatte ich doch tatsächlich gestern erst in der Hand und
hab ihn wieder weggelegt, weil ich ihn mir nur als 'Fetisch' gekauft
hätte. Jetzt kauf ich ihn mir doch. Schön eine gute Ausrede zu haben
;)

Zum Busy-Waiting: Der Begriff ist mir neu. Verstehst Du darunter jedes
Warten in einem Interrupt oder meinst du explizit Dead-Locks?

In einem Interrupt zu warten ist zwar grundsätzlich eine schlechte
Idee, muss aber nicht fatal sein und lässt sich hin & wieder sogar
garnicht vermeiden. Ich würde mal graduell sagen: Sicher kurz warten =
nicht so tolle Idee, Sicher länger warten: noch weniger tolle Idee,
unregelmäßig oder unabschätzbar warten: Ganz besonders blöde Idee.

Dead-Locks sollte man wohl in MCUs unter allen Umständen vermeiden. Ist
hier aber auch meist nicht ganz so schwer wie in OS-Programmierung
(denke ich zumindest), da die Resourcen überschaubarer sind. Das man in
einem Interrupt nicht auf einen anderen warten sollte, versteht sich von
selbst. (wo soll er auch herkommen.). Konstukte wie: Im Interrupt
Interrupts einschalten und auf einen anderen warten lassen mich
schauern.

Aber vielleicht hat das ja sogar jemand schon mal (erfolgreich?)
eingesetzt? Anekdoten?

Schoene Gruesse,

Florian

von Christof Krüger (Gast)


Lesenswert?

Busy Waiting bedeutet: In einer Schleife auf ein bestimmtes Ereignis
warten. Der MC ist also mit Warten beschäftigt, kann zwischendrin also
nichts anderes machen.

Deadlocks sind eine mögliche Folge daraus. Das andere ist, dass man so
den Interrupt wieder in die Länge zieht und evtl. wichtigere INTs
ersteinmal warten müssen. Wäre das nun in der main-loop, dann würde der
wichtige INT die main loop unterbrechen und zuerst abgearbeitet werden.

Letztendlich lässt sich vieles darauf zurückführen und Peter
Dannenberger hat hierzu schon recht viel Wahres geschrieben.

von Malte (Gast)


Lesenswert?

Auf die Idee das es selbst bei if( glob == 0 ) glob++; zu Problemen
führen kann wenn aus zwei verschidenen Stellen (zB. Interrupt und Main)
auf glob geschieben wird kommen kann war mir bisher nicht eingefallen.
Werde mein aktuelles Programm wohl da noch mal etwas anpassen müssen.

Probleme die mir bisher aufgefallen sind:
1)
Bei if( glob == 0 ) kann es bereits alleine (nur Lesen) zu Probelmen
führen wenn glob größer als ein Byte ist (bei einem 8-Bitter). Also
wenn ein Teil der Variable während der Vergleichsoperation geändert
wird. z.B. aus 0x0100 -> 0x00FF. Unter der Annamhe dass erst die
unteren 8Bit verglichen werden, dann die Variable im Interrupt
heruntergezählt wird und dann die oberen 8Bit verglichen werden.
2)
Ich hab einen UART Interrupt, der das UDR Register ausliest und das
ganze in einen Ringbuffer schreibt. Wird der Interrupt zwischen UDR
auslesen und schreiben in den Ringbuffer erneut ausgeführt (z.B. weil
ein anderer langer Interrupt dazwischen kam) so könnte das nachfolgende
Byte vor ersterem im Ringbuffer landen.

von A.K. (Gast)


Lesenswert?

Genau solche Probleme mit globalen Variablen sind übrigens der Charme
von C++ bei Embedded Systems. Zugriffe auf globale Variablen lassen
sich per Zugriffsfunktionen absichern. Kann man zwar in C auch
implementieren, aber in C++ lässt sich das erzwingen und
übersichtlicher ist es auch.

Man muss ja nicht den ganzen Ballast von C++ nutzen. Dann ist das nicht
schlechter als C. Nur die Auswahl an Compilern schrumpft beträchtlich.

von Florian Bantner (Gast)


Lesenswert?

@Malte: Siehste mal, an 16bit hatte ich auch noch nicht gedacht. Genau
solche Tipps suche ich. Werde ich wohl auch noch in meine Checkliste
mit aufnehmen.
2. Sollte nur dann auftreten, wenn ich schon im Interrupt das I-flag
wieder einschalte (oder INTERRUPT() benutzt). Das fällt für mich
(noch?) unter 'grundsätzlich vermeiden'. Ich kann mir kaum Fälle
vorstellen, wo man sich nicht nur zusätzliche Probleme damit
einhandelt, ohne einen wirklichen Vorteil davon zu haben. Auf PCs würde
man davon ausgehen, dass man damit einen perfekten Ansatzpunkt für einen
Denial-of-Service Angriff hat, da man mutwillig sehr gut den Stack
abschiessen könnte. Also muss man nachweisen, dass der Interrupt nicht
zu einem Stack-Overflow führe kann, was an sich schon mal umständlich
ist. Aber kommt wohl auch etwas darauf an, ob man eher mit verlorenen
Nachrichten oder mit einem Komplettabsturz leben kann. Eigentlich
sollte man aber etwas suchen, was weder das eine noch das andere
zulässt.

@A.K. Würde mindestens meinen linken Daumen dafür geben, auf dem AVR
sinnvoll in C++ programmieren zu können. Aber ohne new macht das
keinen echten Spass. Und new braucht in der Regel malloc. Und malloc
wiederum eine Speicherverwaltung. Die wiederrum ist am besten mit
Auslagerungsdatei o.ä. aber mindestens mit einem OS. (Kann man wohl
auch anders sehen, ich jedenfalls fühle mich unwohl mit einem Gerät, wo
eher unbemerkt der Heap überlaufen kann (oder keinen Speicher mehr für
new zur Verfügung stellt). Damit bin ich für mich jedenfalls eher zum
Schluss gekommen, dass ich mich an C halte. (Habe vorher hauptsächlich
Java programmiert, also weiss ich, was ich vermisse.)
Wobei: Für den Vorteil echter Kapselung könnte ich es evtl. schon in
Betracht ziehen. Aber es ist dann so sehr C++ wie VisualBasic eine
richtige Programmiersprache :)

Aber konstruktuiv: Programmierst du den avr in C++? Taugt der GCC
dafür?

Schoene Grüsse,

Florian

von A.K. (Gast)


Lesenswert?

Was an C++ sonst eher nervt (vor allem von einem systematischen
Standpunkt aus), nämlich die Möglichkeit Daten auch statisch oder auf
dem Stack direkt ablegen zu können - hier ist es von Vorteil. Nur so
geht das in kleineren embedded systems.

Richtig ist freilich, dass dies eine ganz andere Art ist, mit C++
umzugehen. Wer von Smalltalk oder Java zu C++ kam und es bislang vor
allem deshalb verflucht hat, weil Stroustrup den garbage collector
vergass, der muss wohl umlernen. Wer von C zu C++ kam hat es leichter.

Ja, ich verwende WinAVR/g++ auf AVR. Stark eingeschränkt natürlich,
also beispielsweise kein new/malloc, kein exception handling (runtime
fehlt). Gründe waren u.A.: interrupt/multithreaded-sichere
encapsulation von globalen Variablen und einheitliche Interfaces per
inheritance. Letzteres vereinfacht beispielsweise den Umgang mit
verschiedenen Typen von Temperatursensoren.

von Florian Bantner (Gast)


Lesenswert?

@A.K. Das mit der runtime verstehe ich nicht. Habe mich aber auch noch
nicht ausführlich mit dem Thema beschäftigt. Generell scheue ich im
Moment den Aufwand und die Unsicherheit, die für mich durch eine
zusätzliche Abstraktionsebene an der Stelle entsteht.

Prinzipiell finde ich es schon sehr interessant, dass es von Leuten
tatsächlich eingesetzt wird & nicht nur eine akademische Spielerei (auf
Mikrocontrollern zumindest) ist. Vielleicht werde ich es mir ja auch
noch genauer anschauen :)

Schoene Gruesse,

Florian

von A.K. (Gast)


Lesenswert?

Manche Feature von C und C++ erfordert entsprechend Runtime-Support,
d.h. spezielle Library-Funktionen. Der Runtime-Support für C ist
vorhanden, der für C++ nicht. Womit also die entsprechende C++ Feature
schon mal nicht geht. Und exception handling benötigt eben libstdc++.

von Mark Hämmerling (Gast)


Lesenswert?

Salve,

erstmal ein Lob für diese Threadidee. :)

Wußte ja gar nicht, daß sich die eeprom_write_*() um das
Löschen/Wiederherstellen des Global-Int-Enable kümmern. Das Problem mit
dem 4-Zyklen-EEPROM-Schreibschutz hatte ich schonwieder aus den Augen
verloren. Man vergißt sowas ja schnell, wenn man sich auf
Lib-Funktionen verläßt (zu recht, wie sich herausstellte). War schon
drauf und dran, mir Sorgen zu machen, weil ich eeprom_write_*() so
gewissenlos mit globalen Ints verwende. Aber das Listfile zeigt mir,
daß sich die Lib-Routinen prima um das Global-Int-Bit kümmern. puh :)

Die ganze Routine mit cli()/sei() zu umklammern hätte ich mir nicht
leisten können, da die Busy-Wait-Schleifen bestimmt viel zu lang sind
für meine Ints, die unbedingt sofort reagieren müssen (bei DMX-Empfang
[250kbps] ist das Verschlucken eines empfangenen Bytes fatal).

Das klammheimliche SREG-Sichern/cli()/SREG-Wiederherstellen machen
bestimmt ganz viele andere Lib-Routinen auch, wo man gar nicht drüber
nachdenkt. Werde mir dessen in Zukunft hoffentlich bewußter sein.

An dieser Stelle Dank an alle avr-libc-Entwickler (z.B. den Jörg) für
die wundervolle Arbeit, die Ihr leistet. Ich find es großartig, was mit
OpenSource (und ich beziehe meine Projekte da mit ein g) alles möglich
ist. Hoffen wir, daß das Gruselthema Softwarepatente ein glimpfliches
Ende nimmt. ;(

Mark

von Florian Bantner (Gast)


Lesenswert?

@A.K.: Jetzt versteh ichs. Stand wohl auf dem Schlauch :)

@Mark: Danke für das Lob. Ich hoffe, es kommen noch viele andere Ideen.
Kann mir nicht vorstellen, dass das schon alles ist.

Florian

von Christof Krüger (Gast)


Lesenswert?

Ich sage es mal so: Man kann auch objektorientiert und mit einheitlichen
Interfaces arbeiten, ohne C++ zu benutzen, allerdings muss mann
natürlich höllisch aufpassen, es dann auch richtig zu machen.

Sehr interessant fand ich auch, dass ich nach einiger Zeit der
C-Programmierung für AVRs mir dann auch viel mehr Gedanken bei
PC-Programmen gemacht habe was Speicher- und Laufzeiteffizienz angeht.
Daher bleibe ich auch gerne "low-level", da ich Compilern nur sehr
bedingt traue, was sich immer wieder bestätigt, wenn ich mir ein
lst-File anschaue.

Huh, das war ja OT!

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.