Hey Leute, ich bin momentan am Programmieren mit der Atmel Studio IDE und bin auf ein unerklärliches Problem gestoßen. Mein Programm verfügt über eine Interrupt Funktion, welche soweit super funktioniert. In der ISR werden zwei LEDs gemultiplext und je nachdem welche an ist, wird die Farbe (sind RGB LEDs) entsprechend ausgegeben. In der Endlosschleife habe ich dann die Farben entsprechend definiert: right_color = 1; left_color = 2; Mache ich das so, passiert NICHTS. Die LEDs leuchten nicht. Füge ich aber ein _delay_us(1) hinzu (egal welcher delay-Wert), dann funktioniert es einwandfrei. Die Frage ist jetzt, warum es ohne delay nicht funktioniert? Ist doch etwas blöd wenn ich einen Systemtakt von 8 MHz habe, aber durch die 1µS dann nur noch theoretisch 1 MHz habe. Aktuell fällt das nicht so ins Gewicht mit der Geschwindigkeit, aber später habe ich Projekte geplant bei der ich Daten im ns Bereich übertrage. Also direkt mit dem Systemtakt spiele. Da wäre also ein delay unerwünscht. Sollte etwas unklar sein, würde ich mich über Fragen freuen ;). HIER DER CODE: #include <avr/io.h> #include <avr/interrupt.h> #include <util/delay.h> uint8_t multiplex[2]; uint8_t color[4]; uint8_t multiplex_value = 0; uint8_t color_value = 0; uint8_t right_color = 0; uint8_t left_color = 0; uint8_t wert = 1; void init_timer_0 (void) { TCCR0A = 0x00; TCCR0B = (0<<CS00) + (1<<CS01) + (0<<CS02); TIMSK0 = (1<<TOIE0); TCNT0 = 0; } int main(void) //void setup { DDRA = 0xFF; multiplex[0] = 0b00010000; multiplex[1] = 0b00000000; color[0] = 0b00000000; color[1] = 0b00100000; color[2] = 0b01000000; color[3] = 0b10000000; init_timer_0(); sei(); while (1) // void Loop { if (wert == 1) { left_color = 1; right_color = 2; wert = 0; _delay_us(1); } } } ISR(TIM0_OVF_vect) { // TCNT0 = 0; if (multiplex_value == 0) { PORTA = (color[left_color]) | (multiplex[0]); } else { PORTA = (color[right_color]) | (multiplex[1]); } multiplex_value ++; if (multiplex_value == 2) { multiplex_value = 0; } }
Nico S. schrieb: > Sollte etwas unklar sein Wann wird wert wieder 1? Wann bekommt der Timer seinen Startwert? Georg
https://www.mikrocontroller.net/articles/Interrupt#Volatile_Variablen Warum es dann mit delay geht? Weis auch nicht.
Mal mit -O0, also ohne Optimierung, übersetzen lassen.
Merke: Variablen, die innerhalb einer ISR gelesen und außerhalb beschrieben werden (oder umgekehrt) muss man als "volatile" deklarieren. Der Compiler bemüht sich darum, die Zugriffe auf das RAM zu reduzieren und stattdessen CPU Register zu benutzen auf die er schneller zugreifen kann. Das Register wird erst ins RAM geschrieben, wenn es unbedingt nötig ist. Zum Beispiel wenn nicht genug Register frei sind oder wenn eine Funktion aufgerufen wird, die aus dem RAM lesen wird. Da im gesamten Programm die ISR nirgends aufgerufen wird, hält der Compiler es nicht für notwendig, den aktuellen Wert ins RAM zu schreiben. Für dich ist klar, dass die ISR ausgeführt wird, weil du weisst, wie der Timer mit Interrupt-Handlern zusammen arbeitet. Der Compiler kennt das Konzept "Interrupt-Handler" aber leider nicht. Deswegen musst du da manuell nachhelfen. Lösung:
1 | volatile uint8_t right_color = 0; |
2 | volatile uint8_t left_color = 0; |
multiplex_value muss nicht volatile sein, weil diese nur in der ISR geänder und gelesen wird. Georg schrieb: > Wann wird wert wieder 1? Gar nicht, spielt auch keine Rolle. Ich denke er hat das Programm hier minimiert um das Problem darzustellen. Daher sieht dieser Teil sinnlos aus. Georg schrieb: > Wann bekommt der Timer seinen Startwert? Er läuft irgendwann über und ab dann im vorgesehenen Intervall. turgut schrieb: > Warum es dann mit delay geht? Weis auch nicht. Die Delay Funktion löst das Problem zufällig, weil sie CPU Register benötigt. Der Compiler muss das Register das die Variable zwischengespeichert hat, ins RAM schreiben, um das Register für die Delay Funktion frei zu bekommen. S. Landolt schrieb: > Mal mit -O0, also ohne Optimierung, übersetzen lassen. Dadurch wird die Code-Optimierung deaktiviert. Variablen werden dann bei jedem Zugriff wirklich aus dem RAM gelesen und ins RAM geschrieben. Das Programm wird dadurch etwas größer und langsamer. Bitte beachten, dass die delay Funktionen nicht mehr richtig funktionieren, wenn man die Optimierung deaktiviert. Siehe https://www.nongnu.org/avr-libc/user-manual/group__util__delay.html Bei AVR liegen die 32 Register tatsächlich auch im RAM. Auf diese kann er aber schneller zugreifen, als auf alle anderen RAM-Zellen.
Hm, ich habe mir das jetzt mal knapp durchgesehen ohne alles nachvollziehen zu können, weil ich gerade keine Controllerhardware zur Verfügung habe, aber: Zum einen solltest du deinen Code besser kommentieren denn, hier muß man nachforschen was du eingestellt hast: TCCR0B = (0<<CS00) + (1<<CS01) + (0<<CS02); Im Code hätte dann stehen können: TCCR0B |= (1<<CS01); // CS02= 0, CS01= 1, CS00= 0 ==> Timerclock = F_CPU / 8 Dein Systemtakt ist also 1 MHz (wenn, wie du sagst, dein Controller mit 8MHz läuft)!! (Wäre auch nicht schlecht, das im Kommentar zu hinterlegen). TIMSK0 = (1<<TOIE0); Hätte einen Kommentar vertragen, dass du den Timer als Overflow verwendest. Weil du keine Wertezuweisungen machst und Timer0 ein 8-Bit Timer ist, wird der Timer alle 256 Takte überlaufen, also wird deine ISR alle 256 µs aufgerufen. Wenn du also in deiner ISR einen Wechsel auf Leuchtdioden anzeigst, die alle 256 µs ändert, wirst du davon nichts sehen können. Zudem: if (wert == 1) { left_color = 1; right_color = 2; wert = 0; _delay_us(1); } Wird in deiner Schleife genau ein einziges mal (beim ersten mal) ausgeführt werden, weil du in der globalen Variable wert hier den Zahlenwert 1 zuweist, danach wird der Inhalt zu 0 und nirgendwo wieder zu 1. Also dürfte auch dein _delay_us(1) keine Auswirkungen haben. Deine Zuweisung an die LED ist hierbei schon extrem gewöhnungsbedürftig und grundsätzlich bin ich der Meinung dürfte dein Teil eigentlich gar nichts machen, ich kann mir jetzt nur noch vorstellen, dass du irgendwo in der Hardware noch etwas hast, das einen Reset oder ähnliches ausführt. Warum gehst du nicht her (wie alle anderen auch) und machst eine "saubere" Zuweisung, an welchen Port deine RGB-LED angeschlossen ist. Ich habe mir deinen Code jetzt ein paar mal angesehen und für mich sieht es so aus, als ob die LED an PA7, PA6 und PA5 angeschlossen ist. Warum benennst du das nicht so?
1 | #define LED_PORT PORTA
|
2 | #define LED_DDR DDRA
|
3 | #define LED_R PA7
|
4 | #define LED_G PA6
|
5 | #define LED_B PA5
|
6 | |
7 | #define INIT_LED (LED_DDR |= (1 << LED_R) | (1 << LED_B) | (1 << LED_G))
|
8 | #define LEDSET_R LED_PORT |= (1 << LED_R)
|
9 | #define LEDCLR_R LED_PORT &= ~(1 << LED_R)
|
10 | |
11 | // setzen und loeschen fuer gruenen und blauen Anteil im gleichen Stil
|
Dann kannst du in deinem Programm, egal ob in der main oder in deiner ISR deine LED deutlich besser leßbar steuern.
Ich habe angefangen zu schreiben, da gabs den Kommentar von Stefan noch nicht... Grundsätzlich sollte man Stefans Aussage noch ergänzen (weil man, ohne böse zu nahe zu treten, vllt. annehmen mag, dass der Threadersteller über volatile-Variable nicht so wirklich bescheid weiß): Eines der Gründe warum man Variable volatile macht ist der, dass der Compiler, selbst dann wenn er annimmt, dass er eine Variable gar nicht benötigt, nicht wegoptimieren darf. Innerhalb einer ISR (und hier weiß der Compiler nicht, dass diese Funktion von Hardware, in deinem Falle von einem Timer, aufgerufen wird und deshalb "meint" der Compiler, dass dieser Code nie ausgeführt wird und optimiert diese weg). Volatile verhindert dieses.
Der Compiler optimiert die ISR nicht weg, weil das Makro "ISR" das Schlüsselwort "volatile" enthält. volatile bewirkt zwei Dinge: Der Compiler darf es (Variable, Funktion) nicht weg optimieren und er darf die Werte nicht cachen, muss also jeden Schreib- und Lese-Zugriff direkt so ausführen, wie es im Quelltext steht.
Okay, ich hätte bei meinem Assembler bleiben sollen, schustermässig betrachtet. Trotzdem fühle ich mich an Meys Klempner erinnert: Immer, wenn er Wasser zapfe, sammle Erdgas sich im Napfe, und klingle zufällig das Telephon, ergäbe sich manch heftige Detonation. Der wusste noch nicht, dass _delay einiges reparieren kann.
Stefan F. schrieb: > volatile bewirkt zwei Dinge: Der Compiler darf es (Variable, Funktion) > nicht weg optimieren und er darf die Werte nicht cachen, muss also jeden > Schreib- und Lese-Zugriff direkt so ausführen, wie es im Quelltext > steht. ... ich habe nichts anderes geschrieben
S. Landolt schrieb: > Der wusste noch > nicht, dass _delay einiges reparieren kann. Das hat mit _delay() nichts zu tun. Für den Compiler gilt, dass jede Funktion für die eigenen Daten allein verantwortlich ist. Wenn dann ein Unterprogramm aufgerufen wird, muss der Compiler davon ausgehen, dass alle Register verändert werden und muss daher die noch benötigten Werte im RAM ablegen. Und damit sind sie auch für die ISR greifbar und aktuell. Statt _delay() kannst du eine beliebige externe Routine nehmen, über die zur Compile Zeit nichts bekannt ist. Der einfachere Weg ist, wie oben geschrieben, das Schlüsselwort volatile.
Ralph S. schrieb: > TCCR0B = (0<<CS00) + (1<<CS01) + (0<<CS02); > > Im Code hätte dann stehen können: > > TCCR0B |= (1<<CS01); // CS02= 0, CS01= 1, CS00= 0 ==> Das geht i.A. schief, weil nicht sicher gestellt ist, dass TCCR0B gelösch ist. Insbesondere macht diese Formulierung die Sache nicht klarer, weil die Anzahl der Schiebebefehle (CS01) vom zu setzenden Wert abhängt und wegen der mitzuschleppenden Kommentare fehlerträchtig ist. Die Table 11-9 im Datenblatt stellet die drei Clock-Select Bits gemeinsam dar und so kann man sie dann sinnvollerweise bei der Konfiguration auch benutzen, also einfach
1 | TCCR0B = (0b010<<CS00) |
oder der Klarheit wegen sogar
1 | #define CS CS00 |
2 | ... |
3 | TCCR0B = (0b010<<CS) |
Wolfgang schrieb: > Das geht i.A. schief, weil nicht sicher gestellt ist, dass TCCR0B > gelösch ist. Zum einen: Das geht deshalb NICHT schief, weil im Datenblatt Seite 83 steht: Initial Value = 0 D.h. mit Einschalten der Betriebsspannung ist das Register TCCR0B gelöscht !! Zum anderen hatte ich noch überlegt gehabt, etwas in dieser Art zu schreiben: TCCR0B &= ~((1<<CS00) | (1<<CS02)); Um die 2 Bits ganz sicher zu löschen. Andererseits gehe ich allerdings davon aus, dass man weiß, ob man dieses Register schon einmal während seines Programmes angefasst hat oder nicht und zum anderen wird der Timer i.a.R. nur einmal eingestellt, beim Initialisieren beim Programmstart. Wolfgang schrieb: > Die Table 11-9 im Datenblatt stellet die drei Clock-Select Bits > gemeinsam dar und so kann man sie dann sinnvollerweise bei der > Konfiguration auch benutzen, also einfachTCCR0B = (0b010<<CS00)oder der > Klarheit wegen sogar#define CS CS00 > ... > TCCR0B = (0b010<<CS) Viele Wege führen nach ROM! Man kann ja auch Namen vergeben wie: Timer1_Prescaler_Clock_Selectbits und dann auch noch Timer1_Prescaler_Clock_Selectbits_Position Nur, wer will das ausschreiben. Grundsätzlich wichtig ist, dass man das kommentiert (egal wo) und vor allen Dingen dann, wenn man den Code jemandem zeigt, der darin einen Fehler finden soll.
Ralph S. schrieb: > Das geht deshalb NICHT schief, weil im Datenblatt Seite 83 steht: > > Initial Value = 0 i.A. geht es genau schief, weil der Initial Value nur nach einem Reset im Register steht. Genau nur nach einem Reset funktioniert es also. Ralph S. schrieb: > Nur, wer will das ausschreiben. Grundsätzlich wichtig ist, dass man das > kommentiert (egal wo) Statt Kommentaren ist es immer zuverlässiger, den Code von sich aus so zu schreiben, dass man ihn schon möglichst du versteht. Ein "CS02= 0, CS01= 1, CS00= 0" als Kommentar hilft da wenig, aussagekräftig und weiterführend wäre ein "clkIO / 8".
Wolfgang schrieb: > Das geht i.A. schief, weil nicht sicher gestellt ist, dass TCCR0B > gelösch ist. Der Zustand nach einem Reset ist sicher. Er ist unmissverständlich spezifiziert. Wolfgang schrieb: > i.A. geht es genau schief, weil der Initial Value nur nach einem Reset > im Register steht. Genau nur nach einem Reset funktioniert es also. Die Initialisierung wird ja auch nur genau einmal nach dem Reset ausgeführt. Stell Dich nicht so an! > Ein "CS02= 0, CS01= 1, CS00= 0" als Kommentar hilft da wenig, > aussagekräftig und weiterführend wäre ein "clkIO / 8". Das ist auch eher mein Stil, würde ich ebenfalls so weiter empfehlen.
Stefan F. schrieb: > Der Zustand nach einem Reset **ist** sicher. Er ist unmissverständlich > spezifiziert. Ach was. Du bist nur nicht paranoid geung.
Wolfgang schrieb: > Ein "CS02= 0, > CS01= 1, CS00= 0" als Kommentar hilft da wenig, aussagekräftig und > weiterführend wäre ein "clkIO / 8". // CS02= 0, CS01= 1, CS00= 0 ==> Timerclock = F_CPU / 8 Und was steht da? Der Zeilenumbruch im Forum hat das F_CPU / 8 in die nächste Zeile verschoben... unglaublich ... Mich würde eher interessieren, was der TO daraus alles gemacht hat, anstelle über Kleinigkeiten zu diskutieren wie etwas gemacht wird und wie übersichtlich etwas ist... von Leuten, die wissen wie es geht und von denen eben jeder seinen eigenen Stil hat. Zumindest gehe ich davon aus, dass die, die antworten, wissen wie es geht !
Danke für die schnellen Antworten! Bin neu hier im Forum und in anderen Foren hat es ewig gedauert bis immerhin eine Antwort kam. Stefan, ich habe mir mal deinen Beitrag ausführlich durchgelesen. Da ich neu im Atmel Studio Bereich bin kannte ich die genaue Funktion dahinter nicht. Ich werde es gleich mal so ausprobieren wie du gesagt hast. > Wann wird wert wieder 1? Wie Stefan sagte, hat das keine Rolle gespielt. Ich habe es vorher ohne die if-Bedingung gemacht und dachte mir (klingt paradox), dass wenn ich ohne ein delay die ganze Zeit die Variablen "beschreibe", dass der microcontroller sich irgendwo in der Art "aufhängt" und deshalb nichts an den LEDs passiert. Deshalb dachte ich mir, ich führe das nur einmal aus, um das "Aufhängen" zu verhindern. Aber das hat auch nichts gebracht :D. Ich werde es in den nächsten Stunden mit volatile austesten und euch dann bescheid geben! Danke nochmals für die Antworten!
Servus Leute, habe es eben gerade ausprobiert ^^. Mit der Funktion volatile funktioniert es einwandfrei ^^. Danke nochmal! Der Code sieht jetzt wie folgt aus: #include <avr/io.h> #include <avr/interrupt.h> #include <util/delay.h> uint8_t multiplex[2]; uint8_t color[4]; uint8_t multiplex_value = 0; uint8_t color_value = 0; volatile uint8_t right_color = 0; volatile uint8_t left_color = 0; uint8_t wert1 = 1; uint8_t wert2 = 1; void init_timer_0 (void) { TCCR0A = 0x00; TCCR0B = (0<<CS00) + (1<<CS01) + (0<<CS02); TIMSK0 = (1<<TOIE0); TCNT0 = 0; } int main(void) //void setup { DDRA = 0xFF; multiplex[0] = 0b00010000; multiplex[1] = 0b00000000; color[0] = 0b00000000; color[1] = 0b00100000; color[2] = 0b01000000; color[3] = 0b10000000; init_timer_0(); sei(); while (wert1 == wert2) // void Loop { left_color = 2; right_color = 3; } } ISR(TIM0_OVF_vect) { if (multiplex_value == 0) { PORTA = (multiplex[0]) | (color[left_color]); } else { PORTA = (multiplex[1]) | (color[right_color]); } multiplex_value++; if (multiplex_value == 2) { multiplex_value = 0; } }
Ralph S. schrieb: > Und was steht da? Der Zeilenumbruch im Forum hat das F_CPU / 8 in die > nächste Zeile verschoben... unglaublich ... Das Problem sitzt in diesem Fall vor dem Rechner. Nicht ohne Grund sollte man Code oder fest formatierten Text immer in passende Tags einschließen, eben genau um sicher zu stellen, dass er nicht durch die automatische Formatierung verändert wird. Probier's mal mit "C"- bzw. "Pre"-Tags
Nico S. schrieb: > Servus Leute, > > habe es eben gerade ausprobiert ^^. > > Mit der Funktion volatile funktioniert es einwandfrei ^^. Das ist keine Funktion sondern ein type qualifier. https://de.wikipedia.org/wiki/Volatile_(Informatik) https://en.cppreference.com/w/c/language/volatile
Wolfgang, ich weiß zwar nicht genau was das Problem jetzt sein soll, aber meins wäre auf jeden Fall geklärt. Das einzige Problem war das fehlende "volatile" gewesen. Der Rest des Codes funktioniert einwandfrei. Die CPU Frequenz wurde nicht im Code, sondern in der Toolchain festgelegt. Das funktioniert auch super. Kommentare habe ich in dem Code nicht eingefügt, da es nur ein kleines Funktions-Code ist, um meine Hardware auf Funktion zu prüfen.
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.