Forum: Mikrocontroller und Digitale Elektronik AtMega8 CTC Timer läuft zu langsam


von Lukas B. (Gast)


Lesenswert?

Hallo,

ich bin noch Anfänger auf dem Gebiet der Mikrocontroller, deshalb wende 
ich mich mit meinem Problem an euch.
Und zwar programmiere ich gerade einen Wecker auf Basis des AtMega8, der 
mit einem externen 4MHz Quarz getaktet ist. Als Grundlage habe ich den 
Quellcode aus dem AVR GCC Tutorial genommen.
Mein Problem besteht nun darin, dass die Uhr ziemlich ungebau ist. Im 
Abgleich mit einigen anderen Uhren beträgt die Ungenauigkeit nach 5 
Minuten schon fast 10 Sekunden. Die Taktfrequenz ist im Quellcode 
richtig angegeben.

Quellcode:
###############################################

Pinbelegung:
PD0 -- LCD D4
PD1 -- LCD D5
PD2 -- LCD D6
PD3 -- LCD D7
PD4 -- LCD RS
PD5 -- LCD E
*/

#include <avr/io.h>
#include <stdlib.h>
#include <avr/interrupt.h>
#include "lcd-routines.h" //LCD Routine von Mikrocontroller.net


//############ variables ###########
//Variablen für die Zeit
volatile unsigned int millisekunden;
volatile unsigned int sekunde;
volatile unsigned int minute;
volatile unsigned int stunde;
volatile unsigned int tag;

//Variablen für die Weckzeit
volatile unsigned int w_sekunde;
volatile unsigned int w_minute;
volatile unsigned int w_stunde;


//############## init ##############
int init(void){

  //Initialisierung des LCDs
    lcd_init();

  //Portinitialisierung
  DDRB=0xFF; //Port B auf Ausgang setzen

  // Timer 1 konfigurieren
    TCCR1A = (1<<WGM11); // CTC Modus
    TCCR1B |= (1<<CS01); // Prescaler 8
    // ((1000000/8)/1000) = 125
    OCR1A = 125-1;

    // Compare Interrupt erlauben
    TIMSK |= (1<<OCIE1A);

    // Global Interrupts aktivieren
    sei();

  while(1)
  {

  }

    return 0;
}

//############ timer-ISR ###########

/*
Der Compare Interrupt Handler
wird aufgerufen, wenn
TCNT1 = OCR1A = 125-1
ist (125 Schritte), d.h. genau alle 1 ms
*/
ISR (TIMER1_COMPA_vect)
{
  //Zeit-Funktionen
    millisekunden++;
    if(millisekunden == 1000)
    {
       sekunde++;
       millisekunden = 0;
      if(sekunde == 60)
      {
       minute++;
       sekunde = 0;
       }
       if(minute == 60)
      {
       stunde++;
        minute = 0;
       }
        if(stunde == 24)
        {
    tag++;
      stunde = 0;
       }
    if(tag == 6)
        {
    tag = 0;
       }

    //Anzeige-Funktionen
    lcd_clear();
    lcd_string("___ ");

      if(stunde<=9){lcd_data( '0' );}
              {  char Buffer[20];
        itoa( stunde, Buffer, 10 );
        lcd_string( Buffer ); }


    lcd_data( ':' );

       if(minute<=9){lcd_data( '0' );}
    {  char Buffer[20];
        itoa( minute, Buffer, 10 );
        lcd_string( Buffer ); }

    lcd_data( ':' );

      if(sekunde<=9){lcd_data( '0' );}
    {  char Buffer[20];
        itoa( sekunde, Buffer, 10 );
        lcd_string( Buffer ); }

    lcd_string(" ___");

    //Wochentag anzeigen
    lcd_setcursor(3,2);

    switch(tag){
      case 0: lcd_string("Mo, "); break;
      case 1: lcd_string("Di, "); break;
      case 2: lcd_string("Mi, "); break;
      case 3: lcd_string("Do, "); break;
      case 4: lcd_string("Fr, "); break;
      case 5: lcd_string("Sa, "); break;
      case 6: lcd_string("So, "); break;
    }

    //Weckzeit anzeigen
    lcd_data( '#' );

      if(w_stunde<=9){lcd_data( '0' );}
    {  char Buffer[20];
        itoa( w_stunde, Buffer, 10 );
        lcd_string( Buffer ); }

    lcd_data( ':' );

      if(w_minute<=9){lcd_data( '0' );}
    {  char Buffer[20];
        itoa( w_minute, Buffer, 10 );
        lcd_string( Buffer ); }


    //Weck-Funktionen
    if(stunde == w_stunde && minute == w_minute && sekunde == 
w_sekunde){
    lcd_setcursor(15,2);
    lcd_string("#");
    //PORTB=(1 << PB2);
    }


    }//1ms...
}



