Hallo zusammen, wollte endlich mal ein bisschen mit dem Timer-Interrupt arbeiten und wie das Schicksal es will kommt nichts aus dem AVR. Es soll eine PWM über 9 invetierte Kanäle erreicht werden mit 16 verschiedenen Helligkeitstufen für LEDs. Hab schon heraus gefunden, dass der Mega48 einen komplexeren Aufbau für die Timer hat als der 8 oder 16er und trotz vieler Veränderungen funktioniert es nicht! #include <avr/io.h> #include <util/delay.h> #include <inttypes.h> #include <avr/interrupt.h> uint8_t red1,green1,blue1; uint8_t red2,green2,blue2; uint8_t red3,green3,blue3; volatile uint8_t i_pwm; int main(void) { DDRD = 0xFF; // Port D Ausgänge DDRB = 0xFF; //PB0 Ausgang PORTD = 0xFF; // auf EINS PORTB |= (1<<PB0); //Timer0 einstellen TCCR0A=0x00; TCCR0B =(1<<CS01)|(1<<CS00); //Vorteiler 64 -> 1225Hz TCNT0=0x00; TIMSK0|=(1<<TOIE0); //Timer0-Interrupt freischalten sei(); while(1) { //PWM Test for (uint8_t i=0;i<17;i++) { red1=i; green2=i; blue3=i; _delay_ms(50); } for (uint8_t i=17;i>0;i--) { red1=i-1; green2=i-1; blue3=i-1; _delay_ms(50); } } } ISR(TIMER0_OVF_vect) { i_pwm++; if(i_pwm>15) i_pwm=0; if(red1>i_pwm) PORTD &= ~(1<<PD2); else PORTD |= (1<<PD2); if(green1>i_pwm) PORTD &= ~(1<<PD1); else PORTD |= (1<<PD1); if(blue1>i_pwm) PORTD &= ~(1<<PD0); else PORTD |= (1<<PD0); if(red2>i_pwm) PORTD &= ~(1<<PD5); else PORTD |= (1<<PD5); if(green2>i_pwm) PORTD &= ~(1<<PD4); else PORTD |= (1<<PD4); if(blue2>i_pwm) PORTD &= ~(1<<PD3); else PORTD |= (1<<PD3); if(red3>i_pwm) PORTB &= ~(1<<PB0); else PORTB |= (1<<PB0); if(green3>i_pwm) PORTD &= ~(1<<PD7); else PORTD |= (1<<PD7); if(blue3>i_pwm) PORTD &= ~(1<<PD6); else PORTD |= (1<<PD6); } Weiß nicht mehr was daran falsch ist und bin über jede Hilfe Dankbar. Gruß Winni
volatile Ausserdem: Für einen ersten Test reicht es mit etwas einfacherem anzufangen. Timer laufen lassen - in der ISR eine LED einfach nur einschalten. Damit sieht man, ob die ISR aufgerufen wird oder nicht.
Hallo und danke für die schnelle Antwort! Es war tatsächlich das volatile, welches vor den globalen Variablen fehlte. Ich dachte aber dass man nur mit volatile arbeiten muss, wenn man wärend einer ISR eine Veränderung der Variable vornimmt oder nicht? Und worin liegt der Unterschied in der Reihenfolge? //So funktioniert es uint8_t volatile red1,green1,blue1; //und so nicht volatile uint8_t red1,green1,blue1; Gruß Christian Ps. Ja...war vllt etwas oversized für die erste ISR :)
Winni schrieb: > Hallo und danke für die schnelle Antwort! Es war tatsächlich das > volatile, welches vor den globalen Variablen fehlte. Ich dachte aber > dass man nur mit volatile arbeiten muss, wenn man wärend einer ISR eine > Veränderung der Variable vornimmt oder nicht? Du brauchst das volatile, wenn du gloable Variablen sowohl in der ISR als auch im restlichen Programm benutzt. Das volatile teilt dem Compiler mit, dass sich der Inhalt einer Variablen auf Wegen ändern kann, bzw. dass diese Variable auf Wegen benutzt wird, die er prinzipiell nicht selbst rauskriegen kann. Der Compiler darf daher auf diesen Variablen keine Optimierungen machen. In deinem Fall wird der Compiler folgendes rausbekommen haben:
1 | while(1) { |
2 | |
3 | //PWM Test
|
4 | for (uint8_t i=0;i<17;i++) { |
5 | red1=i; |
6 | green2=i; |
7 | blue3=i; |
8 | _delay_ms(50); |
9 | }
|
red1, green2, blue3 wird im restlichen 'Programm' nicht benutzt. Aus dem main() heraus wird auch keine Funktion aufgerufen. Daher ist es unsinnig den jeweils aktuellen Wert dieser 'Variablen', der sich nach der Modifikation noch in einem CPU Register befindet wieder in den Speicher zurückzuschreiben. Eventuell hat der Compiler sogar soweit analysiert, dass eine Variable nicht verändert werden braucht, wenn sie sowieso nirgends benutzt wird. Dass die ISR auf den Inhalt dieser Variablen angewiesen ist, kann der Compiler nicht wissen, denn für ihn ist eine ISR auch nur eine Funktion. Eine Funktion die nirgends, zumindest nicht aus main() heraus, aufgerufen wird. Was daher in der ISR passiert, ist für den Verlauf der Variablenveränderungen in main() irrelevant. So zumindest sieht das der Compiler wenn er seine Datenflussanalyse macht. Das die ISR nebenher doch aufgerufen wird, über einen Mechanismus den der Compiler nicht kennt, weiß er nicht. Und deswegen muss man dem Compiler derartige Variablen mit volatile markieren. Damit man dem Compiler mitteilt: Alles was du über diese Variablen annimmst ist falsch. Wenn ich eine Zuweisung an diese Variable schreibe, dann meine ich auch Zuweisung und egal was deine Analyse rausfindet, ich will dass du die Variable tatsächlich veränderst. Und natürlich auch umgekehrt beim Lesen von dieser Variablen.
Ahh jetzt ja...jetzt versteh ich das! Vielen Dank für die gute Antwort! Was wäre denn schneller, wenn ich direkt in die Register schreibe, wie bei der ISR oben oder vorher die Bits einer Variable (lokal oder global?) manipuliere und dann nur einmal das Register schreibe?
Christian W. schrieb: > Ahh jetzt ja...jetzt versteh ich das! > Vielen Dank für die gute Antwort! > > Was wäre denn schneller, wenn ich direkt in die Register schreibe, wie > bei der ISR oben oder vorher die Bits einer Variable (lokal oder > global?) manipuliere und dann nur einmal das Register schreibe? Mal sehen. Dein Hinweis lautet: PORTD ist selber eine volatile 'Variable'. Wenn du volatile wirklich verstanden hast, kannst du dir die Antwort jetzt selber geben.
ok...ich versuchs mal: da PORTD volatile ist muss jeder zugriff darauf direkt im RAM verändert werden und kann nicht in interen Registern "geparkt" oder vom compiler wegoptimiert werden. Daraus schließe ich, dass wenn ich eine normale lokale Variable in der ISR benutzten würde und dann nur einmal auf den PORTD schreibe es schneller sein sollte! Richtig? Wie viele zusätzliche Cycles sind denn dann noch notwendig um die PORTD Variable vom RAM auf die I/O-Module zu schreiben? Beeinflussen diese Cycles dann auch mein Programm?
Christian W. schrieb: > ok...ich versuchs mal: da PORTD volatile ist muss jeder zugriff darauf > direkt im RAM verändert werden und kann nicht in interen Registern > "geparkt" oder vom compiler wegoptimiert werden. Daraus schließe ich, > dass wenn ich eine normale lokale Variable in der ISR benutzten würde > und dann nur einmal auf den PORTD schreibe es schneller sein sollte! > Richtig? Richtig. Allerdings ist die Sache so einfach auch wieder nicht :-) Dein AVR hat spezielle Befehle um 1 Bit an einem Port zu setzen und zu löschen (ok, wenn du dich nie mit Assembler beschäftigt hast, kannst du das nicht wissen). Das heist PORTD &= ~(1<<PD2); ist ja eigentlich nur eine Kurzform von PORTD = PORTD & ( ~(1<<PD2) ); Nach dem Buchstaben des C-Gesetzes müsste hier also auf jeden Fall PORTD ausgelesen werden, selbst wenn es vorher schon einmal ausgelesen wurde und der Wert in einem Register vorliegt. Dann wird verundet und das Ergebnis wieder an PORTD zurückgeschrieben. Soweit zur Theorie. Absolut gleichwertig ist es aber auch, wenn dein Compiler anstelle dieser langen Sequenz die kürzere Form der speziellen Befehle zur Portmanipulation nimmt. Vom Ergebnis her ist das nicht zu unterscheiden. Und daher gilt an dieser Stelle ausnahmsweise: Es spielt so gut wie keine Rolle :-) PORTD ist zwar volatile, dieser 'Nachteil' wird aber durch spezielle Befehle der CPU ausgeglichen, die die Nachteile des volatile aushebeln.
Wow...bin jetzt wieder ein bisschen schlauer geworden ;) Sehr vielen Dank für deine Hilfe!
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.