Anleitung Arduino mit DCF77-Modul und RTC Echtzeituhr DS3231

BAXL

Admin
Mitarbeiter
Arduinoprojekte benötigen oft das genaue Datum und die genaue Uhrzeit. Der Arduino hat zwar einen internen Timer, den man aber nicht wirklich gut für solche Zwecke verwenden kann. Als Alternativen gibt es Uhrenmodule (RTC - DS1302 oder DS3231), die aber oft auch nur mehr schlecht als recht funktionieren. Das DS3231 ist zwar schon sehr genau, weil es Schwankungen der Quarzfrequenz durch eine Temperaturmessung kompensiert, beim DS1302 hat man mit größeren Abweichungen zu kämpfen, hauptsächlich, wenn diese häufig abfragt werden. Besonders unangenehm ist es, wenn diese Module ihre Zeit komplett vergessen oder mit der Zeit komplett falsch gehen. Nicht zu vergessen die im Moment noch aktuellen Zeitumstellungen.

Für einen regelmäßigen Zeitabgleich eignen sich DCF77-Module, d.h., dass im normalen Programmablauf die Zeit aus einem RTC-Modul gezogen, dieses aber in gewissen Zeitabständen mit der "Atomuhr" synchronisiert wird. Normalerweise kommt es nicht auf ein paar Sekunden Abweichung an, sollte die Abweichung aber größer werden, sollte schon eine Korrektur erfolgen. Eine manuelle Korrektur erfordert leider einigen Umstand, sei es, dass man das RCF-Modul abklemmt und an einem zweiten Arduino neu stellt, oder indem man irgendeine Routine implementiert, bei der man mit Tastern umständlich neu hantiert.

Für meine Zwecke habe ich ein ELV Modul gekauft, das bereits einen belastbaren Ausgang bietet (open collector) und mit Spannungen zwischen 12,V und 15V betrieben werden kann.



Die Beschaltung gestaltete sich entsprechend einfach, weil ich zusätzlich nur einen 10 kiloOhm Widerstand von +5V auf den Signalpin legen musste. Erwähnenswert ist dabei, dass das Signal durch diese Schaltung invertiert wird. Das ist wichtig, wenn man den Pin mit einem Arduinoprogramm auswertet. Es gibt eine Vielzahl Librarys, die einem die Arbeit erleichtern, man muß bei meinem speziellen Modul dem Programm allerdings bei der Initialisierung mitteilen, dass das Signal invertiert ist. Dazu setzt man einfach einen zusätzlichen Parameter, aber dazu später mehr.
 
Zuletzt bearbeitet:

BAXL

Admin
Mitarbeiter
Für den Betrieb dieser Schaltung verwende ich ein Beispielprogramm von der Seite https://netcoast.ch/DCF77/DCF77.html. Das programm wurde von mir noch um die Ausgabe auf einem 20x4 LCD-Display erweitert.

C++:
//Beispielcode um einen Arduino Nan Clone mit DCF77 Modul von ELV zu verbinden und Zeit auszulesen.
//Mutationen durch slaps313 - 2018
//DCF77.h und TimeLib.h wurden ohne Anpassungen verwendet.
//DCF77 Bibliothek wurde von hier erhalten: http://thijs.elenbaas.net/downloads/?did=1
//TimeLib.h Bibliothek wurde von hier erhalten: https://github.com/PaulStoffregen/Time
//Die Sketch-Vorlage wurde von hier geladen (hier werden auch andere Module behandelt): https://www.issb.de/mw/index.php/Test_elektronischer_Funkuhren-Module_(DCF77)_am_Mikroconcroller

#include "DCF77.h" //Alle DCF77 Funktionen laufen ueber diese Bibliothek.
#include "TimeLib.h" //Time.h hat bei mir nie funktioniert, habe TimeLib.h eingebunden, dann gehts.

#define DCF_PIN 2 // Verbindung zum DCF77 Arduino UNO PIN
#define DCF_INTERRUPT 0 // Interrupt number associated with pin
#define PIN_LED 13 // Status von der Verbindung an LED-PIN 13

