Dieses Modul erledigt neben dem Decodieren des DCF77-Signals auch das Verwalten der Zeit: ist kein DCF-Signal vorhanden — was bei schlechter Empfangslage öfter vorkommt — wird die Zeit per Prozessor-Takt weitergezählt. Wenn das DCF-Signal wieder brauchbar ist, dann synchronisiert sich die Zeit wieder mit der Funkzeit.
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
- struct time_t
-
Diese Struktur stellt die Zeit dar und hat folgende Komponenten:
- unsigned char second
- Sekunde: 0...59
- unsigned char minute
- Minute: 0...59
- unsigned char hour
- Stunde: 0...23
- unsigned char day_of_week
- Wochentag: 1=Montag, ..., 7=Sonntag
- unsigned char day
- Tag: 1...31
- unsigned char month
- Monat: 1...12
- unsigned char year
- Jahr: 0...99
- unsigned char mesz_p
- Mittaleuropäische Sommerzeit? 1=Sommerzeit, 0=Winterzeit
- struct dcf_t
-
Diese Struktur fasst DCF77-Daten zusammen. Hier nur die wichtigsten Komponenten, die nach aussen hin interessant sind:
- unsigned char level
- Gibt an, wie weit der DCF-Empfang fortgeschritten ist und kann einen der folgenden Werte enthalten:
- DCF_STARTUP
- Kurz nach Initialisierung oder einem Fehler
- DCF_HAVE_SIGNAL
- Es wurde für mindestens 3 Sekunden ein DCF77-konformes Signal erkannt, und es gab noch keine Minuten-Synchronisation
- DCF_HAVE_SECONDS
- Der Empfänger hat sich auf die Minutenmarke synchronisiert und empfängt das Zeitsignal
- DCF_HAVE_TIME
- Die Zeit ist erfolgreich abgeglichen
- unsigned char error
- Der Fehlerstatus des letzten Ausrufs. Wenn dieser Wert gleich DCF_OK ist, dann ist alles in Ordnung.
- unsigned char received_bits
- Die letzten, an den DCF-Empfänger übermittelten Port-Zustände (das Argument bit bei den Funktionen unten): Bit0 ist der Wert beim letzten Aufruf und Bit1 ist der Wert vom vorletzten Aufruf. Der Wert der Variablen ist also immer 0, 1, 2 oder 3.
- struct timeinfo_t
-
In dieser Struktur werden alle für die Verwaltung der Zeit notwendigen Daten gebündelt:
- time_t time
- Die Zeit
- unsigned char new_second_p
- Ungleich Null, wenn eine neue Sekunde angefangen hat.
- unsigned char new_minute_p
- Ungleich Null, wenn eine neue Minute angefangen hat.
- unsigned char have_time_p
- Die Zeit in Komponente .time ist gültig. Dieser Eintrag muss vom Anwender gesetzt werden, er wird von den Routinen des Moduls nicht verändert! Hier kann man zum Beispiel merken, wenn die Zeit von Hand – etwa über eine Tastatur – eingegeben wurde und dadurch richtig ist.
Objekte
- timeinfo_t timeinfo
- Das globale timeinfo-Objekt
Funktionen
- void time_tick_10ms (timeinfo_t * ti, unsigned char bit)
-
Diese Funktion wird alle 10 Millisekunden aufgerufen und zählt die Zeit um 10ms weiter. Übergeben wird der Zustand des Ports, an dem die DCF77-Hardware angeschlossen ist. Dadurch bleicht der eigentliche DCF-Decoder frei von Hardware-Abhängigkeiten. Zudem kann der Wert für bit 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 (Absenkung des DCF-Trägers) wird bit=0 übergeben, ansonsten ein Wert ungleich Null. - void dcf_receive_bit (timeinfo_t * ti, unsigned char bit)
- Erledigt die eigentliche DCF-Decodierung innerhalb der eben beschriebenen Funktion. Muss vom Anwender nicht mehr extra aufgerufen werden. Die Verarbeitung ihrer Werte und Bereitstellung der Zeitbasis, falls kein gutes DCF-Signal vorhanden ist, geschieht alles innerhalb von time_tick_10ms.
- DCF_CHECK_PARITY
-
Dieses Define sollte immer gesetzt werden. Das DCF77-Signal wird einer Paritäts-Prüfung unterzogen. Dazu werden die im DCF77-Protokoll enthaltenen Parity-Bits verwendet. Zudem erfolgt eine kleine Konsistenzprüfung.
Ein Prüfung auf Datenintegrität erfolgt übrigens nicht, und zwar aus folgendem Grund: Ein Wochentag (codiert von 1..7) wird in 3 Bits übertragen, der Tag (codiert von 1..31) wird in 5 Bits übertragen, etc. Es wäre also sehr unwahrscheinlich, durch Integritätsprüfung Fehler aufzudecken. Selbst bei zufälligen Daten und ohne Paritäsprüfung wäre die Chance, einen Übertragungsfehler beim Tag zu entdecken, kleiner als 1/15. Durch die Paritätsprüfung fällt diese Chance weiter. - DCF_TIME_NEXT_DAY
-
Die Funktion time_tick_10ms zählt die Zeit um 10 Millisekunden weiter und aktualisiert gegebenenfalls die Zeitstruktur in timeinfo.time (ein Objekt vom Typ time_t). Ohne dieses Define werden bei schlechter DCF-Empfangslage nur Sekunde, Minute, Stunde und Wochentag der Zeit aktuell gehalten; mit dem Define zusätzlich das Datum (Tag, Monat, Jahr). Die Behandlung von Schaltjahren erfolgt nur für die nicht durch 100 teilbaren Jahre korrekt.
Für die meisten Einsatzfälle braucht dieses Define nicht gesetzt zu werden. - DCF_REPARE
-
Wenn dieses Define aktiv ist, dann läuft das DCF-Signal durch einen Software-Filter. Dieser Filter versucht, ein schlechtes DCF-Signal aufzubessern. Wird zum Beispiel die Bitfolge 00100 empfangen, dann werden diese Bits durch die Bitfolge 00000 ersetzt, denn ein ungestörtes DCF-Signal besteht immer aus Blöcken von ca. zehn Einsen bzw. Nullen.
Dieses Verfahren ist sehr simpel. Wenn man einen anderen Algorithmus einbauen möchte, dann ist die Funktion dcf_get_transition der Ort dafür. - DCF_DEBUG
-
Bei schlechtem DCF-Empfang kann man dieses Define aktivieren, um mehr Information darüber zu erhalten, warum der Empfang fehlschlägt:
- dcf_t dcf_error
- Enthält die Kopie (Schnappschuss) der Struktur dcf zu dem Zeitpunkt, als der letzte Fehler auftrat.
- unsigned char dcf.error
- Diese Komponente enhält einen aussagekräftigeren Wert als im Normalbetrieb, wo Fehlerstatus immer 0 (alles OK) oder 1 (es gab einen Fehler) ist. Mit dem aktivierten Define entalten dcf.error und dcf_error.error einen der folgenden Werte, und die Werte sind alle verschieden:
- enum DCF_OK
- enum DCF_NO_SIGNAL
- enum DCF_ALWAYS_SIGNAL
- enum DCF_BIT_TOO_SHORT
- enum DCF_BIT_INDIFFERENT
- enum DCF_BIT_TOO_LONG
- enum DCF_SECOND59_OVERRUN
- enum DCF_SECOND59_UNDERRUN
- enum DCF_BYTEPOS_OVERRUN
- enum DCF_BAD_PARITY
- enum DCF_FATAL
- const char * dcf_get_error_str (unsigned char errno)
- Liefert zu einer Fehlernummer den Fehlertext. Für AVR ist der String ein Zeiger in den Flash (PROGMEM), für alle anderen Maschinen ist es ein gewöhnlicher String.
Anwendungsbeispiel
für avr-gcc
Für andere Maschinen sieht das ähnlich aus, nur daß Definition und Implementierung einer ISR natürlch maschinenabhängig sind.
#include <avr/io.h> #include <avr/interrupt.h> #ifndef SIGNAL // Fü alte Versionen von avr-gcc # include <avr/signal.h> #endif #include "dcf.h" // Merkt alle 10 Millisekunden den Portzustand des DCF-Ports unsigned char volatile dcf_bit = 0xff; static void job_dcf_10ms (void); ... // Alle 10 Millisekunden aus einer ISR heraus aufrufen // Macht alle 10ms einen Schnappschuss vom DCF-Port (hier an Port B1). void job_dcf_10ms (void) { unsigned char bit = 0; if (PINB & (1 << PB1)) bit = 1; dcf_bit = bit; } ... int main () { ... // Hauptschleife while (1) { ... unsigned char bit = dcf_bit; // Sind 10ms vergangen (also dcf_bit in {0,1}?) if (bit < 2) { // Ja: // Schnappschuss zurücksetzen dcf_bit = 0xff; // Die Zeit tickt 10ms weiter time_tick_10ms (& timeinfo, bit); // DCF-Abgleich erfolgreich? if (DCF_HAVE_TIME == timeinfo.dcf.level) // Ja: die Zeit ist gültig timeinfo.have_time_p = 1; // Ist die Zeit gültig? if (timeinfo.have_time_p) { // Ja: // Sekunde = timeinfo.time.second // Minute = timeinfo.time.minute ... } } // DCF ... } // Hauptschleife } // main
Makros
zum Steuern der Codeerzeugung
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 für TriCore:
tricore-gcc ... -DMAKRONAME ...
dcf.h
#ifndef _DCF_H_ #define _DCF_H_ /************************************************************* * Typedefs, Defines, Enums ************************************************************/ /// Die Länge eines kurzen DCF-Signals (in Einheiten von 10ms) #define DCF_BIT_LENGTH 10 /// Toleranz für die Länge eines Bit (in Einheiten von 10ms) #define DCF_BIT_TOLERANCE 3 // Länge einer Sekunde (in Einheiten von 10ms) #define DCF_SEK_LENGTH (10*DCF_BIT_LENGTH) /// So lange wird zusätzlich gewartet, um Sekunde 59 zu erkennen (Einh=10ms) #define DCF_WAIT_SEK59 5 /// Werte die angeben, wie weit der DCF-EMpfang gediehen ist enum dcf_level { /// Kein Zeitsignal, direkt nach dem Start /// oder nach `dcf_loose_time()' (Zeitsignal verloren) /// Muss 0 sein (`dcf' ist in .bss) DCF_STARTUP = 0, /// Zeitsignal scheint ok und es wurden /// mindestend 3 Sekunden korrekt empfangen DCF_HAVE_SIGNAL, /// Die Sekunden sind abgeglichen DCF_HAVE_SECONDS, /// Die Zeit entspricht DCF DCF_HAVE_TIME }; // Die Zeit typedef struct { unsigned char second; // Sekunde unsigned char minute; // Minute unsigned char hour; // Stunde unsigned char day_of_week; // Wochentag: 1=MO ... 7=SO unsigned char day; // Tag unsigned char month; // Monat unsigned char year; // Jahr % 100 unsigned char mesz_p; // MESZ: mitteleuropäische Sommerzeit? } time_t; /// Struktur, um die Werte des DCF77-Signals aufzunehmen. /// Die Werte dürfen 1 bis 8 Bit lange sein. /// Die jeweilige Länge wird festgelegt in `dcf_max_mask'. /// Die Komponenten als 8-Bit-Werte anzulegen und nicht als /// Bitfelder mit den jeweiligen Längen ist günstiger. /// Die Daten sind BCD-Codiert und little Endian (Bit 0 (LSB) wird zuerst /// übertragen, MSB zuletzt). typedef struct { unsigned char reserved1; // 2*8 unsigned char reserved2; //unsigned char zone_info; // (4) unsigned char a1; // 1 unsigned char z1; // 1 unsigned char z2; // 1 unsigned char a2; // 1 unsigned char start_time; // 1 unsigned char minute; // 7 unsigned char minute_parity; // 1 unsigned char hour; // 6 unsigned char hour_parity; // 1 unsigned char day; // 6 unsigned char day_of_week; // 3 unsigned char month; // 5 unsigned char year; // 8 unsigned char date_parity; // 1 } dcf_time_t; /// Struktur für den DCF-Empfänger mit den benötigten Zustandsvariablen typedef struct { /// Gibt an, wie weit der DCF-Empfang gediehen ist. /// Hat einen Wert aus `enum dcf_level'. unsigned char level; /// Der Fehlerzustand: /// - DCF_DEBUG ist undefined: 0=OK, 1= irgendein Fehler /// - DCF_DEBUG ist defined: 0=OK, >0: Nummer des Fehlers unsigned char error; // Private /// Die zuletzt empfangenen 2 Bits. /// Beim Empfang eines neuen Bits rücken die alten Bits um 1 nach links /// und das neue Bit wird im LSB gespeichert. /// Benutzt in `dcf_receive_bit()'. unsigned char received_bits; #ifdef DCF_REPARE /// Ein Zusatzfeld bei aktivierter Fehlerkorrektur unsigned char repare_bits; #endif // DCF_REPARE // Hat eine neue Minute angefangen? unsigned char new_minute_p; // Zählt die Länge einer Sekunde. // Alle 10ms wird `dcf_sekticks' durch Aufruf // von `dcf_receive_bit()' um 1 erhöht. unsigned char sekticks; // Die eingesammelten Bits dcf_time_t time_bits; // !!!Diese 4 Werte MÜSSEN nach time_bits stehen für `dcf_reset()'!!! // Index für die Schreibposition des nächten Bits unsigned char bytepos; // Maske zu speichern eines Bits unsigned char bitmask; // Zählt Sekunden unsigned char second; // Merker für die 59. Sekunde unsigned char second59_p; } dcf_t; // Die komplette Zeitinformation typedef struct { // Wird vom `dcf_receive_bits()' verwendet dcf_t dcf; /// Die aktuelle Zeit. /// Entweder DCF-Zeit oder Quarzzeit (ja nach Güte des DCF-Signals) time_t time; /// Hat eine neue Sekunde angefangen? unsigned char new_second_p; /// Hat eine neue Minute angefangen? unsigned char new_minute_p; // Wird alle 10 ms um 1 erhöht. unsigned char ticks_10ms; // Ist die Zeit gültig? // Diese Feld wird NICHT in // `time_tick_10ms' oder `dcf_receive_bit' verwaltet/geändert. // In der eigentlichen Anwendung kann man sich merken, ob // die Struktur eine gültige Zeitinformation enthält, zB nachdem // man die Zeit "von Hand" gesetzt hat und die Zeit daher gültig ist // oder es mindestens einen erfolgreichen DCF-Abgleich gegeben hat. unsigned char have_time_p; } timeinfo_t; #if defined (DCF_DEBUG) /// Im Fehlerfalle enthält diese Variable eine Kopie von `dcf' /// zu dem Zeitpunkt, wo der letzte Fehler auftrat extern dcf_t dcf_error; /// Gibt den zur Fehlernummer gehörenden Fehlertext zurück. /// Für AVR ist dies ein Zeiger ins Flash (PROGMEM), /// ansonsten ein normaler String. extern const char * dcf_get_error_str (unsigned char const errno); # define DCF_ERROR_ENUM #else # define DCF_ERROR_ENUM = 1 #endif // DCF_DEBUG /// Fehlernummern: Alles, was schiefgehen kann enum dcf_errno { DCF_OK = 0, DCF_NO_SIGNAL DCF_ERROR_ENUM, DCF_ALWAYS_SIGNAL DCF_ERROR_ENUM, DCF_BIT_TOO_SHORT DCF_ERROR_ENUM, DCF_BIT_INDIFFERENT DCF_ERROR_ENUM, DCF_BIT_TOO_LONG DCF_ERROR_ENUM, DCF_SECOND59_OVERRUN DCF_ERROR_ENUM, DCF_SECOND59_UNDERRUN DCF_ERROR_ENUM, DCF_BYTEPOS_OVERRUN DCF_ERROR_ENUM, DCF_BAD_PARITY DCF_ERROR_ENUM, DCF_FATAL DCF_ERROR_ENUM }; /************************************************************* * Globale Funktionen und Objekte ************************************************************/ extern void time_tick_10ms (timeinfo_t * const, const unsigned char); extern void dcf_receive_bit (timeinfo_t * const, unsigned char); extern timeinfo_t timeinfo; #endif /* _DCF_H_ */
dcf.c
#include <string.h> // memset #include "dcf.h" /* Funktionsweise: =============== (1) Alle 10 ms wird void dcf_receive_bit (unsigned char bit) aufgerufen bit = 0: DCF-Trägersignal ist abgesenkt bit != 0: DCF-Trägersignal ist nicht abgesenkt (2) dcf.level = DCF_STARTUP kein DCF-Signal dcf.level = DCF_HAVE_SIGNAL DCF-Signal wird empfangen für >= 3 Sekunden dcf.level = DCF_HAVE_SECONDS DCF-Sekunden sind gültig dcf.level = DCF_HAVE_TIME DCF-Zeit ist abgeglichen (3) Eine neue DCF-Minute startet, wenn: dcf.new_minute_p = 1 */ /************************************************************* * Globale Objekte *************************************************************/ timeinfo_t timeinfo; #ifdef DCF_DEBUG /************************************************************* * Die Funktionen/Objekte in diesem Abschnitt werden nur dann * übersetzt bzw. stehen nur dann zur Verfügung, wenn das * Makro `DCF_DEBUG' definiert ist. * Damit kann man detailierte Informationen über das DCF-Signal * erhalten und erfahren, warum ggf. der Empfang nicht * funktioniert. Je nach Empfangslage/Güte des DCF-Signals kann * man das Signal filtern/aufmöbeln, wie es unten geschieht, wenn * das Makro `DCF_REPARE' aktiviert ist. Natürlich gibt es * ausgefeiltere Strategien, um ein gestörtes Signal aufzubessern. * Für den nomalen Betrieb der DCF-Software braucht dieses Define * nicht gesetzt zu werden, wodrch die Codegröße schrumpft. *************************************************************/ dcf_t dcf_error; # if defined (__AVR__) # include <avr/pgmspace.h> # else // !__AVR__ # define PROGMEM # define pgm_read_word(ADDR) (*(ADDR)) # endif // __AVR__ const char PROGMEM STR_DCF_OK[] = "DCF_OK"; const char PROGMEM STR_DCF_NO_SIGNAL[] = "DCF_SIG=0"; const char PROGMEM STR_DCF_ALWAYS_SIGNAL[] = "DCF_SIG=1"; const char PROGMEM STR_DCF_BIT_TOO_SHORT[] = "DCF_BIT_SHORT"; const char PROGMEM STR_DCF_BIT_INDIFFERENT[] = "DCF_BIT_INDIFF"; const char PROGMEM STR_DCF_BIT_TOO_LONG[] = "DCF_BIT_LONG"; const char PROGMEM STR_DCF_SECOND59_OVERRUN[] = "DCF_SEC59_OV"; const char PROGMEM STR_DCF_SECOND59_UNDERRUN[] = "DCF_SEC59_UV"; const char PROGMEM STR_DCF_BYTEPOS_OVERRUN[] = "DCF_POS_OV"; const char PROGMEM STR_DCF_BAD_PARITY[] = "DCF_BAD_PARITY"; const char PROGMEM STR_DCF_FATAL[] = "DCF_FATAL"; const char * const dcf_errstr[] PROGMEM = { [DCF_OK] = STR_DCF_OK, [DCF_NO_SIGNAL] = STR_DCF_NO_SIGNAL, [DCF_ALWAYS_SIGNAL] = STR_DCF_ALWAYS_SIGNAL, [DCF_BIT_TOO_SHORT] = STR_DCF_BIT_TOO_SHORT, [DCF_BIT_INDIFFERENT] = STR_DCF_BIT_INDIFFERENT, [DCF_BIT_TOO_LONG] = STR_DCF_BIT_TOO_LONG, [DCF_SECOND59_OVERRUN] = STR_DCF_SECOND59_OVERRUN, [DCF_SECOND59_UNDERRUN] = STR_DCF_SECOND59_UNDERRUN, [DCF_BYTEPOS_OVERRUN] = STR_DCF_BYTEPOS_OVERRUN, [DCF_BAD_PARITY] = STR_DCF_BAD_PARITY, [DCF_FATAL] = STR_DCF_FATAL }; // Gibt den Fehler-Sting zu einer bestimen Fehler-Nummer zurück. // Für AVR ist dies ein Pointer in den Flash, weil für AVR die // Fehler-Strings mit PROGMEM ins Flash lokatiert sind. // Für alle anderen Controller liefert diese Funktion einen "normalen" // Char-Pointer. const char * dcf_get_error_str (unsigned char const errno) { return (const char *) pgm_read_word (& dcf_errstr[errno]); } #endif // DCF_DEBUG /************************************************************* * Modul-lokale Objekte *************************************************************/ // Die Anzahl der Bits eines DCF-Werts wird als Maske codiert: // Hat der Wert n Bits, ist die Maske 1 << n // wobei die Maske für einen 8-Bit-Wert gleich 0 ist // Die Make für die Stunden (6 Bits) ist zB (1<<6) = 64 = 0x40 #define MAX_MASK(x) ((1 << (x)) & 0xff) // Die Längen der einzelnen DCF-Felder, codiert wie eben beschrieben static const dcf_time_t dcf_max_mask = { // Bit 0-15: Reserviert .reserved1 = MAX_MASK(8), .reserved2 = MAX_MASK(8), // Bit 16-19: Info über Sommer- und Winterzeit, ... .a1 = MAX_MASK(1), // Bit 16 : Ankündigung MEZ <-> MESZ .z1 = MAX_MASK(1), // Bit 17 : MESZ --> Z1=1 und Z2=0 .z2 = MAX_MASK(1), // Bit 18 : MEZ --> Z1=0 und Z2=1 .a2 = MAX_MASK(1), // Bit 19 : Ankündigung Schaltsekunde .start_time = MAX_MASK(1), // Bit 20 : 1 Anfang des codierten Zeit-Signals .minute = MAX_MASK(7), // Bit 21-27: Minuten in BCD .minute_parity = MAX_MASK(1), // Bit 28 : Minuten Parity .hour = MAX_MASK(6), // Bit 29-34: Stunden in BCD .hour_parity = MAX_MASK(1), // Bit 35 : Stunden Parity .day = MAX_MASK(6), // Bit 36-41: Kalendertag (1-31) .day_of_week = MAX_MASK(3), // Bit 42-44: Wochentag (1-7) .month = MAX_MASK(5), // Bit 45-49: Monat .year = MAX_MASK(8), // Bit 50-57: Jahr .date_parity = MAX_MASK(1) // Bit 58 : Datum Parität (Bit 36-57) }; /************************************************************* * Prototypen der Modul-lokalen Funktionen *************************************************************/ #if defined (__GNUC__) && defined (__AVR__) # define INLINE __attribute__ ((always_inline)) # define NOINLINE __attribute__ ((noinline)) #else // !__GNUC__ # define INLINE inline # define NOINLINE #endif // __GNUC__ static unsigned char dcf_get_transition (dcf_t * const, unsigned char); static unsigned char dcf_00 (dcf_t * const); static unsigned char dcf_01 (dcf_t * const); static unsigned char dcf_10 (dcf_t * const, timeinfo_t * const); static unsigned char dcf_11 (dcf_t * const); static INLINE void dcf_reset (dcf_t * const); static INLINE unsigned char decode_BCD (unsigned char); // do not say 'static' to avoid inlining static NOINLINE unsigned char dcf_check_and_convert (timeinfo_t * const); #ifdef DCF_TIME_NEXT_DAY static NOINLINE void next_second (time_t * const); static NOINLINE void next_day (time_t * const); #else static INLINE void next_second (time_t * const); #endif // DCF_TIME_NEXT_DAY /************************************************************* * Implementierung der Modul-lokalen Funktionen *************************************************************/ #ifdef DCF_TIME_NEXT_DAY /// Die Zeit wird um 1 Tag weitergezählt. /// Aktualisiert werden .day, .month und .year. /// .day_of_week wird nicht geändert. /// Die Routine berücksichtigt Schaltjahre, allerding nicht für /// durch 100 teilbare Jahreszahlen. void next_day (time_t * const ptime) { unsigned char day = ptime->day; unsigned char month = ptime->month; unsigned char days_per_month = month; // get # days per month if (month >= 8) // toggle bit 0 by adding 1 days_per_month++; // this gives // 31, 30, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 days_per_month = 30 + (1 & days_per_month); // handle february if (2 == month) { days_per_month = 28; // handle leap year (except 0 mod 100) if (ptime->year % 4 == 0) days_per_month = 29; } // the very work: get the next day if (++day > days_per_month) { // first day of next month day = 1; if (++month > 12) { // first month of next year month = 1; ++ ptime->year; } } ptime->month = month; ptime->day = day; } #endif // DCF_TIME_NEXT_DAY /// Die Zeit wird um 1 Sekunde weitergezählt /// Aktualisiert werden .second, .minute, .hour und .day_of_week. void next_second (time_t * const ptime) { static const unsigned char time_max[] = { 60, // Sekunden / Minute 60, // Minuten / Stunde 24, // Stunden / Tag 7, // Tage / Woche 0 }; unsigned char t; unsigned char *ti = (unsigned char*) ptime; const unsigned char *pmax = time_max; do { const unsigned char max = *pmax++; if (max == 0) { #ifdef DCF_TIME_NEXT_DAY next_day (ptime); #endif // DCF_TIME_NEXT_DAY break; } t = 1 + *ti; if (t == max) t = 0; *ti++ = t; } while (t == 0); } void dcf_reset (dcf_t * const d) { memset (& d->time_bits, 0, 4 + sizeof (dcf_time_t)); } #ifdef DCF_CHECK_PARITY static INLINE unsigned char _parity0 (unsigned char); // Implement parity // p.0 = parity (x), other bits of p are undefined unsigned char _parity0 (unsigned char x) { unsigned char p = 0; do { p += x; x >>= 1; } while (x); return p; } #endif // DCF_CHECK_PARITY // Avoid Multiplication unsigned char decode_BCD (unsigned char x) { unsigned char y = x & 0xf0; x &= 0xf; x += y >> 1; x += y >> 3; return x; } // Es ist günstiger, timeinfo_t* zu übergeben // indirekter Zugriff gibt schlankeren Code unsigned char dcf_check_and_convert (timeinfo_t * const ti) { dcf_t * const d = & ti->dcf; // Eine neue Minute hat angefangen: // DCF-Zeit auf Fehler prüfen und // in time-Struktur kopieren, wenn ok. // unwandeln von BCD nach DEC. #ifdef DCF_CHECK_PARITY unsigned char pty; pty = d->time_bits.start_time; pty &= d->time_bits.z1 ^ d->time_bits.z2; pty = ~pty; pty |= _parity0 (d->time_bits.minute ^ d->time_bits.minute_parity); pty |= _parity0 (d->time_bits.hour ^ d->time_bits.hour_parity); pty |= _parity0 (d->time_bits.date_parity ^ d->time_bits.day ^ d->time_bits.day_of_week ^ d->time_bits.month ^ d->time_bits.year); if (pty & 1) return DCF_BAD_PARITY; #endif // DCF_CHECK_PARITY ti->time.second = 0; ti->time.minute = d->time_bits.minute; ti->time.hour = d->time_bits.hour; ti->time.year = d->time_bits.year; ti->time.day = d->time_bits.day; ti->time.day_of_week = d->time_bits.day_of_week; ti->time.month = d->time_bits.month; ti->time.mesz_p = d->time_bits.z1; unsigned char i; unsigned char * y = (unsigned char *) & ti->time; // Alle Einträge ausser second in ti->time von BCD nach HEX for (i=1; i < sizeof (time_t); i++) { unsigned char * x = y + i; *x = decode_BCD (*x); } return DCF_OK; } unsigned char dcf_00 (dcf_t * const d) { if (d->sekticks == 0) return DCF_ALWAYS_SIGNAL; return DCF_OK; } // Zwischen 2 Pulsen sowie // erkennen der 59. Sekunde unsigned char dcf_11 (dcf_t * const d) { if (d->second59_p) { if (d->sekticks >= DCF_SEK_LENGTH + DCF_BIT_TOLERANCE + DCF_WAIT_SEK59) return DCF_NO_SIGNAL; } else { // Test auf 59. (oder 60.) Sekunde if (d->sekticks == DCF_SEK_LENGTH + DCF_WAIT_SEK59) { d->sekticks = DCF_WAIT_SEK59; d->second59_p = 1; d->second++; } } return DCF_OK; } ///////////////////////////////////////// // Absenkung der Trägers: // Start einer Sekundenmarke unsigned char dcf_10 (dcf_t * const d, timeinfo_t * const ti) { // Eine neue Sekunde und ein // neues Bit fangen an d->sekticks = 0; /////set level///////////////// unsigned char level = d->level; if (d->second59_p) { // Standard: die 59. Sekunde wird in der 59. erzeugt. // denn dcf_time.second wird schon nach dem Speichern erhöht. // ebenfalls möglich: in der 60. Sekunde (bei Schaltsekunde) if (d->second > 59) return DCF_SECOND59_OVERRUN; if (level >= DCF_HAVE_SECONDS && d->second < 59) return DCF_SECOND59_UNDERRUN; if (level >= DCF_HAVE_SIGNAL) level++; if (level > DCF_HAVE_TIME) level = DCF_HAVE_TIME; } if (level == DCF_STARTUP && d->second >= 2) level = DCF_HAVE_SIGNAL; d->level = level; ////////////////// if (d->second59_p) { d->second = 0; if (d->level == DCF_HAVE_TIME) { unsigned char error = dcf_check_and_convert (ti); if (error != DCF_OK) return error; d->new_minute_p = 1; } dcf_reset (d); } else d->second++; return DCF_OK; } ///////////////////////////////////////// // Anhebung des Trägers nach // einer Sekundenmarke unsigned char dcf_01 (dcf_t * const d) { // Abtesten der Länge des empfangenen Bits unsigned char bit = 0; unsigned char bitmask = d->bitmask; if (0 == bitmask) bitmask = 1; if (d->sekticks < DCF_BIT_LENGTH - DCF_BIT_TOLERANCE) return DCF_BIT_TOO_SHORT; if (d->sekticks > DCF_BIT_LENGTH + DCF_BIT_TOLERANCE) { if (d->sekticks < 2*DCF_BIT_LENGTH - DCF_BIT_TOLERANCE) return DCF_BIT_INDIFFERENT; if (d->sekticks > 2*DCF_BIT_LENGTH + DCF_BIT_TOLERANCE) return DCF_BIT_TOO_LONG; bit = bitmask; } // Bit ist ok und kann gespeichert werden if (d->bytepos >= sizeof (dcf_time_t)) return DCF_BYTEPOS_OVERRUN; ((unsigned char*) & d->time_bits)[d->bytepos] |= bit; // Bit speichern unsigned char bitmask_max = ((unsigned char*) & dcf_max_mask)[d->bytepos]; // Maske und Schreibposition für nächstes Bit // berechnen bitmask <<= 1; if (bitmask == bitmask_max) { d->bytepos++; bitmask = 1; } d->bitmask = bitmask; return DCF_OK; } unsigned char dcf_get_transition (dcf_t * const d, unsigned char bit) { unsigned char received_bits = (d->received_bits << 1) & 2; #ifdef DCF_REPARE # define REPARE_N_BITS 5 unsigned char repare_bits = (d->repare_bits << 1) & ((1 << REPARE_N_BITS) -1) & ~1; if (bit) repare_bits |= 1; if (repare_bits == 0x1b) // 0b11011 repare_bits = 0x1f; // 0b11111 if (repare_bits == 0x04) // 0b00100 repare_bits = 0x0; // 0b00000 d->repare_bits = repare_bits; repare_bits &= 1 << (REPARE_N_BITS-1); bit = 0; if (repare_bits) bit = 1; #endif // DCF_REPARE if (bit) received_bits |= 1; d->received_bits = received_bits; return received_bits; } /************************************************************* * Implementierung der globalen Funktionen *************************************************************/ /************************************************************* DCF-Job, wird alle 10ms aufgerufen. *************************************************************/ void dcf_receive_bit (timeinfo_t * const ti, unsigned char const bit) { dcf_t * const d = & ti->dcf; unsigned char signal_transition = dcf_get_transition (d, bit); d->sekticks++; d->new_minute_p = 0; unsigned char error = DCF_FATAL; ///////////////////////////////////////////////////////////// if (0 == signal_transition) error = dcf_00 (d); else if (1 == signal_transition) error = dcf_01 (d); else if (2 == signal_transition) error = dcf_10 (d, ti); else if (3 == signal_transition) error = dcf_11 (d); ///////////////////////////////////////////////////////////// if (error != DCF_OK) { #if defined (DCF_DEBUG) dcf_error = *d; dcf_error.error = error; #endif // DCF_DEBUG d->level = DCF_STARTUP; dcf_reset (d); } d->error = error; } void time_tick_10ms (timeinfo_t * const ti, const unsigned char bit) { dcf_receive_bit (ti, bit); unsigned char new_minute_p = ti->dcf.new_minute_p; ti->ticks_10ms++; ti->new_second_p = 0; ti->new_minute_p = 0; if (!new_minute_p && ti->ticks_10ms < 100) return; if (new_minute_p) { ti->new_minute_p = 2; ti->new_second_p = 1; } else { next_second (& ti->time); if (ti->dcf.level != DCF_HAVE_TIME) { if (ti->time.second == 0) ti->new_minute_p = 1; ti->new_second_p = 1; } else { if (ti->time.second != 0) ti->new_second_p = 1; } } ti->ticks_10ms = 0; }