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.