//############## main ##############
int main(void)
{
  init();
}

von spess53 (Gast)


Lesenswert?

Hi

>Die Taktfrequenz ist im Quellcode richtig angegeben.

Läuft dein ATMega auch wirklich mit dem externen Quarz?

MfG Spess

von spess53 (Gast)


Lesenswert?

Hi

Ansonsten würde ich mal schlicht behaupten, das deine LCD-Routinen im 
Interrupt zu lang sind.

Noch etwas. Du schreibst:

>der mit einem externen 4MHz Quarz getaktet ist.

Im Programm steht aber:

>   // ((1000000/8)/1000) = 125
>   OCR1A = 125-1;

MfG Spess

von g457 (Gast)


Lesenswert?

> Im Abgleich mit einigen anderen Uhren beträgt die Ungenauigkeit nach 5
> Minuten schon fast 10 Sekunden.

Erstens wie spess schon frug: läufst Du mit externem (hinreichend 
genauem!) Quarz/Takt/Oszi/..?

Und zweitens sehe ich das richtig dass Du die ganzen LCD-Updates in der 
ISR machst? Wundert mich nicht dass da laufend 'millisekunden' verloren 
gehen (Hint: bei 10 Sekunden Abweichung auf 5 Minuten und 1 
Display-Update pro Sekunde sind das gerade mal 10 Sekunden Abweichung 
auf 300 Updates oder etwa 33ms Abweichung pro Update - das ist für 
Display-IO durchaus haltbar).

HTH

von ... (Gast)


Lesenswert?

Alleine lcd_clear() wird um die zwei Millisekunden dauern, d.h. pro 
Sekunde gehen da schon mal mindestens 2 der 1ms-Interrupt flöten.

von Lukas B. (Gast)


Lesenswert?

Danke für eure Antworten. Der externe Quarz läuft mit 4MHz und die Fuses 
sind auch entsprechend eingestellt. Ich hatte auch mal 1MHz (interner 
Quarz) angegeben, aber das resultierte in einem "Daten-Chaos".
Im Tutorial steht, dass in die ISR keine aufwendigen LCD Ansteuerungen 
gehören, aber wie lagere ich die Aktionen so aus, dass das LCD jede 
Sekunde refresht wird und auch die anderen Aktionen (Weckzeitvergleich, 
Tastenabfrage) ausgeführt werden?

von Lukas B. (Gast)


Lesenswert?

Edit: @Spess:  Im Tutorial ist ein Quarz mit 3,xx MHz angegeben und der 
Wert für OCR1A wurde genauso berechnet, wie in meinem Code. Wenn man 
dort 4MHz einsetzt, bekommt man 500 als Wert für OCR1A.

Wenn ich morgen in der Werkstatt bin, probiere ich mal den anderen Wert 
aus.

von Martin (Gast)


Lesenswert?

Lukas B. schrieb:
> Im Tutorial steht, dass in die ISR keine aufwendigen LCD Ansteuerungen
> gehören, aber wie lagere ich die Aktionen so aus, dass das LCD jede
> Sekunde refresht wird und auch die anderen Aktionen (Weckzeitvergleich,
> Tastenabfrage) ausgeführt werden?

In der ISR setzt du zur vollen Sekunde nur ein Flag. In der 
Endlosschleife im Hauptprogramm wird dieses Flag geprüft und damit 
Vergleiche und Ausgabe gesteuert. Parallel können dann aber schon per 
ISR die ersten paar ms der neuen Sekunde gezählt werden, weil der IRQ 
den Ablauf unterbrechen kann.

von Lukas B. (Gast)


Lesenswert?

Könntest du das eventuell etwas konkretisiseren, denn ich kann mir nicht 
vorstellen, wie ich das programmieren soll. Die Idee ist verständlich, 
aber die Umsetzung...

Meinst du in etwa so etwas?
1
ISR (TIMER1_COMPA_vect)
2
{
3
  //Zeit-Funktionen
4
    millisekunden++;
5
    if(millisekunden == 1000)
6
    {
7
       refresh_flag=1;
8
       sekunde++;
9
       millisekunden = 0;
10
    //...
11
    }
12
}
13
14
int main(void)
15
{
16
  init();
17
18
  if(refresh_flag){
19
     //Anzeige-Funktionen
20
     //...
21
  }
22
}

von Karl H. (kbuchegg)


Lesenswert?

Den Teil mit dem hochzählen der Uhr lässt du im Interrupt, den Teil mit 
der Ausgabe verlagerst du in die Hauptschleife nach main.

