DCF77

[toc]

Im Zuge der Entwicklung einer neuen Firmware für mein  Mini-Anzeige-Modul war es an der Zeit die Uhr zu einer Funkuhr aufzurüsten. Was schon lange vorgesehen war* wurde in die Tat umgesetzt. Zwar hatte ich schon mal Routinen für die Auswertung des DCF77 Signals geschrieben, jedoch gehörten diese Routinen längst einmal überarbeitet. Also noch ein Grund endlich mit den Altlasten aufzuräumen und einen ordentlichen Algorithmus zu programmieren.

Das Signal verstehen

Das DCF77 Signal dient dazu Uhren mit mit dem Funksignal abzugleichen. Das Signal wird auf der Frequenz 77,5 kHz gesendet und sendet über den Zeitraum von 60 Sekunden die Zeitinformation für die jeweils folgende Minute.

DCF77_Scope

Auf dem Foto ist gut zu sehen, dass die Impulse, die vom Empfangsmodul ausgegeben werden unterschiedlich breit sind. Näheres dazu später. . .

Also die Zeitinformation, die Ihr erhaltet, wenn Ihr das DCF77 Signal empfangt und es anschließend decodiert, ist die Information für die nächste Minute! Während der Sendedauer einer Information, also der 60 Sekunden werden 59 Bits übertragen, welche die Information enthalten.

Empfangsmodul

Hier mal ein Beispiel eines gängigen Empfangmoduls, das direkt an den Mikrocontroller angeschlossen werden kann:

Low_cost_DCF77_receiver

Beispielfoto: Empfangsmodul von Conrad

Ich empfehle Euch das Empfangsmodul  von HKW-Elektronik. Zusätzlich zum Modul habe ich mit folgender Antenne  sehr gute Erfahrungen gemacht. Vor allem in Situationen in denen z.B. das Modul von Pollin völlig den Dienst versagt, liefert das proffesionelle Modul von HKW immer noch ein gutes Signal. Ich rate Euch gerade in Gebieten, in denen der Empfang erfahrungsgemäß nicht so gut ist, die paar Euro mehr für das Modul auszugeben.

Das Modul kann direkt an den Eingang eines Mikrocontrollers angeschlossen werden. Es ist lediglich ein Pullup-Widerstand notwendig. Entweder verwendet Ihr den internen des Controllers (maximales Strom von 2 mA des Moduls beachten) oder Ihr verbaut einen externen Widerstand. Ich habe diesen z.B. direkt an die Pins des Moduls angeschlossen.

Anschluss des DCF-Moduls an den Mikrocontroller:

       3V - 12V
          o
          |
UB   o----+------[R]------+
                          |
DCFn o-----< max 2 mA <---+-----> Mikrocontroller
GND  o----+
PON  o----+
          |
         ===

Der Pin des Mikrocontrollers muss dabei als Eingang konfiguriert werden. Ihr müsst das zugehörige Tris-Bit also auf “1” setzten und evtl. den Eingang je nach PIC von analog auf digital umschalten. Entsprechend bei anderen Controllern verfahren.

Die Kodierung

Es genügt natürlich nicht einfach die 59 Bits einer Sequenze zu empfangen – die Daten müssen dekodiert werden. Doch um die Daten zu dekodieren muss man zunächst erstmal wissen wie denn überhaupt kodiert wurde. Und das zeige ich Euch jetzt.

Ihr empfangt 59 Bits vom Sender pro Sequenze. Hier eine Übersicht der Daten mit einer kurzen Beschreibung:

BIT BEDEUTUNG DER WERTE
0 Start einer neuen Minute (ist immer „0“)
1–14 bis Mai 1977: Differenz UT1−UTC als vorzeichenbehaftete Zahlbis November 2006: Betriebsinformationen der PTB (meist alle 14 Bits null)seit Ende 2006: Wetterinformationen der Firma MeteoTime sowie Informationen des Katastrophenschutzes

Die Bits 0 bis 14 sind weniger interessant.

