DCF77 – Funkuhr

Über den Beitrag

Nachdem ich in meinem letzten Beitrag über das Echtzeituhrmodul DS3231 berichtet habe, passt nun ein Folgebeitrag über DCF77 Funkuhren thematisch sehr gut.

Auf die folgenden Punkte gehe ich ein:

  • Was ist der DCF77 Zeitzeichensender?
  • Decodierung des DCF77 Signals.
  • DCF77 Empfängermodule
  • Erfassung und Auswertung des DCF77 Signals mit dem Arduino.
  • Den DCF77 Empfänger komfortabel mit der RTCLib nutzen.

Der DCF77 Zeitzeichensender

Der DCF77 ist ein sogenannter Zeitzeichensender, welcher das aktuelle Datum und die Uhrzeit in digitaler Form auf der Langwellenfrequenz 77.5 kHz verbreitet. Die Uhrzeit ist dabei die MEZ (Mitteleuropäische Zeit) beziehungsweise die MESZ (Mitteleuropäische Sommerzeit). 

Der DCF77 Sender steht in Mainflingen in Hessen. Hier könnt ihr ihn auf Google Maps bewundern. Er hat eine Reichweite von ca. 2000 km und steuert einen großen Teil der Funkuhren in Westeuropa. Andere Teile der Welt haben Zeitzeichensender, die auf anderen Frequenzen senden und auch anders kodiert sind. 

DCF77 steht für:

  • D: Deutschland
  • C: Kürzel für Langwelle
  • F: für die Nähe zu Frankfurt
  • 77: Übertragungsfrequenz

Mehr Informationen über den DCF77 Sender und seine Geschichte findet ihr hier.

Wie das DCF77 Signal aufgebaut ist

Die Informationen werden übertragen, indem das Funksignal einmal pro Sekunde für entweder 100 oder 200 Millisekunden auf 25 Prozent abgesenkt wird. Eine Absenkung für 100 Millisekunden bedeutet eine „0“, 200 Millisekunden bedeutet eine „1“. Eine Sequenz beträgt eine Minute. Damit würden 60 Bit übertragen, allerdings wird das letzte Bit weggelassen, um die Sequenzen eindeutig voneinander zu trennen.

Die meisten DCF77 Empfängermodule sind so aufgebaut, dass sie während der Absenkung ein HIGH-Signal liefern, ansonsten ist es LOW. Damit erhaltet ihr Sequenzen, die folgendermaßen aussehen:

Schema des DCF77 Signals am Ausgangspin des Empfängermoduls
Schema des DCF77 Signals am Ausgangspin des Empfängermoduls

Habt ihr ein Modul, dass andersherum arbeitet, müsst ihr umdenken und die Beispielsketche entsprechend ändern.

Decodierung des DCF77 Signals

Bestimmte Bits beziehungsweise bestimmte Abschnitte sind für einen bestimmten Teil der zu übermittelnden Informationen reserviert:

Bedeutung der Bits einer DCF77 Sequenz
Bedeutung der Bits einer DCF77 Sequenz
  • Um die Wetterinformationen lesen zu können, müsstet ihr eine Lizenz erwerben.
  • Die eigentliche Übertragung der Uhrzeit und des Datums beginnt nach Bit 20. Alles davor betrachte ich nicht.
  • Bei zweistelligen Zahlen werden die „Einer“ und die „Zehner“ getrennt übertragen, jede Ziffer für sich aber im Zweiersystem – eine etwas eigentümliche Mischung von Binär- und Dezimalsystem.
  • Die Zeitangaben beziehen sich auf den Zeitpunkt, wenn das nächste Startbit gesendet wird.

Mit den Paritätsbits lassen sich die übertragenen Daten auf Richtigkeit prüfen. Die Überprüfung erfolgt auf gerade Parität. Am besten erkläre ich das an einem Beispiel. Die Minuten werden in den Bits 21 bis 27 übertragen, also 7 Nullen oder Einsen. Alle Einsen werden zusammengezählt. Ist das Ergebnis eine ungerade Zahl, dann ist das Paritätsbit eine 1. Ist das Ergebnis hingegen eine gerade Zahl, ist das Paritätsbit 0. Genauso wird mit den Stunden (Bits 29-34) und dem Datum (Bits 40-57) verfahren. Anders ausgedrückt: Die Parität des Paritätsbits entspricht der Parität der zu prüfenden Bitfolge.

Sind alle Paritäten OK und habt ihr 59 Bits in einer Sequenz erhalten, könnt ihr euch recht sicher sein, dass die Datenübertragung korrekt war.

DCF77 Empfängermodule

Ein Modul mit Antenne erhaltet ihr für 5 bis 15 Euro in Online-Shops. Für das hier abgebildete Modell habe ich 11 Euro bezahlt und bin mit seiner Empfangsqualität ganz zufrieden:

Ein DCF77 Empfängermodul mit Antenne
Ein DCF77 Empfängermodul mit Antenne

Dieses Modell hier kostete nur die Hälfte und ist deutlich kleiner:

Ein alternatives DCF77 Modul
Ein alternatives DCF77 Modul
Mein Favorit: Das DCF77 Modul von Canaduino
Mein Favorit: Das DCF77 Modul von Canaduino

Es gab dazu keine Dokumentation (nicht mal bezüglich der Versorgungsspannung!), außerdem habe damit unter gleichen Bedingungen deutlich mehr Fehlmessungen als mit dem anderen Modul erhalten. Es gibt hier also deutliche Qualitätsunterschiede.

Fehlmessungen sind aber nicht nur eine Frage der Modulqualität, sondern auch die Ausrichtung der Antenne, das Wetter, der Standort (Geologie) und weitere Faktoren können den Empfang beeinflussen. Ein paar Fehlmessungen sind aber auch nicht schlimm, solange ihr sie identifiziert und euer Code nicht darauf angewiesen ist, jede Minute einen validen Datensatz zu erhalten. Wenn ihr zu viele Fehlmessungen feststellt, dann versucht erst einmal die Antenne anders zu positionieren. Was bei einigen Modulen auch helfen kann, ist Batteriebetrieb.

Wenn ihr euch ein Modul besorgen wollt, dann schaut am besten erst mal, ob es ein Datenblatt oder zumindest grundlegende technische Daten dazu gibt. Einige Vertreter können anscheinend nur bis zu 3.3 V, andere bis 5 V oder noch mehr vertragen. Für den Stromverbrauch habe ich übereinstimmend Angaben von < 100 Mikroampere gefunden.

Das Canaduino Board

Das nach meiner Erfahrung mit weitem Abstand beste Modul ist das oben abgebildete Modell von Canaduino. Dafür habe ich 20 Euro einschließlich der Versandgebühren aus Kanada bezahlt. Ihr könnt es mit 3 -15 Volt betreiben, es besitzt eine automatische Verstärkung und hat einen regulären und einen invertierten Ausgang. Die Qualität der Signale war hervorragend, auch an Orten, wo andere Module Schwierigkeiten hatten. Ein kurzes Manual zu dem Modul gibt es hier.

Beachtet insbesondere die Ausrichtung der Antenne relativ zum Modul. Für eine schnelle Erfassung des Signals verbindet PDN mit einem Pin eures Mikrocontroller-Boards und schaltet diesen in setup() auf Output / LOW.

Das DCF77 Signal empfangen und auswerten

Schaltung

Wie schon erwähnt, variieren die Module in ihrer Ausführung. Für das oben abgebildete Modul habe ich die folgende Schaltung verwendet: 

Schaltung für die DCF77 Beispielsketche
Schaltung für die DCF77 Beispielsketche
  • Die LED ist an sich nicht essenziell, ich nutze sie für einige Beispielsketche.
  • Mit dem Enable Pin lässt sich das Modul an- und ausschalten (das hat nicht jedes Modul).
  • Der Datenausgang muss für die meisten meiner Sketche an einem Interrupt Pin hängen.

Ein einfacher Funktionstest

Wenn ihr einfach prüfen wollt, ob euer Modul prinzipiell Daten empfängt, dann könnt ihr diesen Sketch verwenden:

int interruptPin=2;
int ledPin=7;

void setup(){
  pinMode(ledPin, OUTPUT);
  pinMode(interruptPin, INPUT);
  attachInterrupt(digitalPinToInterrupt(interruptPin), DCF77_ISR, CHANGE);
}

void loop(){
  
}

void DCF77_ISR(){
  if(digitalRead(interruptPin)){
    digitalWrite(ledPin, HIGH);
  }
  else{
    digitalWrite(ledPin, LOW);
  }
}

 

Alternativ ginge es natürlich auch ohne Interrupt, als zum Beispiel so:

int dataPin = 2;
int ledPin = 7;

void setup() {
  pinMode(dataPin, INPUT);
  pinMode(ledPin, OUTPUT);
}

void loop() {
  if(digitalRead(dataPin)){
    digitalWrite(ledPin, HIGH);
  }
  else{
    digitalWrite(ledPin, LOW);
  }
}

 

Die Interrupt Methode hat den Vorteil, dass die Hauptschleife leer ist, ihr also irgendetwas anderes nebenbei machen könnt. Ich werde deswegen auf der Interrupt Methode aufbauen.

Wenn der Sketch läuft, solltet ihr sehen, dass die LED im Sekundentakt blinkt. Wenn ihr genau hinschaut, werdet ihr erkennen, dass die LED mal kürzer und mal länger leuchtet. Einmal pro Minute beträgt die Pause fast zwei Sekunden. Empfangsstörungen machen sich meistens durch schnelles und unregelmäßiges Blinken bemerkbar.  

Sequenzen erfassen

Mit dem nächsten Sketch messen wir die Signallängen und erfassen ganze Sequenzen. Ein paar Anmerkungen zur Signallänge:

  • Nach dem Auslösen des Interrupts wird geprüft, ob der Interrupt Pin HIGH oder LOW ist. Das verrät, ob es bei der abgeschlossenen Phase um das 100/200 Millisekunden-Signal oder die Pause handelt.
  • Die Signallänge wird über die millis() Funktion ermittelt.
  • Alle HIGH-Signale, die kürzer als 150 Millisekunden sind, werden als „0“ interpretiert, die längeren als „1“.
  • Eine LOW-Phase von größer 1500 Millisekunden wird als Minutenmarke interpretiert.

Zur Sequenz:

  • Die 59 Bits einer Sequenz passen in eine acht Byte große unsigned long long Variable, die ich currentBuf genannt habe.
  • Der Zähler für die Position in currentBuf ist bufCounter.
  • currentBuf |= ((unsigned long long)1<<bufCounter); fügt eine „1“ in die Sequenz ein. Ohne die Umwandlung der „1“ in unsigned long long funktioniert es nicht. Diese Erkenntnis hat mich viel Zeit gekostet.
  • Die Serial.print() Funktion ist nicht mit unsigned long long Variablen verträglich. Über Bitoperationen teile ich die currentBuf Variable in zwei unsigned long Stücke um sie ausgeben zu können.
  • Die Ausgabe der Sequenz erfolgt, wenn die Minutenmarke erreicht ist.

Die  Serial.print() Aufrufe nehmen bei 9600 BAUD in Summe eine Zeit im Millisekundenbereich in Anspruch (pro Sekunde). Ich habe deswegen 115200 BAUD gewählt.

Eigentlich predige ich immer Interrupt Service Routinen (hier: DCF77_ISR()) möglichst kurzzuhalten, da das sonst Problemen führen kann. Das gilt insbesondere, wenn ihr weitere Interrupts verwenden wollt. Hier verstoße ich recht drastisch gegen meine Regeln (siehe auch hier). Es dient aber dem Zweck die Uhrzeiterfassung im Hintergrund vorzunehmen. Sonst müsstet Ihr entweder minutenlang warten bis der Prozess abgeschlossen ist oder in der Hauptschleife immer wieder den Zustand des Daten Pins abfragen. Das wiederum kann aber leicht mit dem Rest des Codes kollidieren, insbesondere wenn weitere delays ins Spiel kommen.