// Librarys für LCD-Display
#include <Wire.h>
#include <LiquidCrystal_I2C.h>

LiquidCrystal_I2C lcd(0x27, 20, 4); //Hier wird festgelegt um was für einen Display es sich handelt.

//time_t time;
// DCF77 DCF = DCF77(DCF_PIN, DCF_INTERRUPT); // Dieser Eintrag sollte für andere Module aktiviert werden...
// für das DCF-Modul von ELV muss das Signal invertiert werden, deshalb so:
DCF77 DCF = DCF77(DCF_PIN, DCF_INTERRUPT, false); // false für invertiertes Signal
// wurde ein gueltiges Signal gefunden
bool g_bDCFTimeFound = false;

void setup()
{
pinMode(PIN_LED, OUTPUT);
Serial.begin(9600);

lcd.init(); //Im Setup wird der LCD gestartet
lcd.backlight(); //Hintergrundbeleuchtung einschalten (lcd.noBacklight(); schaltet die Beleuchtung aus).

DCF.Start();
lcd.setCursor(0, 3); // Spalte, Zeile,  0, 0 entspricht 1. Spalte oder 1. Zeile
lcd.print("DCF77 mit LCD V1.0");
Serial.println("Warte auf DCF77 Zeit... ");
Serial.println("Es dauert in den meisten faellen mindestens 2 Minuten bis die Zeit aktualisiert wird.");
}

void loop()
{
// delay(950);
// das Signal wird nur alle 5 Sekunden abgefragt
delay(5000);

digitalWrite(PIN_LED, HIGH);
delay(50);
digitalWrite(PIN_LED, LOW);

time_t DCFtime = DCF.getTime(); // Check if new DCF77 time is available
if (DCFtime != 0)
{
Serial.println("Aktuelle Zeit wurde empfangen!");
setTime(DCFtime);
g_bDCFTimeFound = true;

}

// die Uhrzeit wurde gesetzt, also LED nach kurzer Zeit ein
if (g_bDCFTimeFound)
{
delay(50);
digitalWrite(PIN_LED, HIGH);
}
digitalClockDisplay();
}

void digitalClockDisplay()
{
// Anzeigen der Zeit auf dem 20x4 LCD-Display
 
lcd.setCursor(0, 0);
if (hour()<10){lcd.print("0");}
lcd.print(hour()); lcd.print(":");
if (minute()<10){lcd.print("0");}
lcd.print(minute()); lcd.print(":");
if (second()<10){lcd.print("0");}
lcd.print(second());

lcd.setCursor(0, 1);
if (day()<10){lcd.print("0");}
lcd.print(day()); lcd.print(".");
if (month()<10){lcd.print("0");}
lcd.print(month()); lcd.print(".");
lcd.print(year());

// Anzeigen der Zeit auf dem seriellen Monitor am PC
Serial.print(hour());
printDigits(minute());
printDigits(second());
Serial.print(" ");
Serial.print(day());
Serial.print(" ");
Serial.print(month());
Serial.print(" ");
Serial.print(year());
Serial.println();
}

void printDigits(int digits)
{
// Kleines Skript um bei Minuten und Sekunden eine 0 vornean zu stellen
Serial.print(":");
if(digits < 10)
Serial.print('0');
Serial.print(digits);
}
Monitorausgabe ohne Display:



Die komplette Schaltung mit Display sieht so aus:

 
Zuletzt bearbeitet:

BAXL

Admin
Mitarbeiter
Jetzt kommt der schwierigere Teil des Projektes, nämlich der Abgleich einer angeschlossenen Echtzeituhr (RTC). Dafür sind mehrere Strategien, auch in Kombination, denkbar.

  1. Die RTC wird zum ersten Mal in Betrieb genommen und hat noch keine brauchbare Zeiteinstellung.
  2. Die RTC wurde bereits gestellt, hat aber eine signifikante Abweichung bei den Sekunden, bzw. schon in den Minuten.
  3. Die RTC hat die Uhrzeit "vergessen" und muß neu gestellt werden.
  4. Es hat eine Umstellung von Sommer- auf Winterzeit gegeben, oder umgekehrt.

