Inhaltsverzeichnis

AVR – Warten ohne Delay

Als Anfänger benutzt man ja meist Warteschleifen, um eine gewisse Zeit abzuwarten. Oft mit der _delay_ms()-Funktion, die zu den Standardfunktionen gehört.

Beispielsweise:

while(1) {
  led_on();
  _delay_ms(200);
  led_off();
  _delay_ms(200);
}

Dies ist natürlich nicht gut, da der Prozessor während den Delays blockiert ist und nichts anderes machen kann. Möchte man nun eine zweite LED mit anderer Frequenz blinken lassen, dann hat man ein Problem.

Eine gute Möglichkeit, dies zu umgehen ist eine globale Zeitbasis einzuführen. Im Arduino gibt es beispielsweise die millis()-Funktion, das ist nichts anderes.

Was man dazu benötigt ist eine passende Frequenz zur Zeitbasis. Man kann sich die Register von Hand ausrechnen. Oder aber man nimmt einen Online-Rechner, wie den von DieElektronikerSeite: *klick*

Wenn man nun also beispielsweise den internen RC-Oszillator, eingestellt auf 8Mhz (CLKDIV8-Fuse bei z.B. Atmega88 nicht vergessen rauszunehmen) nutzt und 1ms will, dann kommt man auf folgende Werte:

Prescale: 1
TCNTx (L): &HC0
TCNT1H: &HE0
Interrupt: 0.001 Sek.
Fehlerquote: 0%

Ich habe den 16-Bit-Modus für den Timer1 gewählt. Mit 8bit würde man bei diesen Beispiel aber auch ans Ziel kommen. Sogar mit mehreren Prescaler-Varianten. Der Fehler ist hier 0,0% – perfekt!

Der Preload-Wert ist: HEX: E0C0 = DEC: 57536

Also nochmal kurz nachrechnen, ob das passt:

f = TAKTFREQUENZ/(PRESCALER/(MAX_UINT-PRELOAD+1))
f = 8000000/(1/(65535-57536+1))
f = 1000
Passt!

Der Code

Das ganze mal in Code übertragen:

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

void timer1_init(void) {
  // Timer 1 konfigurieren
  TCCR1B = (1<<CS10); // Prescaler 1
 
  TCNT1 = 57536; // -> Preload
 
  // Overflow Interrupt erlauben
  TIMSK |= (1<<TOIE1);
 
  // Global Interrupts aktivieren
  sei();
}

uint32_t volatile timer1_var;

ISR(TIMER1_OVF_vect) {
  // Interrupt jede ms
  TCNT1 = 57536; //E0C0 -> Preload
  
  timer1_var++;
}
Wie man bestimmt gesehen hat läuft die Variable timer1_var irgendwann über. Nämlich nach 4.294.967.295ms ≈ 49,71d. Das ist aber halb so tragisch, da die Variable sich immer noch auswerten lässt!
uint32_t led1_timer_last = 0;
uint32_t led2_timer_last = 0;
void main() { 
  if (timer1_var - led1_timer_last >= 200) { //200ms
    led1_timer_last = timer1_var;

    if (led1_is_on()) {
      led1_off();
    } else {
      led1_on();
    }
  }

  if (timer1_var - led2_timer_last >= 175) { //175ms
    led2_timer_last = timer1_var;

    if (led2_is_on()) {
      led2_off();
    } else {
      led2_on();
    }
  }

  //sonstiger Code
}
Nun haben wir zwei unabhängig voneinander blinkende LEDs und keine blockierenden Funktionen mehr!

Sollte nun timer1_var übergelaufen sein, so ist dies kein Problem, da man ja die letzte Zeit abzieht. Denn 0 – 1 =4294967295 oder praxisnaher 100 – 4294967195 = 200. Also funktioniert der Vergleich auch noch nach einem Überlauf…

Das ganze funktioniert auch mit einem Arduino, dort halt mit der millis()-Funktion, statt unserer Variable.

Genauigkeit

Keine Frage, ein _delay_ms() ist genauer. Warum?

Die Abfrage, der hier vorgestellten Variante findet in der Main statt. D.h. je häufiger die if-Bedingung abgefragt wird, desto genauer. Im Worst Case entsteht hier eine Abweichung von der Durchlaufzeit der Main-Haupt-Schleife. Dies ist aber eine relative Abweichung der Zeitauswertung, der Zeitgeber ist immer noch genau.

Preload braucht Zeit. Das ist mir bei dieser Variante bei meiner Binäruhr aufgefallen. Im Interrupt wird als erstes der Preload geladen. Aber genau dieses Laden benötigt auch ein paar Takte. Daraus folgt eine Absolute Abweichung, der Timer ist also minimal langsamer als 1ms. Bei einer Uhr fällt dies auf. Man könnte die genaue Abweichung durch Simulieren herausfinden, den Preload anpassen und ggf. noch ein paar Assembler-NOPs einfügen. Dies habe ich aber noch nicht ausprobiert.

Und zu guter Letzt kommt noch die Ungenauigkeit des Taktgebers dazu. Dies ist beim internen RC-Oszillator relativ viel. Das betrifft aber natürlich auch _delay_ms().

Andere Möglichkeiten