Forum: Mikrocontroller und Digitale Elektronik Fehler beim Codevision-AVR


von Martin (Gast)


Lesenswert?

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

von Armin Kniesel (Gast)


Lesenswert?

Wie hast Du denn "portb1" deklariert?

von crazy horse (Gast)


Lesenswert?

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.

von Martin (Gast)


Lesenswert?

@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.

von crazy horse (Gast)


Lesenswert?

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.

von Peter D. (peda)


Lesenswert?

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

von crazy horse (Gast)


Lesenswert?

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
Noch kein Account? Hier anmelden.