DCF77 Empfänger mit Arduino betreiben

DCF77 ist ein Langwellensender in der Nähe von Frankfurt, über den die Physikalisch-Technische Bundesanstalt (PTB) die offizielle Zeit der Bundesrepublik sendet.

Ich hatte in meiner Bastelkiste noch eine DCF77 Empfängerplatine von Conrad liegen und für ein Projekt das ich plane, konnte ich den auch gut gebrauchen.  Empfänger gibt es auch bei PolinReichelt und sicher bei diversen anderen Versendern.

IMG 0266 2

Laut Anschlußbelegung ist der Empfänger wie folgt (von links nach rechts) belegt:

Pin 4: DCF-Ausgang invertiert
Pin 3: DCF-Ausgang
Pin 2: Betriebsspannung 2.5V bis 15V =
Pin 1: GND

Mit einem 10 KΩ Widerstand als Pull up und Pin2 des Arduino zum einlesen, ergibt sich folgender Aufbau.

DCF77 Arduino

In echt sieht das dann so aus.

IMG 0267

Der DCF77-Sender sendet sogenannte „Sekundenmarken“. Sie bilden den Anfang jeder Sekunde und sind entweder 100ms oder 200ms lang. Während dieser 100ms oder 200ms wird die Sendeleistung auf etwa 25% abgesenkt. Danach beginnt eine „Pause“ mit der vollen Sendeleistung (100%). Diese Pause ergänzt die laufende Sekunde: Sie ist also 900ms lang, wenn die Sekundenmarke 100ms lang war oder 800ms lang, wenn die Sekundenmarke 200ms lang war. Die Sekundenmarken werden von 0 bis 58 durchnummeriert. Eine 59. Marke gibt es normalerweise nicht, stattdessen verlängert sich die Pause nach der 58. Sekundenmarke auf 1900ms oder 1800ms (je nach Länge der 58. Marke). Diese Pause dient bei den Decodern zur Synchronisation: Wenn sie endet, beginnt die neue Minute und damit auch das nächste DCF77-Telegramm mit der Sekundenmarke 0.

In dem jeweils einminütigem Zeittelegramm ist die Zeit (Stunde und Minute) der nächstfolgenden Minute sowie das komplette Datum und der jeweilige Wochentag codiert. Die folgende Tabelle zeigt die Bedeutung der 59 Bits, die pro Minute versandt werden.

Bit  Bedeutung                    Wertig-      Bit  Bedeutung                Wertig-
                                  keit                                       keit
------------------------------------------------------------------------------------
 0   Minutenbeginn Low                         30   Stunden Einer                2
 1   Reserve                                   31   Stunden Einer                4
 2   Reserve                                   32   Stunden Einer                8
 3   Reserve                                   33   Stunden Zehner              10
 4   Reserve                                   34   Stunden Zehner              20
 5   Reserve                                   35   Prüfbit 2
 6   Reserve                                   36   Kalendertag Einer            1
 7   Reserve                                   37   Kalendertag Einer            2
 8   Reserve                                   38   Kalendertag Einer            4
 9   Reserve                                   39   Kalendertag Einer            8
10   Reserve                                   40   Kalendertag Zehner          10
11   Reserve                                   41   Kalendertag Zehner          20
12   Reserve                                   42   Wochentag                    1
13   Reserve                                   43   Wochentag                    2
14   Reserve                                   44   Wochentag                    4
15   Reserveantenne                            45   Monat Einer                  1
16   Zeitumstellung Ankündigung                46   Monat Einer                  2
17   Zeitzonenbit 1                            47   Monat Einer                  4
18   Zeitzonenbit 2                            48   Monat Einer                  8
19   Schaltsekunde Ankündigung                 49   Monat Zehner                10
20   Telegrammbeginn High                      50   Jahr Einer                   1
21   Minuten Einer                  1          51   Jahr Einer                   2
22   Minuten Einer                  2          52   Jahr Einer                   4
23   Minuten Einer                  4          53   Jahr Einer                   8
24   Minuten Einer                  8          54   Jahr Zehner                 10
25   Minuten Zehner                10          55   Jahr Zehner                 20
26   Minuten Zehner                20          56   Jahr Zehner                 40
27   Minuten Zehner                40          57   Jahr Zehner                 80
28   Prüfbit 1                                 58   Prüfbit 3
29   Stunden Einer                  1          59   keine Austastung

