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.

Quelle: 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:



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

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);
}

Aus der Beispiel-Liste der DCF77 Library das Script „DCF77Signal“, um die PIN_schalter Funktion erweitert.
Wichtig ist, das vorab mit diesem Code die Funktionalität des DCF77 Modul getestet wird.
Sind die kurzen und die langen Zeitzeichen, gleichmäßig kurz und lang, dann ist das Signal brauchbar und auswertbar.

Sollten die Längen unregelmäßig aussehen, dann einen störungsfreien Platz für das Modul suchen.

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.


Nicht im Video zu sehen
Variante 2:
Mit Wochentag auf 4 Zeilen LCD

#include "DCF77.h"
#include "TimeLib.h"

#include <Wire.h>
#include <LiquidCrystal_I2C.h>

#define DCF_PIN 2           // DCF 77 Modul
#define DCF_INTERRUPT 0    // Interrupt number associated with pin
#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 Wochentag[20];
const char* wochentage[] = {„Sontag“, „Montag“, „Diensttag“, „Mittwoch“, „Donnerstag“, „Freitag“, „Samstag“};
int wochentag_zahl = weekday();

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);
  }
  set_date_string(date_string);
  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()); 
  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 schonmal funktionabel

,