Weil die Abfrage der DCF77 Zeit Rechenzeit abfordert ist es ineffizient den Abgleich permanent durchzuführen, zumal wenige Sekunden Abweichung für die meisten Anwendungsfälle tolerierbar sind. Ich denke 10 Sekunden Differenz ist noch akzeptabel. Eine Überprüfung pro Stunde reicht vollkommen aus, es sei denn, die RTC liefert eine Zeit, oder ein Datum, das offensichtlich nicht stimmen kann (Plausibilitätsprüfung). Wahrscheinlich ist es sogar genug, nur einmal pro Tag zu synchronisieren.

Die Abgleichroutine wird damit aufwändig genug. Das Ganze muß sich noch gut in den übrigen Programmablauf einfügen und darf nicht stören.
 

BAXL

Admin
Mitarbeiter
Neuer Zwischenstand.

Parallel zur DCF77 Uhr läuft jetzt eine RTC DS3231. Das Programm wurde derart modifiziert, dass die Uhrzeiten der DCF (1. Zeile) und der RTC-Uhr(2. Zeile) angezeigt werden. Weiterhin wird angezeigt, ob das DCF-Signal ausgewertet wird (Zeile 3) und wann die letzte Synchonisation statt gefunden hat (unterste Zeile 4). Es wird nicht mehr bei jedem gültigen DCF-Empfang die RTC neu gestellt, sondern nur noch wenn die Schaltung neu gestartet wurde und die RTC "verstellt" ist und dann immer in einem vordeffinierten Zeitabstand (aktuell alle 2 Minuten). Der nächste Schritt wird die Verlängerung des Intervalls sein und dass die DCF-Signalauswertung in der Zeit zwischen den Aktualisierungen deaktiviert wird.



Das momentane, funktionierende Programm sieht so aus:

C++:
//Code um einen Arduino Nano Clone mit DCF77 Modul von ELV zu verbinden und Zeit auszulesen.
//Zur Überbrückung von Empfangsstörungen, bzw. wenn die DCF-Signalauswertung deaktiviert wird,
//steht eine RTC DS3231 parat, die parallel läuft

//DCF77.h und TimeLib.h wurden ohne Anpassungen verwendet.
//DCF77 Bibliothek wurde von hier erhalten: http://thijs.elenbaas.net/downloads/?did=1
//TimeLib.h Bibliothek wurde von hier erhalten: https://github.com/PaulStoffregen/Time
//Die Sketch-Vorlage wurde von hier geladen (hier werden auch andere Module behandelt): https://www.issb.de/mw/index.php/Test_elektronischer_Funkuhren-Module_(DCF77)_am_Mikroconcroller


// Librarys für DCF77 Funkuhrmodul
#include "DCF77.h" //Alle DCF77 Funktionen laufen ueber diese Bibliothek.
#include "TimeLib.h" //Time.h hat bei mir nie funktioniert, habe TimeLib.h eingebunden, dann gehts.

#define DCF_PIN 2 // Verbindung zum DCF77 Arduino UNO PIN
#define DCF_INTERRUPT 0 // Interrupt number associated with pin
#define PIN_LED 13 // Status von der Verbindung an LED-PIN 13

// DCF77 DCF = DCF77(DCF_PIN, DCF_INTERRUPT); // Dieser Eintrag sollte für andere Module aktiviert werden...
// für das DCF-Modul von ELV muss das Signal invertiert werden, deshalb so:

DCF77 DCF = DCF77(DCF_PIN, DCF_INTERRUPT, false); // false für invertiertes Signal
bool g_bDCFTimeFound = false; // wurde ein gueltiges DCF77 Signal gefunden?

// Librarys für LCD-Display
#include <Wire.h>
#include <LiquidCrystal_I2C.h>

