Forum: Compiler & IDEs Timer-Problem


von Markus Krötz (Gast)


Lesenswert?

Hallo,

ich habe bereits im Forum gesucht, aber alle Beispiele haben bei mir
nicht funktioniert.
Ich benutze den Atmega16 mit 8MHz Takt aus dem Shop und haben an Port B
ein LCD dranhängen.

Nun möchte ich noch einen Timer laufen lassen, der jede Sekunde die
Variable state1 um 1 erhöht. Mein Code sieht folgendermaßen aus:

volatile char count;

SIGNAL (SIG_OVERFLOW1) {
  if (count == 1) {
    state1++;
    count--;
    return;
  }
  if (count == 0) {
    count++;
    return;
  }
}

void timer (void) {
  TIMSK = _BV(TOIE1);            //Timer Overflow Interrupt enable
  TCNT1 = 0;                  //Rücksetzen des Timers
  TCCR1B = (1<<CS11);
  sei ();
}

int main(void)
{
  char buffer[20];
  /* initialize display, cursor off */
  lcd_init(LCD_DISP_ON);
  lcd_clrscr();
  timer ();
  for (;;) {
  ...
  }
}

Der obige Code zählt schon annähernd genau, doch er liegt bei 100
Sekunden um knapp 4 Sekunden zurück und das addiert sich dann auf.
Es liegt wohl alles an der Zeile TCCR1B = (1<<CS11), aber welche
Einstellung ich auch probiert habe, der Timer zählt entweder ganz
langsam oder viel zu schnell :-((.

Das ist echt zum Verrücktwerden. Weiß jemand Rat?

Vielen Dank

Markus

von Jörg Wunsch (Gast)


Lesenswert?

Davon abgesehen, daß state1 volatile sein sollte (und nicht count),
was macht Dich glauben, daß Du hier in irgendeiner Form auf einen
Sekundentakt kommen könntest?

Du hast 8 MHz Takt, einen Vorteiler von 8, macht 1 MHz für den Timer.
Da Du den Überlauf eines 16-bit Timers auswertest, werden diese durch
65536 dividiert, TIMER_OVERFLOW1 wird also alle 65,5 ms getriggert.
Deine umständliche count-Mimik teilt nochmal durch 2, d. h. nach
meiner Rechnung sollte state1 ca. alle 131 ms inkrememtiert werden.

Da Du auf ungefähr eine Sekunde kommst, liegt die Vermutung nahe, daß
Du das weder das Datenblatt noch die FAQ gelesen hast und Dein Chip
noch vom internen RC-Oszillator mit 1 MHz betrieben wird.  Das würde
das Timing auf ca. 1,05 Sekunden verändern (was wohl Deiner
Beobachtung entspricht :), natürlich ohne jegliche Kalibrierung (=>
OSCCAL-Register) und mit der deutlich schlechteren Stabilität, die der
RC-Oszillator ohnehin mit sich bringt.

Unabhängig vom Datenblattstudium (und -verständnis), das Dir keiner
abnehmen kann, so wird das kaum was: mit binären Teilerfaktoren (und
mehr erreichst Du mit dem overflow-Interrupt nicht) kannst Du nicht
auf direktem Wege 8 MHz so teilen, daß Du auf ganze Sekunden kommst.
Der passende Trick nennt sich CTC-Modus (clear timer on compare
match).

von Markus Krötz (Gast)


Lesenswert?

Ok, ich habe die Fusebits so gesetzt, dass der Chip nun mit 8MHz
getaktet werden sollte.

Wie kann ich das jetzt mit dem CTC-Modus einbinden?

von Jörg Wunsch (Gast)


Lesenswert?


von Markus Krötz (Gast)


Lesenswert?

;-) Danke, aber ich versteh das Zeugs echt nicht (also, das mit dem
Timer) und glaub mir, ich suche und probiere schon seit heute Mittag
nach so nem CTC-Modus. Habe auch die header-Datei <timer.h> entdeckt,
doch bisher keine Erklärung gefunden, die mir weiterhilft. Meine
Favoriten-Liste füllt sich minütlich, nur mein Hirn läuft nicht in
dieser Geschwindigkeit.

Tut mir leid, wenn ich etwas unsanft gefragt habe, aber ich steh echt
auf dem Schlauch. Wenigsten läuft mein Controller auf alle Fälle nun
mit 8MHz.

Grüßle

Markus

von Markus Krötz (Gast)


Lesenswert?

Ich weiß zwar nicht genau, was ich tue, aber mit diesem Code hier
benutze ich CTC und bin trotzdem bei 5 Minuten knappe 7 Sekunden hinter
der wirklichen Berechnung (Chip taktet mit 8MHz)!

#ifndef CTC1
#define CTC1 WGM12              // for compatibility with ATmega
#endif

...

SIGNAL (SIG_OUTPUT_COMPARE1A)
{
  state1++;
}


void timer (void) {
  TCCR1B = _BV(CS10) | _BV(CS12) | _BV(CTC1); // clear timer on compare
match, no prescaler
  OCR1A  =  8000;
  TIMSK  = _BV(OCIE1A);  // enable Output Compare 1 overflow interrupt
  sei ();
}

von Jörg Wunsch (Gast)


Lesenswert?

Der Weg ist richtig.

Aus'm Kopf bin ich mir aber gerade nicht sicher, ob Du hier keinen
off-by-one error hast, also 7999 nehmen mußt -- steht aber in der Doku
drin (gibt's ne Formel dafür).

Außerdem mußt Du natürlich noch verifizieren, daß Dein Takt auch
wirklich exakt 8 MHz ist.  Wenn man das genau machen will, nimmt man
entweder einen passend genauen Quarzoszillator, oder man führt einen
der beiden Lastkondensatoren am Quarz als Trimmer aus (den an XTAL1
würde ich jetzt tippen -- es muß der Gatter-Eingang am AVR sein) und
zieht den Quarz damit auf die exakte Frequenz.

Das ``toggle OCx output on compare match'' feature zusammen mit
einem
Zählfrequenzmesser kann bei diesem Abgleich hilfreich sein.

von Markus Krötz (Gast)


Lesenswert?

Ok, vielen Dank ;-). D.h. ich muss eigentlich nur noch den Wert von
OCR1A richtig anpassen, selbst wenn die Frequenz nicht exakt 8 MHz ist,
richtig?

von Peter D. (peda)


Lesenswert?

Ich hab gemerkt, der Compiler (Präprozessor) ist besser im Kopfrechnen
als ich.
Deshalb lasse ich einfach ihn die richtigen Teilerfaktoren berechnen:

http://www.mikrocontroller.net/forum/read-4-57760.html


Peter

von Jörg Wunsch (Gast)


Lesenswert?

> D.h. ich muss eigentlich nur noch den Wert von OCR1A richtig
> anpassen, selbst wenn die Frequenz nicht exakt 8 MHz ist, richtig?

Diese Anpassung würde recht grob werden.  Guck mal, Du hast einen Wert
von 8000, den Du in Einzelschritten ,,kalibrieren'' kannst.  Das
macht
eine Schrittdifferenz von 1/8000 = 1,25E-4.  Der Trimmer am Quarz kann
so viel vielleicht gerade mal im Ganzen ziehen, aber dort hast Du noch
gut wenigstens 20 ,,Abstufungen'' dabei (nicht wirklich: ist ja
analog).

Wenn Du den Fehler digital korrigieren willst, dann mußt Du den
anderen Weg gehen: nach einer größeren Anzahl von Zählerumrundungen
wird mal in einer Runde die Zählweite verändert.  Damit ,,tickt''
die
Uhr dann zwar nicht mehr gleichmäßig, aber das stört in aller Regel
nicht weiter.

von Markus Krötz (Gast)


Lesenswert?

Ok, für alle die es interessiert und denen es gleich wie mir geht (und
man erstmal stundenlang im Forum nach passenden Antworten wühlt) ist
hier mein Timer, der bislang ohne Verzögerung auf meinem ATmega16 mit
8Mhz-Quarz läuft:

SIGNAL (SIG_OUTPUT_COMPARE2) {
  static uint16_t lc=0;
  lc++;

  // bis tausend zählen, da wir ja 1ms hatten
  if (lc >= 1000) {
    // hier Code einfügen, was jede Sekunde passieren soll
    lc = 0;
  }
}

void init_timer (void) {
  TCCR2 = _BV (CS22) | _BV (WGM21); // Prescaler von 64, weil 8000000 /
64 = 125 kHz.
  OCR2 = 124; // 125 - 1; Dann kommt genau 1 kHz, also 1 ms raus
  TIMSK = _BV (OCIE2); // Compare setzen
  sei ();
}

Grüßle und viel Erfolg (in der Main-Prozedur einfach init_timer
aufrufen und schon ist das Ding fertig)

Markus

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.