DCF77 Zeitsignal Erkennung mit AVR Microcontroller

DCF77 ist das Signal, welches Funkuhren ansteuert. Es ist ein Langwellensignal auf 77,5 Khz. In einem Zeitraum von einer Minute werden 59 Informationsbits in dem Langwellensignal codiert. Dieser Datensatz von 59 Bits enthält Uhrzeit, Datum und ein paar weitere Informationen. Jedes Bit wird in einem Zeitraum von einer Sekunde gesendet. Innerhalb dieser Sekunde bedeutet ein Low-Pegel von der Dauer von 10ms eine logische „0“ und ein Low-Pegel von der Dauer von 20ms eine logische „1“. Die negative Flanke markiert dabei den Beginn eines Bits und auch den Beginn einer Sekunde. Nach dem 58. Bit bleibt das Signal „1“ bis zur negativen Flanke der Sekunde 0 (und des ersten Bits) der nächsten Minute. Somit bleibt das Signal mindestens eine Sekunde auf „1“, damit kann man den Beginn der Übertragung eines neuen Datensatzes erkennen. Genaueres zu DCF77 kann man hier nachlesen.

Kaum ein AVR-Fan wird um die Implementierung einer Uhr herumkommen, die das DCF77-Signal decodiert.

Der eigentliche Empfänger für das Zeitsignal kann als fertiges Modul beschafft werden, z.B. bei Conrad (ca. 11€). Der Empfänger hat eine kleine Ferritantenne, und gibt das Signal direkt und invertiert ab. An diesen Empfänger habe ich noch eine kleine Transistorstufe angeschlossen und das Signal dann so direkt in den AVR hineingeführt. Wenn man den Empfänger an ein Oszilloskop anschließt, sieht man bei korrekter Synchronisation des Empfängers (Zeitablenkung auf 10ms einstellen) das Zucken der Signale im Sekundentakt. Wenn man da nichts sieht, ist die Antenne nicht sauber ausgerichtet. Obwohl (oder gerade weil?) ich in Frankfurt wohne, muss ich die Antenne nach Süden ausrichten, sonst bekomme ich kein Signal. Der DCF77-Sender steht in Mainflingen ca. 20km südöstlich von Frankfurt am Main.

DCF77 Empfänger. Der Empfänger besitzt 4 Anschlüsse (GND, Vcc, Ausgangssignal und Ausgangssignal invertiert (offener Kollektor)).

Das am invertierten Ausgang vorhandene Signal wird an die Transistorstufe rechts angeschlossen, dort noch mal invertiert so dass am AVR das Signal wie im Text beschrieben anliegt.

Das empfangene Signal ist der Zeitwert für die folgende Minute. Es werden keine expliziten Sekundenwerte geliefert. D.h. wenn das 59. Bit kommt, kann man den bisher gelesenen Zeitwert als korrekte Uhrzeit nehmen und den Sekundenwert der AVR-internen Software-Uhr auf Null setzen. Diese Software-Uhr muss man implementieren, z.B. mit einem Timer und Timer-Interrupt. Die Uhr läuft also auch ohne DCF77 brav vor sich hin.

Die Bedeutung der einzelnen Bits des Signals ist unter http://de.wikipedia.org/wiki/DCF77 ausführlich beschrieben.

Das DCF77-Signal kann man über einen der externen Interrupts (INT0/1) des AVR softwaremäßig aufnehmen. Bei jeder negativen Flanke kann man den Interrupt auslösen lassen und dann einen weiteren Timerwert ab Null hochzählen. Man konfiguriert gleichzeitig die Interruptbedingung so um, dass der nächste Interrupt bei einer positiven Signalflanke ausgelöst wird. Wenn die positive Flanke kommt, kann man dann aus dem Timerwert ablesen, ob es sich um eine Null oder eine 1 handelt. Wenn z.B. 200-mal pro Sekunde der Timerwert hochgezählt wird, bedeutet ein Wert um die 20 dass 10ms verstrichen sind (eine „0“), ein Wert um die 40 dass 20 ms verstrichen sind (eine „1“).

Die einkommende Bitfolge kann man direkt auswerten oder in einem Array ablegen. Aus der Bedeutung der Bits heraus können dann die Werte für Uhrzeit, Datum etc. berechnet werden.

Wenn der Start einer Minute eintritt (negative Flanke für Bit 0), werden die DCF77-Werte in die interne Uhr des AVR kopiert.

Damit hat man die Funktion einer Funkuhr im wesentlichen implementiert.

#define DEBOUNCE 200L /* timer isr is called as many times per second */
 
 volatile uint16_t clock_timer; // clock timer value
 volatile uint16_t bit_timer; // bit timer value
 volatile uint16_t bit_ticks_0; // ticks with value "0"
 volatile uint16_t bit_sequence; // ticks with value "0" or "1", i.e. whole bit sequence 
 volatile uint8_t irq_falling_edge; // 1= irq o falling, 1= irq on raising edge
 
 uint8_t bits[60]; // array for each bit received
 
 // define for "undefined time value"
 #define UNDEF_TIME 99
 
 // vars h,m,s for the timer driven internal clock
 volatile uint8_t s=0, h=0,m=0;
 
 // vars for dcf driven clock and date values
 volatile uint8_t dcf_day=UNDEF_TIME, dcf_y=UNDEF_TIME, dcf_mo=UNDEF_TIME, dcf_d=UNDEF_TIME, 
 dcf_h=UNDEF_TIME, dcf_m=UNDEF_TIME;
 
 // Interrupt Service Routine 
 //  This routine is called when the Timer Value TCNT1 reaches the Output Compare Register Value OCR1A
 // 
 ISR(TIMER1_COMPA_vect) {
     bit_timer++;
 #if SYSCLK % DEBOUNCE
     OCR1A = SYSCLK / DEBOUNCE - 1;
 #endif
     if (--clock_timer==0) {
         clock_timer=DEBOUNCE;
         s++;
 #if SYSCLK % DEBOUNCE
         OCR1A = SYSCLK / DEBOUNCE + SYSCLK % DEBOUNCE - 1;
 #endif
     }
 }
 
 // Interrupt Service Routine 
 //  This routine is called when IRQ0 occurs
 // 
 ISR(INT0_vect) {
     if (irq_falling_edge==1) {
         bit_sequence=bit_timer; // save value
         bit_timer=0; // reset bit_timer
         irq_falling_edge=0; // wait for next raise
         MCUCR = (1<<ISC00)|(1<<ISC01); // raise int0 on rising edge
     } else {
         bit_ticks_0=bit_timer; // save value
         irq_falling_edge=1; // wait for next fall
         MCUCR = (1<<ISC00); // raise int0 on falling edge
     }
 }