(Tabelle von http://www.stefan-buchgeher.info)

Ein ganz einfaches Programm, das nur die Pegeländerungen registriert und auf der Konsole ausgibt, sollte also 2 Pegeländerungen pro Sekunde erkennen.

#define DCF77PIN 2

unsigned char signal = 0;
unsigned char buffer;

void setup(void) {
 Serial.begin(9600);
 pinMode(DCF77PIN, INPUT);
}

void loop(void) {
 signal = digitalRead(DCF77PIN);
 if (buffer != signal) {
 Serial.println(signal);
 buffer = signal;
 }
}

Die Ausgabe sieht dann so aus. Jeweils  2 Werte pro Sekunde.

Pegelaenderungen konsole

Hier hatte ich ein ernstes Problem. Zunächst rauschten bei mir die 0 und 1 nur so durch. Also nicht 2 Pegeländerungen pro Sekunde sondern eher so 3-6. Des Rätsels Lösung: Ich hatte den Arduino über den USB Anschluss mit Strom versorgt, zwar über einen Hub mit eigenen Netzteil, aber letztendlich über das PC Netzteil. Offenbar sind in den 5V des PC Netzteils so starke Störungen, die den Empfang unmöglich machen. Nach dem ich das Arduino Netzteil eingesteckt hatte, hab ich wie erwartet die 2 Pegeländerungen pro Sekunde bekommen.

En Arduino Programm, das ohne weitere Bibliotheken auskommt hat  Mathias Dalheimer geschrieben ( http://gonium.net/md/2007/01/06/arduino-dcf77-v02-released/index.html ). Da diese Version bei mir in der Arduino IDE 1.0 einige Fehler beim Compilieren auswirft, hier die von mir angepasste Version, mit aktiven Debuging.

/**
 * Arduino DCF77 decoder v0.2
 * Copyright (C) 2006 Mathias Dalheimer (md@gonium.net)
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, write to the Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 */

/**
 * Where is the DCF receiver connected?
 */
#define DCF77PIN 2
/**
 * Where is the LED connected?
 */
#define BLINKPIN 13
/**
 * Turn debugging on or off
 */
#define DCF_DEBUG 1
/**
 * Number of milliseconds to elapse before we assume a "1",
 * if we receive a falling flank before - its a 0.
 */
#define DCF_split_millis 140
/**
 * There is no signal in second 59 - detect the beginning of
 * a new minute.
 */
#define DCF_sync_millis 1200
/**
 * Definitions for the timer interrupt 2 handler:
 * The Arduino runs at 16 Mhz, we use a prescaler of 64 -> We need to
 * initialize the counter with 6. This way, we have 1000 interrupts per second.
 * We use tick_counter to count the interrupts.
 */
#define INIT_TIMER_COUNT 6
#define RESET_TIMER2 TCNT2 = INIT_TIMER_COUNT
int tick_counter = 0;
int TIMSK;
int TCCR2;
int OCIE2;
/**
 * DCF time format struct
 */
struct DCF77Buffer {
  unsigned long long prefix	:21;
  unsigned long long Min	:7;	// minutes
  unsigned long long P1		:1;	// parity minutes
  unsigned long long Hour	:6;	// hours
  unsigned long long P2		:1;	// parity hours
  unsigned long long Day	:6;	// day
  unsigned long long Weekday	:3;	// day of week
  unsigned long long Month	:5;	// month
  unsigned long long Year	:8;	// year (5 -> 2005)
  unsigned long long P3		:1;	// parity
};
struct {
	unsigned char parity_flag	:1;
	unsigned char parity_min	:1;
	unsigned char parity_hour	:1;
	unsigned char parity_date	:1;
} flags;
/**
 * Clock variables
 */
volatile unsigned char DCFSignalState = 0;
unsigned char previousSignalState;
int previousFlankTime;
int bufferPosition;
unsigned long long dcf_rx_buffer;
/**
 * time vars: the time is stored here!
 */
volatile unsigned char ss;
volatile unsigned char mm;
volatile unsigned char hh;
volatile unsigned char day;
volatile unsigned char mon;
volatile unsigned int year;

/**
 * used in main loop: detect a new second...
 */
unsigned char previousSecond;

/**
 * Initialize the DCF77 routines: initialize the variables,
 * configure the interrupt behaviour.
 */
void DCF77Init() {
  previousSignalState=0;
  previousFlankTime=0;
  bufferPosition=0;
  dcf_rx_buffer=0;
  ss=mm=hh=day=mon=year=0;
#ifdef DCF_DEBUG
  Serial.println("Initializing DCF77 routines");
  Serial.print("Using DCF77 pin #");
  Serial.println(DCF77PIN);
  pinMode(BLINKPIN, OUTPUT);
  pinMode(DCF77PIN, INPUT);
#endif
  pinMode(DCF77PIN, INPUT);
#ifdef DCF_DEBUG
  Serial.println("Initializing timerinterrupt");
#endif
  //Timer2 Settings: Timer Prescaler /64,
  TCCR2 |= (1< 59,
 * a new minute begins -> time to call finalizeBuffer().
 */
void appendSignal(unsigned char signal) {
#ifdef DCF_DEBUG
  Serial.print(", appending value ");
  Serial.print(signal, DEC);
  Serial.print(" at position ");
  Serial.println(bufferPosition);
#endif
  dcf_rx_buffer = dcf_rx_buffer | ((unsigned long long) signal << bufferPosition);
  // Update the parity bits. First: Reset when minute, hour or date starts.
  if (bufferPosition ==  21 || bufferPosition ==  29 || bufferPosition ==  36) {
    flags.parity_flag = 0;
  }
  // save the parity when the corresponding segment ends
  if (bufferPosition ==  28) {flags.parity_min = flags.parity_flag;};
  if (bufferPosition ==  35) {flags.parity_hour = flags.parity_flag;};
  if (bufferPosition ==  58) {flags.parity_date = flags.parity_flag;};
  // When we received a 1, toggle the parity flag
  if (signal == 1) {
    flags.parity_flag = flags.parity_flag ^ 1;
  }
  bufferPosition++;
  if (bufferPosition > 59) {
    finalizeBuffer();
  }
}

/**
 * Evaluates the information stored in the buffer. This is where the DCF77
 * signal is decoded and the internal clock is updated.
 */
void finalizeBuffer(void) {
  if (bufferPosition == 59) {
#ifdef DCF_DEBUG
    Serial.println("Finalizing Buffer");
#endif
    struct DCF77Buffer *rx_buffer;
    rx_buffer = (struct DCF77Buffer *)(unsigned long long)&dcf_rx_buffer;
    if (flags.parity_min == rx_buffer->P1  &&
        flags.parity_hour == rx_buffer->P2  &&
        flags.parity_date == rx_buffer->P3)
    {
#ifdef DCF_DEBUG
      Serial.println("Parity check OK - updating time.");
#endif
      //convert the received bits from BCD
      mm = rx_buffer->Min-((rx_buffer->Min/16)*6);
      hh = rx_buffer->Hour-((rx_buffer->Hour/16)*6);
      day= rx_buffer->Day-((rx_buffer->Day/16)*6);
      mon= rx_buffer->Month-((rx_buffer->Month/16)*6);
      year= 2000 + rx_buffer->Year-((rx_buffer->Year/16)*6);
    }
#ifdef DCF_DEBUG
      else {
        Serial.println("Parity check NOK - running on internal clock.");
    }
#endif
  }
  // reset stuff
  ss = 0;
  bufferPosition = 0;
  dcf_rx_buffer=0;
}

/**
 * Dump the time to the serial line.
 */
void serialDumpTime(void){
  Serial.print("Time: ");
  Serial.print(hh, DEC);
  Serial.print(":");
  Serial.print(mm, DEC);
  Serial.print(":");
  Serial.print(ss, DEC);
  Serial.print(" Date: ");
  Serial.print(day, DEC);
  Serial.print(".");
  Serial.print(mon, DEC);
  Serial.print(".");
  Serial.println(year, DEC);
}

/**
 * Evaluates the signal as it is received. Decides whether we received
 * a "1" or a "0" based on the
 */
void scanSignal(void){
    if (DCFSignalState == 1) {
      int thisFlankTime=millis();
      if (thisFlankTime - previousFlankTime > DCF_sync_millis) {
#ifdef DCF_DEBUG
        serialDumpTime();
        Serial.println("####");
        Serial.println("#### Begin of new Minute!!!");
        Serial.println("####");
#endif
        finalizeBuffer();
      }
      previousFlankTime=thisFlankTime;
#ifdef DCF_DEBUG
      Serial.print(previousFlankTime);
      Serial.print(": DCF77 Signal detected, ");
#endif
    }
    else {
      /* or a falling flank */
      int difference=millis() - previousFlankTime;
#ifdef DCF_DEBUG
      Serial.print("duration: ");
      Serial.print(difference);
#endif
      if (difference < DCF_split_millis) {
        appendSignal(0);
      }
      else {
        appendSignal(1);
      }
    }
}

/**
 * The interrupt routine for counting seconds - increment hh:mm:ss.
 */
ISR(TIMER2_OVF_vect) {
  RESET_TIMER2;
  tick_counter += 1;
  if (tick_counter == 1000) {
    ss++;
    if (ss==60) {
      ss=0;
      mm++;
      if (mm==60) {
        mm=0;
        hh++;
        if (hh==24)
          hh=0;
      }
    }
    tick_counter = 0;
  }
};

/**
 * Interrupthandler for INT0 - called when the signal on Pin 2 changes.
 */
void int0handler() {
  // check the value again - since it takes some time to
  // activate the interrupt routine, we get a clear signal.
  DCFSignalState = digitalRead(DCF77PIN);
}

/**
 * Standard Arduino methods below.
 */

void setup(void) {
  // We need to start serial here again,
  // for Arduino 007 (new serial code)
  Serial.begin(9600);
  DCF77Init();
}

void loop(void) {
  if (ss != previousSecond) {
    serialDumpTime();
    previousSecond = ss;
  }
  if (DCFSignalState != previousSignalState) {
    scanSignal();
    if (DCFSignalState) {
      digitalWrite(BLINKPIN, HIGH);
    } else {
      digitalWrite(BLINKPIN, LOW);
    }
    previousSignalState = DCFSignalState;
  }
    //delay(20);
}

Wenn man sie startet, wird auf der Konsole ausgegeben welcher Bitwert gerade erkannt wurde und, nach einem vollständigen Telegramm, auch die Uhrzeit.

Download der geänderten Version: dcf77clock_mod.ino

Bildschirmfoto 2012 06 14 um 22 53 03

 

*Info: In diesem Beitrag verweisen orangefarbende Links auf Affiliates.