Mit einem DCF77-Empfangsmodul lassen sich exakte Zeitdaten von einem Funksender wie dem DCF77 empfangen. Diese präzisen Zeitsignale sind ideal für Projekte mit einem Arduino, bei denen eine genaue Uhrzeit erforderlich ist, etwa für zeitgesteuerte Schaltungen oder Datenerfassungssysteme. Das DCF77-Signal wird von der Physikalisch-Technischen Bundesanstalt (PTB) über eine Sendeanlage in Mainflingen ausgestrahlt und stellt eine zuverlässige Quelle für die Mitteleuropäische Zeit (MEZ) sowie die mitteleuropäische Sommerzeit (MESZ) dar.
In diesem Beitrag zeige ich dir, wie du ein DCF77-Empfangsmodul mit dem Arduino verbindest, die empfangenen Daten dekodierst und für dein Projekt weiterverarbeitest. Statt vorgefertigte Bibliotheken zu verwenden, biete ich dir einen einfachen Code, den du direkt in dein eigenes Projekt einfügen kannst. Dadurch behältst du die volle Kontrolle über die Signalverarbeitung und kannst die Daten flexibel an deine spezifischen Anforderungen anpassen.
Dieses Projekt eignet sich sowohl für Einsteiger als auch für Fortgeschrittene, die präzise Zeitsteuerungen oder Datenerfassungen umsetzen möchten.
Inhaltsverzeichnis
- Wozu dient das DCF77-Empfangsmodul?
- Technische Daten vom DCF77 Empfangsmodul
- Bezug
- Aufbau der Schaltung
- Programmieren
- Schritt 1 – Initialisieren der Bibliotheken und Definitionen
- Schritt 2 – Definieren der Hardware-Pins und globalen Variablen
- Schritt 3 – Setup des Arduino und LCD-Displays
- Schritt 4 – Hauptprogramm zur Zeitabfrage und Anzeige
- Schritt 5 – Interrupt zur Verarbeitung des DCF77-Signals
- Schritt 6 – Zeitstempel erstellen und zurückgeben
- Schritt 7 – Hilfsfunktion zur Konvertierung von BCD nach Dezimal
Wozu dient das DCF77-Empfangsmodul?
Das DCF77-Empfangsmodul dient dazu, über Funk genaue Zeit- und Datumsinformationen zu empfangen, die von der DCF77-Sendeanlage ausgestrahlt werden. Diese Informationen werden mit einer hohen Genauigkeit über eine Langwelle (77,5 kHz) gesendet und sind besonders nützlich für Anwendungen, bei denen eine präzise Zeit erforderlich ist, wie etwa bei Funkuhren, Zeiterfassungssystemen oder zeitgesteuerten Schaltungen. In Arduino-Projekten ermöglicht das DCF77-Modul, die empfangenen Zeitdaten in Echtzeit zu verarbeiten, ohne auf externe Zeitserver oder manuelle Einstellungen angewiesen zu sein. Dadurch wird es zu einer idealen Komponente für Automatisierungen, bei denen eine verlässliche Zeitsynchronisierung entscheidend ist.
Sender DCF77 in Mainflingen
Der Sender DCF77 überträgt die Daten dabei mit einer Frequenz von 77.5kHz und einer Leistung von 50kW.
Zu diesem Sender gibt es einen Ausführlichen Wikipedia Artikel, welchen ich hier nun gerne verlinken möchte:
Seite „DCF77“. In: Wikipedia, Die freie Enzyklopädie. Bearbeitungsstand: 6. September 2019, 12:10 UTC. URL: https://de.wikipedia.org/w/index.php?title=DCF77&oldid=192027822 (Abgerufen: 13. September 2019, 19:15 UTC)
Technische Daten vom DCF77 Empfangsmodul
- Betriebsspannung: min. 1,2V – max. 3,3V
- Stromaufnahme: <90 µA (max. 120µA)
- Empfangsfrequenz: min. 40 kHz, max. 100 kHz
- Arbeitstemperatur: -40 °C bis +85 °C
- Lagertemperatur: -55 °C bis +85 °C
Bezug
Das DCF1 Modul bekommst du auf ebay.de für ca. 5 € inkl. Versandkosten*.
Hinweis von mir: Die mit einem Sternchen (*) markierten Links sind Affiliate-Links. Wenn du über diese Links einkaufst, erhalte ich eine kleine Provision, die dazu beiträgt, diesen Blog zu unterstützen. Der Preis für dich bleibt dabei unverändert. Vielen Dank für deine Unterstützung!
Du bekommst dieses Modul auch auf aliexpress.com für deutlich unter einem Euro, jedoch musst du hier mit einer langen Lieferzeit sowie wenn mal was nicht so mit der Lieferung klappt, mit einem “holperigen Support” rechnen.
Lieferumfang
Das kleine Modul war in meinem Fall komplett aufgebaut und ich konnte quasi mit der Programmierung direkt beginnen. In meinem Fall lag dem kleinen Päckchen ein Datenblatt bei und somit musste ich nicht viel im Internet recherchieren.
Aufbau der Schaltung
Für die Schaltung am Arduino benötigst du:
- einen Arduino UNO* oder Arduino Nano*,
- ein USB-Datenkabel*,
- ein DCF77-Empfangsmodul*,
- eine 5mm LED* mit 220 Ohm Vorwiderstand*,
- ein LC-Display* (mit I2C Schnittstelle),
- diverse Breadboardkabel*,
- 10 cm, männlich – weiblich,
- 10 cm, männlich – männlich
Hinweis von mir: Die mit einem Sternchen (*) markierten Links sind Affiliate-Links. Wenn du über diese Links einkaufst, erhalte ich eine kleine Provision, die dazu beiträgt, diesen Blog zu unterstützen. Der Preis für dich bleibt dabei unverändert. Vielen Dank für deine Unterstützung!
Das DCF77-Empfangsmodul besitzt vier Pins welche mit GND, VCC, PON & TCO beschriftet sind. Das mir vorliegende Modul darf mit maximal 3.3V betrieben werden! Es gibt auch Module welche mit 5V betrieben werden können, hier empfehle ich dir zunächst ein Blick in das Datenblatt.
Das LC-Display wird via I2C angeschlossen und du benötigst hier die beiden analogen Pin A4 für SDA und A5 für SDA.
Programmieren
Das DCF77-Empfangsmodul kannst du recht einfach am Arduino programmieren, du benötigst keine zusätzliche Bibliothek dafür. Auf der Seite https://de.wikipedia.org/wiki/DCF77#Amplitudenumtastung findest du eine Tabelle aus welchen du die Positionen der Daten aus dem Bitmuster entnehmen kannst.
Den nachfolgenden Code habe ich von der Seite https://wolles-elektronikkiste.de/dcf77-funkuhr entnommen und umgeschrieben, sodass dieser einfacher aber auch robuster ist.
Schritt 1 – Initialisieren der Bibliotheken und Definitionen
In diesem Abschnitt importierst du notwendige Bibliotheken und definierst grundlegende Strukturen und Variablen, die für das Projekt benötigt werden.
#include <util/parity.h> // Kommentiere diese Zeile aus, wenn du keinen AVR Mikrocontroller verwendest #include <LiquidCrystal_I2C.h> // Initialisierung des LCD-Displays (I2C Adresse: 0x27, 20 Zeichen, 2 Zeilen) LiquidCrystal_I2C lcd(0x27, 20, 2); // Definition der Struktur für den Zeitstempel struct Zeitstempel { bool isValid; int jahr, monat, tag, wochentagInt, stunde, minute; String wochentagStr; }; Zeitstempel zeitstempel;
Schritt 2 – Definieren der Hardware-Pins und globalen Variablen
Hier werden die benötigten Pins und globale Variablen festgelegt, die später in der Signalverarbeitung verwendet werden.
int interruptPin = 3; // Pin, der das DCF77-Signal empfängt int ledPin = 4; // Pin zur Steuerung einer Status-LED volatile unsigned long lastInt = 0; volatile unsigned long long currentBuf = 0; volatile byte bufCounter; String wochentage[] = { "Mo", "Di", "Mi", "Do", "Fr", "Sa", "So" };
Schritt 3 – Setup des Arduino und LCD-Displays
Im setup()
-Abschnitt initialisierst du das LCD-Display und richtest den Interrupt für das DCF77-Signal ein. Außerdem wird die Status-LED konfiguriert.
void setup() { Serial.begin(115200); pinMode(ledPin, OUTPUT); pinMode(interruptPin, INPUT); attachInterrupt(digitalPinToInterrupt(interruptPin), DCF77_ISR, CHANGE); lcd.init(); lcd.backlight(); lcd.setCursor(0, 0); lcd.print("Lese Zeitstempel"); lcd.setCursor(0, 1); lcd.print("von DCF77 Modul"); }
Schritt 4 – Hauptprogramm zur Zeitabfrage und Anzeige
Die loop()
-Funktion fragt den aktuellen Zeitstempel ab und gibt ihn auf dem LCD sowie über die serielle Schnittstelle aus. Die Status-LED blinkt in regelmäßigen Abständen zur Anzeige, dass der Arduino arbeitet.
void loop() { digitalWrite(ledPin, HIGH); delay(750); digitalWrite(ledPin, LOW); delay(750); zeitstempel = getZeitstempel(); if (zeitstempel.isValid) { lcd.clear(); Serial.println("Aktuelle Zeit:"); Serial.println("Jahr: " + String(zeitstempel.jahr)); Serial.println("Monat: " + String(zeitstempel.monat)); Serial.println("Tag: " + String(zeitstempel.tag)); Serial.println("Wochentag: " + zeitstempel.wochentagStr); Serial.println("Stunde: " + String(zeitstempel.stunde)); Serial.println("Minute: " + String(zeitstempel.minute)); char datum[30]; sprintf(datum, "%02d.%02d.%4d", zeitstempel.tag, zeitstempel.monat, zeitstempel.jahr); String datum2 = zeitstempel.wochentagStr + " " + datum; char uhrzeit[30]; sprintf(uhrzeit, "%02d:%02d Uhr", zeitstempel.stunde, zeitstempel.minute); Serial.println(datum2); Serial.println(uhrzeit); lcd.setCursor(0, 0); lcd.print(datum2); lcd.setCursor(0, 1); lcd.print(uhrzeit); } }
Schritt 5 – Interrupt zur Verarbeitung des DCF77-Signals
Die DCF77_ISR()
-Funktion wird jedes Mal ausgelöst, wenn eine Änderung im DCF77-Signal erkannt wird. Hier wird die Pulsbreite des Signals gemessen und die Daten in den Buffer geschrieben.
void DCF77_ISR() { unsigned int dur = millis() - lastInt; if (digitalRead(interruptPin)) { if (dur > 1500) { bufCounter = 0; currentBuf = 0; } } else { if (dur > 150) { currentBuf |= ((unsigned long long)1 << bufCounter); } bufCounter++; } lastInt = millis(); }
Schritt 6 – Zeitstempel erstellen und zurückgeben
Die getZeitstempel()
-Funktion extrahiert die empfangenen Zeitinformationen aus dem Buffer und erstellt daraus einen gültigen Zeitstempel, der dann zurückgegeben wird.
Zeitstempel getZeitstempel() { Zeitstempel zeit; byte dcf77Year = (currentBuf >> 50) & 0xFF; byte dcf77Month = (currentBuf >> 45) & 0x1F; byte dcf77DayOfWeek = (currentBuf >> 42) & 0x07; byte dcf77DayOfMonth = (currentBuf >> 36) & 0x3F; byte dcf77Hour = (currentBuf >> 29) & 0x3F; byte dcf77Minute = (currentBuf >> 21) & 0x7F; int jahr = rawByteToInt(dcf77Year); int monat = rawByteToInt(dcf77Month); int tag = rawByteToInt(dcf77DayOfMonth); int wochentag = rawByteToInt(dcf77DayOfWeek); int stunde = rawByteToInt(dcf77Hour); int minute = rawByteToInt(dcf77Minute); if (jahr > 0 && monat > 0) { zeit.isValid = true; zeit.jahr = jahr + 2000; zeit.monat = monat; zeit.tag = tag; zeit.wochentagInt = wochentag; zeit.wochentagStr = (wochentag > 0) ? wochentage[wochentag - 1] : ""; zeit.stunde = stunde; zeit.minute = minute; } else { zeit.isValid = false; } return zeit; }
Schritt 7 – Hilfsfunktion zur Konvertierung von BCD nach Dezimal
Die Funktion unsigned int rawByteToInt(byte raw)
wandelt ein einzelnes Byte, das im binär kodierten Dezimalformat (BCD) vorliegt, in eine dezimale Zahl um. Bei BCD werden die oberen 4 Bits des Bytes als Zehnerstelle und die unteren 4 Bits als Einerstelle interpretiert.
Die Funktionsweise im Detail:
- Mit
raw >> 4
werden die oberen 4 Bits des Bytes nach rechts verschoben, sodass die Zehnerstelle isoliert wird. raw & 0x0F
maskiert die oberen 4 Bits und behält nur die unteren 4 Bits, die die Einerstelle darstellen.- Schließlich wird das Ergebnis als Dezimalzahl berechnet, indem die Zehnerstelle mit 10 multipliziert und die Einerstelle dazuaddiert wird.
So ergibt sich aus einem BCD-Wert wie 0x25
(Zehnerstelle 2, Einerstelle 5) die Dezimalzahl 25
.
unsigned int rawByteToInt(byte raw) { return ((raw >> 4) * 10 + (raw & 0x0F)); }