int interruptPin=2;
volatile unsigned long lastInt = 0;
volatile unsigned long long currentBuf = 0;
volatile byte bufCounter;

void setup(){
  Serial.begin(115200);
  Serial.println(" HIGH  /  LOW");
  pinMode(interruptPin, INPUT);
  attachInterrupt(digitalPinToInterrupt(interruptPin), DCF77_ISR, CHANGE);
}

void loop(){
  
}

void DCF77_ISR(){
  unsigned int dur = 0;
  dur = millis() - lastInt; 
  
  if(digitalRead(interruptPin)){
    Serial.println(dur);
    if(dur>1500){
      unsigned long highBuf = (currentBuf>>32) & 0x7FFFFFF;
      unsigned long lowBuf = (currentBuf & 0xFFFFFFFF);
      bufCounter = 0;
      Serial.print("Signal, upper 4 bytes: "); 
      Serial.println(highBuf, BIN);
      Serial.print("Signal, lower 4 bytes: "); 
      Serial.println(lowBuf, BIN);
    }
  }
  else{
    Serial.print(bufCounter);
    Serial.print(". ");
    Serial.print(dur);
    Serial.print("  /  ");
    if(dur>150){
      currentBuf |= ((unsigned long long)1<<bufCounter);
    }
    bufCounter++;
  }
  lastInt = millis();
}

 

Ausgabe von dcf77_get_sequence.ino

Hier seht ihr einen Ausschnitt der Ausgabe:

DCF77 Beispielsketch: Ausgabe von dcf77_get_sequence.ino
Ausgabe von dcf77_get_sequence.ino

Die HIGH und LOW Phasen differieren ein wenig von den Ideallängen, aber Nullen und Einsen sind deutlich unterscheidbar.

Die erste aufgenommene Sequenz wird nur in 2 von 60 Fällen vollständig sein, nämlich wenn ihr in den letzten zwei Sekunden einer laufenden Sequenz einsteigt.

Die erfassten Sequenzen auswerten

Nun werten wir die empfangenen Sequenzen aus. Dazu übergibt die ISR die Sequenz currentBuf der Funktion evaluateSequence(). Diese zerhackt die Sequenz zunächst in die relevanten Abschnitte (Minute, Stunde, Wochentag, usw.).

Für die Paritätsprüfung nehme ich die Funktion parity_even_bit(). Um sie verwenden zu können, müsst ihr utils/parity.h einbinden. Diese Datei gehört zur Arduino bzw. AVR Grundausstattung, ihr müsst sie also nicht installieren. parity_even_bit() erwartet einen byte Wert als Argument. Die Funktion liefert „0“ (false) für die Parität „0“ und „1“ für die Parität „1“. Der Ausschnitt für das Datum ist größer als ein Byte, also ermittele ich die Paritäten des Kalendertages, Wochentages, Monats und Jahres einzeln. Ist die Summe der Paritäten eine gerade Zahl, dann ist die Gesamtparität „0“. Ist sie ungerade, dann ist die Parität „1“. Wenn ihr kein AVR Board (Arduino UNO, Nano, Mega, etc.) verwendet, dann bindet parity.h nicht mit ein und entkommentiert die Ersatzfunktion am Ende dieses und der folgenden Sketche.

rawByteToInt() zerlegt den Rohwert für Minute, Stunde, Tag, usw. in den Einer- und Zehnerteil und liefert die Summe als Integer zurück. Diese Werte werden dann ausgegeben.

Die blinkende LED in der Hauptschleife soll nur zeigen, dass ihr dort (fast) unbeeinflusst irgendwelche anderen Dinge tun könnt.

#include <util/parity.h> //comment out if you don't use an AVR MCU
int interruptPin=2;
int ledPin=7;
volatile unsigned long lastInt = 0;
volatile unsigned long long currentBuf = 0;
volatile byte bufCounter;

void setup(){
  Serial.begin(115200);
  pinMode(ledPin, OUTPUT);
  pinMode(interruptPin, INPUT);
  attachInterrupt(digitalPinToInterrupt(interruptPin), DCF77_ISR, CHANGE);
}

void loop(){
  digitalWrite(ledPin, HIGH); // to illustrate the loop can do something else
  delay(750);
  digitalWrite(ledPin, LOW);
  delay(750);
  
}

void DCF77_ISR(){
  unsigned int dur = 0;
  dur = millis() - lastInt; 
  
  if(digitalRead(interruptPin)){
    Serial.println(dur);
    if(dur>1500){
      unsigned long highBuf = (currentBuf>>32) & 0x7FFFFFF;
      unsigned long lowBuf = (currentBuf & 0xFFFFFFFF);
      bufCounter = 0;
      Serial.print("Signal, upper 4 bytes: "); 
      Serial.println(highBuf, BIN);
      Serial.print("Signal, lower 4 bytes: "); 
      Serial.println(lowBuf, BIN);
      evaluateSequence();
      currentBuf = 0;
    }
  }
  else{
    Serial.print(bufCounter);
    Serial.print(". ");
    Serial.print(dur);
    Serial.print("  /  ");
    if(dur>150){
      currentBuf |= ((unsigned long long)1<<bufCounter);
    }
    bufCounter++;
  }
  lastInt = millis();
}

void evaluateSequence(){
  byte dcf77Year = (currentBuf>>50) & 0xFF;    // year = bit 50-57
  byte dcf77Month = (currentBuf>>45) & 0x1F;       // month = bit 45-49
  byte dcf77DayOfWeek = (currentBuf>>42) & 0x07;   // day of the week = bit 42-44
  byte dcf77DayOfMonth = (currentBuf>>36) & 0x3F;  // day of the month = bit 36-41
  byte dcf77Hour = (currentBuf>>29) & 0x3F;       // hour = bit 29-34
  byte dcf77Minute = (currentBuf>>21) & 0x7F;     // minute = 21-27 
  bool parityBitMinute = (currentBuf>>28) & 1;
  bool parityBitHour = (currentBuf>>35) & 1;
  bool parityBitDate = (currentBuf>>58) & 1;

  if((parity_even_bit(dcf77Minute)) != parityBitMinute){
    Serial.println("Minute parity not OK"); 
  }
  if((parity_even_bit(dcf77Hour)) != parityBitHour){
    Serial.println("Hour parity not OK");
  }
  if(((parity_even_bit(dcf77DayOfMonth) + parity_even_bit(dcf77DayOfWeek) 
  + parity_even_bit(dcf77Month) + parity_even_bit(dcf77Year))%2) != parityBitDate)
  {
    Serial.println("Date parity not OK");
  }
  
  Serial.println("Current date and Time:");
  Serial.print("Year: "); Serial.println(rawByteToInt(dcf77Year));
  Serial.print("Month: "); Serial.println(rawByteToInt(dcf77Month));
  Serial.print("Day of Month: "); Serial.println(rawByteToInt(dcf77DayOfMonth));
  Serial.print("Day of the Week: "); Serial.println(rawByteToInt(dcf77DayOfWeek));
  Serial.print("Hours: "); Serial.println(rawByteToInt(dcf77Hour));
  Serial.print("Minutes: "); Serial.println(rawByteToInt(dcf77Minute));
}

unsigned int rawByteToInt(byte raw){
  return ((raw>>4)*10 + (raw &0x0F));
}

//uncomment the following lines if you don't use an AVR MCU
//bool parity_even_bit(byte val){
//  val ^= val >> 4;
//  val ^= val >> 2;
//  val ^= val >> 1;
//  val &= 0x01;
//  return val;
//}

 

Ausgabe von dcf77_sequence_evaluation.ino

DCF77 Beispielsketch: Ausgabe von dcf77_sequence_evaluation.ino
Ausgabe von dcf77_sequence_evaluation.ino

Es ist also Sonntag, der 14. Februar, 2021, 12:59. Es funktioniert, allerdings lässt die Formatierung noch zu wünschen übrig.

Wochen- und Monatsnamen ließen sich zum Beispiel so einfügen:

char dayName[7][12] = {"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"};

Serial.print(dayName[dcf77DayOfWeek]);

Ich gehe aber gleich einen anderen, deutlich bequemeren  Weg.

Den DCF77 mit der RTCLib nutzen

Die RTCLib hatte ich im letzten Beitrag als Bibliothek für das DS3231 Echtzeituhr Modul vorgestellt. Über diese Aufgabe hinaus ermöglicht die RTCLib aber auch (unter anderem):

  • vielfältige Formatierungsoptionen für Datum und Uhrzeit
  • Rechnen mit Datum/Uhrzeit und Zeitspannen
  • Softwaresimulation einer Echtzeituhr (SoftRTC)

Das hatte ich alles in meinem letzten Beitrag vorgestellt. Wenn ihr den nächsten Sketch nicht verstehen solltet, dann geht am besten noch mal dorthin zurück.

Ihr könnt die RTCLib über die Arduino IDE oder hier direkt von GitHub herunterladen.

Wir nutzen SoftRTC und stellen diese „Softwareuhr“ regelmäßig mit dem DCF77 Empfänger. In der Zeit dazwischen läuft die Uhr basierend auf millis().

Genauso gut könnt ihr den DCF77 Empfänger dazu nehmen, ein DS3231 Modul regelmäßig zu stellen.

DCF77 und SoftRTC – der Sketch

Ich habe den im letzten Sketch verwendeten Code von allen verzichtbaren Serial.print() Ausgaben befreit. Dann habe ich ihn mit dem Beispielsketch softrtc.ino der RTCLib vereinigt und etwas angepasst.

Die Uhr wird zunächst auf den 1.1.2000, 00:00 Uhr gestellt. Man könnte auch die Systemzeit des Computers übernehmen, aber ich wollte ein offensichtlich falsches Startdatum, damit deutlicher wird, wenn die Uhr durch das DCF77 Modul gestellt wird.

Die Uhr wird nur gestellt, wenn ein gültiger Datensatz vorliegt, d.h. wenn 59 Bits übermittelt wurden und die drei Paritätsbits stimmen.

#include <util/parity.h> //comment out if you don't use an AVR MCU
#include "RTClib.h"
int interruptPin=2;
volatile unsigned long lastInt = 0;
volatile unsigned long long currentBuf = 0;
volatile byte bufCounter;

RTC_Millis rtc;

void setup(){
  rtc.adjust(DateTime(2000, 1, 1, 0, 0, 0));
  Serial.begin(115200);
  pinMode(interruptPin, INPUT);
  attachInterrupt(digitalPinToInterrupt(interruptPin), DCF77_ISR, CHANGE);
}

void loop(){
  static DateTime now = rtc.now();
  now = rtc.now();
  char buf1[] = "Today is DDD, MMM DD YYYY";
  Serial.println(now.toString(buf1));
  char buf2[] = "Current time is hh:mm:ss";
  Serial.println(now.toString(buf2));
  Serial.println();
  
  delay(3000);
}

void DCF77_ISR(){
  unsigned int dur = 0;
  dur = millis() - lastInt; 
  
  if(digitalRead(interruptPin)){
    if(dur>1500){
      if(bufCounter==59){
        evaluateSequence();
      }
      bufCounter = 0;
      currentBuf = 0;
    }
  }
  else{
    if(dur>150){
      currentBuf |= ((unsigned long long)1<<bufCounter);
    }
    bufCounter++;
  }
  lastInt = millis();
}

void evaluateSequence(){
  byte dcf77Year = (currentBuf>>50) & 0xFF;    // year = bit 50-57
  byte dcf77Month = (currentBuf>>45) & 0x1F;       // month = bit 45-49
  byte dcf77DayOfWeek = (currentBuf>>42) & 0x07;   // day of the week = bit 42-44
  byte dcf77DayOfMonth = (currentBuf>>36) & 0x3F;  // day of the month = bit 36-41
  byte dcf77Hour = (currentBuf>>29) & 0x3F;       // hour = bit 29-34
  byte dcf77Minute = (currentBuf>>21) & 0x7F;     // minute = 21-27 
  bool parityBitMinute = (currentBuf>>28) & 1;
  bool parityBitHour = (currentBuf>>35) & 1;
  bool parityBitDate = (currentBuf>>58) & 1;

  if((parity_even_bit(dcf77Minute)) == parityBitMinute){
    if((parity_even_bit(dcf77Hour)) == parityBitHour){
      if(((parity_even_bit(dcf77DayOfMonth) + parity_even_bit(dcf77DayOfWeek) 
           + parity_even_bit(dcf77Month) + parity_even_bit(dcf77Year))%2) == parityBitDate){
        rtc.adjust(DateTime(rawByteToInt(dcf77Year) + 2000, rawByteToInt(dcf77Month), 
            rawByteToInt(dcf77DayOfMonth), rawByteToInt(dcf77Hour), rawByteToInt(dcf77Minute), 0));
       }
    }
  }
}

