Forum: Compiler & IDEs Atmega8 Timer2 CTC Zeitabweichung


von Thomas W. (ram99)


Lesenswert?

Hi
Ich bin ein wenig verwirrt. Ich benutze den Atmega8 mit einem 12 Mhz 
Quarz und dem Timer2 in CTC Mode.
Das unten aufgeführte Programm ruft den CTC Interrupt alle 1.002ms auf. 
In diesem zähle ich einen Zähler bis auf 998 um auf exakt 1s zu kommen. 
Über das Display gebe ich den Sekundenwert alle ~10Sek. aus.
Wenn ich jetzt warte bis die Puls Variable auf 600 springt (10 min.) 
steht meine Stopuhr (Iphone) welche parallel läuft auf 10 Minuten und 11 
Sekunden. Habe ich noch einen Fehler oder sind die Dinger so ungenau das 
11 Sekunden Abweichung möglich sind.

Gruß
Thomas


#include <avr/io.h>
#include "lcd-routines.h"
#include <util/delay.h>
#include <stdint.h>
#include <stdio.h>
#include <avr/interrupt.h>
#define F_CPU 12000000UL

char Buffer[20];
uint16_t counter1 = 0, counter2 = 0, Puls = 0;

//------------------------------------------------------------------
int main(void)
{
//8 Bit Timer2 Einstellungen
TCCR2 |= (1<<WGM21) | (1<<CS21)| (1<<CS22);  //Teiler: 256, 12Mhz
OCR2 = 47;
TIMSK |= (1<<OCIE2);

sei();
lcd_init();
//------------------------------------------------------------------
//------------------------------------------------------------------
  while(1)
  {

  }
  return 0;
}
//-------------------------------------------------------------------
//-------------------------------------------------------------------
ISR(TIMER2_COMP_vect) //Interrut Timer2 Vergleicher (Alle 1,002ms)
{
  counter1++;
  counter2++;

  if(counter1 >= 998) // Alle 1000ms Drehzahl berechnen (1,002ms X 998)
  {
    Puls++;
    counter1 = 0;
  }

  if(counter2 >= 9980) // Alle ~10s Wert ausgeben
  {
    counter2 = 0;

    lcd_setcursor( 0, 1 );
    itoa( Puls, Buffer, 10 );
    lcd_string(Buffer);
  }
}

von MWS (Gast)


Lesenswert?

Laut Berechnung müsste das Ding 13 Sekunden in 10 Minuten falsch gehen, 
da ist eher's IPhone kaputt. Sieh Dir die Formel im Datenblatt zu CTC 
an, ist eigentlich für PWM zu finden, trifft hier aber genauso zu.

von PittyJ (Gast)


Lesenswert?

Wie kommt man auf die 1002 bzw 998?
Sind da nicht noch irgendwelche Nachkomma-Reste zu beachten, die sich 
aufsummieren?

von Falk B. (falk)


Lesenswert?

@Thomas Webster (ram99)

>Ich bin ein wenig verwirrt. Ich benutze den Atmega8 mit einem 12 Mhz
>Quarz und dem Timer2 in CTC Mode.
>Das unten aufgeführte Programm ruft den CTC Interrupt alle 1.002ms auf.
>In diesem zähle ich einen Zähler bis auf 998 um auf exakt 1s zu kommen.

Ob das so günstig ist?

>TCCR2 |= (1<<WGM21) | (1<<CS21)| (1<<CS22);  //Teiler: 256, 12Mhz

Stimmt. Aber 12 MHz / 256 = 46,875 kHz

>OCR2 = 47;

Das teilt durch 48! Denn der Zähler geht von 0-47!

Aber selbst wenn du durch 47 teilen würdest, kämen immer noch 997,34 Hz 
raus. Bei Teilung durch 998 bleibt eine Fehler von 660ppm, macht 40ms 
Fehler pro Minute und 57s Fehler pro Tag. Wie man es richtig macht, 
siehe

AVR - Die genaue Sekunde / RTC

Dein Methode mit 2 Zählern ist ungünstig. Es ist deutlich besser und 
einfacher, den 2. Zähler mit dem ersten zu verketten, anstatt parallel 
zu zählen. Damit vermeidet man ein Desynchronisieren durch 
Programmfehler.

>ISR(TIMER2_COMP_vect) //Interrut Timer2 Vergleicher (Alle 1,002ms)
>{
>  counter1++;
>  counter2++;

  if(counter1 >= 998) // Alle 1000ms Drehzahl berechnen (1,002ms X 998)
  {
    Puls++;
    counter1 = 0;
  }

>  if(counter2 >= 9980) // Alle ~10s Wert ausgeben
>  {
>    counter2 = 0;

>    lcd_setcursor( 0, 1 );
>    itoa( Puls, Buffer, 10 );
>    lcd_string(Buffer);
>  }
>}

Das ist ein No-GO! Solche Ausgaben haben in der ISR NICHTS zu suchen! 
Wenn nämlich deine LCD-Ausgabe länger als 2x1,002ms dauert, verschluckst 
du Interrupts. Siehe Interrupt. Besser so.

