Funkgesteuerte Uhr mit Arduino erstellen

Wie funktionieren eigentlich die funkgesteuerten Uhren, die automatisch die korrekte Uhrzeit und das Datum anzeigen?
Warum benötigen diese immer so lange nach einem Batteriewechsel, bis diese wieder die Uhrzeit anzeigen und wie kann ich das selber mit einem Arduino erstellen?
Damit diese Funkuhren funktionieren, nutzt man die Zeitzeichen des Langwellensender DCF77 aus Mainflingen bei Frankfurt, der auf der Frequenz 77,5KHz diese Informationen funkt.
Das DCF77-Signal kann – abhängig von der Tages- und Jahreszeit – bis zu einer Entfernung von etwa 2000 km empfangen werden.

Quelle und weitere Details zum Langwellensender: DCF77 – Wikipedia und DCF77 – PTB.de

Verwendete Hardware:
Arduino Uno
DCF77 Antenne 77,5Khz
Flüssigkristallanzeige LCD 1602
I2C Adapter LCM1602

Verdrahtung:

Quellcode und Bibliothek:
„DCF77“ Library von Thijs Elenbaas in der Version 1.0.0
„Time“ Library von Paul Stoffregen in der Version 1.6.0


Datenblatt: DCF77-Modul

DCF77-Modul Pin-Belegung

Decodierung:
Seine im Sekundentakt gesendeten Zeitzeichen übertragen innerhalb einer Minute die mitteleuropäische Zeit in folgender Aufteilung:



Signalunterbrechung 100ms = 0, 200ms = 1
Bit (Sek) Bedeutung der Werte Beispiel (21.10.2021 23:55Uhr)
60 Start der Minute „0“ oder (kein Signal bei Schaltsekunde – Dann aber Sek 59 eine = 0) 0 Sek 59 hatte kein Signal




1 bis 14 seit Dez. 2006: Wetterinformationen, sowie Informationen des Katastrophenschutzes





15 Rufbit (bis Mitte 2003 Reserveantenne) 0 Anlage OK
16 „1“: Am Ende dieser Stunde wird MEZ/MESZ umgestellt. 0 Keine Umstellung
17 Mitteleuropäische Zeit / Sommerzeit 1 Sommerzeit
18 „01“: MEZ oder „10“: MESZ 0
19 „1“: Am Ende dieser Stunde wird eine Schaltsekunde eingefügt.(eine Stunde vorher) 0 Keine Umstellung
20 Immer „1“: Beginn der Zeitinformation 1 Immer
21 Minute (Einer) Bit für 1 1 1010101 = 55
22 Bit für 2 0
23 Bit für 4 1
24 Bit für 8 0
25 Minute (Zehner) Bit für 10 1
26 Bit für 20 0
27 Bit für 40 1
28 Parität Minute
0 4 Einser-Bits durch zwei = 2,0
29 Stunde (Einer) Bit für 1 1 110001 = 23
30 Bit für 2 1
31 Bit für 4 0
32 Bit für 8 0
33 Stunde (Zehner) Bit für 10 0
34 Bit für 20 1
35 Parität Stunde
1 3 Einser-Bits durch zwei = 1,5
36 Kalendertag (Einer) Bit für 1 1 100001 = 21
37 Bit für 2 0
38 Bit für 4 0
39 Bit für 8 0
40 Kalendertag (Zehner) Bit für 10 0
41 Bit für 20 1
42 Wochentag (Mo=1, So=7) Bit für 1 0 001 = 4 (Donnerstag)
43 Bit für 2 0
44 Bit für 4 1
45 Monat (Einer) Bit für 1 0 00001 = 10
46 Bit für 2 0
47 Bit für 4 0
48 Bit für 8 0
49 Monat (Zehner) Bit für 10 1
50 Jahr (Einer) Bit für 1 1 10000100 = 21
51 Bit für 2 0
52 Bit für 4 0
53 Bit für 8 0
54 Jahr (Zehner) Bit für 10 0
55 Bit für 20 1
56 Bit für 40 0
57 Bit für 80 0
58 Parität Datum
0 6 Einser-Bits durch zwei = 3,0
59 Runde beendet In der Regel Signalpause (außer Schaltsekunde wird 0 benötigt)
1000ms kein Signal