LiquidCrystal_I2C lcd(0x27, 20, 4); //Hier wird festgelegt um was für einen Display es sich handelt.

// Library für RTC DS3231
// Das Uhrenmodul wird angeschlossen A4 - SDA, A5 - SCL
// (Jahr, Monat, Tag, Stunde, Minute, Sekunde)
// rtc.adjust(DateTime(2019, 1, 21, 3, 0, 0));

#include "RTClib.h"
RTC_DS3231 rtc; // Es wird eine Objekt rtc aus der Klasse RTC_3231 der RTClib.h erstellt


// Variablen für die Auswertung und den Abgleich der Zeiten
byte ErstStart = 1;
byte DCFaktiv = 0;
byte DCFZeitOK = 0;
long Intervall = 120000; // Abfrageintervall 2 Minuten
long UpdateTimer = 0; // Zwischenspeicher für die Wartezeit

void setup()
{
pinMode(PIN_LED, OUTPUT);
Serial.begin(9600); // Seriellen Monitor starten

DCF.Start(); // DCF Uhr starten
DCFaktiv=1; // Merker, dass das DCF77 Signal ausgerwetet wird, man kann das mit DCF.Stop(); anhalten

rtc.begin(); // RTC DS3231 starten

lcd.init(); //Im Setup wird der LCD gestartet
lcd.backlight(); //Hintergrundbeleuchtung einschalten (lcd.noBacklight(); schaltet die Beleuchtung aus).
lcd.setCursor(0, 3); // Spalte, Zeile,  0, 0 entspricht 1. Spalte oder 1. Zeile
lcd.print("DCF77 RTC LCD V1.0");
lcd.setCursor(0, 2); // Spalte, Zeile,  0, 0 entspricht 1. Spalte oder 1. Zeile
lcd.print("DCF77 aktiv: "); lcd.print(DCFaktiv);

Serial.println("Warte auf DCF77 Zeit... ");
Serial.println("Es dauert meistens ca. 2 Minuten bis die Zeit aktualisiert ist");
}

void loop()
{
// das Signal wird nur alle 5 Sekunden abgefragt
delay(5000);

digitalWrite(PIN_LED, HIGH);
delay(50);
digitalWrite(PIN_LED, LOW);

DCFZeitOK = 0; // noch kein gültiges DCF-Telegramm empfangen
time_t DCFtime = DCF.getTime(); // Prüfen ob eine neue DCF77 Zeit verfügbar ist

  if (DCFtime != 0)
  {
    Serial.println("Aktuelle Zeit wurde empfangen!");
    setTime(DCFtime);
    g_bDCFTimeFound = true;
    DCFZeitOK=1;
  }

  // die Uhrzeit wurde gesetzt, also LED nach kurzer Zeit ein
  if (g_bDCFTimeFound)
  {
    delay(50);
    digitalWrite(PIN_LED, HIGH);
  }

if ((DCFZeitOK=1)&&(year()>2019)&&(((millis()-UpdateTimer) > Intervall)) )
{
  ErstStart = 0;
  // Wenn eine gültige DCF77 Zeit vorliegt, das DCF Jahr > 2019 und Intervall vergangen ist, die RTC aktualisieren
  RTCUpdate();
  UpdateTimer = millis();
  lcd.setCursor(0, 3); // Spalte, Zeile,  0, 0 entspricht 1. Spalte oder 1. Zeile
  lcd.print("Sync: ");
  DateTime now = rtc.now();
  //Stunde = (now.hour()); Minute = (now.minute()); Sekunde = (now.second());
  if (now.hour() <10){lcd.print("0");} lcd.print(now.hour()); lcd.print(":");
  if (now.minute() <10){lcd.print("0");} lcd.print(now.minute()); lcd.print(":");
  if (now.second() <10){lcd.print("0");} lcd.print(now.second());  lcd.print("    ");
}

digitalClockDisplay(); // DCF77 Zeit anzeigen
RTCClockDisplay(); // RTC- Zeit anzeigen
}