unsigned int rawByteToInt(byte raw){
  return ((raw>>4)*10 + (raw & 0x0F));
}

//uncomment the following lines if you don't use an AVR MCU
//bool parity_even_bit(byte val){
//  val ^= val >> 4;
//  val ^= val >> 2;
//  val ^= val >> 1;
//  val &= 0x01;
//  return val;
//}

 

Ausgabe von dcf77_softRTC.ino

Und sieht dann das Ergebnis aus:

Beispielsketch DCF77: Ausgabe von dcf77_softRTC.ino
Ausgabe von dcf77_softRTC.ino

Idealerweise wird die Softwareuhr – so wie hier – nach ein bis zwei Minuten auf die DCF77 Zeit gestellt. Bei schlechtem Empfang kann es unter Umständen länger dauern.

Eine Version des Sketches mit schlankerer ISR findet ihr im Anhang 1.

Wenn ihr keine Interrupts wollt….

Wenn euch das Arbeiten mit den Interrupts stört, habe ich hier eine Version ohne Interrupts. Das Stellen der Uhrzeit ist zeitgesteuert. Da ich keine Lust hatte, lange zu warten, wird die Uhr alle zwei Minuten gestellt. Das macht natürlich praktisch keinen Sinn. Ihr könnt das aber unkompliziert auf z.B. alle paar Stunden abändern.

Der Nachteil an dieser Methode ist, dass ihr während der Einstellprozedur nichts anderes machen könnt.

Der Sketch ist noch recht kommunikativ. Ihr könnt ihn noch deutlich kürzen, wenn ihr etwas damit machen wollt.

#include "RTClib.h"  
#include <util/parity.h> //comment out if you don't use an AVR MCU
#define DCF77_PIN_HIGH (PIND & (1<<PD7))
int dataPin = 7;

RTC_Millis rtc;

void setup () {
  bool validTime = false;
  Serial.begin(115200);

#ifndef ESP8266
  while (!Serial); // wait for serial port to connect. Needed for native USB
#endif

  pinMode(dataPin, INPUT); 
  
  DateTime currentTime = DateTime(2000, 1, 1, 0, 0, 0); // Initial Date
  
  while(!validTime){
    if(getDcf77Time(currentTime)){
      Serial.println("Date and Time updated!");
      validTime = true;
    }
    else{
      Serial.println("Sorry, something went wrong!");
    }
  }
  rtc.adjust(currentTime);
}

bool getDcf77Time(DateTime &dcf77Time){
  unsigned long long dcf77Sequence = 0;
  bool successfulUpdate = true;
  if(receiveSequence(dcf77Sequence)){
    Serial.println("Sequence OK - received 59 bits");
    printSequence(dcf77Sequence);
  }
  else{
    Serial.println("Sequence NOT OK - wrong nuber of bits");
    successfulUpdate = false;
  }
    
  if(evaluateSequence(dcf77Sequence, dcf77Time)){
    Serial.println("Valid Sequence!");   
  }
  else{
    Serial.println("Invalid Sequence!");
    successfulUpdate = false;
  }
  return successfulUpdate;
}

bool evaluateSequence(unsigned long long &buf, DateTime &dcf77Time){
  bool parityOK = true;
  byte dcf77Year = (buf>>50) & 0xFF;    // year = bit 50-57
  byte dcf77Month = (buf>>45) & 0x1F;       // month = bit 45-49
  byte dcf77DayOfWeek = (buf>>42) & 0x07;   // day of the week = bit 42-44
  byte dcf77DayOfMonth = (buf>>36) & 0x3F;  // day of the month = bit 36-41
  byte dcf77Hour = (buf>>29) & 0x3F;       // hour = bit 29-34
  byte dcf77Minute = (buf>>21) & 0x7F;     // minute = 21-27 
  bool parityBitMinute = (buf>>28) & 1;
  bool parityBitHour = (buf>>35) & 1;
  bool parityBitDate = (buf>>58) & 1;

  if((parity_even_bit(dcf77Minute)) != parityBitMinute){
    parityOK = false;
    Serial.println("Minutes not OK"); 
  }
  if((parity_even_bit(dcf77Hour)) != parityBitHour){
    parityOK = false; 
    Serial.println("Hours not OK");
  }
  if(((parity_even_bit(dcf77DayOfMonth) + parity_even_bit(dcf77DayOfWeek) 
  + parity_even_bit(dcf77Month) + parity_even_bit(dcf77Year))%2) != parityBitDate)
  {
    parityOK = false;
    Serial.println("Date not OK");
  }

  if(parityOK==false){
    return parityOK;
  }
  
  dcf77Time = DateTime(rawByteToInt(dcf77Year) + 2000, rawByteToInt(dcf77Month), 
              rawByteToInt(dcf77DayOfMonth), rawByteToInt(dcf77Hour), rawByteToInt(dcf77Minute), 0);

  return parityOK;
}

unsigned int rawByteToInt(byte raw){
  return ((raw>>4)*10 + (raw &0x0F));
}

bool receiveSequence(unsigned long long &buf){
  unsigned int counter = 0;
  unsigned int lowCounter = 0;
  unsigned int highCounter = 0; 
    
  Serial.println("Waiting for sequence start...");
  waitForSequenceStart(); 
  Serial.println("HIGH / LOW");
  
  while(lowCounter < 150){
    lowCounter = 0;
    highCounter = 0;
    
    while(DCF77_PIN_HIGH){
      delay(10);
      highCounter++;
    }
    if(highCounter >= 15){
      buf |= ((unsigned long long)1<<counter);
    }
         
    while(!DCF77_PIN_HIGH){
      delay(10);
      lowCounter++;
    }
      
    Serial.print(counter);
    Serial.print(".  ");
    Serial.print(highCounter);
    Serial.print("  /  ");
    Serial.println(lowCounter);
    
    counter++; 
  }
  if(counter==59){
    return true;    
  }
  else{
    return false;
  }
}

void waitForSequenceStart(){
  unsigned int lowCounter2 = 0;
  
  while(lowCounter2<150){
    lowCounter2 = 0;
    while(DCF77_PIN_HIGH){}
    while(!DCF77_PIN_HIGH){
      delay(10);
      lowCounter2++;
    }
  }  
}

void printSequence(unsigned long long &buf){
  unsigned long highBuf = (buf>>32) & 0x7FFFFFF;
  unsigned long lowBuf = (buf & 0xFFFFFFFF);
  Serial.print("Sequence, upper 4 bytes: "); 
  Serial.println(highBuf, BIN);
  Serial.print("Sequence, lower 4 bytes: "); 
  Serial.println(lowBuf, BIN);  
}

void loop () {
  DateTime now = rtc.now();
  TimeSpan updatePeriod = TimeSpan(0,0,2,0);
  static DateTime nextUpdate = now + updatePeriod;
  
  char buf1[] = "Today is DDD, MMM DD YYYY";
  Serial.println(now.toString(buf1));
  char buf2[] = "Current time is hh:mm:ss";
  Serial.println(now.toString(buf2));
  Serial.println();

  if(nextUpdate < now){
    if(getDcf77Time(now)){
      rtc.adjust(now);
      nextUpdate = now + updatePeriod;
    }
  }
  
  delay(3000);
}

//uncomment the following lines if you don't use an AVR MCU
//bool parity_even_bit(byte val){
//  val ^= val >> 4;
//  val ^= val >> 2;
//  val ^= val >> 1;
//  val &= 0x01;
//  return val;
//}

 

Ausgabe von dcf77_softRTC_without_interrupt.ino

So sieht der Sketch in Aktion aus:

Ausgabe von dcf77_softRTC_without_interrupt.ino: erste Zeiteinstellung
Ausgabe von dcf77_softRTC_without_interrupt.ino: erste Zeiteinstellung

In der oberen Ausgabe seht ihr, dass die Uhr um 17:25 Uhr gestellt wird. Wie im Sketch festgelegt, erfolgt die nächste Einstellung zwei Minuten später:

Ausgabe von dcf77_softRTC_without_interrupt.ino: zweite Zeiteinstellung

Danksagung

Die Grundlage des Beitragsbildes, die Uhr, stammt von OpenClipart-Vectors. Das Funksymbol habe ich Clker-Free-Vector-Images zu verdanken. Beides – wie gewohnt – von Pixabay.

Anhang 1 – Soft-RTC mit schlankerer ISR

Die Interrupt-Routine im Sketch dcf77_softRTC.ino ist ziemlich umfangreich ausgefallen. Ein zu langes Verweilen in der Interrupt-Routine kann mit dem Rest des Codes unverträglich sein. Ich habe einen alternativen Sketch geschrieben, bei dem die Auswertung der DCF77-Sequenz in loop() initiiert wird, sofern aktuelle Daten vorliegen (timeUpdateAvailable).  Dadurch, dass ich die neuen DCF77-Signale in der Variablen nextBuf speichere und letzte vollständige Sequenz in currentBuf, steht mehr Zeit für die Auswertung zur Verfügung. Jede Verzögerung lässt eure Uhr aber entsprechend nachgehen.

#include <util/parity.h> //comment out if you don't use an AVR MCU
#include "RTClib.h"
int interruptPin=2;
volatile unsigned long lastInt = 0;
volatile unsigned long long currentBuf = 0;
volatile unsigned long long nextBuf = 0;
volatile bool timeUpdateAvailable = false;
volatile byte bufCounter;

RTC_Millis rtc;

void setup(){
  rtc.adjust(DateTime(2000, 1, 1, 0, 0, 0));
  Serial.begin(115200);
  pinMode(interruptPin, INPUT);
  attachInterrupt(digitalPinToInterrupt(interruptPin), DCF77_ISR, CHANGE);
}

void loop(){
  static DateTime now = rtc.now();
  if(timeUpdateAvailable){
    evaluateSequence();
    timeUpdateAvailable = false;
  }
  now = rtc.now();
  char buf1[] = "Today is DDD, MMM DD YYYY";
  Serial.println(now.toString(buf1));
  char buf2[] = "Current time is hh:mm:ss";
  Serial.println(now.toString(buf2));
  Serial.println();
  
  delay(3000);
}

void DCF77_ISR(){
  unsigned int dur = 0;
  dur = millis() - lastInt; 
  
  if(digitalRead(interruptPin)){
    if(dur>1500){
      if(bufCounter==59){
        timeUpdateAvailable = true;
        currentBuf = nextBuf;
      }
      bufCounter = 0;
      nextBuf = 0;
    }
  }
  else{
    if(dur>150){
      nextBuf |= ((unsigned long long)1<<bufCounter);
    }
    bufCounter++;
  }
  lastInt = millis();
}