Erster Test:
Vorab ein kleiner Test ob die Antenne gut ausgerichtet ist und saubere (ohne Unterbrechung) Werte erhält. Dafür reicht es erstmal nur das Antennen-Modul mit dem Arduino zu verbinden und in der Arduino IDE die Bibliothek „DCF77“ Version 1.0 von Thijs Elenbaas zu installieren.
Aus der Beispiel-Liste der DCF77 Library, das Script „DCF77Signal“ aufspielen und drei Zeilen hinzufügen, damit der Pin 7 auf LOW gesetzt wird, um ein Startsignal zu geben.
Oder einfach den hier fertigen Sketch „Quellcode1“ verwenden:

Quellcode1:

#define BLINKPIN 13
#define DCF77PIN 2
#define PIN_schalter 7

int prevSensorValue=0;
  
void setup() {
  Serial.begin(9600);
  pinMode(DCF77PIN, INPUT);
  pinMode(13, OUTPUT);
  pinMode(PIN_schalter, OUTPUT);
  digitalWrite(PIN_schalter, LOW); // DCF77 Modul wird hiermit gestartet!
  Serial.println("0ms       100ms     200ms     300ms     400ms     500ms     600ms     700ms     800ms     900ms     1000ms    1100ms    1200ms");
}

void loop() {
  int sensorValue = digitalRead(DCF77PIN);
  if (sensorValue==1 && prevSensorValue==0) { Serial.println("");   }
  digitalWrite(BLINKPIN, sensorValue);
  Serial.print(sensorValue);   
  prevSensorValue = sensorValue;
  delay(10);
}

Jetzt im Seriellen Monitor die Sekundensignale prüfen. Sind die kurzen und die langen Zeitzeichen, gleichmäßig kurz und lang, dann ist das Signal brauchbar und auswertbar.

(Nur die länge der Ziffer 1 betrachten)
Sollten die Längen unregelmäßig aussehen, dann einen störungsfreien Platz für das Modul suchen.

Kann man deutliche und regelmäßige Werte erkennen, dann an die I2C Schnittstelle das LCD anbinden und folgenden Sketch übertragen:

Quellcode2:

#include "DCF77.h"
#include "TimeLib.h"
#include <Wire.h>
#include <LiquidCrystal_I2C.h>

#define DCF_PIN 2           // Connection pin to DCF 77 device
#define DCF_INTERRUPT 0    // Interrupt number associated with pin
#define PIN_LED 13
#define PIN_schalter 7
LiquidCrystal_I2C lcd(0x27,16,2);

//time_t time;
DCF77 DCF = DCF77(DCF_PIN, DCF_INTERRUPT);
// wurde ein gueltiges Signal gefunden
bool g_bDCFTimeFound = false;

void setup() 
{ lcd.init();
  pinMode(PIN_LED, OUTPUT);
  pinMode(PIN_schalter, OUTPUT);
  digitalWrite(PIN_schalter, LOW);
  Serial.begin(9600); 
  DCF.Start();
  Serial.println("Warten auf DCF77-Zeit... ");
  Serial.println("Dies dauert mindestens 2 Minuten, in der Regel eher länger.");
  lcd.backlight();
  lcd.setCursor(1,0);    
  lcd.print("Warte auf Zeit!");
  lcd.setCursor(1,1);
  lcd.print("3 Min Geduld!"); 
  delay(2000);
  lcd.clear();
}

void loop() 
{
  // das Signal wird nur aller 5 Sekunden abgefragt
  delay(950);
  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("Time is updated");
    setTime(DCFtime);
    g_bDCFTimeFound = true;
    lcd.clear();
  }
  
  // die Uhrzeit wurde gesetzt, also LED nach kurzer Zeit ein
  if (g_bDCFTimeFound)
  {
    delay(50);
    digitalWrite(PIN_LED, HIGH);
  }
  digitalClockDisplay();  
}

void digitalClockDisplay()
{
  // digital clock display of the time
  printDigits(hour());
    Serial.print(":");
  printDigits(minute());
    Serial.print(":");
  printDigits(second());
  Serial.print(" ");
  Serial.print(day());
  Serial.print(" ");
  Serial.print(month());
  Serial.print(" ");
  Serial.print(year()); 
  Serial.println();
  lcd.setCursor(0,0);  
  printDigits2(hour());
  lcd.print(":");
  printDigits2(minute());
  lcd.print(":");
  printDigits2(second());
  lcd.setCursor(0,1); 
  lcd.print(day());
  lcd.print(" ");
  lcd.print(month());
  lcd.print(" ");
  lcd.print(year()); 
}