BIT BEDEUTUNG DER WERTE
15 Rufbit (bis Mitte 2003: Reserveantenne)
16 „1“: Am Ende dieser Stunde wird MEZ/MESZ umgestellt.
17 „0“: MEZ, „1“: MESZ
18 „0“: MESZ, „1“: MEZ
19 „1“: Am Ende dieser Stunde wird eine Schaltsekunde eingefügt.

So auch das Bit 15. Ab Bit 16 wird es jedoch langsam interessant.

BIT BEDEUTUNG
20 Beginn der Zeitinformation (ist immer „1“)
21 Minute
(Einer)
Bit für 1
22 Bit für 2
23 Bit für 4
24 Bit für 8
25 Minute
(Zehner)
Bit für 10
26 Bit für 20
27 Bit für 40
28 Parität Minute
29 Stunde
(Einer)
Bit für 1
30 Bit für 2
31 Bit für 4
32 Bit für 8
33 Stunde
(Zehner)
Bit für 10
34 Bit für 20
35 Parität Stunde
36 Kalendertag
(Einer)
Bit für 1
37 Bit für 2
38 Bit für 4
39 Bit für 8
40 Kalendertag
(Zehner)
Bit für 10
41 Bit für 20
42 Wochentag
(1 = Montag)
Bit für 1
43 Bit für 2
44 Bit für 4
45 Monatsnummer
(Einer)
Bit für 1
46 Bit für 2
47 Bit für 4
48 Bit für 8
49 Monatsnummer
(Zehner)
Bit für 10
50 Jahr
(Einer)
Bit für 1
51 Bit für 2
52 Bit für 4
53 Bit für 8
54 Jahr
(Zehner)
Bit für 10
55 Bit für 20
56 Bit für 40
57 Bit für 80
58 Parität Datum
59 keine Sekundenmarke

Die Bits 21 bis enschließlich 58 sind für uns die wichtigsten.

Parität

Wer sich mit Parität bereits auskennt, kann dieses Kapitel überspringen.

Euch ist bestimmt schon aufgefallen, dass in den Daten des öfteren Bits auftauchen die sich Paritätsbit nennen. Diese Bits dienen dazu die empfangenen Daten auf fehler zu untersuchen. Man unterscheidet zwischen gerader und ungerader Parität. Das DCF77 Signal verwendet die gerade Parität. Bevor ich nun anfange groß zu erklären ein einfaches Beispiel:

Empfagene Bits:       0 1 0 1
Parität (gerade):     0
Parität (ungerade):   1

Das Paritätsbis bringt die Anzahl der “1” der empfangenen Bits wieder in Parität. Bei gerader Parität muss die Summe aus allen empfangenen “1” plus dem Paritätsbit eine gerade Zahl ergeben. Bei ungerader Parität entsprechend eine ungerade Zahl.

Unser Beispiel zeigt, dass zwei “1” empfangen wurden. Die Summe ist also 2. Das Bit für gerade Parität ist somit 0, denn die Zahl ist ja schon gerade. Das Bit für ungerade Parität hingegen ist “1”, damit die Summe dann ungerade wird = 3.

Noch ein kleines (Fehler-)Beispiel:

Empfagene Bits:       0 1 0 0
Parität (gerade):     0
Parität (ungerade):   1

Hier wurde nur eine “1” empfangen. Das Bit für gerade Parität ist jedoch “0”. Die Summe würde also keine gerade Zahl ergeben. Somit steht fest, dass die Übertragung fehlerhaft ist und die Daten können nicht verwendet werden.

Übrigens: Wenn 2*n Bits einer Übertragung kippen, kann die Parität diesen Fehler nicht feststellen. Die Steigerung ist die mehrdimensionale Parität, welche auch in der Lage ist Fehler zu korrigieren.

Beispielsequenze

Schauen wir uns mal eine Sequenze als Beispiel an. Folgende Bitfolge wurde empfangen (Bits 0 .. 9 nicht dargestellt):

