Hallo Leute! Eigentlich ist es kein Fehler, sondern eher ein Umstand, den man wissen muss. Ich möchte wissen wie ihr dazu steht. Vielleicht liest auch jemand diese Nachricht, der mit dem GNU-Compiler zu tun hat, um zu sehen wie dieses Problem dort umgangen wird. Kurze Erklärung des Programmablaufes: Es handelte sich um eine reine Testroutine. Das Hauptprogramm bestand aus einer While-Schleife in welcher ständig PORTB.1 invertiert wurde. while (1) { // Place your code here portb1=~portb1; }; Zusätlich war der externe IRQ-0 eingeschaltet und der entsprechende Pin (PD.2 2313) mit einem Frequenzgenerator verbunden. Die IRQ-Routine hatte die Aufgabe den PIN PORTB.0 zu invertieren. // External Interrupt 0 service routine interrupt [EXT_INT0] void ext_int0_isr(void) { // Place your code here portb0=~portb0; } Alle Ein- und Ausgangssignale wurden zusätzlich auf einem OSZI angezeigt. Das Lustige bei dem Ganzen war, dass es ab und zu passierte, dass portb0 nicht nur in der IRQ-Routine invertiert wurden, sondern auch nach dem verlassen dieser!?! Ich sah mir den Assembler-Code mal. Dies ist der Assemblercode aus dem Hauptprogramm: 99: portb1=~portb1; +00000067: 94E8 CLT Flag clear +00000068: 9BC1 SBIS 0x18,1 Skip if bit in I/O register set +00000069: 9468 SET Flag set +0000006A: B3E8 IN R30,0x18 In from I/O location +0000006B: F9E1 BLD R30,1 Bit load from T to register +0000006C: BBE8 OUT 0x18,R30 Out to I/O location 100: }; +0000006D: CFF9 RJMP -0x0007 Relative jump Der Fehler: In Zeile +6a kann man erkennen, dass Portb (0x18) in das Register R30 kopiert wird, um später den PIN PORTB.1 verändern zu können. Wird nach der Ausführung dieses Befehls ein IRQ ausgelöst, so werden die Register erst mal ge push t. Danach wird PortB.0 in der IRQ-Routine verändert. Jetzt werden alle Register wieder ge pop t. Nach dem Rücksprung befindet sich der alte Wert, der sich vor dem Einsprung in die Irq-Routine im Register 30 befunden hat noch immer im R30, also vor der Änderung von Portb.0. Nun fährt das Hauptprogramm fort und nach Kurzem wird, man sieht es in Zeile +6C (out 0x18,R30), R30 wieder auf 0x18 (PORTB) zurückgeschrieben. Die Veränderung, die in der IRQ-Routine vollzogen wurde ist nun futsch. Ich habe eine Fehlerbeschreibung an HPINFOTECH gesendet mit folgender Antwort: Dear Sir, The problem you are describing is not the fault of the compiler. If you want that it will not occur write: while (1) { // Place your code here #asm("cli") // disable interrupts portb1= ~ portb1; #asm("sei") // re-enable interrupts } Also ich finde das einwenig schwach, dass man den IRQ ausschalten muss, wenn man mal einen Portpin in einem Hauptprogramm verändern möchte. Was ist, wenn ich mal einen ganzen PORT verändern möchte, tritt dann das Problem auch auf? Hierbei handelt es sich wieder mal um einen Fehler, wo man wochenlang sucht und sich bestimmte Fehlschaltungen nicht erklären kann. Wie ist das beim GNU-Compiler gelöst? Das wäre interessant. Ich freue mich auf eure Komentare, Anregungen und Erklärungen... Tschüss. Martin
Gegenfrage: wie würdest du es in Assembler lösen, OHNE den Interrupt zu sperren? Und wärest du zufrieden, wenn automatisch bei allen Portzugriffen dieser Art die Ints gesperrt würden? Ein Compiler ist kein Freifahrtsschein, die Besonderheiten des Prozessors zu beachten, liegen in Hand des Programmierers. Ein umfassender Softwaretest ist deshalb schwierig, weil eben auch solche Besonderheiten berücksichtigt werden müssen, bzw. ergibt sich daraus die ernüchternde Erkenntnis, dass es kaum bis gar nicht möglich ist, ein Programm unter allen möglichen Betriebszuständen zu testen. Und gerade Interrupts sind immer besonders zu beachten. Letzten Endes können selbst unscheinbare Änderungen am Programm zu einem kompletten Neutest führen. Wenn dein Fehler vielleicht nur einmal im Monat aufträte - wer denkt da erstmal an einen Softwarefehler? Gibt genug solcher Beispiele, wo nach jahrelangem fehlerfreien Betrieb plötzlich ein Softwarefehler mit z.T. fatalen Folgen auftritt. Berühmt-berüchtigt ist ein Fehler einer Bestrahlungskanone, wurde eine bestimmter Dosis (falsche Eingabe) durch eine bestimmte andere Dosis ersetzt, drehte das Maschinchen durch und verbrutzelte einen Patienten, inzwischen leider verstorben. Hat lange gedauert, bis die Ursache geklärt war.
@Armin Das habe ich so gemacht: #define portb1 PORTB.1 #define portb0 PORTB.0 @crazy horse Natürlich hast du recht mit deiner Meinung und ich bin froh, dass ich bei einem einfachen Softwaretest draufgekommen bin und nicht etwa bei einem komplizierten Projekt. Aber meiner Meinung nach sollte eine Hochsprache solche architektonischen Nachteile ausgleichen. Darum gibt es die Hochsprachen ja als Werkzeug, damit man in die Prozessorarchitektur nur soweit als notwendig einsteigen muss. Ich beschäftige mich mit drei Verschiedenen Prozessorfamilien. Und wenn ich bei jeder Familie den Assemblerdialekt auf das Genaueste lernen hätte müssen, wäre ich heute noch nicht besonders weit. Außerdem stehen leider nie architektonische Nachteile im Datenblatt. Bei C hat man den weiteren Vorteil, dass man den Code auf andere Familien viel leichter übertragen kann. Meiner Meinung nach könnte der Compiler schauen, ob in der IRQ-Routine ein Bit desselben Ports gesetzt wird, um erst dann im Hauptprogramm den IRQ kurzzeitig zu sperren. Wäre eine Möglichkeit, da ich finde, dass der Programmierer schon genung mit Programmablauf und Initialisierungen beschäftigt ist. Aber zumindest ein Hinweis des C-Compiler-Herstellers auf diese Schachstelle wäre von Vorteil.
ich glaube nicht, dass man das Problem durch Änderungen am Compiler lösen kann, und dein Problem tritt nicht nur bei den Ports auf, sondern bei allen read/modify/write_back-Operationen auf. Wenn das Hauptprogramm eine Speicherzelle manipuliert, also i.a. erstmal in ein Register/Akku lädt, dabei unterbrochen wird, ein anderer Programmteil die Ausgangsdaten manipuliert und anschliessend die ursprüngliche Operation zu Ende geführt wird, muss das Ergebnis fehlerhaft werden. Das ist kein Problem des AVR, sondern ein generelles, welches bei allen Prozessoren auftaucht. Es hilft nur eines: gerade bei Interrupts aufpassen wie ein Luchs.
Grundsätzlich muß man immer davon ausgehen, daß C-Instruktionen nicht atomar sind, d.h. sie bestehen fast immer aus mehreren Assemblerinstruktionen. Der Compiler kann nichts dafür und kann deshalb auch nichts dagegen tun. Allein Du kannst und must das prüfen und entsprechende Gegenmaßnahmen treffen. Besonders, wenn man Code von einer anderen Maschine portiert muß trotz einwandfreier Compilierung der Code noch lange nicht laufen. Z.B. ist dieselbe Instruktion auf einem 8051 völlig gefahrlos, da der mit einem einzigen Assemblerbefehl Portpins invertieren kann. D.h. dort ist diese Instruktion rein zufällig atomar. Peter
alles richtig, aber noch nicht umfassend. Das Problem hat mit C oder Compiler gar nichts zu tun, es ist in Assembler genauso vorhanden und auch da nur durch programmiertechnische Lösungen zu umgehen.
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.