void printDigits(int digits)
{
  // utility function for digital clock display: prints preceding colon and leading 0
   if(digits < 10)
    Serial.print('0');
  Serial.print(digits);
}
void printDigits2(int digits)
{
  // utility function for digital clock display: prints preceding colon and leading 0
  if(digits < 10)
    lcd.print('0');
  lcd.print(digits);
}

In dem Video werden gesammelte Hintergrundinformationen zusammen getragen und gezeigt wie die einzelnen Signale in Informationen umcodiert werden.


Optional – Nicht im Video zu sehen
Variante 2:
Hier eine weitere Variante, die ein größeres LCD nun mit 4 Zeilen erhält, um auch den Wochentag mit anzuzeigen:

#include "DCF77.h"  //"DCF77" Library Version 1.0 von Thijs Elenbaas 
#include "TimeLib.h" //"Time" Library V1.6.0 von Michael Margolis
#include <Wire.h>
#include <LiquidCrystal_I2C.h>

#define DCF_PIN 2           // DCF 77 Modul
#define DCF_INTERRUPT 0    // Pin fuer Interrupt 
#define PIN_LED 13
#define PIN_schalter 7
LiquidCrystal_I2C lcd(0x27,20,4);
DCF77 DCF = DCF77(DCF_PIN, DCF_INTERRUPT);
// wurde ein gueltiges Signal gefunden
bool g_bDCFTimeFound = false;
char date_string[20];
const char* weekdays[] = {"Sontag", "Montag", "Diensttag", "Mittwoch", "Donnerstag", "Freitag", "Samstag"};

void setup() 
{ lcd.init();
  pinMode(PIN_LED, OUTPUT);
  pinMode(PIN_schalter, OUTPUT);
  digitalWrite(PIN_schalter, LOW);
  Serial.begin(9600); 
  DCF.Start();
  Serial.println("Warten auf DCF77-Zeit... ");
  Serial.println("Dies dauert ein paar Minuten");
  lcd.backlight();
  lcd.setCursor(1,0);    
  lcd.print("Warte auf Zeit!");
  lcd.setCursor(1,1);
  lcd.print("3 Min Geduld!"); 
  delay(2000);
  lcd.clear();
}

void loop() 
{
  // das Signal wird nur aller 5 Sekunden abgefragt
  delay(950);
  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("Time is updated");
    setTime(DCFtime);
    g_bDCFTimeFound = true;
    lcd.clear();
  }

  if (g_bDCFTimeFound)
  {
    delay(50);
    digitalWrite(PIN_LED, HIGH);
  }
  set_date_string(date_string);
  digitalClockDisplay();  
}

void digitalClockDisplay()
{
  // digital clock display of the time
  printDigits(hour());
  printDigits(minute());
  printDigits(second());
  lcd.setCursor(0,0);  
  printDigits2(hour());
  lcd.print(":");
  printDigits2(minute());
  lcd.print(":");
  printDigits2(second());
  lcd.setCursor(0,1); 
  lcd.print(day());
  lcd.print(".");
  lcd.print(month());
  lcd.print(".");
  lcd.print(year()); 
  lcd.setCursor(0,2); 
  lcd.print(date_string);
}

void printDigits(int digits)
{
  // Utility-Funktion für Digitaluhr-Anzeige: druckt vorangestellten Doppelpunkt und führende 0
   if(digits < 10)
    Serial.print('0');
  Serial.print(digits);
}
void printDigits2(int digits)
{
  if(digits < 10)
    lcd.print('0');
  lcd.print(digits);
}

void set_date_string(char date_string[]) {

  int weekday_num = weekday();
  int position = 0;
  if (weekday_num == 0) {
    return;
  }
  strcpy(date_string, weekdays[weekday_num - 1]);
 }

Noch nicht schön, aber schon mal funktionabel

Tipp:
Sollte das LCD so gar nichts anzeigen, mal an dem I2C-Modul an der Stellschraube drehen, um den Kontrast einzustellen.


,