Dieses Modul dient zum Decodieren des DCF77-Signals. Es wird alle 10 ms mit dem Zustand des DCF-Empfängers aufgerufen und füllt eine Datenstruktur, solbald die Zeit empfangen wird. Bei guter Empfangslage ist das zu jedem Minutenbeginn der Fall. Bei abgesenktem Träger wird eine 0 an die Routine übergeben — dies ist während der ersten 100ms bzw. 200ms einer Sekunde der Fall. Ansonsten wird eine 1 übergeben.
Wie bei vielen meiner anderen Module auch, braucht die DCF77-Auswertung einen Basistakt von 10 Millisekunden. Alle 10ms wird der Port, an dem das DCF-Signal empfangen wird, abgefragt. Ein extra Interrupt wird nicht benötigt. In meinen Anwendungen wird der Port-Zustand in einer Variablen gemerkt, und zwar in einer Interrupt-Routine, die alle 10ms ausgeführt wird. Dieser 10-Millisekunden-Interrupt erledigt noch andere Dinge wie zum Beispiel die Taster-Abfrage oder den Countdown.
Die eigentliche Auswertung des gemerkten Port-Zustands geschieht nicht auf Interrupt-Ebene, sondern auf normaler Anwendungsebene. Dadurch erzeugt die DCF77-Auswertung praktisch keine zusätzliche IRQ-Last. Allerdings muss ausserhalb des DCF-Moduls eine Zeitbasis geschaffen werden. Wie bereits erklärt, kann ein und dieselbe 10ms-Basis für viele verschiedene Module benutzt werden, so auch für das DCF77-Modul.
Benutzerschnittstelle
Typedefs
- time_t
-
Diese Struktur stellt die Zeit dar und hat folgende Komponenten:
- uint8_t second
- Sekunde: 0...59
- uint8_t minute
- Minute: 0...59
- uint8_t hour
- Stunde: 0...23
- uint8_t day_of_week
- Wochentag: 1=Montag, ..., 7=Sonntag
- uint8_t day
- Tag: 1...31
- uint8_t month
- Monat: 1...12
- uint8_t year
- Jahr: 0...99
- uint8_t mesz
- Mitteleuropäische Sommerzeit? 1=Sommerzeit, 0=Winterzeit
- dcf77_t
-
Diese Struktur fasst DCF77-Daten zusammen. Hier nur die wichtigsten Komponenten, die nach aussen hin interessant sind:
- time_t *ptime
- Bei korrekt empfangener Zeit wird diese nach *ptime kopiert. Wichtig: *ptime muss ausserhalb des dcf77-Moduls initialisiert werden. Ansonsten wird die Zeit nicht hineinkopiert!
- uint8_t time_changed
- Wenn die neue Zeit nach *ptime kopiert wird, wird time_changed auf 1 gesetzt. Die Anwendung kann time_changed wieder auf 0 zurücksetzten. So kann erkannt werden, daß ein neues Zeit/Datums-Paket angekommen ist.
Objekte
- dcf77_t dcf77
- Ein globales dcf77-Objekt
Funktionen
- void dcf77_tick (dcf77_t *dcf, uint8_t poll)
- Diese Funktion wird alle 10 Millisekunden aufgerufen und übergibt den Zustand des Ports, an dem die DCF77-Hardware angeschlossen ist. Dadurch bleibt der DCF-Decoder frei von Hardware-Abhängigkeiten. Zudem kann der Wert für poll in einer Interrupt-Routine erhelten werden, während die Ausführung dieser vergleichsweise aufwändigen Funktion auf normaler Anwendungsebene erfolgen kann. Im ersten Teil einer Sekunde, der Absenkung des DCF-Trägers, wird poll=0 übergeben; ansonsten ein Wert ungleich Null.
Makros
Beim Übersetzen des Quellcodes kann man verschiedene Defines setzen, um die Codeerzeugung zu beeinflussen. Dazu gibt man beim Aufruf von gcc die Option -D unmittelbar gefolgt vom Makroname an; hier ein Beispiel: avr-gcc ... -DMAKRONAME ...
- TIME_MESZ
- Falls dieses Makro definiert ist, wird die MESZ-Information des DCF77-Protokolls für mitteleuropäische Sommer-/Winterzeit ausgewertet.
Anwendungsbeispiel
für avr-gcc 4.7
Für andere Maschinen sieht das ähnlich aus, nur daß Definition und Implementierung einer ISR natürlich maschinenabhängig sind.
#include <stdint.h> #include <avr/io.h> #include <avr/interrupt.h> #include "dcf77.h" // Merkt alle 10 Millisekunden den Portzustand des DCF-Ports uint8_t volatile dcf77_bit = 0xff; static void job_dcf77_10ms (void); // Alle 10 Millisekunden aus einer ISR heraus aufrufen // Macht alle 10ms einen Schnappschuss vom DCF-Port (hier an Port B1). void job_dcf77_10ms (void) { uint8_t bit = 0; if (PINB & (1 << PB1)) bit = 1; dcf77_bit = bit; } // Definition von ISRs etc ISR (...) { ... // Alle 10ms job_dcf77_10ms(); ... } time_t time; int main (void) { // Initialisierung: IRQs aktivieren, Hardware initialisieren, etc. // ... dcf77.ptime = &time; // Hauptschleife while (1) { uint8_t bit = dcf77_bit; // Andere Aufgaben in der Hauptschleife // ... // Sind 10ms vergangen, also dcf_bit in { 0, 1 }? if (bit < 2) { // Ja: // Schnappschuss zurücksetzen dcf77_bit = 0xff; // Die Zeit tickt 10ms weiter dcf77_tick (&dcf77, bit); // DCF-Abgleich erfolgreich? if (dcf77.time_changed) { // Neue Zeit ist in time angekommen! // Merker zurücksetzen dcf77.time_changed = 0; // Zeit in dcf77.ptime auswerten // ... } } // DCF // Andere Aufgaben in der Hauptschleife // ... } // Hauptschleife } // main
dcf77.h
#ifndef DCF77_H #define DCF77_H #include <stdint.h> typedef struct { uint8_t second; uint8_t minute; uint8_t hour; uint8_t day; uint8_t day_of_week; uint8_t month; uint8_t year; #ifdef TIME_MESZ uint8_t mesz; #endif // TIME_MESZ } time_t; typedef struct { // In newtime wird die neue Zeit-Information aufgebaut time_t newtime; // Bei korrekt empfangener Zeit wird die Zeit nach *ptime kopiert. // // !!! *ptime MUSS AUSSERHALB DES DCF77-MODULS GESETZT WERDEN !!! // !!! ANSONSTEN WIRD DIE ZEIT NICHT HINEINKOPIERT !!! time_t *ptime; // Wenn die neue Zeit nach *ptime kopiert wird, wird time_changed // auf 1 gesetzt. Die Anwendung kann time_changed wieder auf // 0 zurücksetzten. So kann die Anwendung erkennen, daß ein neues // Zeit/Datums-Paket angekommen ist. uint8_t time_changed; // Für interne Verwendung in dcf77.c // Parity wird on the fly mitberechnet uint8_t parity; // Es gab einen Fehler: error != 0 uint8_t error; // Wertigkeit des aktuellen BCD-Bits: 1, 2, 4, 8, 10, 20, 40, 80. uint8_t bitval; // Dauer der aktuellen Sekunde in Einheiten von 10ms (1 Tick) uint8_t ticks; // Letzter poll-Wert vom Aufruf von dcf77_tick() zur // Flankenerkennung. uint8_t poll; uint8_t byteno; } dcf77_t; // dcf77 wird definiert in dcf77.c extern dcf77_t dcf77; /* Obwohl es nur eine dcf77-Struktur gibt, wird nicht direkt mittels dcf77.xxx darauf zugegriffen, sondern durch dcf->xxx. Grund ist, daß auf AVR durch indirekten Zugriff ein kürzerer Code möglich ist als durch direkten Zugriff. Dazu darf gcc die Adresse von dcf77 nicht zu sehen bekommen; ansonsten macht er auch bei dcf->xxx einen direkten Zugriff. */ extern void dcf77_tick (dcf77_t *dcf, uint8_t poll); #endif /* DCF77_H */
dcf77.c
Wie gesagt ist dies ein Beispiel für avr-gcc 4.7, denn es wird der neue Address-Space Qualifier __flash verwendet, der erst ab Version 4.7.0 zur Verfügung steht.
Möchte man eine ältere Compilerversion verwenden und dabei nicht darauf verzichten, daß die verwendete Tabelle im Flash abgelegt wird, dann muss man sich mit Konstrukten wie PROGMEM und pgm_read_byte behelfen wie sie z.B. die AVR Libc zur Verfügung stellt.
#include "dcf77.h" #if defined (__GNUC__) && defined (__AVR__) && !defined (__FLASH) #warning You need avr-gcc 4.7 or higher to use __flash! #warning ...locating dcf_byteno[] in RAM // Notlösung: Standard-C wird dcf_byteno[] im RAM speichern #define __flash #endif // avr-gcc dcf77_t dcf77; #define PARITY 0xff #define IGNORE 0xfe #define NEWVAL 0x10 // Adressdifferenz von .N zu .minute in time_t #define X(N) ((uint8_t)(&dcf77.newtime.N - &dcf77.newtime.minute)) #define X0 X(minute) #define X1 X(hour) #define X2 X(day) #define X3 X(day_of_week) #define X4 X(month) #define X5 X(year) #define X6 X(mesz) /* Codiert die DCF-Bits 17..58 bzw. 21...58 (je nachdem, ob MESZ empfangen werden soll) und deren Position in time_t. Low-Nibble : Offset relativ zu .minute Bit 4 (NEWVAL) : Neuer Wert fängt an, d.h. die Speicherposition muss 1 Byte voranschreiten. */ static const __flash uint8_t dcf_byteno[] = { #ifdef TIME_MESZ // Zeitzone: MEZ/MESZ (Winter-/Sommerzeit): 2 Bits an Offset 6 NEWVAL | X6, X6, // Ankündigung Schaltsekunde IGNORE, // Parity Zeitzone (immer 1) PARITY, #endif // TIME_MESZ // Minute: 7 Bits an Offset 0 NEWVAL | X0, X0, X0, X0, X0, X0, X0, // Parity Minute PARITY, // Stunde: 6 Bits an Offset 1 NEWVAL | X1, X1, X1, X1, X1, X1, // Parity Stunde PARITY, // Tag: 6 Bits an Offset 2 NEWVAL | X2, X2, X2, X2, X2, X2, // Wochentag: 3 Bits an Offset 3 NEWVAL | X3, X3, X3, // Monat: 5 Bits an Offset 4 NEWVAL | X4, X4, X4, X4, X4, // Jahr: 8 Bits an Offset 5 NEWVAL | X5, X5, X5, X5, X5, X5, X5, X5, // Parity Datum PARITY }; static void dcf77_start_bit (dcf77_t*, uint8_t); static void dcf77_store_bit (dcf77_t*, uint8_t); static void dcf77_end_bit (dcf77_t*, uint8_t); /* Dies ist der Einstiegspunkt für das DCF-Modul. Die Funktion wird alle 10ms (ein Tick) aufgerufen. poll = 0: Träger ist abgesenkt (erste 100ms oder 200ms einer Sekunde) poll = 1: Voller Träger */ void dcf77_tick (dcf77_t *dcf, uint8_t poll) { // Diese Funktion wird zu jedem Tick -- also alle 10ms -- // aufgerufen. Ticks um 1 weiterzählen. uint8_t ticks = 1 + dcf->ticks; // Überlauf der Ticks, z.B. bei schlechtem Signal oder nicht- // angeschlossenem Empfänger. Ticks bleiben dann bei 255 stehen. if (ticks == 0) ticks = UINT8_MAX; dcf->ticks = ticks; // poll normieren auf 0 oder !0 poll = !!poll; // Flanke? if (poll != dcf->poll) { // Ja. poll-Wert zur Erkennung der nächsten Flanke merken. dcf->poll = poll; if (poll) { // Flanke 0->1: Bit-Ende 100ms oder 200ms nach Sekundenstart dcf77_end_bit (dcf, ticks); } else { // Flanke 1->0: Bit-Anfang, Sekundenstart dcf77_start_bit (dcf, ticks); dcf->ticks = 0; } } } /* Wird aufgerufen bei Flanke 1->0 des DCF-Signals. Start eines neuen Bits bzw. Start/Ende einer Sekunde. TICKS sind die Anzahl Ticks seit der vorherigen 1->0 Flanke und ist ca. 100 (also etwa 1s) oder nach dem ausgelassenen 59. Bit ca. 200 (also etwa 2s). */ static void dcf77_start_bit (dcf77_t *dcf, uint8_t ticks) { // Aktuelle Sekunde 0..59 um 1 weiterzählen uint8_t second = dcf->newtime.second; if (second < 60) second++; dcf->newtime.second = second; if (ticks > 200-10 && ticks < 200+10) { // 59. Sekunde: Der Träger wurde für ca. 200 Ticks (2s) // nicht abgesenkt. Nun beginnt eine neue Absenkung, d.h. // eine neue Minute beginnt. dcf->newtime.second = 0; if (dcf->error == 0 && second == 59) { // Während einer ganzen Minute wurden korrekte // Bits empfangen --> aktuelle Zeit/Datum speichern dcf->time_changed = 1; // !!! FÜR .ptime SIEHE DEN KOMMENTAR IN dcf77.h !!! if (dcf->ptime) *(dcf->ptime) = dcf->newtime; } // Eine Minuten-Marke bewirkt ein Neubeginn der // DCF-Auswertung; daher das Fehler-Flag zurücksetzen. dcf->error = 0; } else { // Eine "normale" Sekunde ist vergangen. Deren Länge // ist ca. 100 Ticks (1s). Andernfalls ist's ein Fehler. if (ticks < 100-10 || ticks > 100+10) dcf->error = 1; } } /* Wird aufgerufen bei Flanke 0->1 des DCF-Signals. Ende der Trägerabsenkung, d.h. Bitende. TICKS ist die Dauer der Trägerabsenkung, d.h. die Anzahl der 10ms-Ticks seit der letzten 1->0 Flanke des DCF-Signals und ist für gültige 0-Bits ca. 10 (100ms) und für gültige 1-Bits ca. 20 (200ms). */ static void dcf77_end_bit (dcf77_t *dcf, uint8_t ticks) { uint8_t bit = 0; if (ticks >= 10-3 && ticks < 10+3) // Ein 0-Bit dauert ca. 10 Ticks (100ms) bit = 0; else if (ticks >= 20-3 && ticks < 20+3) // Ein 1-Bit dauert ca. 20 Ticks (200ms) bit = 1; else // Alle anderen Bitlängen sind ungültig --> Fehler dcf->error = 1; if (!dcf->error) dcf77_store_bit (dcf, bit); } /* Speichert BIT in der DCF-Struktur */ static void dcf77_store_bit (dcf77_t *dcf, uint8_t bit) { // Nur Bits 17..58 bzw. 21..58 decodieren. Die aktuelle Sekunde, // vermindert um 17/21, dient als Index ins Feld dcf_byteno[], // wo Informationen dazu stehen, wie BIT zu // behandeln und wo es ggf. zu speichern ist. const uint8_t nbits = sizeof (dcf_byteno) / sizeof (dcf_byteno[0]); uint8_t i = dcf->newtime.second - (59-nbits); if (i >= nbits) return; i = dcf_byteno[i]; #ifdef TIME_MESZ if (i == IGNORE) return; #endif // TIME_MESZ dcf->parity ^= bit; if (i == PARITY) { // Partity-Test. Diese Funktion wird nur aufgerufen mit // .error = 0. Falls parity != 0 ist, liegt also ein // Parity-Fehler vor. Ansonsten bleibt .error = 0 unverändert. dcf->error = dcf->parity; // Parity für nächstes Bit zurücksetzen. dcf->parity = 0; return; } // Low-Nibble -> Offset des Bytes (relativ zu .minute), // zu dem das Bit gehört uint8_t offset = i & 0xf; uint8_t *d = &dcf->newtime.minute + offset; uint8_t val = *d; // Wertigkeit des Bits in BCD: 1, 2, 4, 8, 10, 20, 40, 80 uint8_t bitval = dcf->bitval << 1; if (bitval == 0x10) bitval = 10; // Ein neuer Wert fängt an if (i & NEWVAL) val = 0, bitval = 1; dcf->bitval = bitval; // Falls DCF-Bit gesetzt ist: seine Wertigkeit zum // Zielbyte hinzuaddieren if (bit) val += bitval; *d = val; // Kleiner Konsistenztest: Testet Einer auf gültiges BCD-Format if (bitval == 8 && val >= 10) dcf->error = val; }