Forum: Mikrocontroller und Digitale Elektronik TIMER0_COMPA_vect oder compare match treibt mich zum Wahnsinn


von Stefan K. (skorpion64)


Lesenswert?

Hallo Gemeinde,
hier mein erster Thread in Eurem schönen Forum.

Ich möchte gerne mit einem ATtiny 85 etwa alle 20ms einen Impuls von 
definier Breite an einem frei wählbaren Port generieren und bin dabei 
Programmfragmente zu testen.

der Port ist im Beispiel PB4

Es wird auch etwa alle 20ms ein Impuls ausgegeben. Leider ist der Impuls 
nicht, wie geplant 1,5ms lang, sondern nur 60µs. Ich denke, dass der 
Compare_Interrupt unmittlebar aufgerufen wird und dabei der Ausgang 
wieder auf 0 geschaltet wird.

Wo liegt mein Denkfehler?

Hier der Code.

// Compiler- Warnungen

#ifndef F_CPU
#warning "F_CPU not defined"
#define F_CPU 8000000UL
#endif


/*********************************************************************** 
*************/
// Libraries

#include <avr/io.h>
#include <avr/interrupt.h>
#include <util/delay.h>

//#include <avr/wdt.h>    // für Watchdog- Funktionen
//#include <stdint.h>
//#include <avr/eeprom.h>

/*********************************************************************** 
*************/
// Pin- Belegung
#define In_Port        PORTB  // Port für Eingänge
#define Pin_Eingang      PINB  // Eingangssignal R/C- Empfänger
#define Taster1        PB1    // Pin für Eingangssignal R/C- Empfänger
#define Taster2        PB2    // Eingang für Programmier-Jumper

#define Out_Port      PORTB  // Port für Ausgänge
#define Out_PortDDR      DDRB  // Datenrichtungsregister für Ausgänge
#define LED1        PB3    // Ausgang für Schalt LED 1
#define LED2        PB4    // Ausgang f. Status LED
//#define PwmOut        PB0     // Ausgang f. PWM

/*********************************************************************** 
*************/
// Variablen
//static volatile uint8_t Reading;  //

static volatile uint16_t OvfCount = 0;  // Zähler für Anzahl der 
Overflows
static volatile uint16_t Wert = 187;  // Variable f. Wert 
(Mittelstellung)
static volatile uint16_t ActRead;    // Variable f. Wert
static volatile uint16_t PulsMin = 112;  // Minimale Pulslänge
static volatile uint16_t PulsMax = 255;  // Maximale Pulslänge
static volatile uint16_t Periode = 9;  // Pausenperiode in 
Timeroverflows
static volatile uint8_t Pause = 1;    // Merker f. Status Pause oder 
Puls

static volatile uint8_t ToggleBit ;    // Bit zum Toggeln


/*********************************************************************** 
*************/
// Interruptroutinen

/* Fehlerbehandlung bei Timerüberlauf -> Fehler generieren */
ISR(TIMER0_OVF_vect)
{
  Ovf_Count();
}

ISR(TIMER0_COMPA_vect)
{
  Timer0_Trigger();
}

/*********************************************************************** 
*************/
// Hauptprogramm


