DCF77-Auswertung

für Funkuhren


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.


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_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.
Diese Informationen können sehr hilfreich sein, wenn man einen adäquaten Fehlerkorrektur-Algorithmus in dcf_get_transition einbauen will, und um zu erkennen, ob das Signal gut genug dazu ist, um überhaupt eine Fehlerkorrektur zu versuchen.


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;
}