Hallöchen!
Ist das ein Compiler- oder Controller-Bug? Mache folgendes:
Verwende einen ATXMEGA8E5 mit dem AVR GNU C Compiler in Atmel Studio 7.
Nehme eine nacktes (neues) Projekt (file>new: GCC Executable C project).
Compiler-Optimierungen schalte ich ab. Dann hacke ich folgenden simplen
Code ein:
1
#include<avr/io.h>
2
uint8_ta=1;
3
4
intmain(void)
5
{
6
while(1)
7
{
8
if(a)
9
{
10
a=0;
11
PORTA.OUTTGL=0b00000001;// LED blinken lassen
12
}
13
}
14
}
Was sollte passieren? Einmal sollte der if-Teil ausgeführt werden,
danach nicht mehr. Es sollte der Compiler in der Endlosschleife laufen
und ständig if(a) testen.
Was passiert? Das Programm schmiert ab und landet außerhalb des C-Codes
in der Assembler-Anweisung:
000000AF RJMP PC-0x0000 Relative jump
Eigentlich möchte ich diese Struktur nutzen, um über einen Trigger a=1
zu setzen und dann den if Teil auszuführen. Doch das Sch***teil
produziert Ausschuss.
Kann sich jemand einen Reim drauf machen? Ist das ein Bug oder bin ich
zu blöd?
Danke!
Grüße Christoph
Compiler erkennen dass "a" hier niemals wieder ungleich 0 wird und
optimieren dein Schleigfenkonstrukt mit der Bedingung einfach weg -- und
laesst nur eine enge Endlosschleife ohne Bedingung uebig.
Loesung:
Sage dem Compiler dass "a" von aussen veraendert wird und deklariere es
als
"volatile":
volatile int a = 1
... siehe https://de.wikipedia.org/wiki/Volatile_(Informatik)
Foren-tourist schrieb:> Compiler erkennen dass "a" hier niemals wieder ungleich 0 wird und> optimieren dein Schleigfenkonstrukt mit der Bedingung einfach weg -- und> laesst nur eine enge Endlosschleife ohne Bedingung uebig.
und? deswegen würde ich noch kein ungültiger Sprung entstehen und auch
das Verhalten ( LED schaltet einmal um ) bleibt gleich
Hola! Danke für eure Vorschläge!
@Foren-Tourist: Gute Idee. Hatte ich auch so ziemlich am Anfang. Die
Variable als volatile zu deklarieren bringt leider keine Veränderung.
Die Optimierungen sind auch wie gesagt abgeschaltet. Hier wird auch
nüscht wegoptimiert eigentlich...
Stefan K. schrieb:> Das ist kein Bug, sondern Optimierung.> Benutze volatile für a, dann sieht Dein Ergebnis anders aus.
nein, eine endlosschleife darf nicht wegoptimiert werden.
Timmo H. schrieb:> Also als erstes würde ich mal den Port Pin auch auf Ausgang setzen.
Ja, stimmt. Etwas schlampig. Wenn ich ihn setze bleibt der Programmfluss
jedoch immernoch derselbe.
> Das Listing für ein atxmegaE unterscheidet sich bei mir nicht von einem
A:
Und? Schmiert dein XMEGA A auch ab wie der E?
Draco schrieb:> Woher weißt du das dein µC "abschmiert"? Was läßt dich das vermuten?
Der Controller hängt danach im Befehl
0000007E RJMP PC-0x0000 Relative jump
Wenn ich einen Haltepunkt auf if(a) setze, bleibt er dort nicht mehr
stehen (nur einmal läuft er in diesen Haltepunkt, nämlich das erste
Mal).
Naja ich würde es nicht unbedingt "abschmieren" nennen. Ich meine am
ende passiert doch genau das was man erwartet. Toggle einmal den
Port-Pin und mach dann nichts mehr.
Bei mir springt er von 0xe8 nach 0xF8 und von dort gleich wieder nach
0xe2 um dann gleich wieder nach 0xF8 zu springen. Also alles gut oder?
Christoph M. schrieb:> Der Controller hängt danach im Befehl> 0000007E RJMP PC-0x0000 Relative jump
Diesen Befehl kann er allerdings gar nicht offiziell erreichen.
Irgendwas anderes muss bei dir foul sein.
Lad' doch bitte mal das generierte ELF-File hoch.
Und: benutz' bitte [c]- oder [avrasm]-Tags, um deine Codeschnipsel
zu markieren. Leute, die hier mit einem Smartphone/Tablet browsen,
bekommen Proportionalfonts, da ist das sonst völlig unleserlich.
Hier die generierte .elf-Datei.
Kleiner vielleicht wichtiger Zusatz: Wenn ich im if-Teil Dummy-Code
einfüge, der etwas rechnet und mit ansonsten nicht verwendete Variablen
arbeitet, dann geht's. Dann ist der Programmfluss wie erwartet.
Allerdings muss der Dummy-Code offenbar eine Mindestlänge haben.
Ich würde das für einen Fehler im Debugger des Studios halten.
Der Code sieht völlig ordentlich aus. Hier nochmal das Disassembly
der GNU binutils:
1
000000da <main>:
2
da: cf 93 push r28
3
dc: df 93 push r29
4
de: cd b7 in r28, 0x3d ; 61
5
e0: de b7 in r29, 0x3e ; 62
6
e2: 80 91 00 20 lds r24, 0x2000
7
e6: 88 23 and r24, r24
8
e8: 39 f0 breq .+14 ; 0xf8 <main+0x1e>
9
ea: 10 92 00 20 sts 0x2000, r1
10
ee: 80 e0 ldi r24, 0x00 ; 0
11
f0: 96 e0 ldi r25, 0x06 ; 6
12
f2: 21 e0 ldi r18, 0x01 ; 1
13
f4: fc 01 movw r30, r24
14
f6: 27 83 std Z+7, r18 ; 0x07
15
f8: f4 cf rjmp .-24 ; 0xe2 <main+0x8>
Man sieht deutlich, dass am Ende von main() ein unbedingter Rücksprung
nach 0xe2 (main+8) ist. Da du nicht optimieren lassen hast, wird
die Variable "a" auf Adresse 0x2000 danach auch wirklich wieder
abgefragt und getestet. Wenn sie 0 ist, erfolgt ein Sprung ans Ende
von main() (0xf8, also zum Rücksprung).
Die Wahrscheinlichkeit, dass der Debugger hier Mist macht, halte ich
für sehr viel größer als die, dass der Controller den Code nicht
richtig ausführt.
Christoph M. schrieb:> Der Controller hängt danach im Befehl> 0000007E RJMP PC-0x0000 Relative jump> Wenn ich einen Haltepunkt auf if(a) setze, bleibt er dort nicht mehr> stehen (nur einmal läuft er in diesen Haltepunkt, nämlich das erste> Mal).
Nein, da bleibt er nicht hängen, die 2 Linien:
1
0000007DCLIGlobalInterruptDisable
2
0000007ERJMPPC-0x0000Relativejump
hast du in jedem Program, aber da gelangt dein Program nie hin.
Wie schon mehrfach erwähnt, ATMEL Studio ist Mist, Debugger unter
aller Sau.
Ich denke, daß der Compiler völlig korrekt arbeitet. Er berücksichtigt
offensichtlich sogar mehr, als ich erwartet hätte, und das erklärt den
generierten Code:
Beim Betreten der Funktion "main" sind Interrupts disabled!
Dementsprechend kann unter keinen Umständen die Variable "a" außerhalb
der Funktion "main" beschrieben werden. Der Compiler darf also annehmen,
daß a nich außerhalb der main verändert wird.
Das Resultat ist der generierte - m.E. vollkommen korrekte - Code.
Bernhard
Christoph M. schrieb:> Draco schrieb:>> Woher weißt du das dein µC "abschmiert"? Was läßt dich das vermuten?> Der Controller hängt danach im Befehl> 0000007E RJMP PC-0x0000 Relative jump> Wenn ich einen Haltepunkt auf if(a) setze, bleibt er dort nicht mehr> stehen (nur einmal läuft er in diesen Haltepunkt, nämlich das erste> Mal).
Ist doch korrekt! Das hat genau die selbe Außenwirkung ("Observable
Behavior"¹) wie erst umständlich zu dem if zu springen von dem er
beweisen kann dass es eh immer false gibt, also braucht er da gar nicht
erst hinzuspringen und kann die Endlosschleife kürzer machen.
__________________
¹) was man von außen beobachten kann, also Eingabe und Ausgabe. Was Du
im Debugger siehst gehört NICHT zum observable behavior, im Debugger
kann man auch mal rosa Elefanten sehen, alles was zählt ist Eingabe und
Ausgabe des Programms.
Christoph M. schrieb:> Hier das dazugehörende Assembly Listing.> Im letzen Assembler-Befehl (RJMP)> verreckt der Controller:> 0000007C RJMP PC-0x000B Relative jump
Da steht RJMP PC-0x000B und nicht RJMP PC-0x0000 wie von Dir im ersten
post behauptet. Also mal lieber erstmal die Brille aufsetzen! Er
springt 11 zurück ungefähr dorthin wo das if sich befindet.
> Wenn ich einen Haltepunkt auf if(a) setze, bleibt er dort nicht mehr> stehen (nur einmal läuft er in diesen Haltepunkt, nämlich das erste> Mal).
Bei mir hällt er immer bei der if Abfrage wenn die Optimierung auf -O0
oder -O1 steht.
Ich verwende das Ateml Studio 7.0.582
Gruß JackFrost
Bastian W. schrieb:> Bei mir hällt er immer bei der if Abfrage wenn die Optimierung auf -O0> oder -O1 steht.
Ich hab auch die Optimierung auf -O0 ...
Atmel Studio ist 7.0.790.
Mit welchem Controller hast du das getestet?
Bernd K. schrieb:> Da steht RJMP PC-0x000B und nicht RJMP PC-0x0000 wie von Dir im ersten> post behauptet.
Ja, hab mich vertan. Es ist allerdings genau umgekehr:
Er hängt in
RJMP PC-0x0000
fest und springt dann (nicht verwunderlich) nicht mehr zu if(a) zurück.
Der Controller ist ein XMEGA8E5-U 1604B.
Bastian W. schrieb:> Ich hab das mit einem ATXMEGA8E5 im Simulator geprüft da ich keinen> xMega E zum testen habe
Naja, wenn Jörg Wunsch Recht haben sollte, dann ist ein Bug im Debugger:
> Die Wahrscheinlichkeit, dass der Debugger hier Mist macht, halte ich> für sehr viel größer als die, dass der Controller den Code nicht> richtig ausführt.
Mit dem Simulator nutzt du dann höchstwahrscheinlich ein anderes
Debug-System, und das funktioniert korrekt...
Ich hab mal auf 7.0.790 geupgradet. Da ist es das gleiche.
Ich hab es dann auf meinem Xplaind A1 mit dem JtagICE Mark 3 probiert.
Da ist es auch das gleiche. Bei -O0 und -O1 hält er bei der Abfrage.
Bei -O0 ist auch a in der Watchlist 0. Bei -O1 bleibt a 1 aber der Teil
in der Schleife wird nur einmal ausgeführt.
Gruß JackFrost
Bastian W. schrieb:> Bei -O1 bleibt a 1 aber der Teil in der Schleife wird nur einmal> ausgeführt.
Was für eine nicht-volatile Variable auch zu erwarten war.
Bernd K. schrieb:> Ist doch korrekt! Das hat genau die selbe Außenwirkung ("Observable> Behavior"¹) wie erst umständlich zu dem if zu springen von dem er> beweisen kann dass es eh immer false gibt, also braucht er da gar nicht> erst hinzuspringen und kann die Endlosschleife kürzer machen.
Ne, also was kürzer machen darf er nicht. Die Optiemierung steht auf
-O0, ist also aus.
1
#include<avr/io.h>
2
uint8_ta=1;
3
4
intmain(void)
5
{
6
while(1)
7
{
8
if(a)
9
{
10
a=0;
11
PORTA.OUTTGL=0b00000001;// LED blinken lassen
12
}
13
}
14
asm("NOP");// Assembler-Nop, dann wird Code richtig ausgeführt!?
15
}
Ein Nop hinter dem if sorgt dafür, dass es geht. Warum auch immer.
Wahrscheinlich ist der generierte Assembler dann doch nicht ganz
korrekt.
Christoph M. schrieb:> Ein Nop hinter dem if sorgt dafür, dass es geht.
Das NOP wird aber nie ausgeführt. Es bewirkt lediglich, dass ein
grottiger Debugger nicht abschmiert — warum auch himmer. Frag Atmel.
Christoph M. schrieb:> Wahrscheinlich ist der generierte Assembler dann doch nicht ganz> korrekt.
Der generierte Code war doch oben zu sehen, und ist völlig
nachvollziehbar korrekt.
> Ne, also was kürzer machen darf er nicht. Die Optiemierung steht auf> -O0, ist also aus.
Auch das bedeutet aber nicht, dass du einen irgendwie gearteten
Anspruch auf einen konkreten generierten Code hättest. Du kannst
nur hoffen, dass er damit alle Variablen so behandelt, als wären sie
"volatile" (macht er ja auch, wie oben im Code gezeigt), aber eine
Garantie gibt es auch dafür nicht:
1
5.1.2.3 Program execution
2
The semantic descriptions in this International Standard describe the behavior of an
3
abstract machine in which issues of optimization are irrelevant.
Christoph M. schrieb:> Ne, also was kürzer machen darf er nicht.
Wo steht das geschrieben? Er darf alles wegkürzen was nicht erforderlich
ist um das erwünschte Verhalten (Eingabe, Ausgabe) zu erzielen. Dein
Programm sieht nach Kürzung aller unnötigen Verrenkungen so aus:
1
#include<avr/io.h>
2
3
/**
4
* LED einschalten,
5
* handoptimierte Version
6
*/
7
intmain(void){
8
PORTA.OUTTGL=0b00000001;// LED einschalten und...
9
while(1);// ...Däumchen drehen.
10
}
Es tätigt die selben Eingaben (nämlich keine) und tätigt die selben
Ausgaben (nämlich LED genau einmal einschalten) und danach niemals enden
in genau der selben Reihenfolge. Das ist also eine gültige Optimierung.
Johann L. schrieb:> Das NOP wird aber nie ausgeführt.
Doch, hab gerad geguckt. Er führt es aus und ich kann auch einen
Haltepunkt darauf setzen.
Jörg W. schrieb:> Der generierte Code war doch oben zu sehen, und ist völlig> nachvollziehbar korrekt.
Und wo steckt dann der Bug? Code ist ok, wird auf den Controller geladen
und ausgeführt... der schmiert dann aber ab, d.h. Programm läuft nicht
mehr weiter. Auch ohne Debugger und Haltepunkte.
Bernd K. schrieb:> Wo steht das geschrieben? Er darf alles wegkürzen was nicht erforderlich> ist um das erwünschte Verhalten (Eingabe, Ausgabe) zu erzielen.
Ne, darf er nicht. Und der Fehler tritt auch auf, wenn "a" als volatile
deklariert wird. In einer komplexeren Variante dieses Programmes setze
ich a im Timer wieder auf 1. Das funktioiniert nur mit der Zeile
1
asm("NOP");
und das kann ja wohl eigentlich keinen Unterschied machen.
Christoph M. schrieb:> Code ist ok, wird auf den Controller geladen> und ausgeführt... der schmiert dann aber ab, d.h. Programm läuft nicht> mehr weiter. Auch ohne Debugger und Haltepunkte.
Und wie erhennst du das?
Was ist mit:
- Watchdog
- Pest auf der Spannungsversorgung
- Chinaschrott
Bernd K. schrieb:> Christoph M. schrieb:>> der schmiert dann aber ab,>> Woran siehst Du das?
1. Verwende keinen Debugger, a wird über Timer auf 1 gesetzt:
=> LED blinkt nicht mehr.
2. Ohne Debugger, ohne Timer und mit
1
PORTA.OUT=0b00000001;
=> LED wird nie gesetzt
3. Mit Debugger:
=> Steht im Assembler-Befehl
1
0000007E RJMP PC-0x0000 Relative jump
und läuft nicht mehr weiter.
Mit dem Dummy-Nop gehen alle drei Fälle wie erwartet.
Christoph M. schrieb:> Bernd K. schrieb:>> Wo steht das geschrieben? Er darf alles wegkürzen was nicht erforderlich>> ist um das erwünschte Verhalten (Eingabe, Ausgabe) zu erzielen.>> Ne, darf er nicht.
Doch das darf er. Und das soll er auch, das wünscht man sich nämlich von
einem guten Compiler.
> Und der Fehler tritt auch auf, wenn "a" als volatile> deklariert wird.
welcher Fehler?
> In einer komplexeren Variante dieses Programmes setze> ich a im Timer wieder auf 1. Das funktioiniert nur mit der Zeile> asm("NOP");
Dann deklariere (in der komplexeren Variante die Du nicht gepostet hast)
die Variable a als volatile, dann zählt nämlich jeder Zugriff darauf im
Sinne der obigen Definition als *von außen sichtbare Eingabe/Ausgabe*
(observable bahavior) und muss vom Programm in genau in der
vorgeschriebenen Anzahl und Reihenfolge durchgeführt werden, also darf
der Compiler Zugriffe darauf nicht mehr wegoptimieren.
Fertig.
Wenns dann immer noch nicht geht ist was anderes in Deinem Programm
fehlerhaft. Ein Compilerfehler ist es jedenfalls garantiert nicht, eher
gewinnst Du im Lotto als mit einem Hello-World-Blinky in einem der
meistgenutzten Compiler der Welt noch einen bis dato unentdeckten
Compilerfehler zu finden.
Johann L. schrieb:> - Watchdog> - Pest auf der Spannungsversorgung> - Chinaschrott
- Watchdog ist aus
- Spannung ist sauber 3,3V
- Chinaschrott? Hab das Ding bei mouser bestellt.
Bernd K. schrieb:> Dann deklariere (in der komplexeren Variante die Du nicht gepostet hast)> die Variable a als volatile, dann zählt nämlich jeder Zugriff darauf im> Sinne der obigen Definition als *von außen sichtbare Eingabe/Ausgabe*> (observable bahavior) und muss vom Programm in genau in der> vorgeschriebenen Anzahl und Reihenfolge durchgeführt werden, also darf> der Compiler Zugriffe darauf nicht mehr wegoptimieren.
hab ich ja als volatile deklariert. Bringt nüscht.
Bernd K. schrieb:> Wenns dann immer noch nicht geht ist was anderes in Deinem Programm> fehlerhaft. Ein Compilerfehler ist es jedenfalls garantiert nicht, eher> gewinnst Du im Lotto als mit einem Hello-World-Blinky in einem der> meistgenutzten Compiler der Welt noch einen bis dato unentdeckten> Compilerfehler zu finden.
Ja, find ich ja auch ungewöhnlich. Hardware-Bug? Also auf nem XMega A
geht es, auf nem XMega E nicht.
Poste die komplette Version, das komplette Minimal-Projekt das den
Fehler zeigt (also inclusive Timer-Interrupt und allem drum und dran) so
daß es die Leute hier mal selbst ausprobieren und/oder sehen können was
Du da falsch gemacht hast.
Ich sehe da kein Bug.
Der Ausgang wird einmal umgeschaltet und dann macht das Programm nichts
mehr. Es ist doch egal ob er die While-Schleife durchläuft oder ob er
immer am "RJMP PC" kreist.
Christoph M. schrieb:> a wird über Timer auf 1 gesetzt:
Hä? Dein Programm besteht doch i.w. nur aus einer Zeile:
> PORTA.OUTTGL=0b00000001;
Oder steckt das Problem wie so häufig in der Obfuscation
// Some code
Auch wenn es deiner Meinung nichts bringt, aber Trigger_Sekunde sollte
volatile sein. Aus meiner Sicht bringt die Fehlersuche an einem formal
fehlerhaften Programm wenig.
Da der Compiler für Trigger_Sekunde den Interrupt nicht beachtet und
dieser Variable vorab 0 ist, wird der Teil völlig ignoriert.
Der Code ist leicht anders als dein obiges Beispiel, bei dem die
relevante Variable mit 1 initialisiert wurde.
> Wieso hat main() eigentlich kein return?
Spielt keine Rolle. Wenn main() nicht in einer Endlosschleife endet,
dann fügt der Compiler eine ein. Main endet beim avr-gcc niemals.
Ist eine der wenigen Abweichungen vom C Standard.
Auch hier endet die main() Funktion mit einem Rücksprung zum
if-Ausdruck.
Also wenn da was "abschmiert" dann ist es dein Simulator oder Debugger.
Der Compiler ist in Ordnung.
Stefan U. schrieb:> Wieso hat main() eigentlich kein return?>> Spielt keine Rolle. Wenn main() nicht in einer Endlosschleife endet,> dann fügt der Compiler eine ein. Main endet beim avr-gcc niemals.>> Ist eine der wenigen Abweichungen vom C Standard.
Ich glaube nicht dass das eine Abweichung ist sondern eher dass nach
einer Rückkehr aus main() die Funktion _exit() aufgerufen wird welche
aus einer Endlosschleife besteht und beim Optimieren kurzerhand
geinlined wird.
Stefan U. schrieb:> Ist eine der wenigen Abweichungen vom C Standard.
Nein, ist keine. Der Startupcode macht das Äquivalent von:
1
exit(main());
Die Funktion exit() kannst du dabei bei Bedarf selbst überschreiben.
Die Vorgabe ist ein cli, gefolgt von einem Sprung nach _exit, welches
die besagte Endlosschleife ist.
Christoph M. schrieb:> void initialisiere_System() // CPU auf 8 MHz laufen lassen> {> asm("LDI R24,0xD8"); // (1.) 0xD8 in temporäres Register> R24 laden> asm("OUT 0x34,R24"); // (1.) 0xD8 nach 0x34 (=CCP)> schreiben> asm("LDI R24,0x02"); // (2.) 0x02 in temporäres Register> R24 laden> asm("STS 0x0050,R24"); // (2.) 0x02 nach 0x0050> (==OSC.CTRL) schreiben
Dafür gibt es in <avr/xmega.h> den Makro _PROTECTED_WRITE().
1
_PROTECTED_WRITE(OSC_CTRL,OSC_RC32MEN_bm);
> void initialisiere_Timer4() // initialisiere mit einem> Zeitintervall von 1mSek> {> cli(); // Interrupts deaktivieren> TCC4.CTRLA|= 0b00000110;> TCC4.CTRLA&= 0b11110110; // Prescaler auf 1:256> 1/8MHz = 0,125µSek ; 0,125µSek * 64 = 8µSek> TCC4.CTRLB = 0x00; // select Modus: Normal> TCC4.PER = 125; // Zählerwert bis Überlauf = 125 ;> 8µSek * 125 = 1mSek (Zeitintervall für Timer0)> TCC4.CNT = 0x00; // Zähler zurücksetzen> TCC4.INTCTRLA = 0b00000011; // Interrupt Highlevel> }
Böse Falle! Wenn eine Funktion Interrupts sperrt, dann sollte sie
sie auch wieder freigeben oder die Freigabe auf den vorherigen
Wert rücksetzen.
Dass Trigger_Sekunde "volatile" sein muss, wurde dir ja schon genannt.
Ansonsten vermisse ich die Initialisierung des PMIC. Ich vermute,
dass dein Vektor deshalb gar nicht erst aufgerufen wird, weil du
keinen Interrupt im PMIC freigegeben hast.
Christoph M. schrieb:> void initialisiere_System() // CPU auf 8 MHz laufen lassen> {> asm("LDI R24,0xD8"); // (1.) 0xD8 in temporäres Register R24 laden> [noch mehr asm]
Hat zwar direkt mit dem Problem des Themas nichts zu tun aber was um
alles in der Welt soll das ganze unnötige asm da? Warum schreibst Du es
nicht kürzer und lesbarer in C hin?
Und warum werden dort die Register und die Bits nicht beim Namen genannt
sondern die nackten Zahlen in hex hingeschrieben? Willst Du einen
Obfuscation-Contest gewinnen?
Und warum ist dieses unnötige asm auf 8 einzelne asm() statements
verteilt, ohne clobber, ohne output, ohne volatile? Dir ist schon klar
daß der Compiler das umsortieren oder wegoptimieren darf wenn er keine
Verdachtsmomente darauf findet daß dieser Code zu irgendwas nütze sein
könnte?
Bernd K. schrieb:> Warum schreibst Du es nicht kürzer und lesbarer in C hin?
Weil du damit das Timing für das CCP nicht sicherstellen kannst.
Daher auch mein Hinweis auf _PROTECTED_WRITE(), darin ist der
entsprechende inline-asm-Code für CCP bereits enthalten.