int main(void)
{


  // Vorbereitung des RC- Eingangs
  // RC- Eingang ist schon nach Initialisierung des AVR ein Eingang
  In_Port |= (1<<Taster1);  // interne Pull-Up-Widerstände aktivieren
  In_Port |= (1<<Taster2);  // interne Pull-Up-Widerstände aktivieren

  // Initialisierung LED- Ausgänge
  Out_PortDDR |= (1<<LED1) |(1<<LED2);    // Datenrichtung alle 
Portausgänge sind jetzt high
  Out_Port &= ~(1<<LED1);            // -> LED sind nun aus
  Out_Port &= ~(1<<LED2);            // -> LED sind nun aus

  // Initialisierung Interrupteingang INT0
  //MCUCR |= (1<<ISC00);  // Interrupt wird bei jedem Pegelwechsel an 
INT0 ausgelöst
  //GIMSK |= (1<<INT0);    // Interrupt INT0 aktivieren

  // Initialisierung Timer0
  TIMSK |= (1<<TOIE0);  // Timer0 Overflow Interrupt aktivieren

  // Vorbereitung Status - Flags - kein Fehler liegt an, momentan kein 
Datenenpfang


  OvfCount = 0;

//  settings für PWM
//  DDRB |= (1 << PwmOut);              // LED (PWM) als Ausgang
//  TCCR0A |= ((1 << COM0A1) | (1 << COM0A0) | (1 << WGM01) | (1 << 
WGM00)) ;
//  set OC0A (output compare pin 0A on compare match, set WGM wave form 
generation mode 3 = fast PWM


  TCCR0B |= (1<<CS01) ;  // Start Timer0 mit Vorteiler 8
  // globale Interruptfreigabe
  sei();


  /*********************************************************************** 
************/
  while(1)
  {

    if (OvfCount > Periode) // Puls einleiten
    {
      Out_Port |= (1<<LED2);  // LED 2 an
      cli();
      TCNT0 = 0x00;      // neuen Startwert für Timer laden
      TCCR0A = (1<<WGM01);    // Clear Timer on compare match
      //TCCR0B = (1<<CS01);
      //TIMSK &= ~(1<<TOIE0);  // Overflow interrupt deaktivieren
      TIMSK |= (1<<OCIE0A);  // Interrupt bei Compare match aktivieren 
(Register 0A)
      OCR0A = Wert;      // Pulslänge in Compare Register laden

      sei();
      OvfCount = 0;
    }

    if (Pause ==1 )
    {

    }
    else
    {

    }



    //ActRead = OvfCount + TCNT0; // Wert von Timer lesen und overflows 
dazuzählen.

    if (!(Pin_Eingang & (1<<Taster1)))
      {
        Wert = Wert + 5;
        _delay_ms(10);
      }

    if (!(Pin_Eingang & (1<<Taster2)))
      {
        Wert = Wert - 5;
        _delay_ms(10);
      }

    /*
    if ( Wert <= ActRead)
      {
        Out_Port &= ~(1<<LED2);  // LED2 aus
      }
    else
      {
        Out_Port |= (1<<LED2);  // LED 2 an
      }

    if (OvfCount >= Periode) OvfCount = 0;
    */


  } // End of while
}

void Ovf_Count()
{

OvfCount++;


if (ToggleBit == 1)
  {
    ToggleBit = 0;
    Out_Port |= (1<<LED1);  // LED 1 an
  }
  else
  {
    Out_Port &= ~(1<<LED1);  // LED1 aus
    ToggleBit = 1;
  }

}

void Timer0_Trigger()
{

  Out_Port &= ~(1<<LED2);  // LED2 aus
  TIMSK &= ~(1<<OCIE0A);  // Interrupt bei Compare match deaktivieren 
(Register 0A)
  //TIMSK |= (1<<TOIE0);  // Timer0 Overflow Interrupt aktivieren
  TCCR0A &= ~(1<<WGM01);

}

von STK500-Besitzer (Gast)


Lesenswert?

lass einfach die Finger vom TIMSK in deiner Timer0_Trigger-Routine.
Um Interruptflags kümmert sich der AVR selber.

von Karl H. (kbuchegg)


Lesenswert?

Dafür, dass du nur eine bestimmte Funktionalität testen willst, hast du 
mächtig viel Holz in deinem Programm.

Aber ich denke dein Denkfehler liegt darin, dass du übersehen hast, dass 
das Compare Match Interrupt Flag (welches das Auftreten eines Compare 
Match signalisiert) bei JEDER Übereinstimmun von TCNT0 mit OCR0A gesetzt 
wird. Und dieses "jeder" ist wörtlich zu nehmen.
Wenn TCNT0 auf 0 steht, und du OCR0A noch keinen Wert zugewiesen hast, 
dann ist das bereits eine Übereinstimmung. Und ich denke das gilt sogar 
dann, wenn der Timer noch gar nicht läuft (zb am Programmanfang). 
Übereinstimmung ist Übereinstimmung. Und es gilt auch dann, wenn der 
Timer zwar läuft aber die Interrupts mittels cli() diabled sind oder du 
den Compare Match Interrupt zwischenzeitlich abstellst. Die 
Übereinstimmung ist da und damit wird das Interrupt Request Flag 
gesetzt. Und bei nächster Freigabe kommt dann eben sofort dieser 
Interrupt.