Im Hauptprogramm macht man dann folgendes:

int main(void) {
     int bit_number=0;
     volatile uint8_t bit_value; // the resulting bit value, 0 or 1
     char *str; // some  string for text
 
     ioinit();
     fdevopen(uart_putchar, NULL );
     
     printf("nnDCF Clocknn");
 
     // Initialisierung:
     // (1<<CS10) : Timer1 Vorteiler = 001 = 1. Der Zähler wird also mit f=8Mhz hochgezählt
 
     // OCR1A=XTAL/DEBOUNCE-1 -> Bei Erreichen dieses Wertes (39999) wird die ISR besucht
     // die ISR wird also alle 1/200s aufgerufen. Wenn man dort also bis 200 hochzählt, ist genau
     // 1 Sekunde um! 200 ist der Wert der Variable clock_timer und wird über das define DEBOUNCE festgelegt
 
     TCCR1B = (1<<CS10) ^ (1<<WGM12);    // Prescaler of 1 | CTC mode
     OCR1A  = SYSCLK/DEBOUNCE-1;        // Output compare register value 
     TCNT1 = 0; // Start value for timer register
     s=0; // Initialize second value (s) to zero
     clock_timer = DEBOUNCE; 
     TIMSK |= (1<<OCIE1A);        // activate timer interrupts which starts timer run
 
     // INT0
     bit_ticks_0=0;
     bit_sequence=0;
     irq_falling_edge=1; // start with falling edge detection
     MCUCR = (1<<ISC00); // raise int0 on falling edge
     GIMSK |= (1<<INT0); // enable external int0
 
     /*
      * now enable interrupt, since UART and TIMER library is interrupt controlled
      */
     sei();
 
 
     for(;;) {
         
         // timer driven clock: the second value (s) is increased by interrupt.
         // set up m and h according to s changes
         if (s>=60) {
             s=0;
             m++;
             if (m>=60) {
                 m=0;
                 h++;
                 if (h>=24) {
                     h=0;
                 }
             }
         }
         //printf( "%02d:%02d:%02dr", h, m, s);
 
         // check what bit value was received (0 or 1)
         if (bit_ticks_0>0) {
             if(bit_ticks_0>25)
                 bit_value=1;
             else
                 bit_value=0; 
             if (bit_sequence>250) {
                 // we are at falling edge after second 59
                 bit_number=0;
                 str="- Start of Minute";
                 // now copy DCF clock values to timer driven clock
                 set_clock();
             } else
                 str="";
             
             // decode bits as far as possible
             decode( bit_number, bit_value, str );
             // clear timers for next bit
             bit_sequence=0;
             bit_ticks_0=0;
             // increase bit counter
             bit_number++;
         }
     }
 }

Die Funktion decode() berechnet die DCF77 Werte aus den Bits, die Funktion set_clock() setzt die internen Uhrenwerte aus den DCF77-Werten:

//
 // incrementally decode bits collected so far.
 // for meaning of bits, see DCF description
 //
 void decode( uint8_t i, uint8_t bit_value, char *string ) {
     printf("bits[%02d]=%d (%d of %d ticks low) %sn", i, bit_value, bit_ticks_0, bit_sequence, string );
 
     // save bit value
     bits[i] = bit_value;
 
     if (i==28) {
         // minute value 0..59 complete
         dcf_m = bits[21]+bits[22]*2+bits[23]*4+bits[24]*8+bits[25]*10+bits[26]*20+bits[27]*40;
     }
     if (i==35) {
         // hour value 0..24 complete
         dcf_h = bits[29]+bits[30]*2+bits[31]*4+bits[32]*8+bits[33]*10+bits[34]*20;
     }
     if (i==41) {
         // date value 1..31 complete
         dcf_d = bits[36]+bits[37]*2+bits[38]*4+bits[39]*8+bits[40]*10+bits[41]*20;
     }
     if (i==44) {
         // weekday 0..6 complete
         dcf_day = bits[42]+bits[43]*2+bits[44]*4;
     }
     if (i==49) {
         // month 1..12 complete
         dcf_mo = bits[45]+bits[46]*2+bits[47]*4+bits[48]*8+bits[49]*10;
     }
     if (i==57) {
         // year 00..99 complete
         dcf_y = bits[50]+bits[51]*2+bits[52]*4+bits[53]*8+bits[54]*10+bits[55]*20+bits[56]*40;
     }
 
     printf( "DCF: %d, %02d.%02d.20%02d %02d:%02d - AVR %02d:%02d:%02dr", 
             dcf_day, dcf_d, dcf_mo, dcf_y, dcf_h, dcf_m,
             h, m, s );
 }
 
 //
 // copy dcf values to internal clock values
 //
 void set_clock( void ) {
     h = dcf_h;
     m = dcf_m;
     s = 0;
 }