0000000100100000101110010101000111100010001000001
0123456789012345678901234567890123456789012345678
1         2         3         4         5        

  • Wie man sieht, ist das 17. Bit gesetzt, wir haben also Sommerzeit.
  • Dann ist Bit 20 wieder gesetzt > Zeitbeginn.
  • Bei den Minuten ist nur Bit 26 gesetzt, es waren also 20 Minuten.
  • Da die Anzahl der gesetzten Bits bei den Minuten ungerade war, ist das Prüfbit (28) gesetzt.
  • Bit 29, 30 und 33 sind gesetzt, die Stundenzahl ergibt sich zu 13.
  • Bei einer Zahl von 3 gesetzten Bits (ungerade) muss das Prüfbit (35) wieder gesetzt sein.
  • Hiermit wäre bereits die Uhrzeit zu 13:20 Uhr bestimmt.
  • Analog kann man sich auch das Datum ausrechnen . . .

Biterkennung

Ich möchte an dieser Stelle nochmal eine Grafik aufgreifen, die ich weiter oben in diesem Artikel schon einmal präsentiert habe. Es handelt sich um folgenden Screenshot meines Oszilloskops:

DCF77_Scope

Ihr seht auf dem Screenshot den Ausschnitt einer DC77 Sequenze. Dabei fällt auf, dass die Impule eine unterschiedliche Breite haben. So könnt Ihr erkennen ob eine “1” oder “0” übertragen wird.

  • Impulsbreite ~200 ms = “1”
  • Impulsbreite ~100 ms = “0”

Damit man den Beginn einer Sequenze erkennen kann wird auf die Übertragung des 59. Bits verzichtet (siehe auch Tabelle “keine Sekundenmarke”). Wenn eine neue Sequenze startet dann wird dies also durch das fehlende Bit ersichtlich. Man brauch also lediglich schauen wann eine Low-Impuls länger als 1 Sekunde andauert. Die nun folgenden Impulse sind in der Reihenfolge der Tabelle.

  • Impulspause >1000 ms = Start der Sequenze

Achtung: Es gibt auch Empfangsmodule, welche das Signal invertiert ausgeben! Die hier beschriebenen Routinen sind auf nicht invertierte Signale ausgelegt, können aber natürlich auch angepasst werden.

Beschreibung der Routinen

In diesem Kapitel möchte ich Euch meine Routinen vorstellen und kurz erläutern, wie Ihr diese in Eure Projekte enbinden könnt.

Vorbereitungen

Bevor wir mit den Routinen anfangen müssen wir ein paar kleine Vorbereitungen treffen, damit Ihr die Routinen erfolgreich verwenden könnt. Die Routinen basieren auf einem Timer-Interrupt. Dieser gibt an in welchem Abstand die DCF77 Leitung (des Empfangsmoduls) überprüft werden soll. Ihr müsst hierfür einen Timer-Interrupt konfigurieren, der alle 1 ms auslöst. Innerhalb der Interrupt-Service-Routine wird dann ein Flag gesetzt, dass anmeldet, dass der IO, an dem Ihr das Empfangsmodul angeschlossen habt überprüft werden soll. So kann die Länge der Impulse oder auch der Impulspausen überwacht werden.

Damit die ISR (Inerrupt-Service-Routine) das Flag kennt müssen wir dieses anmelden. Dafür könnt Ihr entweder folgendes in die Headerdtei der ISR schreiben:

extern bool checkDCF;

Oder Ihr bindet die dcf.h Datei in die ISR.c Datei ein:

#include <dcf.h>;

Die  dcf.h Datei muss sich dabei im Projekt-Ordner befinden!

Innerhalb der ISR, die alle 1 ms ausgeführt wird muss das Flag nun gesetzt werden:

checkDCF = true;

Nun haben wir schon mal das Flag fertig. Ist das Flag gesetzt muss der Zustand des Pins überprüft werden. Der Aufruf zu der entsprechenden Routine findet in der Endlosschleife innerhalb der main-Funktion statt. Hierfür fügt Ihr folgendes dort ein:

if(syncDCF && checkDCF)
{
   checkDCF = false;
   dcf_check();
}

Damit die main-Funktion den Aufruf überhaupt durchführen kann müssen wir die dcf.h Datei in die main.c Datei einbinden. Also bitte in der main.c Datei folgendes einfügen:

#include <dcf.h>

Das Flag syncDCF gibt an wann eine Synchronisation stattfinden soll (z. B.: Immer Nachts um 3:00 Uhr o. ä.). Dafür muss das Flag natürlich erstmal angelegt werden. Legt das Flag in der main-Funktion an:

bool syncDCF;

Eventuell müsst Ihr noch die folgende Headerdatei einbinden:

#include <stdbool.h>;

Synchronisiert wird nur, wenn das Flag syncDCF gesetzt ist! Das Flag wird nach einer Empfangenen DCF77-Sequenze automatisch gelöscht.

Zu Testzwecken könnt Ihr das Flag dauerhaft setzten:

syncDCF = true;
if(syncDCF && checkDCF)
{
   checkDCF = false;
   dcf_check();
}

Vielleicht habt Ihr euch schon gefragt wo die Daten dann überhaupt abgelegt werden. Also wo kann ich sehen welche Uhrzeit über das DC77 Signal empfangen wurde? Hierfür bieten die Routinen eine sehr komfortable Möglichkeit:

Nach erfolgreicher Synchronisation, sprich, wenn eine komplette DCF77-Sequenze fehlerfrei (Parität) empfangen wurde, stehen die Informationen in der DCF-Datenstruktur. Zugreifen könnt Ihr auf die Daten dann so:

// Ist es 20:15 Uhr?
if ( (time.Hour == 20) && (time.Min == 15) )
{
   // Zeit für TV
}

Bitte fügt in eurem Projekt die folgende Struktur ein. Am besten in eine Headerdatei, die mit der Uhr in Verbindung steht, die ihr gerade programmiert.

#include <stdbool.h>
#include <stdint.h>

/****************************************************
* DCF (Zeit) Datenstruktur
*/

enum eDay
{
   Montag,
   Dienstag,
   Mittwoch,
   Donnerstag,
   Freitag,
   Samstag,
   Sonntag
};

struct sTime
{
   uint8_t Day;
   uint8_t Month;
   uint16_t Year;
   uint8_t Hour;
   uint8_t Min;
   uint8_t Sec;
   enum eDay wDay;
   bool stime;
};

typedef struct sTime tTime;

Dann legt ihr in eurem Projekt noch irgendwo diese Datenstruktur an. Die DCF-Routinen müssen auf die Struktur zugreifen, daher legt die Struktur global an.

tTime time = { 1,1,2014,12,0,0,Sonntag,MEZ };

Dies ist nun der Ort in dem sich die Uhrzeit, Datum, … befindet. Benutzt nur noch diese Struktur für Euer Projekt.

Das war es auch schon. Nicht vergessen die dcf.c und dcf.h Dateien müsst Ihr natürlich in euer Projekt einbinden.

Allgemeines

Die Routinen arbeiten quasi parallel zu Eurem restlichen Programm. Naja parallel ist etwas schlecht ausgedrückt. Sagen wir es anders:

Die Routinen halten den Rest eures Programmes nicht auf. In einem Rhythmus von 1 ms wird aus der Endlosschleife innerhalb von main die Auswert-Routine aufgerufen und erledigt Schritt für Schritt oder besser gesagt Bit für Bit seine Arbeit.

Nachdem durch die Routinen das Startbit erkannt wurde, stehen die Zeitinformationen 60 Sekunden später bereit. Vorausgesetzt die Übertragung wurde fehlerfrei empfangen.

Das ist ein riesen Vorteil, denn so kann Euer Programm weiterhin andere Aufgaben erledigen und ist durch das Empfangen der DCF77 Daten nicht blockiert.

Auf der anderen Seite bedeutet das natürlich auch, dass Ihr Euch nicht ewig in anderen Funktionen aufhalten dürft, welche die main Routine lange unterbrechen würden. Sowas gilt jedoch generell immer und sollte grundsätzlich vermieden werden!

Falls Ihr noch Fragen habt, stellt diese gerne im Forum 

Download

Ich biete Euch die zuvor beschriebenden Routinen als Download an. Die Dateien sind als ZIP-Achriv komprimiert.

Weblinks

Leave a Comment

Your email address will not be published. Required fields are marked *