Es ist ein Trugschluss zu glauben, dass die Hardware erst dann loslegt, 
wenn du deiner Meinung nach die letzte Einstellung am Timer vorgenommen 
hast.

D.h. es wäre eine gute Idee, wenn du hier
1
      TCNT0 = 0x00;      // neuen Startwert für Timer laden
2
      TCCR0A = (1<<WGM01);    // Clear Timer on compare match
3
      //TCCR0B = (1<<CS01);
4
      //TIMSK &= ~(1<<TOIE0);  // Overflow interrupt deaktivieren
5
      TIMSK |= (1<<OCIE0A);  // Interrupt bei Compare match aktivieren 
6
(Register 0A)
7
      OCR0A = Wert;      // Pulslänge in Compare Register laden
8
9
      sei();
nachdem du den Wert ins OCR0A bugsiert hast und bevor du mittels sei() 
die Interrupts wieder freigibst, erst mal alle inzwischen aufgelaufenen 
Compare Match Interrupt Anforderungen wieder löscht.

von Falk B. (falk)


Lesenswert?

@Stefan K. (skorpion64)

>Ich möchte gerne mit einem ATtiny 85 etwa alle 20ms einen Impuls von
>definier Breite an einem frei wählbaren Port generieren und bin dabei
>nicht, wie geplant 1,5ms lang, sondern nur 60µs.

Sevoansteuerung.

Lange Quelltexte postet man als Anhang, siehe Netiquette.

Dein Quältext sieht reichlich komplex aus. Unnötig komplex. Dabei ist es 
eher einfach.

Lass Timer1  laufen, sodass 20ms Periodendauer rauskommen. Im Overflow 
Interrupt setzt du den Port, im Compare Match Interrupt löschst du ihn 
wieder. So einfach. Dabei musss rein gar nichts an den 
Timereinstellungen oder Interrupts rumgefummet werden. Die Pulsbreite 
änderst du, indem du OCR1A(B) änderst. Fertig.

von Karl H. (kbuchegg)


Lesenswert?

Karl Heinz Buchegger schrieb:

> D.h. es wäre eine gute Idee, wenn du hier

Das wäre zwar im Kontext des Timer-Problems eine gute Idee.
Aber die viel bessere Idee ist es, das Rumgepfriemel an den Timer 
Konfigurationsregistern sein zu lassen und den Timer einfach nur in Ruhe 
seinen Job machen zu lassen.

von Stefan K. (skorpion64)


Lesenswert?

Hallo Falk,
erst einmal vielen Dank für Deine Blitzantwort.

Ja, natürlich Servoansteuerung.

Sorry für den Quältext, aber ich habe das ganze Gerüst einmal stehen 
lassen.

Den Timer 1 wollte ich mir für andere Sachen aufheben, deshalb gehe ich 
den für die Periodendauer den Umweg

von Falk B. (falk)


Lesenswert?

@  Stefan K. (skorpion64)

>Den Timer 1 wollte ich mir für andere Sachen aufheben, deshalb gehe ich
>den für die Periodendauer den Umweg

Du erfindest das Rad neu. Aber fünfeckig. Es hat schon seinen Sinn, den 
Timer 1 zu verwenden. Denn der kann 16 Bit und damit die 1-2ms sehr gut 
auflösen. UNd das ganz allein, ohne CPU und Interrupts mit den beiden 
OCR1A/B Ausgängen. Deine handgestrickte Lösung schafft das nicht mal 
ansatzweise.

von Karl H. (kbuchegg)


Lesenswert?

Stefan K. schrieb:

> Den Timer 1 wollte ich mir für andere Sachen aufheben, deshalb gehe ich
> den für die Periodendauer den Umweg

Wenn du schon unbedingt den Timer 0 benutzen willst:

Lass den Timer im normalen Modus durchlaufen.
Alles was du tun musst ist, dir auszurechnen wann vom letzten Compare 
Match aus gerechnet der nächste kommen muss. Dieses OCR Wert rechnest du 
dir aus und schreibst ihn ins OCR0A Register. Und zwar in der ISR.

