Forum: Mikrocontroller und Digitale Elektronik ATMega48 und Timer0-Interrupt


von Winni (Gast)


Lesenswert?

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

von Karl H. (kbuchegg)


Lesenswert?

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.

von Winni (Gast)


Lesenswert?

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 :)

von Karl H. (kbuchegg)


Lesenswert?

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.

von Christian W. (Firma: privat) (winni)


Lesenswert?

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?

von Karl H. (kbuchegg)


Lesenswert?

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.

von Christian W. (Firma: privat) (winni)


Lesenswert?

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?

von Karl H. (kbuchegg)


Lesenswert?

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.

von Christian W. (Firma: privat) (winni)


Lesenswert?

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
Noch kein Account? Hier anmelden.