Damit die Hauptschleife weiß, dass es die Anzeige aktualisieren muss, 
setzt du eine globale Variable ein.
1
...
2
volatile uint8_t updateTime = 0;
3
...
4
5
ISR (TIMER1_COMPA_vect)
6
{
7
  //Zeit-Funktionen
8
  millisekunden++;
9
  if(millisekunden == 1000)
10
  {
11
    millisekunden = 0;
12
    sekunde++;
13
14
    updateTime = 1;
15
16
    if(sekunde == 60)
17
    {
18
      sekunde = 0;
19
      minute++;
20
21
      if(minute == 60)
22
      {
23
        minute = 0;
24
        stunde++;
25
   
26
        if(stunde == 24)
27
        {
28
          stunde = 0;
29
          tag++;
30
31
          if(tag == 6)
32
          {
33
            tag = 0;
34
          }
35
        }
36
      }
37
    }
38
  }
39
}
40
41
int main()
42
{
43
  char Buffer[20];
44
  ....
45
46
  while( 1 ) {
47
    if( updateTime == 1 )
48
    {
49
      updateTime = 0;
50
51
      // ... Anzeige aktualisieren
52
      sprintf( buffer, "%02d:%02d:%02d", stunde, minute, sekunde );
53
      lcd_setcursor(3, 0);
54
      lcd_string( buffer );
55
    }
56
  }
57
}

Und noch was:
Mach deine Sekunden, Minten, Stunden und Tag Variable als uint8_t und 
nicht als unsigned int. Du musst deinem AVR keine 16 Bit Arithmetik 
aufzwingen, wenn deine Zahlen nie größer als 60 werden können. Dazu 
reichen 8 Bit locker aus.


Das Hochzählen der Zeit in der ISR dauert ein paar Takte und ist 
insofern nicht zeitkritisch. Du willst die Zeit aber in der ISR komplett 
hochzählen, weil es dir nicht passieren darf, dass Sekunden nicht 
gewertet werden, weil das Hauptprogramm gerade längere Zeit mit etwas 
anderem beschäftigt ist. So läuft die Uhr unabhängig von allem anderen 
in der ISR autonom und richtig. Was du dann weiter mit der Uhrzeit 
machst, Ausgeben oder mit einer Weckzeit vergleichen beeinflusst die Uhr 
selber nicht mehr. Wobei: Das Auswerten der WEckzeit könnte man noch mit 
in die ISR packen. Denn auch das darf auf keinen Fall verloren gehen und 
soll klarerweise auch dann passieren, wenn der Benutzer zb gerade den 
Wochentag einstellt.

ISR sollen so kurz wie möglich sein und nur das notwendigste machen. Du 
willst so schnell wie möglich wieder aus der ISR raus. Man muss aber 
auch nicht päpstlicher als der Papst sein. Ein bischen was darf man 
schon auch in der ISR machen. Vor allen Dingen dann, wenn es dich in 
Schwierigkeiten bringen kann, wenn du es nicht tust. Das erhöhen einer 
Uhrzeit gehört dazu. Die Anzeige aber nicht. Die Anzeige kannst du auch 
woanders machen oder zb überhaupt unterlassen, weil der Benutzer gerade 
die Weckzeit einstellt. Aber nur weil dein Benutzer die Weckzeit 
einstellt, darf ja die Uhr nicht aufhören zu laufen.

von Lukas B. (Gast)


Lesenswert?

Danke dir, das hilft mir sehr weiter.

Den Abgleich mit in die ISR zu packen, macht rein theoretisch keinen 
Sinn, da der Anwender kaum kurz vor dem Auslösen des Wecksignals, die 
Uhrzeit umstellen wird, es sei denn, er ist Schlafwandler. ;)
Wenn es zeitlich allerdings unkritisch ist, kann man es ja 
sicherheitshalber machen.

von Lukas B. (Gast)


Lesenswert?

Ich habe es jetzt mal ausprobiert, aber leider besteht immer noch 
dasselbe Problem und die Ungenauigkeit ist sehr groß.
Mir ist aber aufgefallen, dass ich beim Anpassen der Register 
irgendetwas falsch gemacht habe. Denn im Tutorial ist der Code für einen 
Attiny2313 aufgeführt, allerdoings gab es hier beim Kompilieren Fehler, 
solange man in den Projekt-Optionen AtMega8 angegeben hat.

Mein Code:
1
// Timer 1 konfigurieren
2
    TCCR1A = (1<<WGM11); // CTC Modus
3
    TCCR1B |= (1<<CS01); // Prescaler 8
4
    // ((4000000/32)/1000) = 125
5
    OCR1A = 125-1;
6
 
7
    // Compare Interrupt erlauben
8
    TIMSK |= (1<<OCIE1A);
9
 
