Forum: Compiler & IDEs Bug in WinAVR ? Ich dreh bald durch !


von Benedikt (Gast)


Lesenswert?

Ich programmiere mir gerade ein Leistungsmessgerät mit einem mega48 das
mir auf einer 3 stelligen 7-Segment Anzeige die Wirkleistungsaufnahme
eines Geräts anzeigt.
Leider habe ich ein absolut unerklärliches Problem mit der Anzeige:

Zu Beginn werden alle Segmente eingeschaltet (als Displaytest), dann
werden die Werte angezeigt. Nur bei mir tut sich nichts. Die Anzeige
und ADCs laufen per Interrupt. Alle Segmente bleiben an, obwohl ich
DDRAM[] auf 0 setze. Lasse ich das for weg gehen alle Anzeigen wie
gewünscht aus, dasselbe passiert es vor der Endlosschleife mache.
Im Interrupt wird DDRAM[0-3] der Reihe nach an die Anzeige ausgegeben.

Ist das ein Bug in WinAVR oder sollte ich nicht 10 Stunden ohne Pause
durchprogrammieren ?

  DDRAM[0]=255;
  DDRAM[1]=255;
  DDRAM[2]=255;

  for(;;)
  {
    DDRAM[0]=0;  //pgm_read_byte(&CGROM[peff/1000%10]);
    DDRAM[1]=0;  //pgm_read_byte(&CGROM[peff/100%10]);
    DDRAM[2]=0;  //pgm_read_byte(&CGROM[peff/10%10]);
  }

PS: Ich habe auch definitiv kein Strichpunkt hinter dem for(;;) falls
das jemand vermutet.

von Jens (Gast)


Lesenswert?

Ist das ein Leistungsmesser für das 230V-Netz?

von Stefan Seegel (Gast)


Lesenswert?

Deklarier mal dein DDRAM-Array als volatile. Vielleicht gehts dann...

Stefan

von Benedikt (Gast)


Lesenswert?

Ja, der funktioniert auch ganz gut, da die ganzen drecksteile die es bei
Pollin, C usw. gibt mir absolut falsche Ergebnisse liefern:
60W Halogenlampentrafo: 1-2W
Mit 50W Halogenlampe: 60W
Die Schaltung ist extrem einfach, ohne Netztrennung misst aber ziemlich
genau, da ich die Signale direkt mit dem mega48 ohne irgendwelche Opamps
dazwischen mit 11kHz abtaste.

Hier mal das Assembler Listung von obigem Code. Der Inhalt der for
Schleife wird komplett ignoriert:
  82 0024 8FEF          ldi r24,lo8(-1)
  83 0026 84B9          out 36-0x20,r24
  84                 .LM9:
  85 0028 8AB9          out 42-0x20,r24
  86                 .LM10:
  87 002a 8093 7E00     sts 126,r24
  88                 .LM11:
  89                 /* #APP */
  90 002e 7894          sei
  91                 .LM12:
  92                 /* #NOAPP */
  93 0030 8093 0000     sts DDRAM,r24
  94                 .LM13:
  95 0034 8093 0000     sts DDRAM+1,r24
  96                 .LM14:
  97 0038 8093 0000     sts DDRAM+2,r24
  98                 .L2:
  99                 .LM15:
 100 003c FFCF          rjmp .L2

von Benedikt (Gast)


Lesenswert?

>Deklarier mal dein DDRAM-Array als volatile. Vielleicht gehts dann...

Ja, es geht.
Aber müsste es eigentlich nicht auch ohne volatile gehen ? Ich dachte
das bräuchte man nur für Interrupts ?

von Dirk (Gast)


Lesenswert?

Hi,

maybe wurde es wegoptimiert.

Gruß,
Dirk

von Stefan Seegel (Gast)


Lesenswert?

"Ich dachte das bräuchte man nur für Interrupts ?"

Du benutzt doch auch Interrupts?

"Die Anzeige und ADCs laufen per Interrupt."

Stefan

von Benedikt (Gast)


Lesenswert?

Also wird das volatile nicht nur gebraucht, wenn der Wert im Interrupt
verändert werden kann, sondern auch wenn er nur ausgelesen wird ?

von Fritz G. (fritzg)


Lesenswert?

Genau.

Weil die Variable sonst ev. nur im Register steht und nie auf eine
RAM-Stelle geschrieben wird, Wozu auch, solange noch genug Register
frei sind.

Für den Compiler wird ja der Interrupt nirgends aufgerufen, der weiss
nix von Hardware Interrupts.