Das wird dann darauf hinauslaufen, dass du einige male hintereinander 
denselben OCR Wert benutzt um die Pause zwischen den Pulsen zu 
realisieren und einmalig einen berechneten Wert benutzt, der sich aus 
der zu erzeugenden Pulslänge errechnet. (Anstatt einmalig geht natürlich 
auch mehrmals, wenn es sich mit dem Timerzyklus ansonsten nicht ausgeht, 
so dass man eine vernünftige AUflösung in den Servopositionen bekommt).

Auf jeden Fall gibt es keinen Grund, da an den 
Timer-Konfigurationsregistern rumzupfriemeln. Das lässt sich alles 
lediglich durch die richtigen Werte im OCR0A Register zum richtigen 
Zeitpunkt und Mitzählen der ISR Aufrufe erreichen.


Mit dem Timer 1 geht das alles einfacher, wegen seines größeren 
Zählumfangds. Wozu brauchst du den? Wenn es nur darum geht Pulslängen 
auszumessen ... der kann auch beides miteinander.

von Stefan K. (skorpion64)


Lesenswert?

Karl Heinz Buchegger schrieb:
> D.h. es wäre eine gute Idee, wenn du hier
>       TCNT0 = 0x00;      // neuen Startwert für Timer laden
>       TCCR0A = (1<<WGM01);    // Clear Timer on compare match
>       //TCCR0B = (1<<CS01);
>       //TIMSK &= ~(1<<TOIE0);  // Overflow interrupt deaktivieren
>       TIMSK |= (1<<OCIE0A);  // Interrupt bei Compare match aktivieren
> (Register 0A)
>       OCR0A = Wert;      // Pulslänge in Compare Register laden
>
>       sei();
> nachdem du den Wert ins OCR0A bugsiert hast und bevor du mittels sei()
> die Interrupts wieder freigibst, erst mal alle inzwischen aufgelaufenen
> Compare Match Interrupt Anforderungen wieder löscht.

Hallo Karl Heinz,
vielen Dank für Deinen Hinweis. Das war mir so nicht klar.

Ich habe versucht mit TIFR den Flag zu löschen. Hat sich nichts geändert 
an der Situation.

-------------------------------------------------------------------

      TIMSK |= (1<<OCIE0A);  // Interrupt bei Compare match aktivieren 
(Register 0A)
      TIFR &= ~(1<<OCF0A);  // Interrupt Flag löschen vorm reaktivieren 
der Interrupts
      sei();
      OvfCount = 0;
    }

--------------------------------------------------------------------

Den anderen Timer brauche ich wie gesagt für andere Zwecke.

Gibt es nicht einen saubern Quellcode f. einen Soft timer, welcher den 8 
Bit auf 16 Bit aufmotzt?

Vielen Dank

von Karl H. (kbuchegg)


Lesenswert?

>      TIFR &= ~(1<<OCF0A);  // Interrupt Flag löschen vorm reaktivieren


Diese Art von Flags werden gelöscht, indem man ein 1 Bit draufschreibt.

von Karl H. (kbuchegg)


Lesenswert?

Stefan K. schrieb:

> Gibt es nicht einen saubern Quellcode f. einen Soft timer, welcher den 8
> Bit auf 16 Bit aufmotzt?

Hab ich schon durchgerechnet. Geht sich mit den Zahlen nicht vernünftig 
aus.
Wenn du den Timer bei Prescaler 1 auf 256 Schritte lässt und dich an den 
Overflow hängst, kriegst du 1ms gerade mal in 31 Schritte aufgelöst. Gut 
im CTC-Modus mit 128 wären es schon 62 Schritte. Ist aber für ein Servo 
immer noch sehr unbefriedigend, wenn es nicht mehr Positionen anfahren 
kann.

Aber sieh dir mein Posting über deinem an. Da gibt es einen Vorschlag.

von Stefan K. (skorpion64)


Lesenswert?

Karl Heinz,
vielen Dank noch einmal für die gute Idee mit dem Ausrechnen der 
Periodendauer. Super!! Ich werde das mal umschreiben.

@Falk, leider hat der Attiny 85 "nur" zwei 8 bit Timer.

Viele Grüße

Stefan

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.