10
    // Global Interrupts aktivieren
11
    sei();

Tutorial:
1
TCCR0A = (1<<WGM01); // CTC Modus
2
  TCCR0B |= (1<<CS01); // Prescaler 8
3
  // ((1000000/8)/1000) = 125
4
  OCR0A = 125-1;
5
 
6
  // Compare Interrupt erlauben
7
  TIMSK |= (1<<OCIE0A);
8
 
9
  // Global Interrupts aktivieren
10
  sei();

Ich denke, da stimmt irgendetwas nicht, oder? Da der Timer 0 beim 
Atmega8 kein CTC unterstützt habe ich die Register (versucht) an den 
Atmega8 anzupassen.
Wenn ich in TCCR0A WGM01 setze, gibt mir der Compiler Fehler aus.

von spess53 (Gast)


Lesenswert?

Hi

Hör mal auf, dich an das Tutorial zu klammern. Maßgebend ist das 
Datenblatt.

MfG Spess

von Stefan E. (sternst)


Lesenswert?

Lukas B. schrieb:
> Da der Timer 0 beim
> Atmega8 kein CTC unterstützt habe ich die Register (versucht) an den
> Atmega8 anzupassen.

Offensichtlich aber ohne in das schon von Spess erwähnte Datenblatt zu 
schauen. Denn statt des CTC hast du einen PWM Modus eingestellt.

PS: Und wo kommt in deiner Rechnung die 32 her?

von Lukas B. (Gast)


Lesenswert?

Das Datenblatt habe ich gerade zu Rate gezogen und jetzt geht es.
So muss es aussehen:
1
      
2
// Timer 1 konfigurieren
3
TCCR1B |= (1<<WGM12); // CTC Modus     
4
TCCR1B |= (1<<CS11); // Prescaler 8     
5
// ((4000000/8)/1000) = 500             
6
OCR1A = 500-1;


Danke nochmal für eure Tipps, jetzt kann ich mich der Tasterabfrage 
widmen und wenn es fertig ist, poste ich noch einmal den gesamten Code.

von Martin (Gast)


Lesenswert?

Lukas B. schrieb:
> Denn im Tutorial ist der Code für einen
> Attiny2313 aufgeführt, allerdoings gab es hier beim Kompilieren Fehler,
> solange man in den Projekt-Optionen AtMega8 angegeben hat.

Und wenn der Compiler keine Fehler anzeigt, heißt das noch lange nicht, 
dass alles richtig ist. Guck mal, in welchem Register des ATmega8 der 
Teilerfaktor für den Prescaler vom Timer0 festgelegt wird.

von Lukas B. (Gast)


Angehängte Dateien:

Lesenswert?

Genau das war ja mein Fehler, die Register stimmten vorne und hinten 
nicht. Aber der Fehler ist ja behoben und auf 40 Minuten konnte ich 
keine Abweichung zu meiner Referenzuhr wahrnehmen. Ich schließe daraus, 
dass der Timer jetzt ordnungsgemäß im CTC-Modus mit korrektem Vorteiler 
und Obergrenze für den Interrupt initialisiert ist.

Im Anhang ist der aktuelle Stand des Codes mit implemetierter 
Tasterabfrage zu sehen. Ich kann jetzt über einen Taster den Alarm ein 
und ausschalten und über einen weiteren zwischen fünf Modi (w_stunde; 
w_minute; tag; stunde; minute) toggeln und die entsprechenden Variablen 
mit zwei weiteren Tastern inkrementieren bzw. dekrementieren. Was noch 
nicht funktioniert, ist der Auto-Repeat bei längerem Drücken.

von Lukas B. (Gast)


Lesenswert?

Hallo nochmal,

ich bin jetzt fast komplett fertig und bei der Autorepeat-Funktion der 
Taster ist eine Aktuelaisierungsrate von 1Hz doch etwas unkomfortabel. 
Kann man die LCD Funktionen problemlos alle 100ms aufrufen oder läuft 
die ISR dann wieder zu ungenau?

von Falk B. (falk)


Lesenswert?

@  Lukas B. (Gast)

>Kann man die LCD Funktionen problemlos alle 100ms aufrufen

Sicher, aber

> oder läuft die ISR dann wieder zu ungenau?

Nö, denn die LCD-Sachen ruft man NICHT in der ISR auf sondern in der 
Hauptschleife. Dort hat man (fast) alle Zeit der Welt. Hast du ja 
mittlerweile richtig in deinem Programm.

MFG
Falk

von Lukas B. (Gast)


Lesenswert?

Wäre es dann auch möglich, den Programmteil in Main jede Millisekunde 
aufzurufen oder wird die ISR und damit die Uhr dann wieder zu langsam?

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.