void RTCUpdate()
{


// (Jahr, Monat, Tag, Stunde, Minute, Sekunde)
// rtc.adjust(DateTime(2019, 1, 21, 3, 0, 0));
Serial.println("Stelle RCT neu! ");
Serial.print(hour());
printDigits(minute());
printDigits(second());
Serial.print(" ");
Serial.print(day());
Serial.print(" ");
Serial.print(month());
Serial.print(" ");
Serial.print(year());
Serial.println();
   // Datum und Uhrzeit der FCF77 in RTC übertragen
   rtc.adjust(DateTime(year(), month(), day(), hour(), minute(), second()));
}

void RTCClockDisplay()
{
  // Anzeigen der RTC-Zeit auf dem 20x4 LCD-Display
  int Stunde; int Minute; int Sekunde;
  int Tag; int Monat; int Jahr;

  DateTime now = rtc.now();
  Stunde = (now.hour()); Minute = (now.minute()); Sekunde = (now.second());
  Jahr = (now.year()); Monat = (now.month()); Tag = (now.day());

  lcd.setCursor(0, 1);
  if (Stunde <10){lcd.print("0");} lcd.print(Stunde); lcd.print(":");
  if (Minute <10){lcd.print("0");} lcd.print(Minute); lcd.print(":");
  if (Sekunde <10){lcd.print("0");} lcd.print(Sekunde); lcd.print(" ");

  //lcd.setCursor(0, 1);
  if (Tag <10){lcd.print("0");} lcd.print(Tag); lcd.print(".");
  if (Monat <10){lcd.print("0");}lcd.print(Monat); lcd.print(".");
  lcd.print(Jahr);
}

void digitalClockDisplay()
{
// Anzeigen der Zeit auf dem 20x4 LCD-Display

lcd.setCursor(0, 0);
if (hour()<10){lcd.print("0");} lcd.print(hour()); lcd.print(":");
if (minute()<10){lcd.print("0");} lcd.print(minute()); lcd.print(":");
if (second()<10){lcd.print("0");}lcd.print(second()); lcd.print(" ");

//lcd.setCursor(0, 1);
if (day()<10){lcd.print("0");} lcd.print(day()); lcd.print(".");
if (month()<10){lcd.print("0");} lcd.print(month()); lcd.print(".");
lcd.print(year());

// Anzeigen der Zeit auf dem seriellen Monitor am PC
Serial.print(hour());
printDigits(minute());
printDigits(second());
Serial.print(" ");
Serial.print(day());
Serial.print(" ");
Serial.print(month());
Serial.print(" ");
Serial.print(year());
Serial.println();
}

void printDigits(int digits)
{
// Kleines Skript um bei Minuten und Sekunden eine 0 vorne an zu stellen
Serial.print(":");
if(digits < 10)
Serial.print('0');
Serial.print(digits);
}
Komplette Schaltung:

 
Zuletzt bearbeitet:

BAXL

Admin
Mitarbeiter
Das Programm aus Post #5 ist jetzt einen ganzen Tag durchgelaufen. Es gab keine Probleme und die RTC wurde zuverlässig alle 2 Minuten aktualisiert. Es gab auch keine Unregelmäßigkeiten beim Betrieb sowohl am PC als auch an einem USB-Netzteil. Ich erwähne das, weil ich bei vielen Berichten über Probleme von "unsauberen" Schaltnetzteilen gelesen habe. Die DCF77 Antenne war im 1. OG stets waagrech in südwest Richtung ausgerichtet und lag quasi 1m neben meinem PC.

Zwischenzeitlich ist ein zweites Projekt für eine genaue Zeitbasis dazugekommen, die Verwendung der GPS-Satellitendaten. In jedem GPS-Signal steckt auch die genaue UTC-Uhrzeit, diese Information kann man aus dem übrigen serellen Datenstrom herausfiltern und ebenfalls zum Stellen der Uhrzeit verwenden. In einem anderen Projekt stelle ich das vor: Arduino - Zeitabgleich mit GPS-Modul
 