1
#include <avr/io.h>
2
#include "lcd-routines.h"
3
#include <util/delay.h>
4
#include <stdint.h>
5
#include <stdio.h>
6
#include <avr/interrupt.h>
7
#define F_CPU 12000000UL
8
9
char Buffer[20];
10
volatile uint16_t Puls = 0;
11
volatile uint8_t flag_10s;
12
13
//------------------------------------------------------------------
14
int main(void)
15
{
16
//8 Bit Timer2 Einstellungen
17
TCCR2 |= (1<<WGM21) | (1<<CS21)| (1<<CS22);  //Teiler: 256, 12Mhz
18
OCR2 = 47;
19
TIMSK |= (1<<OCIE2);
20
21
sei();
22
lcd_init();
23
//------------------------------------------------------------------
24
//------------------------------------------------------------------
25
  while(1)
26
  {
27
     if (flag_10s) {
28
        flag_10s=0;
29
        lcd_setcursor( 0, 1 );
30
        itoa( Puls, Buffer, 10 );
31
        lcd_string(Buffer);
32
     }
33
  }
34
  return 0;
35
}
36
//-------------------------------------------------------------------
37
//-------------------------------------------------------------------
38
ISR(TIMER2_COMP_vect) //Interrut Timer2 Vergleicher (Alle 1,002ms)
39
{
40
  static uint16_t counter1 = 0, counter2 = 0;
41
42
  counter1++;  
43
44
  if(counter1 >= 998) // Alle 1000ms Drehzahl berechnen (1,002ms X 998)
45
  {
46
    Puls++;
47
    counter1 = 0;
48
    counter2++;
49
    if(counter2 = 10) // Alle ~10s Wert ausgeben
50
    {
51
      counter2=0; 
52
      flag_10s=1;
53
    }
54
  }
55
}

von Falk B. (falk)


Lesenswert?

12 MHz / 256 = 46,875 kHz

46,875 kHz = 5^6 * 3 (Primfaktorzerlegung)

Diese Zahl kann man nun ohne Rest aufteilen in 5^3=125 und 5^3*3=375. 
Den Teilerfaktor 125 packt man in den Hardwarezähler Timer 2 mit

OCR2 = 124; // Teiler durch 125!

Den Teilerfaktor 375 packt man in die ISR als Softwaretimer.

Damit bleibt nur noch die Toleranz vom Quarz als Fehler übrig.

Et voilà!

von Falk B. (falk)


Lesenswert?

Mist, oben ist noch der falsche Teiler drin! Also gleich richtig machen.

1
#include <avr/io.h>
2
#include "lcd-routines.h"
3
#include <util/delay.h>
4
#include <stdint.h>
5
#include <stdio.h>
6
#include <avr/interrupt.h>
7
#define F_CPU 12000000UL
8
9
char Buffer[20];
10
volatile uint16_t Puls = 0;
11
volatile uint8_t flag_10s;
12
13
//------------------------------------------------------------------
14
int main(void)
15
{
16
//8 Bit Timer2 Einstellungen
17
TCCR2 |= (1<<WGM21) | (1<<CS21)| (1<<CS22);  //Teiler: 256, 12Mhz
18
OCR2 = 124;    // teilt durch 125 !!!
19
TIMSK |= (1<<OCIE2);
20
21
sei();
22
lcd_init();
23
//------------------------------------------------------------------
24
//------------------------------------------------------------------
25
  while(1)
26
  {
27
     if (flag_10s) {
28
        flag_10s=0;
29
        lcd_setcursor( 0, 1 );
30
        itoa( Puls, Buffer, 10 );
31
        lcd_string(Buffer);
32
     }
33
  }
34
  return 0;
35
}
36
//-------------------------------------------------------------------
37
//-------------------------------------------------------------------
38
ISR(TIMER2_COMP_vect) //Interrut Timer2 Vergleicher , 375 Hz
39
{
40
  static uint16_t counter1 = 0, counter2 = 0;
41
42
  counter1++;  
43
44
  if(counter1 >= 375) // Alle 1000ms Drehzahl berechnen (1,002ms X 998)
45
  {
46
    Puls++;
47
    counter1 = 0;
48
    counter2++;
49
    if(counter2 = 10) // Alle ~10s Wert ausgeben
50
    {
51
      counter2=0; 
52
      flag_10s=1;
53
    }
54
  }
55
}

von STK500-Besitzer (Gast)


Lesenswert?

Falk Brunner schrieb:
> if(counter2 == 10) // Alle ~10s Wert ausgeben

Schönheitsfehler...

von Falk B. (falk)


Lesenswert?

@  STK500-Besitzer (Gast)

>> if(counter2 == 10) // Alle ~10s Wert ausgeben

>Schönheitsfehler...

Aufmerksamkeitstest ;-)

(Scheiß C-Stolperfallen, ich vermisse TurboPascal 8-0)

von Thomas W. (ram99)


Lesenswert?

Hi,
vielen Dank für die Hilfe. Ich habe jetzt erst nach über 5 Jahren wieder 
angefangen mit den Teilen zu arbeiten. Freu mich auch über den Hinweiß 
mit der Ausgabe des Werts in der ISR.
Die Idee mit der Primfaktorzerlegung finde ich auch cool

Danke

Thomas

von Praktiker (Gast)


Lesenswert?

Thomas Webster schrieb:
> Wenn ich jetzt warte bis die Puls Variable auf 600 springt (10 min.)
> steht meine Stopuhr (Iphone) welche parallel läuft auf 10 Minuten und 11
> Sekunden.

Zum Überprüfen des Programms ist ein iPhone denkbar ungeeignet, weil es 
die Fehler nicht analysieren und nach Software- und Taktfehler trennen 
kann. Der Simulator in Atmel-Studio zeigt mit "Stop Watch" und "Cycle 
Counter" taktgenau die Zeiten und Prozessorzyklen an. Damit sieht man 
gleich, ob die Anzahl von Schleifendurchläufen oder die Zeit zwischen 
Timer-Interrupts richtig ist.

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.