void evaluateSequence(){
  byte dcf77Year = (currentBuf>>50) & 0xFF;    // year = bit 50-57
  byte dcf77Month = (currentBuf>>45) & 0x1F;       // month = bit 45-49
  byte dcf77DayOfWeek = (currentBuf>>42) & 0x07;   // day of the week = bit 42-44
  byte dcf77DayOfMonth = (currentBuf>>36) & 0x3F;  // day of the month = bit 36-41
  byte dcf77Hour = (currentBuf>>29) & 0x3F;       // hour = bit 29-34
  byte dcf77Minute = (currentBuf>>21) & 0x7F;     // minute = 21-27 
  bool parityBitMinute = (currentBuf>>28) & 1;
  bool parityBitHour = (currentBuf>>35) & 1;
  bool parityBitDate = (currentBuf>>58) & 1;

  if((parity_even_bit(dcf77Minute)) == parityBitMinute){
    if((parity_even_bit(dcf77Hour)) == parityBitHour){
      if(((parity_even_bit(dcf77DayOfMonth) + parity_even_bit(dcf77DayOfWeek) 
           + parity_even_bit(dcf77Month) + parity_even_bit(dcf77Year))%2) == parityBitDate){
        rtc.adjust(DateTime(rawByteToInt(dcf77Year) + 2000, rawByteToInt(dcf77Month), 
            rawByteToInt(dcf77DayOfMonth), rawByteToInt(dcf77Hour), rawByteToInt(dcf77Minute), 0));
      }
    }
  }
}

unsigned int rawByteToInt(byte raw){
  return ((raw>>4)*10 + (raw & 0x0F));
}

//uncomment the following lines if you don't use an AVR MCU
//bool parity_even_bit(byte val){
//  val ^= val >> 4;
//  val ^= val >> 2;
//  val ^= val >> 1;
//  val &= 0x01;
//  return val;
//}

 

Anhang 2 – Uhrzeit auf einem 4-Ziffern Display

Und dann habe ich noch einen Sketch geschrieben, der die Uhrzeit auf einem TM1637-basierten 4-Ziffern Display ausgibt. Ich habe dazu die Bibliothek TM1637 von Avishay verwendet, die ihr auch über den Arduino Bibliotheksmanager installieren könnt.

So kann die Uhr dann aussehen:

4-Ziffern-Display als Uhr
4-Ziffern-Display als Uhr

Der Sketch ist recht umfangreich geworden. Man könnte theoretisch den Umweg über die Soft RTC weglassen und die Sequenz direkt in die Uhrzeit übersetzen. Allerdings hat das den Nachteil, dass eine Fehlsequenz zu einer falschen oder veralteten Zeit führen würde. In meiner Implementierung wird bei einer Fehlsequenz lediglich die Soft RTC nicht nachjustiert. Aber für eine oder wenige Minuten dürfte man auch mit der weniger genauen Soft RTC leben können.

#include <util/parity.h> //comment out if you don't use an AVR MCU
#include "RTClib.h"
#include <TM1637Display.h>
#define CLK 9 // Display pin CLK
#define DIO 10 // Display pin DIO

int interruptPin=2;
volatile unsigned long lastInt = 0;
volatile unsigned long long currentBuf = 0;
volatile unsigned long long nextBuf = 0;
volatile bool newMinute = false;
volatile bool timeValid = false;
volatile byte bufCounter;

RTC_Millis rtc;
TM1637Display display(CLK, DIO);

void setup(){
  rtc.adjust(DateTime(2000, 1, 1, 0, 0, 0));
  display.setBrightness(0x0f);
  pinMode(interruptPin, INPUT);
  attachInterrupt(digitalPinToInterrupt(interruptPin), DCF77_ISR, CHANGE);
}

void loop(){
  static DateTime now = rtc.now();
  if(newMinute){
    if(timeValid){
      evaluateSequence();
    }
    else delay(2000); // if time not valid and soft rtc is slow 
    now = rtc.now();
    int timeAsInt = now.minute() + now.hour() * 100;
    display.showNumberDecEx(timeAsInt, 0b11100000, true);
    //display.showNumberDec(timeAsInt, true); // without "__:__"
    newMinute = false;
  }
}

void DCF77_ISR(){
  unsigned int dur = 0;
  dur = millis() - lastInt; 
  
  if(digitalRead(interruptPin)){
    if(dur>1500){
      if(bufCounter==59){
        timeValid = true;
        currentBuf = nextBuf;
      }
      else {
        timeValid = false; 
      }
      nextBuf = 0;
      newMinute = true;
      bufCounter = 0;
    }
  }
  else{
    if(dur>150){
      nextBuf |= ((unsigned long long)1<<bufCounter);
    }
    bufCounter++;
  }
  lastInt = millis();
}

void evaluateSequence(){
  byte dcf77Year = (currentBuf>>50) & 0xFF;    // year = bit 50-57
  byte dcf77Month = (currentBuf>>45) & 0x1F;       // month = bit 45-49
  byte dcf77DayOfWeek = (currentBuf>>42) & 0x07;   // day of the week = bit 42-44
  byte dcf77DayOfMonth = (currentBuf>>36) & 0x3F;  // day of the month = bit 36-41
  byte dcf77Hour = (currentBuf>>29) & 0x3F;       // hour = bit 29-34
  byte dcf77Minute = (currentBuf>>21) & 0x7F;     // minute = 21-27 
  bool parityBitMinute = (currentBuf>>28) & 1;
  bool parityBitHour = (currentBuf>>35) & 1;
  bool parityBitDate = (currentBuf>>58) & 1;

  if((parity_even_bit(dcf77Minute)) == parityBitMinute){
    if((parity_even_bit(dcf77Hour)) == parityBitHour){
      if(((parity_even_bit(dcf77DayOfMonth) + parity_even_bit(dcf77DayOfWeek) 
           + parity_even_bit(dcf77Month) + parity_even_bit(dcf77Year))%2) == parityBitDate){
        rtc.adjust(DateTime(rawByteToInt(dcf77Year) + 2000, rawByteToInt(dcf77Month), 
            rawByteToInt(dcf77DayOfMonth), rawByteToInt(dcf77Hour), rawByteToInt(dcf77Minute), 0));
      }
    }
  }
}

unsigned int rawByteToInt(byte raw){
  return ((raw>>4)*10 + (raw & 0x0F));
}

//uncomment the following lines if you don't use an AVR MCU
//bool parity_even_bit(byte val){
//  val ^= val >> 4;
//  val ^= val >> 2;
//  val ^= val >> 1;
//  val &= 0x01;
//  return val;
//}

 

Anhang 3 – den DS3231 mit dem DCF77 stellen

Ihr könnt einen DS3231 mit einem DCF77 nachstellen. Die Kombination dieser Module macht Sinn, wenn ihr keinen zuverlässigen Empfang mit dem DCF77 habt. Ihr findet die Schaltung und meinen Sketch in diesem Beitrag.