Zuletzt bearbeitet:

crazysky

Neuer Benutzer
Das ist ja super! Genau so etwas habe ich gesucht. Gibt es inzwischen schon einer Version mit einem stündlichen Abgleich?
Grüße
 

BAXL

Admin
Mitarbeiter
Das ist ja super! Genau so etwas habe ich gesucht. Gibt es inzwischen schon einer Version mit einem stündlichen Abgleich?
Grüße
Hallo Crazysky,

wie oft Du den Abgleich machen willst, kannst Du im Programm ganz einfach selbst einstellen.
Dazu muß nur die Zeile
long Intervall = 120000; // Abfrageintervall 2 Minuten
entsprechend angepasst werden in
long Intervall = 3600000; // Abfrageintervall 60 Minuten

Ein kürzeres Intervall ist bei einer Digitalanzeige aber kein Drama, weil die Anzeige direkt wieder da ist. Analoge Uhren, also mit Zeigern, haben idR. längere Intervalle, teilweise sogar nur einmal am Tag. Das ist aber dem Umstand geschuldet, das dafür der Minutenzeiger 12x die Runde machen muß. Das ist störend, weil in der Zeit die Uhrzeit nicht abgelesen werden kann und die Uhr Laufgeräusche macht. Hinzu kommt der höhere Stromverbrauch für die Mechanik beim Umstellen. Ich würde bei einer Digitalanzeige das Intervall deshalb nicht länger als eine Stunde und nicht kürzer als zwei Minuten machen. Wenn der DCF77-Empfänger als Zeit-Basis dient, ist kürzer als 2 Minuten sowieso sinnlos, weil nur jede Minute das aktuelle Zeitsignal empfangen wird.

Edit:

Bei meiner Variante mit GPS-Modul mache ich einen Abgleich sogar erst, wenn ich eine Abweichung von 1s habe.
Arduino - Zeitabgleich mit GPS-Modul
 
Zuletzt bearbeitet:

crazysky

Neuer Benutzer
Hallo,

vielen Dank für die schnelle Antwort!
Ich habe eher schlechte Erfahrungen gemacht, mit so großen intervall-Zahlen. Ich habe mit der Hilfe aus einem anderen Forum eine elegantere Lösung gestern erfolgreich eingebaut.
Hierbei wird zu jeder vollen Stunde einmal abgeglichen.

Code:
static byte stdMerker = 255;


if ((DCFZeitOK=1)&&(year()>2019)&&(hour()!=stdMerker) )
{
  Serial.println("Bedingungen für das Synchroniseren der Zeit sind erfüllt");
  ErstStart = 0;
  Serial.print("Merker derzeit:");
  Serial.println(stdMerker);
  // Wenn eine gültige DCF77 Zeit vorliegt, das DCF Jahr > 2019 und die aktuelle Stunde ungleich derjenigen ist, die im Merker noch hinterlegt ist, wird die RTC aktualisiert
  RTCUpdate();
  DateTime now = rtc.now();
  Serial.println("Merker neu setzen");
  stdMerker = hour();
}
else
{
  Serial.print("Bedingungen für das Synchroniseren der Zeit sind NICHT erfüllt");
  Serial.println();
}
Gruß
 

BAXL

Admin
Mitarbeiter
Das sieht doch gut aus. Jeder gestaltet sein Programm nach eigenen Wünschen und Anforderungen. Wichtig ist nur, dass man eine übersichtliche und verständliche Basis hat, die nicht mit unnötigen Gadgets überladen ist. Es fällt schon schwer genug sich in ein fremdes Programm reinzudenken, da machen trickreiche Speziallösungen das Verständnis zusätzlich schwer. Weiterhin viel Erfolg. Wenn Du Fragen hast, Du weißt ja wo Du uns findest :)
Auf jeden Fall vielen Dank für Deine Rückmeldung und die konstruktive Ergänzung.

Gruß
BAXL
 
Zuletzt bearbeitet:
Top Bottom