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
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.
@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
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.
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.
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.
@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
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.
@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
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++.
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
@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
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
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.