// 03.10.2024 - slaps313
// Dieser Code demonstriert das Beziehen der DCF77 Zeit und das Umwandeln eines komplett erhaltenen Zeit-Telegramms.
// Er soll als Grundlage dienen um sich seine eigene Zeitsynchronisation umzusetzen.
//
// Erklärungen und Tipps:
//
// Equipment:
//
// DCF77 Empfangsmodul (ich habe es mit dem von ELV getestet).
// Das Skript habe ich mit diversen Geräte testen können: Arduino Uno, Arduino Mega, Arduino Nano, Arduino Micro, ESP32S
//
// Zeitsignal empfangen (inkl. meine Erfahrungen):
//
// Erhält man keine Zeit weil die Zeitbits nicht Sekündlich gelesen werden können, kann es daher kommen, dass man zu wenig Leistung für die Antenne und den Mikrocontroller liefert.
// Mein ELV-Empfangsgerät benötigte jeweils 5 Volt und je nach Mikrocontroller eine Zusätzliche Spannungsversorgung (rein über USB-Port reichte nicht).
// Das Skript geht davon aus, dass die Zeitbits mit "LOW" daher kommen. Bei einem invertierten Signal, muss man entsprechend umdenken.
// Je nach Witterung kann die Sendeantenne in Deutschland komplett abgestellt sein!
// Je nach Witterung erhält man kein zuverlässiges Signal.
// In der Theorie ist es ein exaktes Signal, ich würde aber für ein 0-Bit LOW von 30 - 190 ms ausgehen und für ein 1-Bit LOW von 190 - 500 ms ausgehen.
//
// 100 ms LOW heisst: Bit = 0
// 200 ms LOW heisst: Bit = 1
//
// Das Zeitsignal wird während einer Minute im Sekundentakt (1 Bit pro Sekunde) übertragen (Bit 0 - 58) -> https://de.wikipedia.org/wiki/DCF77
// Paritätsbit
// Zu Minute, Stunde und Datum gibt es jeweils ein Paritätsbit (gerade Parität (https://de.wikipedia.org/wiki/Parit%C3%A4tsbit)).
// Das Paritätsbit hilft die Uhrzeit einigermassen zu verifizieren, aber eine Garantie ist es nicht.
// Ich würde, wenn man es etwas zuverlässiger wissen möchte, für einen abgeschlossenen Datenempfang jeweils 3 komplette Datensätze miteinander abgleichen.
// Dann kann man eher beurteilen, wenn sich die Uhrzeit in einem 10-Minuten-Bereich liegt, dass sie wohl stimmen sollte. Das habe ich aber im Minimalskript hier unterlassen.
//
//
// Hinweis zum Code:
// Programmtechnisch wird sich sicherlich noch einiges optimieren lassen, es geht aber ja mehr darum mal ein Bild zu machen.
unsigned long SignalstartMillis; // Wird benötigt um eruieren zu können, wie lange das Signal LOW resp. HIGH war.
bool ZeitBeziehen = false; // Am Pin 4 ist ein Schalter angehängt, welchen man um zu schalten auf LOW ziehen muss, vorher beginnt er nicht mit dem Zeit beziehen.
unsigned short SignaleingangStand = 0; // Ist der Zähler für das Array "SignalWerte"
unsigned short SignalPin = 2; // Digitalpin, muss aber an einem Interrupt-Fähigen GPIO-Pin hangen.
unsigned short SchalterPin = 4; // Ist INPUT_PULLUP, muss auf LOW geschaltet werden, damit das Skript anfängt die Zeit zu beziehen.
// Paritäten (gerade Parität) benötigen wir um die Zeit auf Korrektheit zu verifizieren:
unsigned short ParitaetMinute = 0;
unsigned short ParitaetStunde = 0;
unsigned short ParitaetDatum = 0;
unsigned int Signaldauer = 0; // Wird benötigt um eruieren zu können, wie lange das Signal LOW resp. HIGH war.
int SignalWerte[59]; // In dieses Array werden die Sekunden-Bits geladen.
int StatusSignalAktuell = 0; // steht für den aktuellen Wert am SignalPin.
int Vorher = 5; // Wird benötigt um Signaländerungen am Interrupt zu erkennen und darauf zu reagieren (nach jedem LOW kommt ein HIGH während einer Sekunde...).
bool TelegrammendeErreicht = false; //Diese Variable habe ich nicht verwendet, man könnte aber damit entsprechend etwas machen...
void setup() {
Serial.begin(115200); // Für Debugzwecke, die Zeit wird man sich ja danach in ein Zeitmodul über I2C oder ähnlich schreiben.
pinMode(SignalPin, INPUT);
pinMode(SchalterPin, INPUT_PULLUP);
SignalstartMillis = millis();
}
void loop() {
if(digitalRead(SchalterPin) == LOW && ZeitBeziehen == false)
{
// Wird nur gestartet, wenn PIN 4 auf LOW ist.
Serial.println("GO"); // Zur Information
SignaleingangStand = 0;
ParitaetMinute = 0;
ParitaetStunde = 0;
ParitaetDatum = 0;
attachInterrupt(digitalPinToInterrupt(SignalPin), meldeStatus, CHANGE); // Muss auf CHANGE stehen, wir müssen beide Stati (LOW und HIGH) lesen.
ZeitBeziehen = true;
}
if(ZeitBeziehen == true)
{
if(Vorher == 5)
{
//Initialisiert den Startwert.
Vorher = StatusSignalAktuell;
SignalstartMillis = millis();
}
if(StatusSignalAktuell != Vorher)
{
int Signaldauer = millis()-SignalstartMillis;
if(Vorher == 0)
{
//0 Heisst es ist eine Information, weil Low -> 100ms Low = 0, 200ms low = 1
if(Signaldauer > 30)
{
if(Signaldauer < 191)
{
//Gilt als 0
Serial.print(SignaleingangStand);
Serial.println(" : 0");
SignalWerte[SignaleingangStand]=0;
}
if(Signaldauer > 190 && Signaldauer < 500 )
{
//Gilt als 1
Serial.print(SignaleingangStand);
Serial.println(" : 1");
SignalWerte[SignaleingangStand]=1;
//Parität aufrechnen:
if(SignaleingangStand > 20 && SignaleingangStand < 28)
{
ParitaetMinute++;
}
if(SignaleingangStand > 28 && SignaleingangStand < 35)
{
ParitaetStunde++;
}
if(SignaleingangStand > 35 && SignaleingangStand < 58)
{
ParitaetDatum++;
}
}
SignaleingangStand++;
}
}
else
{
if(Signaldauer > 1500)
{
Serial.println("-----Telegrammende-----");
Serial.println(SignaleingangStand);
TelegrammendeErreicht = true;
bool ParitaetKorrekt = true;
String DebugInfos = "";
DebugInfos = ParitaetMinute;
DebugInfos += " - ";
DebugInfos += (SignalWerte[28]);
DebugInfos += "ParitätMinute\n";
if(ParitaetMinute %2 == 0)
{
ParitaetMinute = 0;
}
else
{
ParitaetMinute = 1;
}
DebugInfos += ParitaetStunde;
DebugInfos += " - ";
DebugInfos += SignalWerte[35];
DebugInfos += "ParitätStunde\n";
if(ParitaetStunde %2 == 0)
{
ParitaetStunde = 0;
}
else
{
ParitaetStunde = 1;
}
DebugInfos += ParitaetDatum;
DebugInfos += " - ";
DebugInfos += SignalWerte[58];
DebugInfos += "ParitätDatum\n";
if(ParitaetDatum %2 == 0)
{
ParitaetDatum = 0;
}
else
{
ParitaetDatum = 1;
}
Serial.println(DebugInfos);
if(ParitaetMinute != SignalWerte[28])
{
ParitaetKorrekt = false;
}
if(ParitaetStunde != SignalWerte[35])
{
ParitaetKorrekt = false;
}
if(ParitaetDatum != SignalWerte[58])
{
ParitaetKorrekt = false;
}
//Nun das Array SignalWerte durchgehen für die Zeit:
if(ParitaetKorrekt == true)
{
ZeitAusgeben();
}
Serial.println("<----ENDE DURCHGANG---->");
SignaleingangStand = 0;
ParitaetMinute = 0;
ParitaetStunde = 0;
ParitaetDatum = 0;
}
}
//Variablen zurücksetzen:
SignalstartMillis = millis();
Vorher = StatusSignalAktuell;
}
if(SignaleingangStand>59){
SignaleingangStand = 0;
ParitaetMinute = 0;
ParitaetStunde = 0;
ParitaetDatum = 0;
}
}
}
void meldeStatus() {
StatusSignalAktuell = digitalRead(SignalPin); //Nur Variable aktualisieren, hier kein Serial.print usw...!
}
void ZeitAusgeben() {
//Diese Funktion zählt anhand der Tabelle von Wikipedia einfach die Werte zusammen.
//Hier würde man die Werte dann in ein Zeitmodul schreiben.
int Minute = 0;
int Stunde = 0;
int Tag = 0;
int Monat = 0;
int Jahr = 0;
if(SignalWerte[21] == 1)
{
Minute = Minute+1;
}
if(SignalWerte[22] == 1)
{
Minute = Minute+2;
}
if(SignalWerte[23] == 1)
{
Minute = Minute+4;
}
if(SignalWerte[24] == 1)
{
Minute = Minute+8;
}
if(SignalWerte[25] == 1)
{
Minute = Minute+10;
}
if(SignalWerte[26] == 1)
{
Minute = Minute+20;
}
if(SignalWerte[27] == 1)
{
Minute = Minute+40;
}
if(SignalWerte[29] == 1)
{
Stunde = Stunde+1;
}
if(SignalWerte[30] == 1)
{
Stunde = Stunde+2;
}
if(SignalWerte[31] == 1)
{
Stunde = Stunde+4;
}
if(SignalWerte[32] == 1)
{
Stunde = Stunde+8;
}
if(SignalWerte[33] == 1)
{
Stunde = Stunde+10;
}
if(SignalWerte[34] == 1)
{
Stunde = Stunde+20;
}
if(SignalWerte[36] == 1)
{
Tag = Tag+1;
}
if(SignalWerte[37] == 1)
{
Tag = Tag+2;
}
if(SignalWerte[38] == 1)
{
Tag = Tag+4;
}
if(SignalWerte[39] == 1)
{
Tag = Tag+8;
}
if(SignalWerte[40] == 1)
{
Tag = Tag+10;
}
if(SignalWerte[41] == 1)
{
Tag = Tag+20;
}
if(SignalWerte[45] == 1)
{
Monat = Monat+1;
}
if(SignalWerte[46] == 1)
{
Monat = Monat+2;
}
if(SignalWerte[47] == 1)
{
Monat = Monat+4;
}
if(SignalWerte[48] == 1)
{
Monat = Monat+8;
}
if(SignalWerte[49] == 1)
{
Monat = Monat+10;
}
if(SignalWerte[50] == 1)
{
Jahr = Jahr+1;
}
if(SignalWerte[51] == 1)
{
Jahr = Jahr+2;
}
if(SignalWerte[52] == 1)
{
Jahr = Jahr+4;
}
if(SignalWerte[53] == 1)
{
Jahr = Jahr+8;
}
if(SignalWerte[54] == 1)
{
Jahr = Jahr+10;
}
if(SignalWerte[55] == 1)
{
Jahr = Jahr+20;
}
if(SignalWerte[56] == 1)
{
Jahr = Jahr+40;
}
if(SignalWerte[57] == 1)
{
Jahr = Jahr+80;
}
Serial.print(Tag);
Serial.print(".");
Serial.print(Monat);
Serial.print(".20");
Serial.print(Jahr);
Serial.print(" ");
Serial.print(Stunde);
Serial.print(":");
Serial.println(Minute);
}