von Benedikt (Gast)


Lesenswert?

>Wozu auch, solange noch genug Register frei sind.

Das frage ich mich oft, wenn ich mit Pointern arbeite, und der Compiler
bei einer for Schleife jedesmal den Pointer aus dem RAM liest und wieder
reinschreibt, obwohl dieser kein volatile ist.

Der Compiler scheint genau das wegzuoptimiern was ich brauche, und
unnötige Sachen lässt er stehen...

von Stefan Seegel (Gast)


Lesenswert?

Und noch eins: Wenn du 16 bit (oder größer) Variablen hast und in der
Main-Schleife UND in einem Interrupt(s) darauf zugreifst muss man noch
mehr aufpassen. Wenn du z.B. in der Mainfunktion die Variable liest,
kann's passieren dass grad nachdem das erste Byte der Variablen
gelesen wurde der Interrupt aufgerufen wird und die Variable verändert
wird. Dann hast du als Ergebniss Müll. Man muss sicherstellen dass das
nicht passiert. Unter Multitaskingbetriebssystemen (Windows, Linux,
RTOS etc etc) würde man das "Thread-Synchronisierung" oder "Critical
Section" nennen. Am besten mal danach googeln, ist für's prinzipielle
Verständniss gut.
In diesem kongreten Fall würde ich bevor ich in der Mainfunktion auf so
eine Variable zugreife die Interrupts abschalten, danach wieder ein.

Stefan

von Patrick D. (oldbug) Benutzerseite


Lesenswert?

1
#define ENTER_CRITICAL do { sreg_copy = SREG; cli(); } while(0)
2
#define LEAVE_CRITICAL do { SREG = sreg_copy; } while(0)
3
4
volatile unsigned char sreg_copy = 0;
5
volatile int x = 0;
6
7
void foo(void)
8
{
9
    /* code ... */
10
    ENTER_CRITICAL;
11
    if ( x > 32000)
12
        printf("bar");
13
    LEAVE_CRITICAL;
14
}

So in etwa dürfte das aussehen, was Stefan beschreibt.

von Stefan Seegel (Gast)


Lesenswert?

Sieht interessant aus :-)
Wozu ist denn das while(0) ?

Stefan

von Rolf Magnus (Gast)


Lesenswert?

> Wozu ist denn das while(0) ?

Wenn da jetzt stünde:

  if (x)
      ENTER_CRITICAL;
  else
      foo();

Was würde der Präprozessor daraus machen ohne das do...while?

von peter dannegger (Gast)


Lesenswert?

Das "while(0)" dient nur dazu, damit der Compiler meckert, wenn
dahintger kein ";" kommt.

Einige Leute halten das für sicherer gegen Schrehbfeiler.

Ist wie wenn manche Leute schreiben "if(0 == i)" statt "if(i ==
0)".


Ich halte es für Unsinn, da man nicht jeden Schreibfehler abfangen
kann, warum sollte man es dann für einige tun ?


Peter

von ---- (Gast)


Lesenswert?

> Wozu ist denn das while(0) ?
Das ist im Endeffekt nur dazu da, um sich etwaige geschweifte
Klammern bedenkenlos sparen zu können (in denen dieser Code
irgendwann mal stehen könnte).

Wenn man sich immer sauber an alle Klammerungen hält, dann sollte
dieser Kunstgriff nicht notwendig sein. Richtig?

----, (QuadDash).

von Daniel B. (khani)


Lesenswert?

Hallo,

wenn man wirklich nur das "do" und das "while(0)" weglässt, macht
der Präprozessor nichts Schlimmes, zumindest in diesem Beispiel. Die
geschweiften Klammern ((curly) braces) sollte man aber dranlassen.


MfG, Daniel.

von peter dannegger (Gast)


Lesenswert?

Was Rolf meint, läßt sich bereits durch Einklammern vermeiden.

Macros sollte man generell immer einklammern, sonst kann es z.B.
passieren, daß float-Konstanten nicht zur Compilezeit berechnet werden
sondern die fp-lib mit einbinden.


Peter

von Rolf Magnus (Gast)


Lesenswert?

> Was Rolf meint, läßt sich bereits durch Einklammern vermeiden.

Nein.

von FPGA-User (Gast)


Lesenswert?

@Benedikt

Du schreibst, die Drecksteile von ... liefern falsche Ergebnisse...
60W Halogenlampentrafo: 1-2W
Mit 50W Halogenlampe: 60W

