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.
Deklarier mal dein DDRAM-Array als volatile. Vielleicht gehts dann... Stefan
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 ?
"Ich dachte das bräuchte man nur für Interrupts ?" Du benutzt doch auch Interrupts? "Die Anzeige und ADCs laufen per Interrupt." Stefan
Also wird das volatile nicht nur gebraucht, wenn der Wert im Interrupt verändert werden kann, sondern auch wenn er nur ausgelesen wird ?
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
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.
Sieht interessant aus :-) Wozu ist denn das while(0) ? Stefan
> 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?
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
> Was Rolf meint, läßt sich bereits durch Einklammern vermeiden.
Nein.
@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 | 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
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.