Datumsanzeige mit DCF77 (5) – die Software: Timer-Interrupt

16.000 mal pro Sekunde wird folgende Interrupt-Routine aufgerufen:

ISR(TIMER1_COMPA_vect)
{
tim_leds();
if ((–prescale_20ms.wordval) < 4)
{
switch (prescale_20ms.byteval[0] & 0x03)
{
case 3:
tim_dcf();
break;

case 2:
tim_secs();
break;

case 1:
tim_keys();
break;

case 0:
prescale_20ms.wordval = PRESCALE_20MS;
break;
}
}
}

tim_leds() wird jedesmal aufgerufen. Die 3 anderen Routinen tim_dcf(), tim_secs() und tim_keys() werden aber nur jedes 320. mal, d.h. 50mal in der Sekunde. Dies wird erreicht, in dem ein Vorteiler (eine eine statische Variable) “prescale_20ms” heruntergezählt wird (und bei “0” wieder mit dem Startwert 320 geladen  wird). Hier sind 2 Tricks bemerkenswert:

1) prescale_20ms ist als union definiert:

union word_u
{
word wordval;
byte byteval[2];
};

Dadurch ist prescale_20ms.wordval der (16-Bit-)word-Zugriff und prescale_20ms.byteval[0] bzw. …[1] der Zugriff auf die einzelnen Bytes. Wenn (wie bei switch) nur ein Byte relevant ist, sind die Operationen für den 8-bit-Mikrocontroller dadurch schneller.

2) Klassischerweise würden die Routinen, die nur nach Ablauf des Vorteilers (hier also jedes 320. mal) aufgerufen werden, alle nacheinander genau dann aufgerufen, wenn der Vorteiler auf 0 gekommen ist. Nachteil dieser Lösung: Da alle 1/16kHz = 0,0666 ms ein neuer Timer-Interrupt ausgelöst wird, sollte die Abarbeitung der Interrupt-Routine möglichst nicht mehr als diese Zeit brauchen, weil sonst am Ende der Interrupt-Routine gleich wieder (aber eben mit Verzögerung, also nicht mehr im 16 kHz-Raster), ein neuer Interrupt ausgelöst wird. Dauert die Abarbeitung mehr als die doppelte Zeit, so geht ein zwischenzeitlich eigentlich nochmal anfallender Zeitgeber-Impuls verloren.

Daher werden die einzelnen Routinen, die mit Vorteiler vom Faktor 320 aufgerufen werden, nicht erst im letzten der 320 Vorteiler-Zyklen gestartet, sondern etwas vorgezogen schon 1-3 Zyklen vorher (aber jede für sich dennoch genau in einem 50Hz-Raster) in der case-Anweisung. Dadurch stehen jeder Routine 0,0666 ms, also etwas weniger als 1000 Assembler-Befehle bei 16MHz Taktfrequenz zur Verfügung statt allen 3 zusammen (abzüglich der der Dauer von tim_leds() und etwas für die Interrupt-Verarbeitung). Dennoch sollten aufwändige Analysen in den Routinen vermieden werden und dem Hauptprogramm überlassen werden.

Was passiert nun in den einzelnen Routinen?

tim_leds(): Gibt pro Zyklus die Bitmuster für eine Reihe auf Port B0..7 aus und das entsprechende Bitmuster (nur ein Bit auf “1” für die entsprechende Reihe) auf Port C0..7 und Port D0..3.

tim_dcf(): Tastet den DCF77-Daten-Eingang (Port A0) ab und misst die Zeiten, die das Signal “0” bzw. “1” war. Versucht, aus diesen Zeiten die Bits “1” und “0” aus dem DCF-77-Signal zu dekodieren (vgl. Kodierung des DCF-77-Signals!). Über die statische Variable “dcf_message” wird ggf. die Botschaft (1/0/Fehler/Ende) an die Verarbeitung in main() weitergegeben.

tim_secs(): Verwaltet einen “Software”-Sekunden-Timer “status_timer”, d.h. eine Byte-Variable, die hier im Sekundentakt (d.h. mit weiterer interner Vorteiler-Variable) auf 0 heruntergezählt wird. Der Wert muss vom Hauptprogramm abgefragt werden und bei “0” entsprechend darauf reagiert werden. Danach soll der Timer vom Hauptprogramm auf 0xFF gesetzt werden, denn dieser Wert signalisiert “kein Timer aktiv” und wird von tim_secs()  nicht heruntergezählt. Wird z.B. beim Uhrenstellen für Timeouts verwendet.

tim_keys(): Analysiert den Zustand der Tasten und gibt Botschaften wie “Taste gedrückt, länger gedrückt, losgelassen…” über die Variablen key0_stat und key1_stat an das Hauptprogramm.

Hier der C-Quelltext:

dcf77-c

Dieser Beitrag wurde unter Mikrocontroller/Arduino abgelegt und mit , , verschlagwortet. Setze ein Lesezeichen auf den Permalink.

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.