Meinst Du damit, dass eine 50W Halogenlampe immer 50,0 W haben
sollte (bei exakt 12V?)
Der Halogentrafo wird doch auch nicht immer exakt 12 V liefern
oder hab ich da was nicht verstanden (als Digitalmensch :-))

von JoachimSchaefer (Gast)


Lesenswert?

Hier noch was zu deinem Problem, das du zwar gelößt hast
aber ich poste es doch mal.

für den Compiler ist im Winavr Makefile default auf Optimierung s
geschalten

siehe makefile.
   # Optimization level, can be [0, 1, 2, 3, s].
   # 0 = turn off optimization. s = optimize for size.
   # (Note: 3 is not always the best optimization level. See avr-
      libc AQ.)
   OPT = s


was ihm nun nach bestimmten regeln erlaubt Code zu optimieren.
Eine Variable die 2 Mal im Code beschrieben wird und nirgends
ausgelesen wird ist nun aus Sicht des Compiler blödsinn daher entfernt
er hier beide stellen!
Beim optimieren geht der Compiler den normalen codelauf durch.
Eine Interrupt Routine wird aber nirgens direkt Aufgerufen also
berücksichtigt er diese nicht (und ohne volatile würde er sie wohl auch
komplett wegoptimieren !)
Du kannst dies eben wie von den vorrednern beschrieben umgehen indem du
die variable als volatile deklarierst.
Du kannst natürlich auch die optimierung abschalten dann gehts auch.
(Ist aber die schlechtere Lösung !)

(testSource)
int dummy;
  dummy=255;
  for (;;)
  {
      dummy=0;
  }

(Optimierung auf s = optimize for size.)
  96                 .L2:
  10:main.c        ****   int dummy;
  11:main.c        ****   dummy=255;
  12:main.c        ****
  13:main.c        ****   for (;;)
  97                 .LM2:
  98 0008 FFCF          rjmp .L2

(Optimierung auf 0 = turn off optimization.)

10:main.c        ****   int dummy;
  11:main.c        ****   dummy=255;
  96                 .LM2:
  97 0008 8FEF          ldi r24,lo8(255)
  98 000a 90E0          ldi r25,hi8(255)
  99 000c 8D8B          std Y+21,r24
 100 000e 9E8B          std Y+22,r25
 101                 .L2:
  12:main.c        ****
  13:main.c        ****   for (;;)
  14:main.c        ****   {
  15:main.c        ****       dummy=0;
 103                 .LM3:
 104 0010 1D8A          std Y+21,__zero_reg__
 105 0012 1E8A          std Y+22,__zero_reg__
 107                 .LM4:
 108 0014 FDCF          rjmp .L2

von Andreas S. (andreas) (Admin) Benutzerseite


Lesenswert?

Oldbug: warum das SREG sichern?

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Ich bin nicht Patrick :), aber:

damit kann man zu einem beliebigen Zeitpunkt die Interrupts
abschalten und danach den vorherigen Zustand des I-Flags
wiederherstellen.

Wenn man den Code mit Sicherheit nicht innerhalb einer ISR
benutzen will, kann man natürlich gleich cli()/sei() schreiben.

von Werner B. (Gast)


Lesenswert?

Eine etwas verspätete Reaktion auf OldBugs "SREG-Rettung" ;)

@OldBug

Dein SREG-sichern ist aber auch ... sagen wir mal problematisch
1
#define ENTER_CRITICAL do { sreg_copy = SREG; cli(); } while(0) 
2
define LEAVE_CRITICAL do { SREG = sreg_copy; } while(0) 
3
   
4
volatile unsigned char sreg_copy = 0; 
5
volatile int x = 0; 
6
   
7
void foo(void) 
8
{ 
9
    /* code ... */ 
10
    ENTER_CRITICAL; 
11
      ...
12
    LEAVE_CRITICAL; 
13
}
Nehmen wir an interrupts sind enabled und foo wird aufgerufen...
Aktuelle Codezeile "sreg_copy = SREG;" ist in bearbeitung...
  INTERRUPT*
  ==>der Befehl wird zu ende geführt,
     interrupts werden disabled
     und dann nach SIGNAL_xyz verzweigt.
       SIGNAL_xyz ruft nun ebenfalls foo auf
       -> "sreg_copy = SREG;";
          sreg_copy enthält jetzt sreg mit
          Interrupts disabled ...
Nach der Rückkehr aus SIGNAL_xyz enhält Dein globales sreg_copy den
falschen Wert. Mache sowas als lokale variablen auf dem stack, da
kenn kein Interrupt dran rumdrehen. ;-)

Gruß  Werner

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.