Update: Mai 2026
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.
Diese Abkürzung steht für: D: Deutschland, C: Langwellensender, F: Frankfurt am Main, 77: Sendefrequenz 77,5 kHz.
Quelle und weitere Details zum Langwellensender: DCF77 – Wikipedia und DCF77 – PTB.de
Verwendete Hardware:
Arduino Uno
DCF77 Antenne 77,5Khz
Flüssigkristallanzeige LCD 1602 oder mit 4 Zeilen: LCD2004
I2C Adapter LCM1602
Verdrahtung:

Datenblatt: DCF77-Modul

Funktion und 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 | |
Quellcode und Bibliothek:
- 1. DCF77″ Library von Thijs Elenbaas in der Version 1.0.0
- Hier muss in der Library –> Datei DCF77.cpp eine kleine änderung vorgenommen werden:
- Suche in Zeile 25 nach der Zeile: #include <Time.h>
- Ändere diese Zeile zu: #include <TimeLib.h>
- Time“ Library von Michael Margolis in der Version 1.6.1
- LiquidCrystal_I2C
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:
Quellcode1a:
#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.
Quellcode1b:
Geht auch mit angeschlossenen 4 Zeilen LCD-Display:
#include <Wire.h>
#include <LiquidCrystal_I2C.h>
#define BLINKPIN 13
#define DCF77PIN 2
#define PIN_schalter 7
LiquidCrystal_I2C lcd(0x27, 20, 4); // Adresse ggf. auf 0x3F ändern
String bitStream = " "; // 20 Leerzeichen
int prevSensorValue = 0;
void setup() {
pinMode(DCF77PIN, INPUT);
pinMode(BLINKPIN, OUTPUT);
pinMode(PIN_schalter, OUTPUT);
digitalWrite(PIN_schalter, LOW); // Modul aktivieren
lcd.init();
lcd.backlight();
lcd.setCursor(0, 0);
lcd.print("DCF77 Rohdaten-Test");
delay(2000);
lcd.clear();
}
void loop() {
int sensorValue = digitalRead(DCF77PIN);
// LED Status spiegeln
digitalWrite(BLINKPIN, sensorValue);
// Bit-Stream Logik
// Wir nehmen das Signal (0 oder 1) und schieben es in unsere Anzeige
char bitChar = (sensorValue == HIGH) ? '1' : '0';
// Nur bei Änderung oder in Intervallen aktualisieren (sonst flackert LCD)
// Hier: Wir speichern die letzten 20 Bits
bitStream = bitStream.substring(1) + bitChar;
// Display-Ausgabe
lcd.setCursor(0, 0);
lcd.print("Stream (letzte 20):");
lcd.setCursor(0, 1);
lcd.print(bitStream); // Zeigt die Bits live
lcd.setCursor(0, 2);
lcd.print("Status Pin 2: ");
lcd.print(sensorValue);
lcd.print(" ");
lcd.setCursor(0, 3);
lcd.print("Zeit seit Start:");
lcd.setCursor(17, 3);
lcd.print(millis() / 1000); // Einfacher Sekundenzähler
delay(100); // 100ms Taktung für die Abfrage
}Ausgabe auf LCD:

Finale Version:
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);
}Dieser Sketch gibt es auch für
Visual Studio Code auf GitHub: prilchen/DCF77-Funk-Uhr
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.
