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.
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
>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 ?
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.
>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...
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
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
> 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).
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.
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
@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 :-))
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
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.
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
defineLEAVE_CRITICALdo{SREG=sreg_copy;}while(0)
3
4
volatileunsignedcharsreg_copy=0;
5
volatileintx=0;
6
7
voidfoo(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