92 thoughts on “DCF77 – Funkuhr

  1. Hallo Wolfgang,

    nachdem meine ersten Versuche mit DCF77vollkommen fehlgeschlagen sind habe ich nach weiteren Anleitungen im Internet zu suchen. Dabei bin ich auch auf deine Webseite gestoßen.
    Wie von die empfohlen habe ich mir auch dieses Canaduino Modul besorgt.
    Welchen Trick muss man anwenden damit dieses Modul funktioniert ?
    Offensichtlich muss man die Pin „AON“ und „PON“ an diesem speziellen Modul auch beschalten und ansteuern.

    Leider finde ich keinen Hinweis ( oder ich habe es übersehen ) wie dieses zu geschehen hat. Welche der beiden Pins müssen in welcher Reihenfolge geschalten werden und wie, damit dieses Modul überhaupt über OUT ( ohne Überstrich ) ein Signal ausgibt ?

    Es wäre schön, wenn du für dieses wohl speziellere Modul auch den Programmcode erklären, bzw. bereitstellen würdest.

    Danke Roland

    1. Hallo Roland,

      der Anbieter stellt auf seinen Seiten eine Beschreibung des Moduls und seiner Anschlüsse zur Verfügung:
      https://universal-solder.ca/downloads/CANADUINO_Atomic_Clock_Receiver_Kit_SMD.pdf
      In Kurzform: PON an GND aktiviert das Modul, AON (Auto Gain) kann unverbunden bleiben). OUT mit Oberstrich gibt das Signal in der Logik aus, die ich in meinen Beispielen verwende, OUT ohne Unterstrich dreht das Signal um.
      Wenn du Signale empfängst, dann blinkt die LED OUT entsprechend, d.h. 800-900 ms an / 100-200 ms aus. Wenn du die kleine Kontaktbrücke an LED trennst, dann sind die LEDs aus und du sparst Strom.
      Ich werde den Link zur Anleitung in den Artikel übernehmen.
      VG, Wolfgang

    1. Hi lux,
      pulseIn ist auch ein interessanter Ansatz. Ich habe den Beispielsketch ausprobiert und er funktioniert nach einer kleinen Anpassung. Ich werde dir das über GitHub als Issue mitteilen. Ein gewisser Nachteil ist, dass die Signalauswertung den Sketch für eine Minute blockiert. Das schränkt die Anwendbarkeit ein. Eine mögliche Applikation wäre das Stellen einer RTC einmal pro Stunde oder Tag. Da wäre dann eine Blockade von einer Minute akzeptabel. So ähnlich wie ich das in meinem Sketch dcf77_softRTC_without_interrupt.ino gemacht habe.
      Wenn du vorhast, die Bibliothek über den Arduino Library Manager auffindbar zu machen, dann ist hier die Anleitung dazu:
      https://github.com/arduino/library-registry
      VG, Wolfgang

      1. Deine Antworten sind klasse und ich schätze deine faire und konstruktive Art.
        Ich habe die Vorschläge auf github berücksichtigt und eingepflegt, vielen Dank dafür.

        Du hast bereits den Hauptnachteil der Implementierung durch die Blockierung offen gelegt. Diesen Punkt kann ich so schnell nicht ändern.

        Die Geschichte mit dem „Arduino Library Manager“ ist mir tatsächlich noch unklar und ich habe überlegt es zu tun. Aber ich bin mir noch im Unklaren darüber ob die Welt meine DCF Implementierung braucht 😉

        Liebe Grüße,

        (dl5)lux

  2. Hallo Wolfgang,

    muss im Moment für die Schule eine Funkuhr mit dem Arduino bauen. Dabei benutze ich den Arduino UNO, ein DCF77 Reichelt Modul und eine 4 Stellige 7 Segmentanzeige (4 Digit Display V1.2). Dank deiner Seite habe ich es hinbekommen auf dem Seriellen Monitor die richtige Uhrzeit zu empfangen. Doch jetzt scheitere ich leider daran diese Uhrzeit auf der 7 Segmentanzeige anzeigen zu lassen. Jetzt wollte ich fragen ob du eventuell irgendwelche Lösungsvorschläge hast.

    MfG Emilius

    1. Hi Emilius, du hattest die Frage schon einmal gestellt, aber zu meinem Beitrag über das DS3231 RTC Modul. Aber egal, hier meine Antwort:
      Hi, das müsste so ein TM1637 basiertes Teil sein, richtig? Dann würde die folgende Bibliothek funktionieren:

      https://github.com/avishorp/TM1637

      Die Bibliothek findest du auch über den Bibliotheksmanager der Arduino IDE. Da gibt es auch einen Beispielsketch. Für die Uhrzeit sollte es so gehen:
      Ausgabe = Stunden × 100 + Minuten
      und dann:
      display.showNumberDec(Ausgabe, true);
      VG, Wolfgang

      1. Hallo Wolfgang,

        schonmal vielen Dank für deine Hilfe und sorry für das erneute nachfragen. Bei mir war meine erste Frage nur leider Verschwunden. Deine oben genannten Tipps habe ich versucht in einen Code zu schreiben. Jetzt funktioniert mein Display auch nur leider zeigt es jetzt meistens 00:00 oder eine willkürliche Uhrzeit an. Vielleicht sieht du ja einen Fehler in meinem Code:
        #include
        #include // Kommentiere es aus, wenn du keinen AVR-MCU verwendest

        #define CLK 9
        #define DIO 10

        TM1637Display display(CLK, DIO);

        int interruptPin = 2;
        volatile unsigned long lastInt = 0;
        volatile unsigned long long currentBuf = 0;
        volatile byte bufCounter;

        void setup() {
        Serial.begin(115200);
        pinMode(interruptPin, INPUT);
        attachInterrupt(digitalPinToInterrupt(interruptPin), DCF77_ISR, CHANGE);

        display.setBrightness(0x0f); // Setze die Helligkeit der Anzeige (0x00 bis 0x0f)
        }

        void loop() {
        // Nichts im Loop außer Anzeige der Uhrzeit
        displayTime();
        delay(1000); // Wartezeit, um die Anzeige nicht zu flackern
        }

        void displayTime() {
        byte dcf77Minute = (currentBuf >> 21) & 0x7F; // minute = 21-27
        byte dcf77Hour = (currentBuf >> 29) & 0x3F; // hour = bit 29-34

        int minute = rawByteToInt(dcf77Minute);
        int hour = rawByteToInt(dcf77Hour);

        // Anzeige der Uhrzeit auf der 4-stelligen 7-Segmentanzeige
        display.showNumberDec(hour * 100 + minute, true);
        }

        void DCF77_ISR() {
        unsigned int dur = 0;
        dur = millis() – lastInt;

        if (digitalRead(interruptPin)) {
        if (dur > 1500) {
        currentBuf = 0;
        }
        } else {
        if (dur > 150) {
        currentBuf |= ((unsigned long long)1 <> 4) * 10 + (raw & 0x0F));
        }

        //uncomment the following lines if you don’t use an AVR MCU
        //bool parity_even_bit(byte val){
        // val ^= val >> 4;
        // val ^= val >> 2;
        // val ^= val >> 1;
        // val &= 0x01;
        // return val;
        //}

        1. Hallo Emilius,
          ein wenig vom Sketch ist verloren gegangen. Das liegt daran, dass die Kommentarfelder den Text als HTML interpretieren. Ich habe das dann rekonstruiert. Neben ein paar kleineren Dingen gab es ein Hauptproblem, und zwar dass du sekündlich currentBuf zur Ermittlung der Uhrzeit heranziehst. Die Variable currentBuf wird aber zu Beginn des Minutenzyklus auf 0 gesetzt. D.h., bis die Stunden und Minuten übermittelt werden, erhältst du 0. In meinem softRTC Sketch ist das kein Problem, da ich die softRTC immer nur nach Beendigung einer Minutensequenz nachstelle.
          Ich habe das mal geändert, indem ich eine neue Variable nextBuf eingeführt habe, die die neuen Werte aufnimmt. Nach einer vollständigen Sequenz wird nextBuf in currentBuf kopiert. Ich sende dir den Sketch per mail. Er gibt die Zeiten auf dem seriellen Monitor aus. Du müsstest das nur auf das 4 Zifferndisplay umschreiben, aber das sollte kein Problem sein.
          VG, Wolfgang

        2. Hallo Emilius,
          zusätzlich zu den Versionen, die ich dir geschickt habe, habe ich noch eine „Luxus“-Version geschrieben und in Anhang 2 des Beitrages veröffentlicht. Der Sketch ist recht umfangreich, hat aber den Vorteil, dass die Uhr auch bei einer Fehlsequenz über die Soft RTC weiterläuft.
          VG, Wolfgang

  3. Hi, Arduino Programmierung ist toll, ich möchte aber das DCF77 Programm in einen ESP32 unter Homeassitant einbinden. Weiss jemand wie man sowas macht ? Include Befehl sagt mir was, aber wie macheich die Daten dann im YAML bzw Homeassistant verfübar. Leider ist das Netz hier wenig aussagekräftig. Jeder Hinweis ist willkommen. Danke

  4. Hallo Wolfgang,

    habe erst vor kurzem Deinem „Elektronikkiste“ entdeckt. Lebe in Österr. und heisse Alex.
    Programmiere schon einige Zeit und bin echt begeistert von Deinen Sketchen und vor allem von Deinen Beschreibungen.
    Arbeite gerade an einer Wetterstation (BME280, RTC, HC-12, Arduino Nano mit 6V Solarmodul) im Glashaus, schickt brav die Daten mittels Structur ins Haus, leider die Umstellung auf Sommerzeit habe ich vergessen

    Da wir heute die Sommerzeit wieder haben musste ich die Uhr im Glashaus umstellen und das hat mich geärgert.
    Deshalb möchte ich jetzt den Sketch nutzen um dies nicht mehr tuen zu müssen.

    Jedenfalls echt super Deine Beiträge.

  5. Hallo Wolfgang,

    ich teste gerade einen DCF77 Empfänger mit Deinen Sketches auf einem NANO (328P).
    Der „dcf77_softRTC_without_interrupt.ino“ funktioniert wie er soll, nur der „dcf77_sequence_evaluation.ino“ bringt immer parity not ok, immer.
    Bei genauerem hinsehen fällt auf, daß Year, Month, Day of Month, … irgendwie immer das doppelte vom erwarteten Wert ist (heutige Werte!):

    16:29:56.998 -> 57. 82 / 905
    16:29:58.038 -> 58. 111 / 888
    16:29:59.158 -> 59. 224 / 1782
    16:30:00.918 -> Signal, upper 4 bytes: 1001000001101000010110101
    16:30:00.918 -> Signal, lower 4 bytes: 10001100001010000110101010100000
    16:30:00.918 -> Minute parity not OK
    16:30:00.918 -> Hour parity not OK
    16:30:00.918 -> Current date and Time:
    16:30:00.918 -> Year: 48
    16:30:00.918 -> Month: 6
    16:30:00.958 -> Day of Month: 11
    16:30:00.958 -> Day of the Week: 4
    16:30:00.958 -> Hours: 32
    16:30:00.958 -> Minutes: 61
    16:30:00.958 -> 0. 0 / 16
    16:30:00.998 -> 1. 63 / 898
    16:30:02.038 -> 2. 114 / 910

    Ich tu mich da beim Finden der richtigen Stelle schwer …

    Gruß André

    1. Hi Andre,
      sind deine LOW Phasen klar über 150 ms? Das ist die Grenze, die ich gesetzt habe (Zeile 38). Bei den Modulen gibt es große Qualitätsunterschiede. Idealerweise bekommst du Wertepaare 100 / 900 oder 200 / 800. Schwächere Module neigen zu zu kurzen High-Phasen und zu Fehlmessungen. Was ich in deinem Ausschnitt sehe, ist, dass dein letzter Wert 59 ist. Da ist schon mal irgendetwas im Argen. Der letzte Wert muss Nr. 58 sein. Und der erste Wert der neuen Reihe (Nr. 0) ist 0 / 16. Das ist eine Fehlmessung. Wenn das Modul keine sauberen Wertepaare ausgibt, kann die Reihe nicht ausgewertet werden. Evtl. kannst du die Signalqualität verbessern, wenn du das Modul per Batterie mit Strom versorgst. Evtl. hilft auch ein anderer Standort. Wenn beides nicht hilft, dann hilft nur ein anderes Modul.
      VG, Wolfgang

      1. Hallo Wolfgang,

        das Modul funktioniert, und ich habe guten Empfang. Dein Sketch dcf77_softRTC_without_interrupt.ino“ rennt ja, nur selten mal ein Fehler! Ich wollte halt den erwähnten „dcf77_sequence_evaluation.ino“ mit Interrupt für mich als „Basis“ nehmen, und den weiter ausbauen, nur da bekomme ich nie gültige Werte.
        Ja, die „0“ und „1“ lässt sich eindeutig unterscheiden, ca. 75 … 115 und 190 … 216 sind ganz grob die Werte.

        Gruß André

        1. Du weißt sicherlich besser, was du misst, aber in dem kleinen Ausschnitt, den du mir geschickt hast, waren allein schon zwei Fehler. Bekommst du denn sonst immer brav Wertepaare bis Nr. 58 und nicht bis Nr. 59? Ein Fehler am Sketch kann ich eigentlich ausschließen, da er schon so häufig von mir und anderen erfolgreich im Einsatz war. Dass der Sketch dcf77_softRTC_without_interrupt.ino bei dir funktioniert und dieser nicht, kann ich mir nicht erklären. Beide Sketche geben ja Wertepaare aus. Du könntest mal beide hintereinanderlaufen lassen und die Sequenzen vergleichen. Wenn du nicht gerade einen Stundenwechsel überschreitest, sollten alle Bits bis auf 21 – 28 (also die Minuten) gleich sein. Mehr fällt mir dazu im Moment nicht ein.

        2. Du könntest mir auch mal 2-3 Minuten Wertepaare senden (wolfgang.ewald@wolles-elektronikkiste.de). Vielleicht fällt mir ja irgendetwas auf.

  6. Hallo Wolfgang,

    simple Frage: Woher hast du das DCF Modul in deinem Fritzing Schaltplan?

    Ich habe ein ähnliches Projekt gemacht und kann jetzt den Schaltplan nicht vervollständigen da ich es nicht finden kann…

    Danke im Voraus
    Steven

  7. Hallo Wolle,

    ich habe ein Problem. Ich verwende den Interuptcode des DCF77 und SoftRTC – der Sketch

    Bei mir bleibt die Anzeige bei „Waiting for sequence start…“ stehen und macht nichts.

    Empfangsqualität meines Moduls ist aber gut. Hast du eine Idee, woran das liegen könnte?

    1. Hi Sven,
      wie lange hast du denn gewartet? Es dauert bis zu einer Minute, bis die Sequenz startet. Und bekommst du mit dem Sketch dcf77_sequence_evaluation.ino gültige Sequenzen, sprich ein sinnvolles Datum? Wenn ja, dann wundert mich das, weil beide Sketche im Prinzip dasselbe machen. Wenn nein, könnte es sein, dass du ein Modul mit invertiertem Ausgang hast? Das würde sich darin äußern, dass der Signalpin die meiste Zeit HIGH ist und die eigentlichen Signale (100 bzw 200 ms) LOW sind.
      VG, Wolle

      1. Moin Wolfgang. Ich habe für mich den Fehler gefunden. Ich habe einen Pro Mini 3,3V.

        Theoretisch hat er zwei Interrupt Pins: 2,3

        An Pin 2 funktioniert die Synchronisierung nach ca. 2 min.

        Wenn ich auf Pin 3 gehe, passiert nichts. Der Code ohne Interrupt bleibt auch nach mehr als 5 Minuten leer, gibt also keine Daten.

        Wie kann ich den PIN 3 für das Pollin Modul verwenden, denn ich wecke den Arduino über das DS3231 über PIN 2?

        Wofür ist der Interrupt des DCF77 gut? Denn in meinem Code wird abhängig von DS3231 der Abgleich mit dem DCF77 zu einer bestimmten Zeit ausgelöst. Ich brauche also weder einen Weckinterrupt noch einen regelmäßigen Abgleich.

        1. Wenn du den Pin 3 nutzen willst, dann änderst du einfach die Zeile
          byte interruptPin = 2;
          auf 3 ab. Ich wüsste nicht warum das nicht funktionieren sollte.
          Die andere Frage habe ich vielleicht nicht verstanden. Die Interrupts sind dazu da, damit das sekündlich kommende DCF77 Signal ausgewertet wird. Du schreibst etwas vom DS3231, den ich hier ja gar nicht verwende, sondern lediglich die Bibliothek, um damit die „Software Uhr“ zu stellen, die recht ungenau ist, da sie auf dem Systemtakt basiert. Wenn du einen DS3231 verwendest brauchst du das nicht. Oder höchstens um den DS3231 ab und zu nachzustellen.
          VG, Wolfgang

  8. FORTSETZUNG DER DISKUSSION MIT MANUEL (s.u.)

    Hallo Manuel,
    ich habe gute Neuigkeiten! Ich habe ein Modul von Pollin getestet und festgestellt, dass die HIGH Signale bei Verwendung des Arduinos als Stromquelle erheblich kürzer sind als die erwarteten 100 bzw. 200 ms. Die Werte lagen eher bei 65 und 150 und waren stark schwankend, sodass relativ viele Fehlmessungen auftraten. Eine Besserung hat sich ergeben, als ich das Kriterium für ein Langsignal von > 150ms auf > 120ms gesenkt habe (das ist die Zeile if(dur > 150){ .Du könntest auch einmal den Sketch dcf77_sequence_evaluation nehmen, um die Signalzeiten zu überprüfen.

    Das ist aber nur Symptombekämpfung. Ich habe mir mal das Datenblatt vom Pollin-Modul durchgelesen und dort steht, dass die Signalqualität sehr stark von der Qualität der Spannungsversorgung abhängt. Der Ripple sollte kleiner als 10 mV sein. Der Ripple ist eine periodische Spannungsschwankung, die beim Herunterregeln von Spannungen entstehen kann. Das könnte dein Problem bei der Versorgung mit dem Ladegerät erklären.

    Ich habe die Spannungsversorgung des Moduls auf eine Li-Batterie (3.7 V) umgestellt. Die Signalqualität hat daraufhin für mich sehr überraschenden Sprung gemacht. Viel näher an den 100/200 ms. Was ich auch noch festgestellt habe, ist, dass das Modul empfindlich auf Berührung während der Messung reagiert und es anscheinend bestimmte elektrische Felder nicht mag. Nähe zu meinem Monitor hat das Signal verschlechtert.

    Vielleicht schaust du mal in die Richtung.

    1. Hallo Wolfgang,

      ja genau, ich hatte zwischenzeitlich auch mal gemessen und festgestellt, dass bei Betrieb mit einem 1 A Netzteil quasi nichts aus dem DCF Modul kam. Als ich dann ein 30 W Netzteil genommen habe ging es schon besser (Zeit wurde richtig synchronisiert, Datum war aber falsch). Da ich von den 1 A Netzteilen jetzt so viele rumliegen habe, schreibe ich den Sketch dahingehend um, dass ich das LCD-Modul nur 1x / h für 10 Minuten aktiviere. Dient eh nur zur gelegentlichen Kontrolle.

      Oder gäbe es eine bessere Idee?

      Danke für alles.

    2. …oder könnte man vielleicht an den 3,3 V Ausgang des Aruino einfach einen 3,6 V Akku (2032) löten und die Spannung so stabilisieren?

      1. Akkus verwende ich auch und ist die beste Variante.

        Ich empfehle dann noch ein Einschalten per Mosfet oder Relais, denn so oft braucht man den Zeitsbgleich nicht.

        Ich mache ihn einmal am Tag und schalte dafür über ein Relais die Versorgung ein.

      2. Ich stimme Sven zu. Zu der anderen Frage: Zusätzlich einen Akku an den 3.3 V Ausgang zu hängen, würde ich nicht empfehlen. Du könntest aber probieren, den Arduino mit deinem Netzteil zu betreiben und das DCF77 Modul nur mit dem Akku. Die DCF77-Module brauchen nur wenig Strom. Pollin gibt für sein Modul < 0.1 mA an. Da kommst du selbst mit einer Knopfzelle ein paar Monate hin. Wenn du das machst, dann darfst du nicht vergessen, dass das Modul und der Arduino dasselbe GND haben (also nicht einfach nur die Daten-/Steruerleitungen verbinden).

        1. Ich plapper mal nun aus dem Nähkästchen. Ich baue gerade eine mechanische Uhr, die maximal Energie spart, da ich an dem Ort der Uhr keine Stromversorgung bekomme.

          Ich bin soweit, dass die Uhr läuft, aber noch nicht mit dem DCF77 Signal synchronisiert wird. Das soll noch kommen, aber gerade habe ich nicht die Zeit dafür.

          Was die Uhr nun kann:
          Aufwachen, Zeiger bewegen, und dann wieder bis zur nächsten Aktion maximal Energie sparen. Dann sind Routinen eingebaut, um die Uhr bei mutwilligem Verstellen wieder zurück zur Zeit zu bringen. Das erfolgt einmal die Stunde bzw. zwei mal am Tag. Es braucht also etwas Geduld. Alles was nicht unbedingt notwendig ist, wird abgeschaltet. Dazu verwende ich Mosfets, da normale Magnetrelais nicht genug Schaltvorgänge (10000) durchlaufen können,damit die Uhr länger als zwei Monate lebt. Nach 68 Tagen sind bei einer Aktion die Lebenszeit von 10000 Schaltzyklen erreicht.
          Des weiteren gibt es die Option eines längeren Schlafmodus, dass z.B. in einem bestimmten Zeitintervall die Uhr abgeschaltet wird. Das spart auch noch unheimlich viel Energie.

          Nun zum DCF 77 Modul:
          Mir ist aufgefallen, dass beim Powerdown Modus auch der Spannungswandler abschaltet. Für das DCF Modul bedeutet das, dass es zu viel Strom bekommt. Daher muss ich die Stromversorgung (Plus Leitung) mit einem Mosfet einschalten und damit auch den Spannungswandler.

    3. Hm nein, das DCF-Modul mit Batterie zu betreiben hilft auch nicht, leider. Es muss noch irgend etwas anderes sein. Es passiert genau das gleiche. 🙁

  9. Hallo Wolfgang,

    nach vielem hin und her mit diesen ganzen Funkuhrmodulen bin ich auf Modul gestoßen, welches sich durch eine Besonderheit hervorhebt, welches so kein anderes Modul im Funktionsumfang mitbringt. Es ist eine automatische Gain Anpassung, um bei äußeren Störungen, oder Empfangsgebieten mit sehr schwachen Signaleingang nach meinen bisherigen Erfahrungen eine sehr gute und schnelle Synchronisation bietet.
    Dieses Modul eines Kanadischen Herstellers -> https://www.amazon.de/CANADUINO-Atomuhr-AM-Empf%C3%A4nger-ADK-5kHz/dp/B01KH439ZG
    biettet sowohl einen positiven, wie auch invertierten Signalausgang an,man kann es schlafen legen, aber auch via eines Auto-Gains sich eigenständig den Umgebungsbedingungen anzupassen.
    Zwar muss man in diese Platine noch das passende Quarz selber einlöten, aber selbst in Ecken mit extrem schlechten Empfangsbedingungen und zum Teil in Kellern funktioniert dieses Modul einwandfrei. Ich musste bisher nie länger als 2 Minuten unabhängig des Standortes auf eine vollständige Synchronisation warten.

    1. Vielen Dank noch einmal. Ich habe mir das Modul mittlerweile und erstaunlich schnell aus Kanada erhalten und ich muss sagen, ich bin begeistert. Die Signalqualität ist viel stabiler als die aller anderen Module, die ich getestet habe. Und das auch an Orten, wo andere Module aufgegeben haben. Ich habe es in den Beitrag aufgenommen.
      VG, Wolfgang

  10. Herzlichen Dank, ich verstehe im Ansatz was passiert.

    Wenn ich jetzt aber die Zeit sekündlich ausgeben möchte, wie mache ich das? Das Delay von 3000 ms auf 1000 ms verkürzen funktioniert nicht. Ich denke mal wegen der 1500ms Low-Phase, richtig?

    Herzlichen Gruß

    1. Das müsste auch sekündlich gehen. Ich weiß nicht mehr, warum ich drei Sekunden gewählt habe. Ich glaube, damit die Ausgabe nicht zu hektisch ist. Probiere es einfach aus! Und genau, einfach das Delay variieren. Viel Erfolg!

      1. Das habe ich gemacht und eben genau das festgestellt. Verändere ich auf 1000 ms, synchronisiert sich nichts. 🙁
        Mein plan war nämlich die Ausgabe auf ein LCD Display. Funktioniert bei 3000, nicht aber bei 1000

        1. Ich habe das DCF77 Modul noch einmal aus der Schublade geholt und die Ausgabe im Sekundentakt probiert, also mit delay(1000) im loop. Das funktioniert bei mir sowohl mit dcf77_softRTC.ino als auch mit dcf77_softRTC_without_interrupt.ino. Also ist die Frage, was bei dir anders ist. Du sagst, du nutzt ein LCD-Display. Funktioniert die sekündliche Ausgabe nur nicht bei Benutzung des LCDs oder funktioniert auch die sekündliche Ausgabe auf dem seriellen Monitor nicht? Mit anderen Worten: Wenn du beispielsweise den Sketch dcf77_softRTC.ino nimmst und nicht anderes änderst als delay(3000) zu delay(1000), geht es dann? Wenn nein, dann wird es schwierig, wenn ja, dann müssen wir mal schauen inwiefern die LCD Ansteuerung die Ansteuerung des DCF77 stören kann.
          VG, Wolfgang

          1. Nach vielem Rumprobieren und Verzweifeln komme ich zu dem Entschluss: Das war wahrscheinlich doch nicht der Grund, stimmt. Insgesamt funktioniert das bei mir leider sehr unzuverlässig (mal geht´s, dann wieder nicht). Im Moment habe ich tatsächlich auch den Verdacht, dass nicht sauber unterschieden und deshalb nicht synchronisiert werden kann.
            Der Arduino soll nämlich parallel auch noch das Signal eines Bewegungsmelders verarbeiten und Audioausgaben über ein serielles MP3 Modul machen. Vielleicht stört das alles doch zu sehr!?
            Ich habe etwas über eine analogComp Bibliothek gelesen. Leider verstehe ich das alles noch zu wenig um die zu implementieren und den Code anzupassen. 🙁

            1. Weitere Tests: Der Sketch läuft… meistens… allerdings synchronisiert der DCF77 Empfänger nur, wenn ich am USB meines Rechners bin. Sobald ich ein Steckernetzteil vom Handy nehme, wird nichts mehr synchronisiert. Sollte die Stromaufnahme von einem Arduino Nano, einem HD 44780 Display und einem seriellen HW 311 mp3-Modul tatsächlich so hoch sein, dass der Empfänger nicht sauber arbeiten kann?

              Danke und viele Grüße
              Manuel

              1. Das kann ich mir nicht wirklich erklären. Ladestecker für Smartphones haben in der Regel mehr Power als ein PC-USB-Anschluss. Schwierig, das alles aus der Ferne zu beurteilen. Vielleicht fällt mir ja noch etwas ein, dann melde ich mich.
                VG, Wolfgang

                  1. Ja das tut sie. Sie merkt wenn der Akku sich füllt. Man kann ein Ladegerät aber auch zur Stromversorgung nutzen. Ich habe es eben ausprobiert: mit meinem Samsung Ladestecker kann ich einen Arduino Nano, an dem ein DCF77 hängt, betreiben und es klappt mit der Akualisierung. Was ich festgestellt habe, ist dass die Qualität der DCF77 Module schon recht unterschiedlich ist. Und ich habe festgestellt, dass der Empfang davon abhängig ist, wo ich die Antenne positioniere.

                    1. Ja, wirklich seltsam. Ich habe es mit 3 unterschiedlichen Ladegeräten versucht. Den Arduino und den DCF-Empfänger habe ich nicht bewegt, ich habe nur das USB-Kabel umgesteckt. Bei allen Ladegeräten keine synchronisation, beim Computer-USB ging es jedes Mal.

                      Der Empfänger ist übrigens der von Pollin.

                      Danke und herzlichen Gruß

  11. Hallo Wolfgang,

    zunächst vielen Dank für deine vielen tollen Beiträge. Für mich als Elektronik-Newbie ist deine Website eine unglaubliche Quelle an nützlichen Informationen und Inspirationen für meine ‚Basteleien‘.

    Dank deiner Beschreibungen und des Codes habe ich mein DCF77-Modul ohne größere Probleme auslesen können. Über die RTCLib werden die Werte in einen einzigen Ausgabewert umgewandelt, den ich in 74HC595 Shift-Register schiebe. Von dort werden sie über 74LS138 8-Channel De-Multiplier und CMOS4543 BCD-to-7-Segment auf LED-Displays ausgegeben. Funktioniert alles soweit prima, nach etwas mehr als zwei Minuten werden Zeit und Datum korrekt angezeigt. Danke deines Beitrags über ATtiny-Programmierung nun auch ohne Arduino. Dennoch: eine Menge ICs und Kabel. Um das Projekt übersichtlicher zu gestalten, habe ich versucht die Ausgabe auf einem 2×16 LCD (mit I²C-Schnittstelle) darzustellen. Prinzipiell funktioniert die Darstellung, nur Zeit und Datum kann der Arduino-Sketch anscheinend nicht mehr verarbeiten oder zeitgerecht (?) einbinden. Es scheint, als wenn die Ausgabe über die I²C-Schnittstelle sehr langsam ist, und vielleicht kollidieren für die internen Routinen für den Datentransfer über SCL und SDA mit dem Interrupt für das DCF-Modul? Eine Ausgabe von Zeit und Datum benötigt knapp 120ms. Nachdem ich die Anzeige nur verändere, wenn sich die Zeit tatsächlich ändert, konnte ich so die benötigte Zeit für den Schleifendurchlauf auf etwa 2ms drücken, jedoch wird jede Sekunde natürlich einmal das Display aktualisiert, also wieder 120ms. Mit dieser Modifikation scheint der Arduino jedoch zumindest irgendwelche Daten zu verarbeiten, allerdings erscheinen Zeit und Datum mit rein zufälligen Werten. Hast du eventuelle irgendwelche Empfehlungen für mich?

    Vielen Dank und viele Grüße

    Matthias

    1. Hallo Matthias,

      so auf die Ferne eine gewisse Herausforderung. So aus dem Stand kann ich nicht sicher sagen, woran es liegt. Interrupts sind allerdings immer gut für unerwartete, komplexe Fehler. Mir sticht aber auch in Auge, dass eine Displayaktualisierung 120 ms benötigt, das ist wirklich lang.
      Ein paar Fragen / Ideen:
      1) Lass das DCF77Modul mal ganz außen vor, indem du im Sketch die Interrupts abschaltest bzw. gar nicht erst aktivierst. Stelle die SoftRTC im Setup auf die Kompilierungszeit oder lege einfach irgendeine Zeit fest. Bekommst du dann vernünftige Ausgaben? Falls ja, dann ist zumindest bestätigt, dass die Programmteile für den DCF77 verantwortlich sind. Falls nein, dann liegt das Problem woanders.
      2) Die I2C Geschwindigkeit lässt sich mit Wire.setClock(clockFrequency) einstellen. Voreinstellung ist 100 kHz. Vielleicht lässt das Display 400 kHz zu? Probiere einfach mal Wire.setClock(400000) irgendwo ins Setup hinter Wire.begin(), vielleicht mit einem delay dazwischen (100 ms).
      3) Vielleicht kann ich auch etwas aus den willkürlichen Daten lesen, die dir angezeigt werden. Kannst du mir mal ein paar Werte nennen, die hintereinander angezeigt wurden?
      4) Ich würde mir gerne mal deinen ganzen Sketch anschauen. Zum Nachbauen und Ausprobieren fehlt mir die Zeit, aber vielleicht sehe ich ja was.
      5) Welchen ATtiny verwendest du?

      Da das Ganze jetzt etwas speziell wird und die Möglichkeiten in den Kommentarfeldern begrenzt sind, schlage ich vor, dass wir auf E-Mail umsteigen. Bitte schreibe an wolfgang.ewald@wolles-elektronikkiste.de.

      VG, Wolfgang

      1. Hallo Manuel,

        Das Problem rührt von den Netzteilen. Je nachdem, wie sie die Versorgungsspannung für den Verbraucher herstellen, sorgen sie Netzteile für Interferenzen.
        Ich kann so bewirken, dass auch kein Signal empfangen wird.
        Aus diesem Grund gehe ich nur über Akkus, da sie eine störungsfreie Stromquelle darstellen.

  12. Hallo Wolfgang,
    ich bin einmal mehr bei meinen „Basteleien“ auf deine Seite gestoßen. Wir hatten schon einmal wegen des Bootloaders Kontakt 😉 Danke für diesen schön erklärten Beitrag!

    Ein kleiner Nachtrag zu deinen Modulen. Die meisten DCF-Moduln, die jetzt (noch) angeboten werden, sind nicht „unempfindlich“, sondern werden am ARDUINO (selbst mit 3,3 V) an der Grenze betrieben. Sie sind eigentlich dafür ausgelegt, dass sie nicht einer Mignonzelle (1,5 V) betrieben werden. Dem zur Folge ist auch ihr Ausgang auf diese Spannung ausgelegt. So auch das Modul von Pollin
    https://www.pollin.de/p/dcf-77-empfangsmodul-dcf1-810054
    Bei diesem Modul und bei ziemlich vielen weiteren Moduln gibt es zusätzlich noch das Problem, dass der Signalausgang „hochohmig“ ist und durch den ARDUINO-Eingang sehr stark belastet wird, so dass vielfach ein Dekodieren des Signals nicht möglich ist.

    Es gibt einen Bausatz, der auch dieses Problem „umschifft“
    https://www.aatis.de/content/bausatz/AS732_DCF77-RX-Antenne

    Hier wird für das Pollin-Modul ein zusätzlicher Verstärker angeboten, der inkl. Modul, Verstärker und Gehäuse noch günstiger ist als das Modul von reichelt 😉
    Für den Bastler und Experimentierer ist dieser Bausatz ideal, da er direkt an den 5V- Arduino anzuschließen ist.

    Vielleicht ist dies noch für dich oder deine Leser interessant.

    Liebe Grüße
    Mathias

    1. Hallo, ich habe das Dcf77 Modul aus einen alten Wecker ausgebaut. Das Modul läuft auf 3Volt. Am Datenausgang, open collektor, kommt ein Pullup Widerstand von ca. 20 K Ohm. Das Signal ist so aber zu schwach.,daher habe ich einen Transistor in Kollektorschaltung nachgeschaltet. Dieser liefert als Impedanzwandler ein stabiles Signal von weniger als einen Volt. Ein weiterer Transistor, mit einer Kollektorspannung von 5 Volt (vom Arduino gespeist) , hebt den Pegel auf einen ausreichendenden Pegel(60% VCC). Diese Schaltung läuft bei mir stabil. Eine LED zeigt das Signal ein. Durch diese Schaltung wird das Signal nicht invertiert. Nun geht es bei mir an das Decodieren. Der Empfänger ist bisher sofort nach dem Einschalten synchron. Der PON ist erstmal bei mir auf Masse. Er kann zum Ausschalten des Module über einen weiteren Transistor und einem Spannungsteiler (5Volt zu 3Volt)auf high gezogen werden. Grüße aus Hamburg

  13. Hi, super erklärt und sehr sauber dokumentiert. Ich habe sehr viel gelernt mit deinem Modul. Ich hab hier ein paar Uhren, die leider wegen der nicht anders zu lösenden positioierung das Signal nicht richtig empfagen. Daher werde ich wohl ein NTCtoDCF Modul bauen und kann deine Software sehr gut zum Testen verwenden.

    Übrigens, wenn man ein Modil von ELV verwednet und es mittels PullUp Widerstand an den Arduino anschließt funktioniert die Erkennung ob der Interrupt aktiv ist nur mit einer Negierung der Abfrage also mit:

    if(!digitalRead(interruptPin)){
    ….
    }

    Nur falls noch jemand das gleiche Phänomen hat.

    Gruß Michael

  14. Hallo Wolfgang,
    die Sketche laufen perfect. Ich habe mir damit eine Funkuhr gebaut die neben Uhrzeit und Datum auch die Wochentage per LED anzeigt. Und jeweils zur vollen Stunde per JQ6500 Soundmodul das Horn der AIDASol erklingen läßt. Nachts natürlich nicht 😉 Leider kann ich hier ja kein Foto anhängen …

    Ich habe nun versucht „SoftRTC“ durch den DS3231-Baustein zu ersetzen und im Sketch „RTC_Millis“ durch „RTC_DS3231“ ausgetauscht. Bei mir funktioniert das aber nicht, der Serial-Monitor-Schirm bleibt leer. Woran könnte es liegen?

    Vielen Dank und viele Grüße

    1. Hallo Michael,

      danke für das Foto. Im Sketch fehlt ganz einfach ein „rtc.begin()“. Ich habe gerade nicht die Zeit alles zusammenzustecken und auszuprobieren. Probier mal selbst und melde dich, wenn es noch nicht funktioniert.

      …. und deine Uhr ist sehr sehenswert! Danke für das Foto.
      VG, Wolfgang

  15. Hallo Wolfgang,
    muss man in der Ausgaberoutine den 64bit Wert für currentBuf nicht um 32 bit nach rechts schieben, um die oberen 32 Bit zu bekommen? Du schiebst hier nur um 16 Bit.

    1. Hallo Peter,
      ups! Stimmt – komisch, dass es noch keinem aufgefallen ist. Wahrscheinlich, weil nichts damit gemacht wird, sondern nur zur Info ausgegeben wird. Vielen Dank! Werde es gleich ändern.
      VG, Wolfgang

  16. Hallo Wolfgang,
    Das von dir gezeigte DCF77 Modil kleiner Bauart, mit dem du weniger Erfolge erzieht hast, ist nehme ich an von dem Elektronik-Versender Reichelt. -> https://www.reichelt.de/index.html?ACTION=7&LA=3&OPEN=0&INDEX=0&FILENAME=C300%2FSPEZIFIKATIONF.NR.9500511.pdf
    Wohl dieses ?
    Das Modul ist sehr klein, aber auch sehr empfindlich, was die Restwelligkeit der Versorgungsspannung angeht.
    Wenn man das in den Griff bekommen hat, nicht mit einem Festspannungsregler wie 2950 oder LM78x, funktioniert es in einem sehr breiten Spannungsbereich von 1,2 Volt bis 5,0 Volt.
    Ich habe dazu eine Referenzspannungsquelle MCP1501-3302 ( 3,3 Volt Variante ) mißbraucht.
    Dann gibt das Modul keinen wirklich sauberes LOw-High Signal aus. Dem kommt man nur mit einem OPV oder einem Komparator bei. Am besten dafür eigenen sich Rail-to-Rail-OPVs die als Window-Komparator beschaltet sind, und den man die Schaltschwelle entsprechend der Vc auf den unteren Wert der HIGH-Pegels einstellt.
    Ansonsten super Beitrag und viele wertvolle Tipps

    1. Hallo, vielen Dank. Ich bin immer froh über solche konkreten Hinweise. Das Modul habe ich in „Amazonien“ gekauft, aber ich gehe auch davon aus, dass es mit dem Reichelt-Teil identisch ist. Kommt wahrscheinlich alles aus einer Fabrik.

  17. Hallo Wolfgang, ich finde deinen Beitrag sehr interessant aber an einem Punkt komm‘ ich nicht weiter.
    Mir fehlt diese Bibliothek: #include
    Du hast geschrieben, dass sie zur Arduino Grundaustattung gehört. Ich verwende die neueste IDE 1.8.19
    aber da konnte ich sie nicht finden. Wo bekomme ich diese Bibliothek/Datei her?
    auf https://github.com/omnidan/ArduinoPure/blob/master/tools/avr/lib/avr/include/util/parity.h
    sehe ich sie nur als Text-Datei. Kannst du mir da weiter helfen?
    LG Andre

    1. Hallo Andre, wahrscheinlich verwendest du keinen AVR Microcontroller (Arduino UNO, Nano, usw.), richtig? Ich habe nicht bedacht, dass die AVR Bibliotheken, von den utils/parity.h ein Teil ist, nicht eingebunden werden, wenn man einen anderen Mikrocontroller verwendet. Nachträgliches, manuelles Einbinden nützt nichts, da die Funktion in Maschinencode geschrieben ist, also hardwarespezifisch.

      Darf ich dich als Versuchskaninchen missbrauchen? Bitte lasse den include-Befehl weg und füge die folgende Funktion am Ende des Sketches hinzu:

      bool parity_even_bit(byte val){
      val ^= val >> 4;
      val ^= val >> 2;
      val ^= val >> 1;
      val &= 0x01;
      return val;
      }

      Das ist der Ersatz für die Funktion. Geht es?

      VG, Wolfgang

      1. Ich habe es selbst ausprobiert und es funktioniert. Die Ersatzfunktion ist jetzt in den Beispielsketchen integriert und muss nur entkommentiert werden. Die include Anweisung für parity.h muss entsprechend gelöscht oder auskommentiert werden.

      2. Wenn der gcc Compiler verwendet wird (was bei der Arduino IDE wohl der Fall ist), kann man auch die inkludieren und die Funktionen „__builtin_parity()“ und/oder „__builtin_parityl()“ verwenden. Letztere Funktion hat den Charme, dass sie 32 Bit „verkraftet“ und man somit die Parität des Datums in einem Rutsch berechnen kann z.B.:

        if ( (__builtin_parity(dcf77Seq->minutes) == dcf77Seq->parityBitMinutes) &&
        (__builtin_parity(dcf77Seq->hours) == dcf77Seq->parityBitHours) ) {
        dcf77Seq->parityTimeOK = true;
        }
        if(__builtin_parityl((isr_dcf77DataBuffer >> 36) &0x3FFFFF) == dcf77Seq->parityBitDate ) {
        dcf77Seq->parityDateOK = true;
        }
        Ich verwende das so bei einem Versuchsaufbau mit einem Arduino Nano Clone.

        Was ich aber noch los werden möchte: Dein Blog ist sehr gut gemacht und lehrreich. Mach weiter so 😉 *thumbup*

        Gruß Kai

        1. irgendwie ist „die stdlib.h inkludieren“ im Text verschwunden. Wahrscheinlich, weil ich das in spitzen Klammern geschrieben habe.

  18. Hallo Wolfgang,
    vielen Dank für Deine tolle Seite und die ausführlichen Erklärungen. Da gibt es auf einen Schlag Dutzende Tipps!
    Ich nutze das Modul von Pollin-Electronic, alle Sketche haben auf Anhieb funktioniert. Der Empfang ist Tag und Nacht stabil (Standort Schleswig-Holstein). Macht Spaß ….

  19. Hallo Wolfgang,
    vielen Dank für deine Mühe mit dieser Seite. Ich konnte mein DCF77-Modul mit deiner Software sofort in Betrieb nehmen. Es hat alles funktioniert wie beschrieben.
    Leider kursieren im Internet viele fehlerhafte Anleitungen.

    Viele Grüße,
    Bernd.

  20. Hallo,

    endlich habe ich eine Seite gefunden, mit deren Hilfe ich mein DCF77 Modul in Betrieb nehmen konnte.
    Vielen Dank für die sehr übersichtlichen Erklärungen und Beispielsketche.
    Ich habe schon Dutzende Seiten durch, aber nun ist es mir erstmal gelungen, das aktuelle Datum und die Zeit zu bekommen.
    Ok, zugegeben, im Zimmer, mit mehreren PCs und Monitoren ist es schwierig und nicht zuletzt hängt es ja auch von der Qualität der Antenne und des Empfängers ab,
    Sobald meine Monitore laufen, ist der Empfang quasi nicht mehr möglich, aber ich hatte ohnehin nur vor, meine Uhr 1x in der Nacht zu synchronisieren, was, wie ich denke, völlig ausreichend ist.

    Ich bin mal gespannt, wie der fertige Aufbau nachher laufen wird.

    Viele Grüße und schon mal ein schönes Weihnachtsfest,

    Andreas.

    1. Hallo Andreas,

      danke für’s Feedback. Hätte ich nicht gedacht, dass PCs und Monitore den Empfang so stören. Auf jeden Fall ein wertvoller Hinweis. Die auch ein schönes Weihnachtsfest!

      VG, Wolfgang

      1. Sei gegrüßt, Wolfgang.

        Ein kleines Feedback zu meiner noch im Bau befindlichen Uhr.
        Ich habe die Empfangseigenschaften etwas verbessern können, indem ich die Antenne (Ferritkern) über ein ca. 0.5m langes, abgeschirmtes Kabel an das DCF Mudul (das erste Bild hier, ganz oben) angeschlossen habe. Die Masse des Kabels habe ich einseitig mit Masse des DCF Moduls verbunden, um Einstreuungen zu vermindern.
        Zusätzlich habe ich direkt an den Lötaugen der Spannungsversorgung des Moduls noch einen 0.1uF Kondensator angelötet.

        Ich hatte u.A. festgestellt, dass in der Nähe des Arduino NANO, der über einen Stepup Spannungswandler von einem 3.7V Akku versorgt wird, wohl auch ganz „wilde“ Schwingungen auftreten müssen und der Empfang daher fast unmöglich war.

        Als meine Schaltung noch ohne Stepup Regler auf einem Breadboard lief, trat dies nämlich nicht auf.

        Außerdem habe ich an der USB Buchse, über die später auch der Akku geladen wird, ebenfalls einen 0.1uF Kondensator angelötet, da die Spannung aus PC entgegen allen Erwartungen eher „unsauber“ zu sein scheint.

        Grüße,

        Andreas

  21. Hallo Wolfgang,

    erst einmal ein Lob auf diese gelungene Website. Ich konnte mir schon einige wichtige Anregungen holen. Mein DCF77-Modul weigert sich bislang aber, mir eine korrekte Zeitangabe zu machen. Ich verwende ein Modul wie das auf Deinem ersten Bild mit zwei P-Anschlüssen, verbunden habe ich nur den ersten an Pin 2:
    Das erste Testprogramm lässt bereits nach wenigen Sekunden die LED blinken. Doch schon bei der sequence-evaluation erhalte ich nur Listen von Wertepaaren, die bis 255 hochgezählt werden und dann wieder bei 0 beginnen. Der zweite Wert bewegt sich in einer Größenordnung <250.
    Verwende ich die letzten beiden Sketche wird beim vorletzten nur die Zeit seit dem 01.01.2000 hochgezählt, beim letzten erscheint nur der Hinweis, dass auf den Beginn der Sequenz gewartet wird.
    Das Modul steht im ersten Stock vor einem Fenster, die Antenne ist senkrecht aufgestellt, was die Schnelligkeit des Empfangs deutlich erhöht hat.
    Was kann ich noch tun, um ein gültiges Zeitsignal zu erhalten?

    Danke vorab,
    Thomas

    1. Hallo Thomas, tut mir leid, dass es nicht funktioniert. Auf die Ferne ist es immer schwer Fehler zu finden. Immerhin blinkt es schon mal, dass ist ja ein Anfang. Wenn die Wertepaare bis 255 hochzählen, zeigt das, dass das lange Minutensignal nach Bit 58 nicht gefunden wird. Da der Zähler als Byte definiert ist, ist das dann nur folgerichtig. Kommen die Wertepaare denn im Sekundentakt? Wenn ja, ist das das nächste gute Zeichen. Und wie sehen die Wertepaare aus. Wenn ich das richtig verstanden habe liegt der zweite Wert immer irgendwo kleiner 250? Wie sehen denn die ersten Werte aus? Hast du vielleicht eines der Module, die ein umgedrehtes Signal abgeben, also kurzes Low und langes High? Das bekommt man ja leicht heraus. Wenn das der Fall ist, müsstest du die High / Low Logik in den Sketchen umdrehen oder das Signal invertieren. Letztere habe ich hier beschrieben:
      https://wolles-elektronikkiste.de/ltc6995-long-timer-low-frequency-oscillator#Power-On-Reset
      Dem Link folgen und ein wenig herunterscrollen.
      VG, Wolfgang

  22. Sehr schöner Beitrag. Ich möchte nur eine Sache noch hinzugeben:
    Der Ausgang der DCF77 Module ist wohl in der Regel ein OpenCollector Ausgang. Das heißt man muss für einen uC den internen PULLUP aktivieren oder einen externen PULLUP Widerstand nutzen. Möglichst keinen zu „steifen/niederohmigen“ PULLUP, da der Ausgangsstrom des Signals sonst nicht reicht ihn auf Masse zu ziehen.
    Ich kann mein DCF Modul (von ELV) problemlos am Arduino UNO auslesen, aber nur, wenn ich den internen PULLUP einschalte. Eine Verstärkerschaltung für das Signal (und diese Ansicht haben glaube ich viele) ist in der Regel nicht nötig, da der CMOS Input eines uC viele MOhm hat und die Signalquelle nicht stark belastet.

  23. Hallo Wolfgang,
    wirklich gute Rutinen für DCF77!!
    Ich habe deinen Code mit RTC_DS3231 statt RTC_MILLIS versucht, das brachte das Programm zu HUNG beim Übertrag der DCF Zeit zur I2C_RTC in der ISR.
    Die Lösung war das setzen der RTC aus der ISR in die Main LOOP zu verlegen.
    Die dazu nötigen Anpassungen waren: Variablen aus der ISR als volatile im Header zu definieren und anstatt des rtc.adjust(DateTime… in der ISR, einen Flag zu setzen. Das rtc.adjust(DateTime… in die Main verlegen … Flag reset und fertig.
    Hardware: LGT8F328P-LQFP32 MiniEVB + DCF77 Pollin
    PS: rtc.begin() nicht vergessen…

  24. Hallo Wolfgang,

    super Beitrag!!!
    Kannst Du bitte Sagen, wo Du Dein Modul gekauft hast. Ich hatte mit zwei Modulen (ähnlich deinem zweiten Modul) große Empfangsprobleme.
    Als Hilfe hatte ich einen Artikel in der HEISE MAKE.
    Ich habe es dann gelassen und einen ESP_32 per ntp synchronisiert.

    Gruß Harald!

    1. Hallo Harald,

      das Teil ist von Amazon:

      https://www.amazon.de/Unbekannt-Empfangsmodul-Funkzeit-Funkuhr-Frankfurt/dp/B07RRXRY36/ref=mp_s_a_1_5?dchild=1&keywords=dcf77&qid=1613815244&sprefix=dcf&sr=8-5

      Sporadisch habe ich damit auch mal Empfangsstörungen, aber im großen und ganzen bin ich zufrieden. Hängt nicht nur vom Modul ab. Manchmal nützt es schon, die Antenne anders auszurichten und sie von Störfaktoren wie elektrischen Geräten und Kabeln fernzuhalten.

      VG , Wolfgang

  25. Sehr toller Bericht und sehr interssante Infurmationen zur Funktion der Funkuhr Übertragung
    Weiter so Wolfgang

  26. Hallo Wolfgang,

    toller Beitrag!
    Frage wie wäre es wenn du mit dem Interupt nur ein Flag setzt, und dann mit dem Flag die ISR als funktion in der loop() ausführst?
    Würde es dann damit dann auch funktionieren?

    viele Grüße
    Frank

    1. Ja, das geht auch. Man darf dann nur nicht zu viel anderes in der loop() machen, was die Auswertung zu sehr verzögert. Solange man sicherstellen kann, dass man den Flag nach sagen wir mal nach spätestens einer hundertstel Sekunde behandelt, würde das gehen. Normalerweise bin ich auch eher ein Freund einer solchen Vorgehensweise.

  27. Ich habe das mal zu einer Zeit, als es noch keine Arduino und kein C für die Megas gab in Assemler
    programmiert.
    Ein anderes , auch sehr effizientes Prinzip:
    Über einen Timer -Interrupt werden alle z.b. 5 ms Zeitscheiben-Unterprogramme aufgerufen.
    Eines zählt die Aufrufe bis zum Wechsel am Eingang, speichert jew. ‚1‘ oder ‚0‘ , bei Pause >100 ms wird dann ausgewertet.
    Zur Datensicherung bei der Übertragung werden zwei aufeinander folgende Zeiten verglichen, die dürfen sich dann nur um 1 Minute unterscheiden. Der Einfachheit halber dürfen sich die Werte für Tag, WTag, Jahr etc gar nicht unterscheiden.
    Das Zeitscheiben – Verfahren lässt eine grosse Zahl gleichzeitig ablaufender Prozesse zu.
    In den Üblichen Steuerungen mit Mega168 ff laufen Scheiben mit 0,125 , 1, 5, 20, 100 ms und etwa 100 unabhängige Prozesse, viele davon natürlich nur in der 20 oder 100 ms Schleife.
    Empfehle sehr, das mal zu probieren. Die Module von Conrad damals haben sehr gut funktioniert,
    5 V- Betrieb und Transistor-Ausgang.

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert