Dirk (Diskussion | Beiträge) |
Dirk (Diskussion | Beiträge) K (→Wetterinformationen im DCF77-Telegramm) |
||
(33 dazwischenliegende Versionen von 4 Benutzern werden nicht angezeigt) | |||
Zeile 3: | Zeile 3: | ||
[[:Kategorie:Grundlagen]] | [[:Kategorie:Grundlagen]] | ||
− | Im RN-Forum ist das Thema DCF77-Decodierung schon häufig diskutiert worden. Dieser Wiki-Artikel soll einmal grundlegend beschreiben, wie man einen DCF77-Decoder programmieren kann. Da ich hauptsächlich mit Bascom arbeite, war es naheliegend, den Decoder als Bascom-Asm-Library zu schreiben. Er hat bis zur jetzigen Version 4. | + | Im RN-Forum ist das Thema DCF77-Decodierung schon häufig diskutiert worden. Dieser Wiki-Artikel soll einmal grundlegend beschreiben, wie man einen DCF77-Decoder programmieren kann. Da ich hauptsächlich mit Bascom arbeite, war es naheliegend, den Decoder als Bascom-Asm-Library zu schreiben. Er hat bis zur jetzigen Version 4.11 einen langen Weg mit unterschiedlichen Verbesserungen hinter sich. |
== DCF77-Grundlagen == | == DCF77-Grundlagen == | ||
Zeile 26: | Zeile 26: | ||
Tendenziell dreht sich unsere Erde immer langsamer um ihre Achse und "trudelt" dabei noch. Das bedeutet, dass in unregelmäßigen Abständen sogenannte "Schaltsekunden" in die UTC eingefügt werden müssen, um den Gleichlauf der UTC mit der Erdrotation sicher zu stellen. Die vorläufig letzte Einfügung einer Schaltsekunde fand im Januar 2006 statt. Möglich sind solche Einfügungen am 1.1. und 1.7. jedes Jahres. | Tendenziell dreht sich unsere Erde immer langsamer um ihre Achse und "trudelt" dabei noch. Das bedeutet, dass in unregelmäßigen Abständen sogenannte "Schaltsekunden" in die UTC eingefügt werden müssen, um den Gleichlauf der UTC mit der Erdrotation sicher zu stellen. Die vorläufig letzte Einfügung einer Schaltsekunde fand im Januar 2006 statt. Möglich sind solche Einfügungen am 1.1. und 1.7. jedes Jahres. | ||
Das Einfügen einer Sekunde bedeutet für die Aussendung dieses speziellen DCF77-Telegramms, dass die Minute aus 61 Sekunden besteht. Das wird so umgesetzt, dass die 58. Sekundenmarke (P3) nicht wie sonst ein LastBit, sondern ein DataBit ist und sich noch ein weiteres LastBit anschließt. Dieses zusätzliche LastBit hat eine Sekundenmarke von 100ms (LastBit0) und eine Pause von 1900ms. Damit beginnt die neue Minute mit einer Verzögerung von 1 Sekunde, d.h. eine Schaltsekunde wurde eingefügt. | Das Einfügen einer Sekunde bedeutet für die Aussendung dieses speziellen DCF77-Telegramms, dass die Minute aus 61 Sekunden besteht. Das wird so umgesetzt, dass die 58. Sekundenmarke (P3) nicht wie sonst ein LastBit, sondern ein DataBit ist und sich noch ein weiteres LastBit anschließt. Dieses zusätzliche LastBit hat eine Sekundenmarke von 100ms (LastBit0) und eine Pause von 1900ms. Damit beginnt die neue Minute mit einer Verzögerung von 1 Sekunde, d.h. eine Schaltsekunde wurde eingefügt. | ||
+ | |||
+ | Ein DCF77-Telegramm mit Schaltsekunde sieht z.B. so aus (Bits 15..59): | ||
+ | Bits 15..20 Minute P1 Stunde P2 Tag Wochentag Monat Jahr P3 59 | ||
+ | 001011 0000000 0 010000 1 100000 010 11100 11101001 0 0 | ||
+ | Es enthält folgende Informationen: 02.00 Uhr MESZ, Dienstag der 1.7.97. Da gab's übrigens wirklich eine Schaltsekunde! | ||
=== Informationen im DCF77-Telegramm === | === Informationen im DCF77-Telegramm === | ||
Zeile 33: | Zeile 38: | ||
| BIT-NR. || BEDEUTUNG DES DCF77-BITS: | | BIT-NR. || BEDEUTUNG DES DCF77-BITS: | ||
|- | |- | ||
− | | 0..14 || Interne PTB-Informationen | + | | 0..14 || Interne PTB-Informationen / Seit Oktober 2006 Wetterinformationen |
|- | |- | ||
| 15 R || Rufbit für Alarmierung der PTB-Mitarbeiter | | 15 R || Rufbit für Alarmierung der PTB-Mitarbeiter | ||
Zeile 47: | Zeile 52: | ||
| 20 S || Startbit f. Zeitinformationen (immer 1) | | 20 S || Startbit f. Zeitinformationen (immer 1) | ||
|- | |- | ||
− | | 21 || Minute | + | | 21 || Minute × 1 |
|- | |- | ||
− | | 22 || Minute | + | | 22 || Minute × 2 |
|- | |- | ||
− | | 23 || Minute | + | | 23 || Minute × 4 |
|- | |- | ||
− | | 24 || Minute | + | | 24 || Minute × 8 |
|- | |- | ||
− | | 25 || Minute | + | | 25 || Minute × 10 |
|- | |- | ||
− | | 26 || Minute | + | | 26 || Minute × 20 |
|- | |- | ||
− | | 27 || Minute | + | | 27 || Minute × 40 |
|- | |- | ||
| 28 P1 || Minutenparität (gerade Parität Bits 21..27) | | 28 P1 || Minutenparität (gerade Parität Bits 21..27) | ||
|- | |- | ||
− | | 29 || Stunde | + | | 29 || Stunde × 1 |
|- | |- | ||
− | | 30 || Stunde | + | | 30 || Stunde × 2 |
|- | |- | ||
− | | 31 || Stunde | + | | 31 || Stunde × 4 |
|- | |- | ||
− | | 32 || Stunde | + | | 32 || Stunde × 8 |
|- | |- | ||
− | | 33 || Stunde | + | | 33 || Stunde × 10 |
|- | |- | ||
− | | 34 || Stunde | + | | 34 || Stunde × 20 |
|- | |- | ||
| 35 P2 || Stundenparität (gerade Parität Bits 29..34) | | 35 P2 || Stundenparität (gerade Parität Bits 29..34) | ||
|- | |- | ||
− | | 36 || Tag | + | | 36 || Tag × 1 |
|- | |- | ||
− | | 37 || Tag | + | | 37 || Tag × 2 |
|- | |- | ||
− | | 38 || Tag | + | | 38 || Tag × 4 |
|- | |- | ||
− | | 39 || Tag | + | | 39 || Tag × 8 |
|- | |- | ||
− | | 40 || Tag | + | | 40 || Tag × 10 |
|- | |- | ||
− | | 41 || Tag | + | | 41 || Tag × 20 |
|- | |- | ||
− | | 42 || Wochentag | + | | 42 || Wochentag × 1 |
|- | |- | ||
− | | 43 || Wochentag | + | | 43 || Wochentag × 2 |
|- | |- | ||
− | | 44 || Wochentag | + | | 44 || Wochentag × 4 |
|- | |- | ||
− | | 45 || Monat | + | | 45 || Monat × 1 |
|- | |- | ||
− | | 46 || Monat | + | | 46 || Monat × 2 |
|- | |- | ||
− | | 47 || Monat | + | | 47 || Monat × 4 |
|- | |- | ||
− | | 48 || Monat | + | | 48 || Monat × 8 |
|- | |- | ||
− | | 49 || Monat | + | | 49 || Monat × 10 |
|- | |- | ||
− | | 50 || Jahr | + | | 50 || Jahr × 1 |
|- | |- | ||
− | | 51 || Jahr | + | | 51 || Jahr × 2 |
|- | |- | ||
− | | 52 || Jahr | + | | 52 || Jahr × 4 |
|- | |- | ||
− | | 53 || Jahr | + | | 53 || Jahr × 8 |
|- | |- | ||
− | | 54 || Jahr | + | | 54 || Jahr × 10 |
|- | |- | ||
− | | 55 || Jahr | + | | 55 || Jahr × 20 |
|- | |- | ||
− | | 56 || Jahr | + | | 56 || Jahr × 40 |
|- | |- | ||
− | | 57 || Jahr | + | | 57 || Jahr × 80 |
|- | |- | ||
| 58 P3 || Datumsparität (gerade Parität Bits 36..57) | | 58 P3 || Datumsparität (gerade Parität Bits 36..57) | ||
Zeile 138: | Zeile 143: | ||
* Es wurden genau 58 Bits (Ausnahme: Schaltsekunde!) decodiert | * Es wurden genau 58 Bits (Ausnahme: Schaltsekunde!) decodiert | ||
* Im Fall der Schaltsekunde ist das Bit 59 ein (Last-)Bit0 | * Im Fall der Schaltsekunde ist das Bit 59 ein (Last-)Bit0 | ||
+ | * Die beiden Sommer-/Winterzeit-Kennungen Bit17 und Bit18 haben immer einen unterschiedlichen Wert | ||
Dieses intakte DCF77-Telegramm muss anschließend noch auf Plausibilität überprüft werden, bevor es zum Stellen einer Uhr benutzt werden kann. | Dieses intakte DCF77-Telegramm muss anschließend noch auf Plausibilität überprüft werden, bevor es zum Stellen einer Uhr benutzt werden kann. | ||
Zeile 161: | Zeile 167: | ||
Die zweite o.g. Prüf-Frage beinhaltet den Vergleich des aktuellen mit dem vorherigen Telegramm. Da das aktuelle Telegramm die Folgeminute des vorherigen Telegramms abbildet, kann man durch den Vergleich weitere Sicherheit gewinnen. In der Regel werden sich zwei aufeinanderfolgende Telegramme nur geringfügig (durch die Minute!) unterscheiden, d.h. sie sind hoch redundant. Bei Überträgen (z.B. am Monats- oder Jahresende) wird der Anteil redundanter Informationen jedoch deutlich geringer sein. Dies gilt auch bei MEZ-MESZ-Wechseln und bei Schaltjahren (am 29.2.!). Die Prüfung kann in Decodern recht unterschiedlich oder gar nicht umgesetzt werden. Im hier beschriebenen DCF-Decoder wird diese Prüfung nicht durchgeführt. | Die zweite o.g. Prüf-Frage beinhaltet den Vergleich des aktuellen mit dem vorherigen Telegramm. Da das aktuelle Telegramm die Folgeminute des vorherigen Telegramms abbildet, kann man durch den Vergleich weitere Sicherheit gewinnen. In der Regel werden sich zwei aufeinanderfolgende Telegramme nur geringfügig (durch die Minute!) unterscheiden, d.h. sie sind hoch redundant. Bei Überträgen (z.B. am Monats- oder Jahresende) wird der Anteil redundanter Informationen jedoch deutlich geringer sein. Dies gilt auch bei MEZ-MESZ-Wechseln und bei Schaltjahren (am 29.2.!). Die Prüfung kann in Decodern recht unterschiedlich oder gar nicht umgesetzt werden. Im hier beschriebenen DCF-Decoder wird diese Prüfung nicht durchgeführt. | ||
− | == | + | === Wetterinformationen im DCF77-Telegramm === |
+ | Seit neustem (Oktober 2006) werden auch genaue Wetterinformationen zu vielen Städten in Europa im DCF-Signal versteckt. Genau genommen stecken diese Informationen in den ersten 14 Bits des Telegrammes. Interessant zu wissen ist die Tatsache, dass diese Informationen amplitutenmoduliert sind. Die Wetterdaten werden über 24 Stunden in jeder Minute ergänzend zur Uhrzeit und dem Datum von Sekunde 1 bis Sekunde 14 übertragen. Ein Wetterprotokoll für eine Wetterregion und eine Vorhersage wird über den Verlauf von 3 Minuten gesendet. Diese Wetterdaten sind über ihre Sendezeit einer festen Region zugeordnet. | ||
+ | |||
+ | Leider ist das genaue Protokoll zum Entschlüsseln dieser Information bislang nicht komplett veröffentlicht worden. Eine private Firma bietet offenbar Lizenzen zum Dekodieren an. So gibt es im Handel jetzt erste "Wetterstationen" für's Wohnzimmer, welche diese DCF-Informationen auswerten. Aufwendige Sensortechnik ist bei diesen "Wetterstationen" dann hinfällig, denn die Informationen stammen von einem großen, professionellen Wettermessnetzwerk. Aber so spannend wie 'ne eigene Wetterstation mit Sensoren ist's dann doch nicht, oder? | ||
+ | |||
+ | == Der DCF77-Decoder == | ||
=== Hardware === | === Hardware === | ||
Zeile 168: | Zeile 179: | ||
=== Software === | === Software === | ||
− | Der eigentliche DCF-Decoder ist eine Bascom-Library (dcf77.lib), aktuell in der Version 4. | + | Der eigentliche DCF-Decoder ist eine Bascom-Library (dcf77.lib), aktuell in der Version 4.11. |
Dazu gehört ein Include-File (dcf77_soft.bas), also ein Bascom-Programmteil, das mit dem Befehl ... | Dazu gehört ein Include-File (dcf77_soft.bas), also ein Bascom-Programmteil, das mit dem Befehl ... | ||
$include "dcf77_soft.bas" | $include "dcf77_soft.bas" | ||
Zeile 177: | Zeile 188: | ||
;Decodertest.bas: | ;Decodertest.bas: | ||
<pre>'########################################################################### | <pre>'########################################################################### | ||
− | 'Programmname: Decodertest.bas Version 4. | + | 'Programmname: Decodertest.bas Version 4.11 |
' | ' | ||
'Aufgabe: | 'Aufgabe: | ||
Zeile 360: | Zeile 371: | ||
=== Bascom Include-File === | === Bascom Include-File === | ||
− | Das Include-File (dcf77_soft.bas V4. | + | Das Include-File (dcf77_soft.bas V4.11) muss wie auch das Hauptprogramm im Bascom-Ordner \PROGRAMS gespeichert werden. |
;dcf77_soft.bas: | ;dcf77_soft.bas: | ||
<pre>'########################################################################### | <pre>'########################################################################### | ||
Zeile 441: | Zeile 452: | ||
=== Bascom-Library === | === Bascom-Library === | ||
− | Das hier in mehreren Abschnitten beschriebene Library-File (dcf77.lib V4. | + | Das hier in mehreren Abschnitten beschriebene Library-File (dcf77.lib V4.11) ist der eigentliche DCF-Decoder und muss im Bascom-Ordner \LIB abgelegt werden. |
Was kann die dcf77.lib? | Was kann die dcf77.lib? | ||
Zeile 456: | Zeile 467: | ||
<pre>copyright = D. Ottensmeyer | <pre>copyright = D. Ottensmeyer | ||
comment = DCF77-Decoder und Softclock | comment = DCF77-Decoder und Softclock | ||
− | libversion = 4. | + | libversion = 4.11 |
− | date = | + | date = 15.01.2010 |
− | statement = --------------------------------- | + | statement = ---------------------------------- |
+ | statement = Version 4.11: | ||
+ | statement = ! | ||
+ | statement = Version 4.10: | ||
+ | statement = Jahr: BCD->DEZ-Fehler korrigiert! | ||
+ | statement = Version 4.00: | ||
statement = Komplette Paritätsprüfung | statement = Komplette Paritätsprüfung | ||
statement = Auswertung der DCF77-Bits 15..20 | statement = Auswertung der DCF77-Bits 15..20 | ||
Zeile 522: | Zeile 538: | ||
Dcf77_soft ist der eigentliche DCF-Decoder, der 40x pro Sekunde aufgerufen werden muss. Mit "rcall Softclock" wird zuerst die Softclock (s. Abschnitt 7!) ausgeführt. Dann wird der Decoder beim Label dcf77_start: (siehe Abschnitt 2!) gestartet. | Dcf77_soft ist der eigentliche DCF-Decoder, der 40x pro Sekunde aufgerufen werden muss. Mit "rcall Softclock" wird zuerst die Softclock (s. Abschnitt 7!) ausgeführt. Dann wird der Decoder beim Label dcf77_start: (siehe Abschnitt 2!) gestartet. | ||
+ | ;dcf77.lib - Abschnitt 2: | ||
+ | <pre> | ||
+ | dcf77_start: | ||
+ | *lds Counter,{DCF77Counter} ;Pausenzeit-Zähler laden | ||
+ | *lds Parity,{DCF77Parity} ;Fehler-Flag + 1-Zähler laden | ||
+ | sbis PIND,PIND3 ;Input-Pin für DCF-Empfänger | ||
+ | rjmp dcf77_5 ;low: Bit auswerten! | ||
+ | inc Counter ;high: Pausenzeit zählen! | ||
+ | brne dcf77_3 | ||
+ | dcf77_1: | ||
+ | sbr Parity,&B10000000 ;Fehler-Flag setzen | ||
+ | *sts {DCF77Parity},Parity | ||
+ | andi Status,&B11000000 ;Statusbits 0..5 löschen | ||
+ | dcf77_2: | ||
+ | clr Counter ;Pausenzeit-Zähler zurücksetzen | ||
+ | dcf77_3: | ||
+ | *sts {DCF77Counter},Counter | ||
+ | *sts {Dcfstatus},Status | ||
+ | dcf77_4: | ||
+ | ret | ||
− | + | dcf77_5: | |
+ | </pre> | ||
+ | Beim Label dcf77_start: werden zunächst zwei Variablen in Register geladen (Pausenzeit-Zähler und Fehler-Flag/1-Zähler). Dann erfolgt mit "sbis PIND,PIND3" die Abfrage des Eingangs, an den der DCF-Empfänger angeschlossen ist. Wenn der Eingang "high" (1) ist, ist gerade Pause (100% Sendeleistung) und der Pausenzeit-Zähler wird erhöht (inc Counter). Solange der nicht überläuft, wird zu Label dcf77_3 verzweigt. Da werden die Register wieder gesichert und das war's. | ||
+ | Wenn die Pause andauert, wird auf diese Weise alle 25ms der Pausenzeit-Zähler hochgezählt. Bei seinem Überlauf nach über 6 Sekunden wird am Label dcf77_1: das Fehler-Flag gesetzt und der Zähler zurückgesetzt (clr Counter). So ein Überlauf sollte aber eigentlich nicht vorkommen. Wenn ein solcher oder anderer Fehler im Verlauf der weiteren Decodierung (mit einem Sprung zu dcf77_1) dennoch vorkommt, werden zusätzlich die Bits 0..5 im Statusregister gelöscht. Zur Bedeutung dieser Bits siehe den Kommentar in Abschnitt 1 unter ".def Status = r16"! | ||
+ | |||
+ | Wenn am Eingang Pind.3 "low"-Pegel (0) anliegt, dann dürfte es sich um eine Sekundenmarke (25% Sendeleistung) handeln. Das bedeutet, dass der Pausenzeit-Zähler jetzt ausgewertet werden kann, und es erfolgt der Sprung zum Label dcf77_5: (siehe Abschnitt 3!). | ||
+ | |||
+ | ;dcf77.lib - Abschnitt 3: | ||
+ | <pre> | ||
+ | dcf77_5: | ||
+ | tst Counter ;Auswertung fertig? | ||
+ | breq dcf77_4 ;ja: Ende! | ||
+ | *lds Impulse,{DCF77Impulse} ;nein: Auswerten! | ||
+ | *lds Shifter,{DCF77Shifter} | ||
+ | *lds ZL,{DCF77TAL} | ||
+ | *lds ZH,{DCF77TAH} | ||
+ | cpi Counter,LastBit0Max ;LastBit 0 obere Zeitgrenze + 1 | ||
+ | brsh dcf77_1 ;überschritten: Fehler | ||
+ | cpi Counter,LastBit_Mid ;LastBit-Grenze | ||
+ | brlo dcf77_6 | ||
+ | rjmp dcf77_20 ;75..78 = LastBit0 | ||
+ | dcf77_6: | ||
+ | cpi Counter,LastBit1Min ;LastBit 1 untere Zeitgrenze | ||
+ | brlo dcf77_7 | ||
+ | rjmp dcf77_19 ;71..74 = LastBit1 | ||
+ | dcf77_7: | ||
+ | </pre> | ||
+ | Da die Pause jetzt zuende ist, kann die Auswertung der Pausenlänge erfolgen. Das ist aber nur erforderlich, wenn es nicht schon geschehen (d.h. Counter = 0) ist. Die Auswertung beginnt zunächst mit dem Laden von Variablen in Register. Dann erfolgt der Test auf ein LastBit0 oder LastBit1. Die Pause eines LastBit0 ist ja 1900ms lang, der Counter erreicht dafür den Wert 76 (..77). Für ein LastBit1 mit der Pausenlänge 1800ms zählt der Counter bis 72 (..73). | ||
+ | |||
+ | Wie kommt es zu diesen Counter-Werten? | ||
+ | Bei einem Hochzählen des Counters alle 25ms (40Hz) errechnen sich 1900ms als 76 x 25ms | ||
+ | und 1800ms als 72 x 25ms. | ||
+ | |||
+ | In diesem Abschnitt 3 wird also geprüft, ob das Ende des DCF-Telegramms vorliegt. Wenn das der Fall ist, erfolgt die Verzeigung nach dcf77_20 (LastBit0) oder dcf77_19 (LastBit1). Die Auswertung des Telegrammendes wird in Abschnitt 5 beschrieben! | ||
+ | |||
+ | Wenn die obere Zeitgrenze für ein LastBit0 (1975ms) erreicht oder überschritten ist, liegt ein Fehler vor (d.h. Sprung nach dcf77_1, s. Abschnitt 2!). | ||
+ | Wenn die untere Zeitgrenze für ein LastBit1 (1775ms) unterschritten ist, dann kann es sich um die Pause eines DataBits (800/900ms) oder irgendeinen kürzeren Impuls (z.B. eine Störung) handeln. Das Programm verzweigt dann zum Label dcf77_7 (s. Abschnitt 4!), wo die Auswertung der DataBits erfolgt. | ||
+ | |||
+ | ;dcf77.lib - Abschnitt 4: | ||
+ | <pre> | ||
+ | dcf77_7: | ||
+ | sbrc Parity,7 ;wenn Fehler aufgetreten, ... | ||
+ | rjmp dcf77_2 ;... dann Abbruch! | ||
+ | |||
+ | cpi Counter,Spike_Limit ;Störimpulsgrenze | ||
+ | brlo dcf77_2 ;Störimpuls wird ignoriert | ||
+ | cpi Counter,DataBit0Max ;Datenbit 0 obere Zeitgrenze + 1 | ||
+ | brsh dcf77_1 ;überschritten: Fehler | ||
+ | cpi Counter,DataBit1Min ;Datenbit 1 untere Zeitgrenze | ||
+ | brlo dcf77_1 ;unterschritten: Fehler | ||
+ | *sts {DCF77Databit},Counter ;DataBit0/1-Pausenlänge für Debug | ||
+ | cpi Counter,DataBit_Mid ;Datenbit-Grenze | ||
+ | brlo dcf77_8 ;Bit1! | ||
+ | |||
+ | cpi Impulse,20 ;DataBit = 0 (35..38): | ||
+ | breq dcf77_2 ;Bit 20 = 0: Abbruch! | ||
+ | cpi Impulse,58 ;P3 (= 0) bei Schaltsekunde? | ||
+ | breq dcf77_2 ;Abbruch: LastBit 59 (= 0) folgt! | ||
+ | rjmp dcf77_10 ;andere 0-Bits: weiter | ||
+ | |||
+ | dcf77_8: ;DataBit = 1 (31..34): | ||
+ | cpi Impulse,21 ;DCF77-Bits 0..20 (= 1)? | ||
+ | brlo dcf77_9 ;keine Parität! | ||
+ | inc Parity ;sonst alle 1-Bits zählen | ||
+ | *sts {DCF77Parity},Parity | ||
+ | cpi Impulse,28 | ||
+ | breq dcf77_10 ;Minutenparitätsbit nicht addieren! | ||
+ | cpi Impulse,35 | ||
+ | breq dcf77_10 ;Stundenparitätsbit nicht addieren! | ||
+ | cpi Impulse,58 ;P3 (= 1) bei Schaltsekunde? | ||
+ | breq dcf77_2 ;Abbruch: LastBit 59 (= 0) folgt! | ||
+ | dcf77_9: | ||
+ | ld Counter,Z ;Counter = aktuelle DCF77Buffer Zelle | ||
+ | add Counter,Shifter ;addiere 1 an der aktuellen Bitposition | ||
+ | st Z,Counter ;zurückspeichern | ||
+ | |||
+ | dcf77_10: | ||
+ | lsl Shifter ;Shifter auf nächste Bitposition | ||
+ | *sts {DCF77Shifter},Shifter | ||
+ | cpi Impulse,7 | ||
+ | brlo dcf77_16 ;DCF77-Bits 0..7 Zyklus | ||
+ | breq dcf77_15 ;letztes Bit 0..7 Zyklus | ||
+ | cpi Impulse,14 | ||
+ | brlo dcf77_16 ;DCF77-Bits 8..14 Zyklus | ||
+ | brne dcf77_11 | ||
+ | andi Status,&B11000111 ;Statusbits 3..5 löschen | ||
+ | sbr Status,&B00000001 ;Status 15. Impuls erreicht setzen | ||
+ | rjmp dcf77_15 ;letztes Bit 8..14 Zyklus | ||
+ | dcf77_11: | ||
+ | cpi Impulse,20 | ||
+ | brlo dcf77_16 ;DCF77-Bits 15..20 Zyklus | ||
+ | breq dcf77_15 ;letztes Bit (DCF77-Bit 20) | ||
+ | cpi Impulse,28 | ||
+ | brlo dcf77_16 ;Minuten-Zyklus | ||
+ | breq dcf77_17 ;Minutenparität | ||
+ | cpi Impulse,35 | ||
+ | brlo dcf77_16 ;Stunden-Zyklus | ||
+ | breq dcf77_17 ;Stundenparität | ||
+ | cpi Impulse,41 | ||
+ | brlo dcf77_16 | ||
+ | breq dcf77_12 ;letztes Bit TAG | ||
+ | cpi Impulse,44 | ||
+ | brlo dcf77_16 | ||
+ | breq dcf77_12 ;letztes Bit WOCHENTAG | ||
+ | cpi Impulse,49 | ||
+ | brlo dcf77_16 | ||
+ | breq dcf77_12 ;letztes Bit MONAT | ||
+ | cpi Impulse,57 | ||
+ | brne dcf77_16 ;nicht letztes Bit JAHR | ||
+ | dcf77_12: ;DCF77Buffer Zelle in DEZ wandeln: | ||
+ | ld Counter,Z ;Counter = aktuelle DCF77Buffer Zelle | ||
+ | mov Shifter,Counter ;Counter (BCD) | ||
+ | andi Shifter,&H0F | ||
+ | dcf77_13: | ||
+ | subi Counter,&H10 | ||
+ | brcs dcf77_14 | ||
+ | subi Shifter,&HF6 | ||
+ | rjmp dcf77_13 | ||
+ | dcf77_14: ;-> Shifter (DEZ) | ||
+ | st Z,Shifter ;DEZ in Zelle zurückspeichern | ||
+ | cpi Impulse,57 | ||
+ | breq dcf77_16 ;letztes Bit JAHR | ||
+ | dcf77_15: ;aktuelle DCF77Buffer Zelle fertig: | ||
+ | ldi Shifter,1 ;Bitposition zurücksetzen | ||
+ | adiw ZL,1 ;nächste DCF77Buffer Zelle | ||
+ | *sts {DCF77Shifter},Shifter | ||
+ | *sts {DCF77TAL},ZL ;Adresse der nächsten Zelle speichern | ||
+ | *sts {DCF77TAH},ZH | ||
+ | dcf77_16: | ||
+ | inc Impulse ;nächstes DCF77-Bit | ||
+ | *sts {DCF77Impulse},Impulse | ||
+ | rjmp dcf77_2 | ||
+ | |||
+ | dcf77_17: ;Parität Min./Std. Auswertung: | ||
+ | sbrc Parity,0 | ||
+ | rjmp dcf77_2 ;Parität Fehler: Abbruch! | ||
+ | cpi Impulse,28 ;Minutenparität? | ||
+ | brne dcf77_18 ;nein: Stundenparität (35) | ||
+ | sbr Status,&B00000010 ;Status Minutenparität OK setzen | ||
+ | rjmp dcf77_12 | ||
+ | dcf77_18: | ||
+ | sbr Status,&B00000100 ;Status Stundenparität OK setzen | ||
+ | rjmp dcf77_12 ;Parität Min./Std. OK | ||
+ | |||
+ | </pre> | ||
+ | Die Auswertung der DataBits erfolgt in diesem Abschnitt 4 nur, wenn das Fehler-Flag (Parity.7) nicht gesetzt ist. | ||
+ | |||
+ | Mit den vier "cpi"-Befehlen werden jetzt die DataBits decodiert: Ganz kurze Impulse (unter 75ms) werden als Störimpulse ignoriert. Bei Über- oder Unterschreiten der Zeitlimits wird ein Fehler ausgelöst (Sprung zu dcf77_1). Der letzte Test (cpi Counter,DataBit_Mid) entscheidet, ob ein DataBit0 (weiter ab Befehl "cpi Impulse,20") oder ein DataBit1 (ab Label dcf77_8) vorliegt. | ||
+ | |||
+ | Zunächst der Fall "DataBit0": | ||
+ | |||
+ | Counter hat in diesem Fall einen Wert von 36 (..37), entsprechend 900ms Pausenlänge. Es wird mit "cpi Impulse,20" zunächst getestet, ob es sich um das DCF77-Bit 20 (S) handelt. Dieses Bit darf nicht "0" sein, deshalb erfolgt in diesem Fall ein Abbruch (breq dcf77_2). Das Register "Impulse" ist also der Zähler für die DCF77-Bits (0..58). | ||
+ | |||
+ | Dann wird mit "cpi Impulse,58" noch auf eine andere Abbruchbedingung getestet: Wenn das DCF77-Bit 58 ein DataBit0 ist, dann handelt es sich um die Sonderbedingung "Schaltsekunde". Es wird dann noch ein LastBit folgen, deshalb wird hier die Verarbeitung abgebrochen. | ||
+ | Für alle anderen DataBits0 geht's bei dcf77_10 weiter. | ||
+ | |||
+ | Dann der Fall "DataBit1" (ab Label dcf77_8): | ||
+ | |||
+ | Counter hat in diesem Fall einen Wert von 32 (..33), entsprechend 800ms Pausenlänge. Ab den Labeln dcf77_8 und dcf77_9 passieren zwei wichtige Dinge mit den DataBits1: | ||
+ | * Mit "inc Parity" werden die 1-Bits gezählt, um die Parität berechnen zu können. Dabei gibt es eine Ausnahme: Die DCF77-Bits 0..20 werden nicht berücksichtigt. | ||
+ | * Ab dem Label dcf77_9 wird das Bit im Zeit-Zwischenspeicher abgelegt. Der Zwischenspeicher besteht aus 9 Bytes (Bascom: Dcf77buffer(9)). In der Lib werden die einzelnen "Zellen" des Zwischenspeichers durch den Z-Pointer adressiert. Mit "ld Counter,Z" wird zuerst die aktuelle Zelle (z.B. die Minute oder das Jahr) in das Register Counter geladen. Dann wird mit "add Counter,Shifter" eine "Maske" addiert. In dieser Maske ist immer nur 1 Bit gesetzt, und zwar an der Stelle, an der das aktuelle DCF77-Bit in die aktuelle Zelle eingefügt werden muss. Beispiel: Das DCF77-Bit 46 ist das zweite Monatsbit. Die Maske Shifter wäre in diesem Fall binär 00000010. Addiert man diese Maske zur aktuellen Zelle des Zwischenspeichers, dann wird Bit 1 hier gesetzt. Wenn das DCF77-Bit 47 folgt und auch ein DataBit1 ist, ist die Maske 00000100, und auch dieses Bit wird richtig in den Zwischenspeicher eingefügt. Mit "st Z,Counter" wird der Inhalt von Counter im Zwischenspeicher gesichert. | ||
+ | |||
+ | Die Sicherung im Zwischenspeicher gilt nicht für zwei DCF77-Bits: Die Paritätsbits P1 (28) und P2 (35) dürfen nicht in die aktuelle Zelle kopiert werden. Mit "cpi Impulse,58" wird eine weitere Ausnahmebedingung abgefangen: Die Schaltsekunde. Ist P3 ein DataBit1, dann wird noch ein LastBit0 folgen, also hier zunächst ein Abbruch. | ||
+ | |||
+ | Ab dem Label dcf77_10 werden die DataBits0 und DatBits1 wieder gemeinsam weiterverarbeitet. Hier wird zunächst der Shifter in die nächste Bitposition geschoben (lsl Shifter). Danach gibt es mehrere Einzeltests: Von "cpi Impulse,7" bis "cpi Impulse,57" werden einige Positionen von DCF77-Bits abgefragt. Bei manchen DCF77-Bits gibt es eigentlich nichts mehr zu tun: Wenn ein DCF77-Bit irgendwo mitten in einer Zelle angekommen ist, muss nur noch der Zähler "Impulse" hochgezählt werden (dcf77_16). Etwas anderes ist es, wenn das LETZTE DCF77-Bit einer Zelle (z.B. 20 oder 41) decodiert wurde: Hier muss entweder nur die Maske in Shifter auf binär 00000001 (Label dcf77_15) zurückgesetzt und der Z-Pointer auf die nächste Zelle ("adiw ZL,1") gesetzt werden, ODER der Inhalt der aktuellen Zelle muss vorher noch (Label dcf77_12 bis dcf77_14) vom BCD- ins Dezimal-Format umgewandelt werden. | ||
+ | |||
+ | Im Rahmen der o.g. Einzeltests werden auch Statusbits beeinflusst: Mit "andi Status,&B11000111" werden die Statusbits 3..5 gelöscht, danach mit "sbr Status,&B00000001" das Statusbit 0 (15. Impuls erreicht) gesetzt. Damit endet die Gültigkeit des alten, und es beginnt die Decodierung eines neuen Telegramms ab dem DCF77-Bit 15. | ||
+ | |||
+ | Die Paritätsbits 28 und 35 sind noch auszuwerten (ab Label dcf77_17): Wenn die Minuten- oder Stunden-Parität UNGERADE ist (Parity.0 = 1), erfolgt der Abbruch. Ist die Parität o.k., wird das zugehörige Statusbit (Bit 1 oder Bit 2) gesetzt. | ||
+ | |||
+ | In diesem Abschnitt 4 hat der Decoder die Hauptarbeit geleistet. Aber wie geht's von hier aus weiter? Das Telegrammende wird in Abschnitt 3 festgestellt, von dort verzweigt der Decoder zum nächsten Abschnitt 5 (Label dcf77_19, dcf77_20)! | ||
+ | |||
+ | ;dcf77.lib - Abschnitt 5: | ||
+ | <pre> | ||
+ | dcf77_19: ;Telegramm Ende: | ||
+ | inc Parity ;LastBit = 1 | ||
+ | dcf77_20: ;LastBit = 0 | ||
+ | *sts {DCF77Lastbit},Counter ;LastBit0/1-Pausenlänge für Debug | ||
+ | cpi Impulse,58 ;58 DCF77-Bits erfasst? | ||
+ | brne dcf77_21 ;unvollständig: weiter | ||
+ | sbr Status,&B00010000 ;ja: Status 58 Impulse empfangen setzen | ||
+ | dcf77_21: | ||
+ | andi Parity,&B10000001 | ||
+ | brne dcf77_22 ;Paritäts- oder sonst. Fehler aufgetreten? | ||
+ | sbr Status,&B00001000 ;nein: Status Parität OK setzen | ||
+ | dcf77_22: | ||
+ | mov Counter,Status | ||
+ | andi Counter,&B00011111 ;Status Bits 0..4? | ||
+ | cpi Counter,&B00011111 ;alle 5 Bits = 1? | ||
+ | breq dcf77_24 | ||
+ | dcf77_23: | ||
+ | clr Shifter ;Validitäts-Zähler zurücksetzen | ||
+ | rjmp dcf77_25 ;Fehler: Softclock NICHT stellen! | ||
+ | |||
+ | dcf77_24: ;Plausibilitäts-Check: | ||
+ | ld Counter,Z ;Jahr (0..99) | ||
+ | cpi Counter,100 | ||
+ | brsh dcf77_23 ;> 99: Nicht plausibel! | ||
+ | ld Counter,-Z ;Monat (1..12) | ||
+ | tst Counter | ||
+ | breq dcf77_23 | ||
+ | cpi Counter,13 | ||
+ | brsh dcf77_23 | ||
+ | ld Counter,-Z ;Wochentag (1..7) | ||
+ | tst Counter | ||
+ | breq dcf77_23 | ||
+ | cpi Counter,8 | ||
+ | brsh dcf77_23 | ||
+ | ld Counter,-Z ;Tag (1..31) | ||
+ | tst Counter | ||
+ | breq dcf77_23 | ||
+ | cpi Counter,32 | ||
+ | brsh dcf77_23 | ||
+ | ld Counter,-Z ;Stunde (0..23) | ||
+ | cpi Counter,24 | ||
+ | brsh dcf77_23 | ||
+ | ld Counter,-Z ;Minute (0..59) | ||
+ | cpi Counter,60 | ||
+ | brsh dcf77_23 | ||
+ | adiw ZL,5 ;Pufferzeiger auf Jahr zurücksetzen | ||
+ | sbr Status,&B00100000 ;Status Gültiges DCF77-Telegramm setzen | ||
+ | |||
+ | *lds Shifter,{DCF77Val_cnt} ;Validitäts-Zähler laden | ||
+ | inc Shifter ;Zähler erhöhen | ||
+ | cpi Shifter,Val_Limit ;Stand Validitäts-Zähler? | ||
+ | brlo dcf77_25 ;< Val_Limit: Softclock NICHT stellen! | ||
+ | ldi Shifter,Val_Limit ;Zähler wird nicht > Val_Limit! | ||
+ | |||
+ | sbrs Status,7 ;Status DCF-Decoder EIN/AUS? | ||
+ | rjmp dcf77_25 ;AUS: Softclock NICHT stellen! | ||
+ | |||
+ | ;Softclock nach DCF stellen: | ||
+ | loadadr _year,X ;Zeiger auf Softclock-Variablen | ||
+ | ld Counter,Z ;Jahr = DCF77Buffer(9) | ||
+ | st X,Counter ;in _year sichern | ||
+ | ld Counter,-Z ;Monat = DCF77Buffer(8) | ||
+ | st -X,Counter ;in _month sichern | ||
+ | ld Counter,-Z ;Wochentag = DCF77Buffer(7) | ||
+ | *sts {_dayofweek},Counter ;in _dayofweek sichern | ||
+ | ld Counter,-Z ;Tag = DCF77Buffer(6) | ||
+ | st -X,Counter ;in _day sichern | ||
+ | ld Counter,-Z ;Stunde = DCF77Buffer(5) | ||
+ | st -X,Counter ;in _hour sichern | ||
+ | ld Counter,-Z ;Minute = DCF77Buffer(4) | ||
+ | st -X,Counter ;in _min sichern | ||
+ | clr Counter | ||
+ | st -X,Counter ;_sec zurücksetzen | ||
+ | *sts {Dcf77hsec},Counter ;Sek.-Bruchteile zurücksetzen | ||
+ | ld Counter,-Z ;DCF77-Bits 15..20 = DCF77Buffer(3) | ||
+ | *sts {Dcfflags},Counter ;in Dcfflags sichern | ||
+ | ld Counter,-Z ;DCF77-Bits 8..14 = DCF77Buffer(2) | ||
+ | *sts {Dcf77bits8_14},Counter ;in Dcf77bits8_14 sichern | ||
+ | ld Counter,-Z ;DCF77-Bits 0..7 = DCF77Buffer(1) | ||
+ | *sts {Dcf77bits0_7},Counter ;in Dcf77bits0_7 sichern | ||
+ | sbr Status,&B01000000 ;Status Softclock nach DCF gestellt setzen | ||
+ | |||
+ | dcf77_25: | ||
+ | </pre> | ||
+ | Dieser Abschitt 5 wird nur dann aufgerufen, wenn ein LastBit decodiert wurde (s. Abschnitt 3!). Damit ist das vollständige Minutentelegramm empfangen worden und muss jetzt noch geprüft werden. | ||
+ | In der Regel handelt es sich bei diesem letzten Bit ja um das DCF77-Bit 58 (P3). Wenn es ein LastBit1 ist (Label dcf77_19), wird zunächst mit "inc Parity" die Datumsparität vervollständigt. | ||
+ | Dann wird getestet, ob es sich tatsächlich um das DCF77-Bit 58 handelt. Wenn ja, wird mit "sbr Status,&B00010000" das Bit 4 im Statusregister gesetzt. | ||
+ | Dann erfolgt mit "andi Parity,&B10000001" der letzte Fehler- und Paritätstest. Er ist bestanden, wenn das Ergebnis Null ist. Dann wird Bit 3 im Statusregister gesetzt. | ||
+ | Jetzt (Label dcf77_22) sind alle Tests vollständig, um das DCF77-Telegramm als intakt oder fehlerhaft bewerten zu können. Wenn alle Statusbits 0..4 gesetzt sind, ist die Decodierung ohne Fehler verlaufen: Das Telegramm ist intakt! Die Verzweigung erfolgt zu Label dcf77_24. | ||
+ | |||
+ | Wenn das Telegramm nicht intakt ist (Label dcf77_23), wird der Validitäts-Zähler (dazu später mehr) gelöscht. Die weitere Prüfung wird abgebrochen (dcf77_25). | ||
+ | |||
+ | Wenn die Verarbeitung das Label dcf77_24 erreicht, dann ist das Telegramm intakt und wird auf Plausibilität geprüft. Wenn der Test erfolgreich ist, wird mit "sbr Status,&B00100000" das Bit 5 im Statusregister (Gültiges DCF77-Telegramm) gesetzt. | ||
+ | |||
+ | Danach (ab "*lds Shifter,{DCF77Val_cnt}") entscheidet sich, ob dieses gültige Telegramm zum Stellen der Softclock verwendet wird. Dazu verfügt der Decoder über einen Validitäts-Zähler, der nach jedem gültigen Telegramm hochgezählt wird. Wenn sein Stand kleiner als die festgelegte Grenze (Konstante "Val-Limit", s. Abschnitt 1!) ist, wird die Softclock nicht gestellt. Val-Limit ist aktuell auf "1" eingestellt, so dass jedes gültige Telegramm genutzt wird. | ||
+ | |||
+ | Zuletzt wird noch (durch "sbrs Status,7") geprüft, ob überhaupt ein Stellen der Softclock gewünscht wird: Ist Status.7 nicht gesetzt, wird die Softclock nie auf die aktuelle DCF-Zeit gestellt. | ||
+ | Das Bascom Hauptprogramm muss also das Stellen bewusst einschalten: | ||
+ | Dcfstatus.7 = 1 'DCF-Decoder EIN | ||
+ | |||
+ | Jetzt erfolgt endlich das Stellen der Softclock. Danach wird Bit 6 im Statusregister gesetzt (Softclock nach DCF gestellt). | ||
+ | |||
+ | Zum Schluss noch ein Wort zum Sonderfall "Schaltsekunde": | ||
+ | In diesem Abschnitt 5 ist dafür keinerlei Sonderbehandlung zu sehen. Das ist auch nicht erforderlich: Das DCF77-Bit 59 ist ja ein LastBit0. Dadurch ist die Datumsparität fehlerfrei (Bit 59 wird quasi als Paritätsbit für die DCF77-Bits 36..58 angesehen und muss dann ja immer "0" sein. Wenn es ein LastBit1 sein sollte, wird das als Fehler erkannt.). | ||
+ | |||
+ | ;dcf77.lib - Abschnitt 6: | ||
+ | <pre> | ||
+ | dcf77_25: | ||
+ | andi Status,&B11111000 ;Statusbits 0..2 löschen | ||
+ | rjmp dcf77_26 | ||
+ | |||
+ | ;------------------------------------------------------------------------------- | ||
+ | [Dcf77_init] | ||
+ | $EXTERNAL Dcf77_soft | ||
+ | |||
+ | Dcf77_init: ;DCF77-Variablen Reset | ||
+ | clr Shifter ;Validitätszähler zurücksetzen | ||
+ | andi Status,&B11000000 ;Statusbits 0..5 löschen | ||
+ | dcf77_26: | ||
+ | *sts {Dcfstatus},Status ;Status speichern | ||
+ | *sts {DCF77Val_cnt},Shifter ;Validitäts-Zähler speichern | ||
+ | loadadr DCF77Buffer(1),Z ;Adresse DCF77Buffer | ||
+ | mov Parity,ZL | ||
+ | mov Impulse,ZH | ||
+ | loadadr DCF77Counter,Z ;Adresse DCF77Counter | ||
+ | ldi Counter,12 | ||
+ | clr Shifter | ||
+ | dcf77_27: ;DCF77Counter..DCF77Buffer = 0 | ||
+ | st Z+,Shifter | ||
+ | dec Counter | ||
+ | brne dcf77_27 | ||
+ | inc Counter | ||
+ | st Z+,Counter ;DCF77Shifter = 1 | ||
+ | st Z+,Parity ;Adresse DCF77Buffer -> DCF77TAL/AH | ||
+ | st Z,Impulse | ||
+ | ret | ||
+ | |||
+ | </pre> | ||
+ | Der Abschnitt 6 ist das 3. Unterprogramm "Dcf77_init" in der dcf77.lib. In diesen Programmteil wird über Label dcf77_25 eingesprungen, wenn das Ende eines Telegramms im Abschnitt 5 festgestellt worden war. In diesem Fall werden nur die Bits 0..2 des Statusregisters gelöscht. | ||
+ | |||
+ | In Dcf77_init werden alle Variablen wieder initialisiert, so dass ein neues Telegramm decodiert werden kann. Die Statusbits 0..5 werden gelöscht. Das Statusbit 6 (Softclock nach DCF gestellt) bleibt jedoch unverändert. Im Bascom-Programm ist durch dieses Flag also dauerhaft nachweisbar, dass die Softclock MINDESTENS EINMAL nach DCF gestellt wurde. Zurückgesetzt wird dieses Bit nur durch bestimmte seltene Bedingungen in der Softclock (s. Abschnitt 7!) oder bei Bedarf durch das Bascom-Programm selbst (Dcfstatus.6 = 0). | ||
+ | |||
+ | ;dcf77.lib - Abschnitt 7: | ||
+ | <pre> | ||
+ | ;------------------------------------------------------------------------------- | ||
+ | Softclock: | ||
+ | *lds Counter,{Dcf77hsec} | ||
+ | inc Counter ;Sek.-Bruchteile erhöhen | ||
+ | cpi Counter,ISR_freq ;1000ms = 1s erreicht? | ||
+ | breq Soft_10 ;ja: weiter | ||
+ | *sts {Dcf77hsec},Counter | ||
+ | ret ;sonst Ende | ||
+ | Soft_10: | ||
+ | clr Counter ;Sek.-Bruchteile löschen | ||
+ | *sts {Dcf77hsec},Counter | ||
+ | *lds Counter,{_sec} | ||
+ | inc Counter ;Sekunde erhöhen | ||
+ | cpi Counter,60 ;60 Sekunden erreicht? | ||
+ | breq Soft_20 ;ja: weiter | ||
+ | *sts {_sec},Counter | ||
+ | ret | ||
+ | Soft_20: | ||
+ | clr Counter ;Sekunde löschen | ||
+ | *sts {_sec},Counter | ||
+ | *lds Counter,{_min} | ||
+ | inc Counter ;Minute erhöhen | ||
+ | cpi Counter,60 ;60 Minuten erreicht? | ||
+ | breq Soft_30 ;ja: weiter | ||
+ | *sts {_min},Counter | ||
+ | ret | ||
+ | Soft_30: | ||
+ | clr Counter ;Minute löschen | ||
+ | *sts {_min},Counter | ||
+ | *lds Counter,{_hour} | ||
+ | inc Counter ;Stunde erhöhen | ||
+ | cpi Counter,24 ;24 Stunden erreicht? | ||
+ | breq Soft_40 ;ja: weiter | ||
+ | *sts {_hour},Counter | ||
+ | ret | ||
+ | Soft_40: | ||
+ | clr Counter ;Stunde löschen | ||
+ | *sts {_hour},Counter | ||
+ | *lds Counter,{_dayofweek} | ||
+ | inc Counter ;Wochentag erhöhen | ||
+ | cpi Counter,8 ;letzter Wochentag erreicht? | ||
+ | brne Soft_50 ;nein: weiter | ||
+ | ldi Counter,1 ;Wochentag auf "1" (Montag) | ||
+ | Soft_50: | ||
+ | *sts {_dayofweek},Counter | ||
+ | *lds Counter,{_day} ;Tag holen | ||
+ | *lds Shifter,{_month} ;Monat holen | ||
+ | ldi zl,low(Daysofmonth*2) | ||
+ | ldi zh,high(Daysofmonth*2) ;Anzahl Tage pro Monat holen | ||
+ | add zl,Shifter ;Zeiger auf aktuellen Monat | ||
+ | lpm ;Anzahl Tage holen | ||
+ | cp Counter,r0 ;Monatsende erreicht? | ||
+ | brne Soft_90 ;nein: weiter | ||
+ | cpi Shifter,2 ;Monatsende Februar (wg. Schaltjahr)? | ||
+ | brne Soft_60 ;nein: weiter | ||
+ | cbr Status,&B01000000 ;Status Softclock nach DCF gestellt löschen | ||
+ | Soft_60: | ||
+ | cpi Shifter,6 ;Monatsende Juni (wg. Schaltsekunde)? | ||
+ | brne Soft_70 ;nein: weiter | ||
+ | cbr Status,&B01000000 ;Status Softclock nach DCF gestellt löschen | ||
+ | Soft_70: | ||
+ | ldi Counter,1 ;Tag auf 1 | ||
+ | cpi Shifter,12 ;Jahresende (+ wg. Schaltsekunde)? | ||
+ | brne Soft_100 ;nein: weiter | ||
+ | cbr Status,&B01000000 ;Status Softclock nach DCF gestellt löschen | ||
+ | *lds Shifter,{_year} ;Jahr holen | ||
+ | inc Shifter ;Jahr erhöhen | ||
+ | cpi Shifter,100 ;Jahr 100 erreicht? | ||
+ | brne Soft_80 ;nein: Ende | ||
+ | clr Shifter ;Jahr 00 setzen | ||
+ | Soft_80: | ||
+ | *sts {_year},Shifter ;speichern | ||
+ | ldi Shifter,1 ;Monat auf 1 | ||
+ | rjmp Soft_110 | ||
+ | Soft_90: | ||
+ | inc Counter ;Tag erhöhen | ||
+ | rjmp Soft_110 | ||
+ | Soft_100: | ||
+ | inc Shifter ;Monat erhöhen | ||
+ | Soft_110: | ||
+ | *sts {_day},Counter ;Datum speichern | ||
+ | *sts {_month},Shifter | ||
+ | ret | ||
+ | |||
+ | Daysofmonth: | ||
+ | .db 00, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 | ||
+ | [end] | ||
+ | </pre> | ||
+ | Dies ist die Softclock. Sie verwendet (mit Ausnahme der Sekunden-Bruchteile und des Wochentages) die Variablen der Bascom-Softclock, so dass die Uhrzeit im Hauptprogramm wie gewohnt dargestellt werden kann. | ||
+ | Die Funktion muss hier nicht detailliert beschrieben werden. Ein Punkt soll aber noch erwähnt werden: | ||
+ | |||
+ | In der Softclock wird das Statusbit 6 (Softclock nach DCF gestellt) unter folgenden Bedingungen gelöscht: | ||
+ | * Monatsende Februar (ggf. Schaltjahr mit 29.2.) | ||
+ | * Jahresmitte (ggf. Schaltsekunde) | ||
+ | * Jahresende (ggf. Schaltsekunde) | ||
+ | Das Statusbit 6 bleibt also normalerweise nach dem ERSTEN Stellen der Uhr nach DCF dauerhaft gesetzt. Das Bit wird nur dann "0", wenn die Softclock evtl. wegen Schaltsekunde/-jahr nicht mehr aktuell ist. | ||
+ | |||
+ | Hinweis: Wenn ihr den Quellcode der lib aus den einzelnen Abschnitten koppiert, bitte aufpassen da sind einige Label doppelt! | ||
+ | dcf77_25: , dcf77_7: , dcf77_start: jeweils einmal rauslöschen. | ||
+ | |||
+ | |||
+ | ==Alternative: DCF77 Seriell Wandler Chip== | ||
+ | Statt mit Controller lässt sich das DCF Signal auch mittels eines kleinen Decoderchips in ein serielles Signal (RS232 mit 5V oder 3V Pegel) wandeln und einfach einlesen. Vorteil: Keine Belegung von Timer oder Interrupt und eine interne Uhr die auch bei DCF-Ausfall Zeit- und Datum-Informationen auf Abruf liefert. | ||
+ | |||
+ | * [http://www.robotikhardware.de/download/DCF_RS1.pdf DCF-RS1 Datenblatt] | ||
+ | |||
+ | http://www.shop.robotikhardware.de/shop/catalog/images/artikelbilder/dcfrs1/chip.jpg http://www.shop.robotikhardware.de/shop/catalog/images/artikelbilder/dcfrs1/schaltbild_dcfrs1_390px.png | ||
+ | |||
+ | ===Übertragung erfolgt über einen Port === | ||
+ | Wahlweise über Pin-DOUT, wo die Ausgabe durch einen Low-Impuls angestossen wird (siehe Bild), oder über den TX-Pin im Minutentakt. | ||
+ | |||
+ | [[Bild:protokolldcfrs1.png]] | ||
+ | |||
+ | ===Chip Ausgabe erfolgt als Text oder wahlweise Binär=== | ||
+ | |||
+ | [[Bild:terminaltext.png]] | ||
+ | |||
+ | [[Bild:terminalbinaer.png]] | ||
+ | |||
+ | ===Beispielcode zum Abrufen der Zeit per Chip=== | ||
+ | |||
+ | <pre> | ||
+ | |||
+ | '############################################################## | ||
+ | 'DCF_Decoder3.bas | ||
+ | ' | ||
+ | 'Das Programm liest die Uhrzeit aus dem digetalen DCF-Decoder aus | ||
+ | 'und gibt die Zeit über RS232 aus | ||
+ | 'Es muss lediglich PIN PD7 mit dem DOUT Pin des Chips DCF-RS1 verbunden werden | ||
+ | 'Natürlich auch GND und VCC | ||
+ | ' | ||
+ | ' (c) Frank roboternetz.de | ||
+ | 'Weitere Beispiele auf DVD oder im www.Roboternetz.de und rn-wissen.de | ||
+ | '###################################################################### | ||
+ | |||
+ | |||
+ | Declare Sub DCF_UhrzeitLesen() | ||
+ | |||
+ | $programmer = 12 'MCS USB (Zeile weglassen wenn anderer Programmer) | ||
+ | $prog &HFF , &HFF , &HD9 , &HFE 'Fusebits richtig programmieren (Quarz ein,Jtag aus) | ||
+ | $regfile = "m644pdef.dat" | ||
+ | $framesize = 32 | ||
+ | $swstack = 32 | ||
+ | $hwstack = 64 | ||
+ | $crystal = 8000000 'Quarzfrequenz | ||
+ | $baud = 9600 'Baudrate (Übertragungsgeschwindigkeit) | ||
+ | Baud = 9600 | ||
+ | Config Pind.6 = Output 'LED | ||
+ | Led1 Alias Portd.6 | ||
+ | |||
+ | |||
+ | |||
+ | 'Diese Variablen enthalten nach dem Abruf die Uhrzeit und Datum Informationen | ||
+ | Dim uhr_sek As byte | ||
+ | Dim uhr_min As byte | ||
+ | Dim uhr_std As byte | ||
+ | Dim uhr_tag As byte | ||
+ | Dim uhr_monat As byte | ||
+ | Dim uhr_jahr As byte | ||
+ | Dim uhr_wochentag As byte | ||
+ | Dim uhr_sommerzeit As byte | ||
+ | Dim uhr_zeitumschaltung As byte | ||
+ | Dim uhr_sync As byte | ||
+ | |||
+ | dim bstart as byte 'Enthaelt Byte Zeilenanfang immer 126 | ||
+ | dim bende as byte 'Enthaelt Byte Zeilenanfang immer 127 | ||
+ | |||
+ | dim zeitgueltig as byte | ||
+ | |||
+ | wait 1 | ||
+ | zeitgueltig=0 | ||
+ | Do | ||
+ | DCF_UhrzeitLesen | ||
+ | if zeitgueltig=1 then | ||
+ | Print "Heute haben wir den: "; uhr_tag;".";uhr_monat;".";uhr_jahr;" ";uhr_std;":"; uhr_min;":";uhr_sek;" Wochentag:";uhr_wochentag | ||
+ | if uhr_sommerzeit=1 then print "Es gilt Sommerzeit!" | ||
+ | print "Die Uhr wurde von "; uhr_sync; " Minuten per Funk abgeglichen" | ||
+ | print | ||
+ | endif | ||
+ | Wait 5 | ||
+ | toggle led1 | ||
+ | Loop | ||
+ | |||
+ | |||
+ | 'Die eigentliche Abruffunktion: | ||
+ | |||
+ | 'Diese Funktion ruft der Zeit im binären Modus ab | ||
+ | 'und uerbergibt diese den globalen Uhr Variablen | ||
+ | Sub DCF_UhrzeitLesen() | ||
+ | Config Pind.7 = Input | ||
+ | if Pind.7 =1 then 'DCF Uhrzeit bereits vorhanden? | ||
+ | 'Kurz auf GND setzen um DCF Decoderzeit ausgeben zu lassen | ||
+ | Config PORTD.7 = output | ||
+ | PORTD.7=0 | ||
+ | waitus 1 | ||
+ | Config Pind.7 = Input | ||
+ | |||
+ | Open "comd.7:2400,8,n,1" For input As #1 | ||
+ | Inputbin #1,bstart,uhr_tag,uhr_monat,uhr_jahr,uhr_std, uhr_min,uhr_sek,uhr_wochentag,uhr_sommerzeit,uhr_zeitumschaltung,uhr_sync,bende | ||
+ | Close #1 | ||
+ | Config Pind.7 = Input | ||
+ | uhr_tag=uhr_tag-65 | ||
+ | uhr_monat =uhr_monat-65 | ||
+ | uhr_jahr =uhr_jahr-65 | ||
+ | uhr_std=uhr_std-65 | ||
+ | uhr_min =uhr_min-65 | ||
+ | uhr_sek =uhr_sek-65 | ||
+ | uhr_wochentag = uhr_wochentag-65 | ||
+ | uhr_sommerzeit=uhr_sommerzeit-65 | ||
+ | uhr_zeitumschaltung=uhr_zeitumschaltung-65 | ||
+ | uhr_sync=uhr_sync-65 | ||
+ | zeitgueltig=1 | ||
+ | else | ||
+ | zeitgueltig=0 | ||
+ | Print "Zeit noch nicht verifiziert!" | ||
+ | endif | ||
+ | |||
+ | end sub | ||
+ | |||
+ | </pre> | ||
+ | = Autor/Autoren= | ||
+ | * [[Benutzer:Dirk|Dirk]] 19:49, 12. Dez 2006 (CET) | ||
+ | * --[[Benutzer:Frank|Frank]] 20:05, 16. Feb 2007 (CET) (Kleine Wetter Ergänzung) | ||
+ | * ---[[Benutzer:Dirk|Dirk]] 22:50, 23. Jan 2010 (CET) (Library Version 4.11 Update) | ||
= Quellen = | = Quellen = | ||
− | ;Quelle1: Piester, Hetzel, Bauch; Zeit- und Normalfrequenzverbreitung durch DCF77; PTB-Mitteilungen 114, Heft 4; 2004 | + | ;Quelle1: [http://www.ptb.info/de/org/4/44/pdf/dcf77.pdf Piester, Hetzel, Bauch; Zeit- und Normalfrequenzverbreitung durch DCF77; PTB-Mitteilungen 114, Heft 4; 2004] |
;Quelle2: [http://de.wikipedia.org/wiki/DCF77 Wikipedia] | ;Quelle2: [http://de.wikipedia.org/wiki/DCF77 Wikipedia] | ||
;Quelle3: [http://www.dcf77.de/index.htm DCF77] | ;Quelle3: [http://www.dcf77.de/index.htm DCF77] | ||
;Quelle4: [http://www.wolfgang-back.com/PDF/DCF77.pdf Wolfgang Back] | ;Quelle4: [http://www.wolfgang-back.com/PDF/DCF77.pdf Wolfgang Back] | ||
+ | == Siehe auch== | ||
+ | * [http://www.robotikhardware.de/download/DCF_RS1.pdf DCF-RS1 DCF-RS1-Datenblatt] | ||
− | + | [[Kategorie:Microcontroller]] | |
+ | [[Kategorie:Quellcode Bascom]] |
Aktuelle Version vom 17. März 2018, 18:36 Uhr
Inhaltsverzeichnis
DCF77-Decodierung
Im RN-Forum ist das Thema DCF77-Decodierung schon häufig diskutiert worden. Dieser Wiki-Artikel soll einmal grundlegend beschreiben, wie man einen DCF77-Decoder programmieren kann. Da ich hauptsächlich mit Bascom arbeite, war es naheliegend, den Decoder als Bascom-Asm-Library zu schreiben. Er hat bis zur jetzigen Version 4.11 einen langen Weg mit unterschiedlichen Verbesserungen hinter sich.
DCF77-Grundlagen
Seit dem 1.9.1970 wird in Deutschland die amtliche Zeit über 24 Stunden ausgesendet. Der DCF77-Sender steht in Mainflingen bei Frankfurt. Sein 77,5 kHz Trägersignal wird von meistens schmalbandigen Empfängern überall in Westeuropa in Form von "Funkuhren" empfangen. Die Empfänger in den Funkuhren liefern am Ausgang die "Hüllkurve" des amplitudenmodulierten Signals. DCF77-Empfänger werden auch unabhängig von Funkuhren angeboten (z.B. CONRAD Best.-Nr. 641138 oder von Reichelt ...). Das Ausgangssignal der Empfänger kann direkt an einen Eingangs-Pin eines Mikrokontrollers gelegt und von diesem weiter verarbeitet werden. Diese Weiterverarbeitung ist die DCF77-Decodierung, die dieser Artikel beschreibt.
Aufbau des DCF77-Signals
Der DCF77-Sender sendet sogenannte "Sekundenmarken". Sie bilden den Anfang jeder Sekunde und sind entweder 100ms oder 200ms lang. Während dieser 0,1s oder 0,2s wird die Sendeleistung auf etwa 25% abgesenkt. Danach beginnt eine "Pause" mit der vollen Sendeleistung (100%). Diese Pause ergänzt die laufende Sekunde: Sie ist also 900ms lang, wenn die Sekundenmarke 100ms lang war oder 800ms lang, wenn die Sekundenmarke 200ms lang war. Die Sekundenmarken werden von 0 bis 58 durchnummeriert. Eine 59. Marke gibt es normalerweise nicht, stattdessen verlängert sich die Pause nach der 58. Sekundenmarke auf 1900ms oder 1800ms (je nach Länge der 58. Marke). Diese Pause dient bei den Decodern zur Synchronisation: Wenn sie endet, beginnt die neue Minute und damit auch das nächste DCF77-Telegramm mit der Sekundenmarke 0.
Im weiteren Text verwenden wir diese Begriffe:
- Pause: Eine Pause bezeichnet die Phasen mit 100% Sendeleistung.
- Sekundenmarke: Eine Sekundenmarke ist die Phase mit 25% Sendeleistung.
- DataBit: Ein DataBit besteht aus der Sekundenmarke (100/200ms) und aus der zugehörigen Pause (900/800ms).
- LastBit: Ein LastBit kommt nur EINMAL in jeder Minute vor und kennzeichnet das Ende der Minute. Es besteht aus der (normalerweise 58.) Sekundenmarke (100/200ms) und der zugehörigen Pause (1900/1800ms).
- (DCF77-)Telegramm: Ein Telegramm ist die vollständige DCF77-Information, die in einer Minute gesendet wird. Sie endet mit einem LastBit. Mit dem Ende des LastBits beginnt die Minute, auf die sich die Informationen des Telegramms beziehen. Somit kann am Ende des LastBits eine Uhr gestellt werden.
- (Data-/Last-)Bit0: Ein DataBit oder LastBit ist ein Bit0, wenn die Sekundenmarke 100ms lang ist. Bit0 entspricht dem logischen "low" oder "0".
- (Data-/Last-)Bit1: Ein DataBit oder LastBit ist ein Bit1, wenn die Sekundenmarke 200ms lang ist. Bit1 entspricht dem logischen "high" oder "1".
Wenn ich gesagt habe, dass es eine 59. Sekundenmarke normalerweise nicht gibt, dann war das sicher richtig. Es gibt aber eine wichtige Ausnahme.
Einfügen einer Schaltsekunde
Tendenziell dreht sich unsere Erde immer langsamer um ihre Achse und "trudelt" dabei noch. Das bedeutet, dass in unregelmäßigen Abständen sogenannte "Schaltsekunden" in die UTC eingefügt werden müssen, um den Gleichlauf der UTC mit der Erdrotation sicher zu stellen. Die vorläufig letzte Einfügung einer Schaltsekunde fand im Januar 2006 statt. Möglich sind solche Einfügungen am 1.1. und 1.7. jedes Jahres. Das Einfügen einer Sekunde bedeutet für die Aussendung dieses speziellen DCF77-Telegramms, dass die Minute aus 61 Sekunden besteht. Das wird so umgesetzt, dass die 58. Sekundenmarke (P3) nicht wie sonst ein LastBit, sondern ein DataBit ist und sich noch ein weiteres LastBit anschließt. Dieses zusätzliche LastBit hat eine Sekundenmarke von 100ms (LastBit0) und eine Pause von 1900ms. Damit beginnt die neue Minute mit einer Verzögerung von 1 Sekunde, d.h. eine Schaltsekunde wurde eingefügt.
Ein DCF77-Telegramm mit Schaltsekunde sieht z.B. so aus (Bits 15..59):
Bits 15..20 Minute P1 Stunde P2 Tag Wochentag Monat Jahr P3 59 001011 0000000 0 010000 1 100000 010 11100 11101001 0 0
Es enthält folgende Informationen: 02.00 Uhr MESZ, Dienstag der 1.7.97. Da gab's übrigens wirklich eine Schaltsekunde!
Informationen im DCF77-Telegramm
In jeder Minute werden Informationen durch die unterschiedliche Länge der Sekundenmarken übertragen.
BIT-NR. | BEDEUTUNG DES DCF77-BITS: |
0..14 | Interne PTB-Informationen / Seit Oktober 2006 Wetterinformationen |
15 R | Rufbit für Alarmierung der PTB-Mitarbeiter |
16 A1 | Ankündigung des Wechsels MEZ <-> MESZ |
17 Z1 | \__ Z1/Z2: 10 = MESZ, 01 = MEZ |
18 Z2 | / |
19 A2 | Ankündigung einer Schaltsekunde |
20 S | Startbit f. Zeitinformationen (immer 1) |
21 | Minute × 1 |
22 | Minute × 2 |
23 | Minute × 4 |
24 | Minute × 8 |
25 | Minute × 10 |
26 | Minute × 20 |
27 | Minute × 40 |
28 P1 | Minutenparität (gerade Parität Bits 21..27) |
29 | Stunde × 1 |
30 | Stunde × 2 |
31 | Stunde × 4 |
32 | Stunde × 8 |
33 | Stunde × 10 |
34 | Stunde × 20 |
35 P2 | Stundenparität (gerade Parität Bits 29..34) |
36 | Tag × 1 |
37 | Tag × 2 |
38 | Tag × 4 |
39 | Tag × 8 |
40 | Tag × 10 |
41 | Tag × 20 |
42 | Wochentag × 1 |
43 | Wochentag × 2 |
44 | Wochentag × 4 |
45 | Monat × 1 |
46 | Monat × 2 |
47 | Monat × 4 |
48 | Monat × 8 |
49 | Monat × 10 |
50 | Jahr × 1 |
51 | Jahr × 2 |
52 | Jahr × 4 |
53 | Jahr × 8 |
54 | Jahr × 10 |
55 | Jahr × 20 |
56 | Jahr × 40 |
57 | Jahr × 80 |
58 P3 | Datumsparität (gerade Parität Bits 36..57) |
(59) | Evtl. Schaltsekunde (immer 0) |
Ein intaktes DCF77-Telegramm sieht also z.B. so aus (Bits 15..58):
Bits 15..20 Minute P1 Stunde P2 Tag Wochentag Monat Jahr P3 000101 0000000 0 000000 0 100000 011 10000 01100000 0
Es enthält folgende Informationen: 0.00 Uhr MEZ am Samstag, den 1.1.06. Die Uhrzeit- und Datumsangaben sind BCD-codiert. Der dezimale Wert ergibt sich durch Addition der in der Tabelle angegebenen Multiplikatoren an den Stellen, an denen das jeweilige Bit ein Bit1 ist. Die Paritätsbits P1..P3 ergänzen die zugeordneten DCF77-Bits jeweils auf eine gerade Parität. Wenn es z.B. bei den Stunden-Bits 29..34 genau 3 Bits gibt, die 1 sind, dann muss P2 ein Bit1 sein, um eine gerade Parität zu erreichen. Wenn P2 dann ein Bit0 ist, ist das Telegramm fehlerhaft.
Ein intaktes DCF77-Telegramm erfüllt folgende Bedingungen:
- Bit 20 (S) ist ein Bit1
- Die Paritätsbits P1..P3 ergeben eine korrekte gerade Parität
- Es wurden genau 58 Bits (Ausnahme: Schaltsekunde!) decodiert
- Im Fall der Schaltsekunde ist das Bit 59 ein (Last-)Bit0
- Die beiden Sommer-/Winterzeit-Kennungen Bit17 und Bit18 haben immer einen unterschiedlichen Wert
Dieses intakte DCF77-Telegramm muss anschließend noch auf Plausibilität überprüft werden, bevor es zum Stellen einer Uhr benutzt werden kann. Grund: Ein Telegramm könnte zwar intakt sein, aber z.B. folgendes Datum enthalten: 0.13.101. Da es keinen Tag 0, keinen Monat 13 und kein Jahr 101 geben kann, ist solch ein Telegramm nicht plausibel, also ebenfalls fehlerhaft. Eine Uhr darf mit solchen Informationen nicht gestellt werden.
Zulässige (plausible) Werte sind:
- Minute: 0..59
- Stunde: 0..23
- Tag: 1..31
- Wochentag: 1.. 7 (für Montag..Sonntag)
- Monat: 1..12
- Jahr: 0..99
Wenn ein DCF-Decoder ein Telegramm als intakt und plausibel getestet hat, steht prinzipiell dem Stellen der Uhr mit diesen Informationen nichts mehr im Wege.
Bei besonders hohen Anforderungen an die Zuverlässigkeit von Zeit-Informationen werden jedoch noch weitere Prüfungen durchgeführt, bevor das Telegramm als "gültig" bzw. valide angesehen wird:
- Wie viele Telegramme vor dem aktuellen Telegramm waren ebenfalls intakt und plausibel?
- Ist das aktuelle Telegramm gleich dem vorherigen Telegramm plus 1 Minute?
Die Prüfung der ersten Frage kann in Form eines Grenzwertes erfolgen: Das aktuelle Telegramm ist nur dann gültig (und wird übernommen), wenn z.B. mindestens 2 vorherige Telegramme ebenfalls intakt und plausibel waren. Damit würde nach einer Empfangsstörung (d.h. einem fehlerhaften Telegramm) erst das 3. intakte und plausible Telegramm in Folge wieder übernommen. Durch diese Prüfung kann eine zusätzliche, durch die Wahl des Grenzwertes auch quasi "einstellbare" Sicherheit besonders bei schlechtem Empfang erreicht werden.
Die zweite o.g. Prüf-Frage beinhaltet den Vergleich des aktuellen mit dem vorherigen Telegramm. Da das aktuelle Telegramm die Folgeminute des vorherigen Telegramms abbildet, kann man durch den Vergleich weitere Sicherheit gewinnen. In der Regel werden sich zwei aufeinanderfolgende Telegramme nur geringfügig (durch die Minute!) unterscheiden, d.h. sie sind hoch redundant. Bei Überträgen (z.B. am Monats- oder Jahresende) wird der Anteil redundanter Informationen jedoch deutlich geringer sein. Dies gilt auch bei MEZ-MESZ-Wechseln und bei Schaltjahren (am 29.2.!). Die Prüfung kann in Decodern recht unterschiedlich oder gar nicht umgesetzt werden. Im hier beschriebenen DCF-Decoder wird diese Prüfung nicht durchgeführt.
Wetterinformationen im DCF77-Telegramm
Seit neustem (Oktober 2006) werden auch genaue Wetterinformationen zu vielen Städten in Europa im DCF-Signal versteckt. Genau genommen stecken diese Informationen in den ersten 14 Bits des Telegrammes. Interessant zu wissen ist die Tatsache, dass diese Informationen amplitutenmoduliert sind. Die Wetterdaten werden über 24 Stunden in jeder Minute ergänzend zur Uhrzeit und dem Datum von Sekunde 1 bis Sekunde 14 übertragen. Ein Wetterprotokoll für eine Wetterregion und eine Vorhersage wird über den Verlauf von 3 Minuten gesendet. Diese Wetterdaten sind über ihre Sendezeit einer festen Region zugeordnet.
Leider ist das genaue Protokoll zum Entschlüsseln dieser Information bislang nicht komplett veröffentlicht worden. Eine private Firma bietet offenbar Lizenzen zum Dekodieren an. So gibt es im Handel jetzt erste "Wetterstationen" für's Wohnzimmer, welche diese DCF-Informationen auswerten. Aufwendige Sensortechnik ist bei diesen "Wetterstationen" dann hinfällig, denn die Informationen stammen von einem großen, professionellen Wettermessnetzwerk. Aber so spannend wie 'ne eigene Wetterstation mit Sensoren ist's dann doch nicht, oder?
Der DCF77-Decoder
Hardware
Das Programm wurde für einen ATmega 32 mit 8 MHz geschrieben. Als DCF-Empfänger wurde das Conrad-Modul (Best.-Nr. 641138) an Pind.3 angeschlossen. Es wird der sogenannte "invertierte" Ausgang des Empfängers (Klemme 4) benutzt. Zur Anzeige der Uhrzeit wird ein LCD 4x16 verwendet.
Software
Der eigentliche DCF-Decoder ist eine Bascom-Library (dcf77.lib), aktuell in der Version 4.11. Dazu gehört ein Include-File (dcf77_soft.bas), also ein Bascom-Programmteil, das mit dem Befehl ...
$include "dcf77_soft.bas"
... in das Hauptprogramm eingebunden wird. Es enthält die wesentlichen Definitionen für die dcf77.lib. Man könnte diese Definitionen auch in das Hauptprogramm aufnehmen. Der Vorteil eines solchen Include-Files ist jedoch, dass bei einer zukünftigen Änderung der dcf77.lib nur das zugehörige Include-File angepasst werden muss und nicht das Hauptprogramm.
Bascom Hauptprogramm
Das Hauptprogramm (Decodertest.bas) ist sehr einfach aufgebaut.
- Decodertest.bas
'########################################################################### 'Programmname: Decodertest.bas Version 4.11 ' 'Aufgabe: 'Es wird ein DCF-Empfänger (CONRAD 641138) an Portd.3 angeschlossen 'und DCF-Uhrzeit und -Datum auf dem LCD 4x16 ausgegeben. 'Dieses Programm ist ausgelegt für die Dcf77.lib ab V4.00. '########################################################################### $regfile = "m32def.dat" 'ATmega 32 $crystal = 8000000 'Quarz: 8 MHz $baud = 9600 $hwstack = 32 'hardware stack $swstack = 10 'software stack $framesize = 40 'frame space $include "dcf77_soft.bas" '########################################################################### 'Hauptprogramm: Declare Sub Showtime() 'LCD konfigurieren: Config Lcdpin = Pin , Db4 = Portb.4 , Db5 = Portb.5 , Db6 = Portb.6 , Db7 = Portb.7 , E = Portb.1 , Rs = Portb.0 Config Lcd = 16 * 4 'LCD 164A Waitms 500 Initlcd 'Startmeldung: Cls Lcd "DCF-Decoder-Test" Wait 5 Dim I As Byte Dim Sekunde As Byte '--------------------------------------------------------------------------- 'Hauptprogramm: Cls Cursor Off Date$ = "01.01.00" Time$ = "00:00:00" I = 0 Sekunde = 0 Dcfstatus.7 = 1 'DCF-Decoder EIN '****************** HAUPTPROGRAMMSCHLEIFE ********************************** 'Testprogramm: Do If Sekunde <> _sec Then Cls Call Showtime() Sekunde = _sec End If ' If _min = 0 And Dcfstatus.7 = 0 Then '1x pro Stunde: ' Dcfstatus.6 = 0 'Softclock inaktuell ' End If 'Wenn Softclock inaktuell, dann nach DCF stellen: ' If Dcfstatus.6 = 0 Then Dcfstatus.7 = 1 Else Dcfstatus.7 = 0 Loop End '************* ENDE HAUPTPROGRAMM ****************************************** Sub Showtime() If I = 0 Then I = 1 Else I = 0 Locate 1 , 1 'Zeile 1: Lcd Time$ ; " " 'Uhrzeit If Dcfstatus.6 = 1 Then Lcd "-Y-" Elseif Dcfstatus.7 = 1 And I = 0 Then Lcd "-Y-" Else Lcd " " End If Locate 2 , 1 'Zeile 2: Lcd Lookupstr(_dayofweek , Wochentag) ; ". " 'Datum Lcd Date$ Locate 3 , 1 'Zeile 3: If Dcfflags.0 = 1 Then 'Flags Lcd "R " Else Lcd " " End If If Dcfflags.1 = 1 Then Lcd "A1 " Else Lcd " " End If If Dcfflags.2 = 1 Then Lcd "Z1 " Else Lcd " " End If If Dcfflags.3 = 1 Then Lcd "Z2 " Else Lcd " " End If If Dcfflags.4 = 1 Then Lcd "A2 " Else Lcd " " End If If Dcfflags.5 = 1 Then Lcd "S " Else Lcd " " End If ' Locate 3 , 1 'Zeile 3: ' Lcd Bin(dcf77bits8_14) ; Bin(dcf77bits0_7) 'Bits 0..14 Locate 4 , 1 'Zeile 4: If Dcfstatus.0 = 1 Then 'Status Lcd "15 " '15. Impuls erreicht Else Lcd " " End If If Dcfstatus.1 = 1 Then Lcd "M " 'Minutenparität OK Else Lcd " " End If If Dcfstatus.2 = 1 Then Lcd "H " 'Stundenparität OK Else Lcd " " End If If Dcfstatus.3 = 1 Then Lcd "P " 'Parität OK Else Lcd " " End If If Dcfstatus.4 = 1 Then Lcd "58 " '58 Impulse empfangen Else Lcd " " End If If Dcfstatus.5 = 1 Then Lcd "G " 'Gültiges DCF77-Telegramm Else Lcd " " End If If Dcfstatus.6 = 1 Then Lcd "S " 'Softclock nach DCF gest. Else Lcd " " End If End Sub '--------------------------------------------------------------------------- Getdatetime: 'Nicht genutzt! Setdate: Settime: Return '---------------------- Interrupt Routine DCF77 ---------------------------- Tim2_isr: Timer2 = Startwert Call Dcf77_soft Return '---------------------- Daten für Wochentag -------------------------------- Wochentag: Data " " , "Mo" , "Di" , "Mi" , "Do" , "Fr" , "Sa" , "So" '###########################################################################
Das Hauptprogramm stellt in Zeile 1 des LCD die Uhrzeit, in Zeile 2 das Datum, in Zeile 3 die "Flags" (DCF77-Bits 15..20) und in Zeile 4 die Statusbits dar. Die Bedeutung der Statusbits wird in der Beschreibung der dcf77.lib erklärt. Wichtig für die Funktion des DCF-Decoders ist am Ende des Hauptprogramms die Interrupt Routine (Tim2_isr:). Durch sie wird der Decoder in der dcf77.lib in regelmäßigen Abständen aufgerufen. Die Frequenz der Aufrufe beträgt 40 Hz.
Bascom Include-File
Das Include-File (dcf77_soft.bas V4.11) muss wie auch das Hauptprogramm im Bascom-Ordner \PROGRAMS gespeichert werden.
- dcf77_soft.bas
'########################################################################### 'Programmname: DCF77_soft.BAS 'Aufgabe: 'Include-Programm für die dcf77.lib ab Version 4.00 ' 'Hardware: 'M32 mit 8 MHz 'Es ist ein DCF-Empfänger (CONRAD 641138) an Portd.3 angeschlossen. '########################################################################### Config Date = Dmy , Separator = . 'deutsches Datumsformat Config Clock = User 'Softclock aktivieren $lib "dcf77.lib" 'LIB für DCF77 $external Dcf77_init Declare Sub Dcf77_init $external Dcf77_soft Declare Sub Dcf77_soft Config Timer2 = Timer , Prescale = 1024 'Timer 40Hz Const Startwert = 61 'Für 8MHz Timer2 = Startwert On Timer2 Tim2_isr 'Überlauf 40x/s Enable Interrupts Enable Timer2 '---------------------- Pin für DCF Eingang definieren --------------------- 'Jeder Portpin kann benutzt werden. 'Auch in der "dcf77.lib" entsprechend anpassen !!! Config Pind.3 = Input 'DCF77 Eingang Set Portd.3 'Pullup ein '--------------------- Variablen der dcf77.lib ----------------------------- Dim _dayofweek As Byte , Dcfstatus As Byte , Dcfflags As Byte '_dayofweek -> 1..7 (Mo..So) 'Dcfstatus -> Bit0: 15. Impuls erreicht Bit4: 58 Impulse empfangen ' Bit1: Minutenparität OK Bit5: Gültiges DCF77-Telegramm ' Bit2: Stundenparität OK Bit6: Softclock nach DCF gestellt ' Bit3: Parität OK Bit7: DCF-Decoder EIN ' ACHTUNG: Bits 0..5 NUR LESEN!!! 'Dcfflags -> Bit0 (R) : Rufbit für Alarmierung der PTB-Mitarbeiter ' Bit1 (A1): Ankündigung des Wechsels MEZ <-> MESZ ' Bit2 (Z1): \__ Z1/Z2: 10 = MESZ, 01 = MEZ ' Bit3 (Z2): / ' Bit4 (A2): Ankündigung einer Schaltsekunde ' Bit5 (S) : Startbit f. Zeitinformationen (immer 1) '_sec, _min, _hour, _day, _month, _year sind die Variablen der Softclock 'Interne Variablen der dcf77.lib: Dim Dcf77counter As Byte Dim Dcf77parity As Byte Dim Dcf77impulse As Byte 'Dcf77buffer(9): bits 0..7, bits 8..14, flags (= bits 15..20), min, hour, ' day, dayofweek, month, year Dim Dcf77buffer(9) As Byte Dim Dcf77shifter As Byte Dim Dcf77tal As Byte Dim Dcf77tah As Byte Dim Dcf77hsec As Byte Dim Dcf77bits0_7 As Byte 'DCF77-Bits 0..7 Dim Dcf77bits8_14 As Byte 'DCF77-Bits 8..14 Dim Dcf77val_cnt As Byte 'Validitäts-Zähler Dim Dcf77lastbit As Byte 'LastBit-Pausenlänge Dim Dcf77databit As Byte 'DataBit-Pausenlänge '--------------------------------------------------------------------------- Dcfstatus.7 = 0 'DCF-Decoder AUS Dcfstatus.6 = 0 'Softclock inaktuell Call Dcf77_init 'LIB initialisieren
In der Include-Datei sind die wesentlichen Definitionen und Anweisungen für die dcf77.lib enthalten. Mit Config Clock = User werden die Bascom-Softclock Variablen (_sec, _min, time$ ...) eingerichtet. Damit "läuft" die Bascom-Softclock jedoch nicht. Dies wird durch die Softclock in der dcf77.lib gewährleistet (s.u.!). Timer2 wird mit einem Startwert von 61 vorgeladen, hierdurch ergibt sich eine Frequenz für den Aufruf der Interrupt Routine von 40 Hz (alle 25ms). Die drei Variablen _dayofweek, Dcfstatus und Dcfflags der dcf77.lib enthalten den Wochentag, Status und die DCF-Flags (Bits 15..20). Die übrigen Variablen sollten nicht verwendet werden und müssen in der angeführten Reihenfolge bleiben. Abschließend wird die dcf77.lib initialisiert durch den Aufruf von Dcf77_init.
Bascom-Library
Das hier in mehreren Abschnitten beschriebene Library-File (dcf77.lib V4.11) ist der eigentliche DCF-Decoder und muss im Bascom-Ordner \LIB abgelegt werden.
Was kann die dcf77.lib?
- Decodierung des vollständigen DCF77-Telegramms (Bits 0..58)
- Bit 20 - Prüfung
- Komplette Paritätsprüfung
- Decodierung der Schaltsekunde mit Bit 59 - Prüfung
- Plausibilitätsprüfung
- Validitätsprüfung
- Softclock
- Stellen der Softclock schaltbar
- dcf77.lib - Abschnitt 1
copyright = D. Ottensmeyer comment = DCF77-Decoder und Softclock libversion = 4.11 date = 15.01.2010 statement = ---------------------------------- statement = Version 4.11: statement = ! statement = Version 4.10: statement = Jahr: BCD->DEZ-Fehler korrigiert! statement = Version 4.00: statement = Komplette Paritätsprüfung statement = Auswertung der DCF77-Bits 15..20 statement = NEU: DCF77-Bit 20 - Prüfung! statement = NEU: Schaltsekunde decodiert! statement = NEU: DCF77-Bits 0..14 erfasst! statement = NEU: Plausibilitäts-Check! statement = NEU: Validitäts-Zähler! statement = --------------------------------- [Dcf77_soft] ;Eingang für DCF77-Empfänger-Anschluss: .equ PIND = &H10 ;Pind .equ PIND3 = 3 ;3 = Pin 3 ;Timer-Interruptfrequenz: .equ ISR_freq = 40 ;40 Hz ;Grenzwerte für Pausenzeit-Zähler (/ ISR_freq [s]): .equ LastBit0Max = 79 ;LastBit 0 obere Zeitgrenze + 1 .equ LastBit_Mid = 75 ;LastBit-Grenze .equ LastBit1Min = 71 ;LastBit 1 untere Zeitgrenze .equ DataBit0Max = 39 ;Datenbit 0 obere Zeitgrenze + 1 .equ DataBit_Mid = 35 ;Datenbit-Grenze .equ DataBit1Min = 31 ;Datenbit 1 untere Zeitgrenze .equ Spike_Limit = 3 ;Störimpulsgrenze ;Validitäts-Zähler Limit: .equ Val_Limit = 1 ;Jedes Telegramm ist gültig .def Status = r16 ;Bit0 = 15. Impuls erreicht ;Bit1 = Minutenparität OK ;Bit2 = Stundenparität OK ;Bit3 = Parität OK ;Bit4 = 58 Impulse empfangen ;Bit5 = Gültiges DCF77-Telegramm ;Bit6 = Softclock nach DCF gestellt ;Bit7 = DCF-Decoder EIN .def Counter = r17 .def Parity = r18 .def Impulse = r19 .def Shifter = r20 Dcf77_soft: *lds Status,{Dcfstatus} ;Status laden rcall Softclock ;Softclock bearbeiten rjmp dcf77_start ;------------------------------------------------------------------------------- [Dcf77] $EXTERNAL Dcf77_soft Dcf77: *lds Status,{Dcfstatus} ;Status laden dcf77_start:
Die dcf77.lib stellt sich für Bascom so dar, dass drei Unterprogramme enthalten sind:
- Dcf77 => DCF-Decoder OHNE Softclock
- Dcf77_soft => DCF-Decoder MIT Softclock
- Dcf77_init => Initialisierung des DCF-Decoders
Dies sind die Definitionen für die dcf77.lib. Wenn die Bascom-Softclock unabhängig vom DCF-Decoder läuft (ein Uhrenquarz ist z.B. an den M32 angeschlossen), kann man das Unterprogramm Dcf77 nutzen. Im aktuellen Test-Beispiel wird diese Möglichkeit nicht genutzt. Dcf77_soft ist der eigentliche DCF-Decoder, der 40x pro Sekunde aufgerufen werden muss. Mit "rcall Softclock" wird zuerst die Softclock (s. Abschnitt 7!) ausgeführt. Dann wird der Decoder beim Label dcf77_start: (siehe Abschnitt 2!) gestartet.
- dcf77.lib - Abschnitt 2
dcf77_start: *lds Counter,{DCF77Counter} ;Pausenzeit-Zähler laden *lds Parity,{DCF77Parity} ;Fehler-Flag + 1-Zähler laden sbis PIND,PIND3 ;Input-Pin für DCF-Empfänger rjmp dcf77_5 ;low: Bit auswerten! inc Counter ;high: Pausenzeit zählen! brne dcf77_3 dcf77_1: sbr Parity,&B10000000 ;Fehler-Flag setzen *sts {DCF77Parity},Parity andi Status,&B11000000 ;Statusbits 0..5 löschen dcf77_2: clr Counter ;Pausenzeit-Zähler zurücksetzen dcf77_3: *sts {DCF77Counter},Counter *sts {Dcfstatus},Status dcf77_4: ret dcf77_5:
Beim Label dcf77_start: werden zunächst zwei Variablen in Register geladen (Pausenzeit-Zähler und Fehler-Flag/1-Zähler). Dann erfolgt mit "sbis PIND,PIND3" die Abfrage des Eingangs, an den der DCF-Empfänger angeschlossen ist. Wenn der Eingang "high" (1) ist, ist gerade Pause (100% Sendeleistung) und der Pausenzeit-Zähler wird erhöht (inc Counter). Solange der nicht überläuft, wird zu Label dcf77_3 verzweigt. Da werden die Register wieder gesichert und das war's. Wenn die Pause andauert, wird auf diese Weise alle 25ms der Pausenzeit-Zähler hochgezählt. Bei seinem Überlauf nach über 6 Sekunden wird am Label dcf77_1: das Fehler-Flag gesetzt und der Zähler zurückgesetzt (clr Counter). So ein Überlauf sollte aber eigentlich nicht vorkommen. Wenn ein solcher oder anderer Fehler im Verlauf der weiteren Decodierung (mit einem Sprung zu dcf77_1) dennoch vorkommt, werden zusätzlich die Bits 0..5 im Statusregister gelöscht. Zur Bedeutung dieser Bits siehe den Kommentar in Abschnitt 1 unter ".def Status = r16"!
Wenn am Eingang Pind.3 "low"-Pegel (0) anliegt, dann dürfte es sich um eine Sekundenmarke (25% Sendeleistung) handeln. Das bedeutet, dass der Pausenzeit-Zähler jetzt ausgewertet werden kann, und es erfolgt der Sprung zum Label dcf77_5: (siehe Abschnitt 3!).
- dcf77.lib - Abschnitt 3
dcf77_5: tst Counter ;Auswertung fertig? breq dcf77_4 ;ja: Ende! *lds Impulse,{DCF77Impulse} ;nein: Auswerten! *lds Shifter,{DCF77Shifter} *lds ZL,{DCF77TAL} *lds ZH,{DCF77TAH} cpi Counter,LastBit0Max ;LastBit 0 obere Zeitgrenze + 1 brsh dcf77_1 ;überschritten: Fehler cpi Counter,LastBit_Mid ;LastBit-Grenze brlo dcf77_6 rjmp dcf77_20 ;75..78 = LastBit0 dcf77_6: cpi Counter,LastBit1Min ;LastBit 1 untere Zeitgrenze brlo dcf77_7 rjmp dcf77_19 ;71..74 = LastBit1 dcf77_7:
Da die Pause jetzt zuende ist, kann die Auswertung der Pausenlänge erfolgen. Das ist aber nur erforderlich, wenn es nicht schon geschehen (d.h. Counter = 0) ist. Die Auswertung beginnt zunächst mit dem Laden von Variablen in Register. Dann erfolgt der Test auf ein LastBit0 oder LastBit1. Die Pause eines LastBit0 ist ja 1900ms lang, der Counter erreicht dafür den Wert 76 (..77). Für ein LastBit1 mit der Pausenlänge 1800ms zählt der Counter bis 72 (..73).
Wie kommt es zu diesen Counter-Werten?
Bei einem Hochzählen des Counters alle 25ms (40Hz) errechnen sich 1900ms als 76 x 25ms und 1800ms als 72 x 25ms.
In diesem Abschnitt 3 wird also geprüft, ob das Ende des DCF-Telegramms vorliegt. Wenn das der Fall ist, erfolgt die Verzeigung nach dcf77_20 (LastBit0) oder dcf77_19 (LastBit1). Die Auswertung des Telegrammendes wird in Abschnitt 5 beschrieben!
Wenn die obere Zeitgrenze für ein LastBit0 (1975ms) erreicht oder überschritten ist, liegt ein Fehler vor (d.h. Sprung nach dcf77_1, s. Abschnitt 2!). Wenn die untere Zeitgrenze für ein LastBit1 (1775ms) unterschritten ist, dann kann es sich um die Pause eines DataBits (800/900ms) oder irgendeinen kürzeren Impuls (z.B. eine Störung) handeln. Das Programm verzweigt dann zum Label dcf77_7 (s. Abschnitt 4!), wo die Auswertung der DataBits erfolgt.
- dcf77.lib - Abschnitt 4
dcf77_7: sbrc Parity,7 ;wenn Fehler aufgetreten, ... rjmp dcf77_2 ;... dann Abbruch! cpi Counter,Spike_Limit ;Störimpulsgrenze brlo dcf77_2 ;Störimpuls wird ignoriert cpi Counter,DataBit0Max ;Datenbit 0 obere Zeitgrenze + 1 brsh dcf77_1 ;überschritten: Fehler cpi Counter,DataBit1Min ;Datenbit 1 untere Zeitgrenze brlo dcf77_1 ;unterschritten: Fehler *sts {DCF77Databit},Counter ;DataBit0/1-Pausenlänge für Debug cpi Counter,DataBit_Mid ;Datenbit-Grenze brlo dcf77_8 ;Bit1! cpi Impulse,20 ;DataBit = 0 (35..38): breq dcf77_2 ;Bit 20 = 0: Abbruch! cpi Impulse,58 ;P3 (= 0) bei Schaltsekunde? breq dcf77_2 ;Abbruch: LastBit 59 (= 0) folgt! rjmp dcf77_10 ;andere 0-Bits: weiter dcf77_8: ;DataBit = 1 (31..34): cpi Impulse,21 ;DCF77-Bits 0..20 (= 1)? brlo dcf77_9 ;keine Parität! inc Parity ;sonst alle 1-Bits zählen *sts {DCF77Parity},Parity cpi Impulse,28 breq dcf77_10 ;Minutenparitätsbit nicht addieren! cpi Impulse,35 breq dcf77_10 ;Stundenparitätsbit nicht addieren! cpi Impulse,58 ;P3 (= 1) bei Schaltsekunde? breq dcf77_2 ;Abbruch: LastBit 59 (= 0) folgt! dcf77_9: ld Counter,Z ;Counter = aktuelle DCF77Buffer Zelle add Counter,Shifter ;addiere 1 an der aktuellen Bitposition st Z,Counter ;zurückspeichern dcf77_10: lsl Shifter ;Shifter auf nächste Bitposition *sts {DCF77Shifter},Shifter cpi Impulse,7 brlo dcf77_16 ;DCF77-Bits 0..7 Zyklus breq dcf77_15 ;letztes Bit 0..7 Zyklus cpi Impulse,14 brlo dcf77_16 ;DCF77-Bits 8..14 Zyklus brne dcf77_11 andi Status,&B11000111 ;Statusbits 3..5 löschen sbr Status,&B00000001 ;Status 15. Impuls erreicht setzen rjmp dcf77_15 ;letztes Bit 8..14 Zyklus dcf77_11: cpi Impulse,20 brlo dcf77_16 ;DCF77-Bits 15..20 Zyklus breq dcf77_15 ;letztes Bit (DCF77-Bit 20) cpi Impulse,28 brlo dcf77_16 ;Minuten-Zyklus breq dcf77_17 ;Minutenparität cpi Impulse,35 brlo dcf77_16 ;Stunden-Zyklus breq dcf77_17 ;Stundenparität cpi Impulse,41 brlo dcf77_16 breq dcf77_12 ;letztes Bit TAG cpi Impulse,44 brlo dcf77_16 breq dcf77_12 ;letztes Bit WOCHENTAG cpi Impulse,49 brlo dcf77_16 breq dcf77_12 ;letztes Bit MONAT cpi Impulse,57 brne dcf77_16 ;nicht letztes Bit JAHR dcf77_12: ;DCF77Buffer Zelle in DEZ wandeln: ld Counter,Z ;Counter = aktuelle DCF77Buffer Zelle mov Shifter,Counter ;Counter (BCD) andi Shifter,&H0F dcf77_13: subi Counter,&H10 brcs dcf77_14 subi Shifter,&HF6 rjmp dcf77_13 dcf77_14: ;-> Shifter (DEZ) st Z,Shifter ;DEZ in Zelle zurückspeichern cpi Impulse,57 breq dcf77_16 ;letztes Bit JAHR dcf77_15: ;aktuelle DCF77Buffer Zelle fertig: ldi Shifter,1 ;Bitposition zurücksetzen adiw ZL,1 ;nächste DCF77Buffer Zelle *sts {DCF77Shifter},Shifter *sts {DCF77TAL},ZL ;Adresse der nächsten Zelle speichern *sts {DCF77TAH},ZH dcf77_16: inc Impulse ;nächstes DCF77-Bit *sts {DCF77Impulse},Impulse rjmp dcf77_2 dcf77_17: ;Parität Min./Std. Auswertung: sbrc Parity,0 rjmp dcf77_2 ;Parität Fehler: Abbruch! cpi Impulse,28 ;Minutenparität? brne dcf77_18 ;nein: Stundenparität (35) sbr Status,&B00000010 ;Status Minutenparität OK setzen rjmp dcf77_12 dcf77_18: sbr Status,&B00000100 ;Status Stundenparität OK setzen rjmp dcf77_12 ;Parität Min./Std. OK
Die Auswertung der DataBits erfolgt in diesem Abschnitt 4 nur, wenn das Fehler-Flag (Parity.7) nicht gesetzt ist.
Mit den vier "cpi"-Befehlen werden jetzt die DataBits decodiert: Ganz kurze Impulse (unter 75ms) werden als Störimpulse ignoriert. Bei Über- oder Unterschreiten der Zeitlimits wird ein Fehler ausgelöst (Sprung zu dcf77_1). Der letzte Test (cpi Counter,DataBit_Mid) entscheidet, ob ein DataBit0 (weiter ab Befehl "cpi Impulse,20") oder ein DataBit1 (ab Label dcf77_8) vorliegt.
Zunächst der Fall "DataBit0":
Counter hat in diesem Fall einen Wert von 36 (..37), entsprechend 900ms Pausenlänge. Es wird mit "cpi Impulse,20" zunächst getestet, ob es sich um das DCF77-Bit 20 (S) handelt. Dieses Bit darf nicht "0" sein, deshalb erfolgt in diesem Fall ein Abbruch (breq dcf77_2). Das Register "Impulse" ist also der Zähler für die DCF77-Bits (0..58).
Dann wird mit "cpi Impulse,58" noch auf eine andere Abbruchbedingung getestet: Wenn das DCF77-Bit 58 ein DataBit0 ist, dann handelt es sich um die Sonderbedingung "Schaltsekunde". Es wird dann noch ein LastBit folgen, deshalb wird hier die Verarbeitung abgebrochen. Für alle anderen DataBits0 geht's bei dcf77_10 weiter.
Dann der Fall "DataBit1" (ab Label dcf77_8):
Counter hat in diesem Fall einen Wert von 32 (..33), entsprechend 800ms Pausenlänge. Ab den Labeln dcf77_8 und dcf77_9 passieren zwei wichtige Dinge mit den DataBits1:
- Mit "inc Parity" werden die 1-Bits gezählt, um die Parität berechnen zu können. Dabei gibt es eine Ausnahme: Die DCF77-Bits 0..20 werden nicht berücksichtigt.
- Ab dem Label dcf77_9 wird das Bit im Zeit-Zwischenspeicher abgelegt. Der Zwischenspeicher besteht aus 9 Bytes (Bascom: Dcf77buffer(9)). In der Lib werden die einzelnen "Zellen" des Zwischenspeichers durch den Z-Pointer adressiert. Mit "ld Counter,Z" wird zuerst die aktuelle Zelle (z.B. die Minute oder das Jahr) in das Register Counter geladen. Dann wird mit "add Counter,Shifter" eine "Maske" addiert. In dieser Maske ist immer nur 1 Bit gesetzt, und zwar an der Stelle, an der das aktuelle DCF77-Bit in die aktuelle Zelle eingefügt werden muss. Beispiel: Das DCF77-Bit 46 ist das zweite Monatsbit. Die Maske Shifter wäre in diesem Fall binär 00000010. Addiert man diese Maske zur aktuellen Zelle des Zwischenspeichers, dann wird Bit 1 hier gesetzt. Wenn das DCF77-Bit 47 folgt und auch ein DataBit1 ist, ist die Maske 00000100, und auch dieses Bit wird richtig in den Zwischenspeicher eingefügt. Mit "st Z,Counter" wird der Inhalt von Counter im Zwischenspeicher gesichert.
Die Sicherung im Zwischenspeicher gilt nicht für zwei DCF77-Bits: Die Paritätsbits P1 (28) und P2 (35) dürfen nicht in die aktuelle Zelle kopiert werden. Mit "cpi Impulse,58" wird eine weitere Ausnahmebedingung abgefangen: Die Schaltsekunde. Ist P3 ein DataBit1, dann wird noch ein LastBit0 folgen, also hier zunächst ein Abbruch.
Ab dem Label dcf77_10 werden die DataBits0 und DatBits1 wieder gemeinsam weiterverarbeitet. Hier wird zunächst der Shifter in die nächste Bitposition geschoben (lsl Shifter). Danach gibt es mehrere Einzeltests: Von "cpi Impulse,7" bis "cpi Impulse,57" werden einige Positionen von DCF77-Bits abgefragt. Bei manchen DCF77-Bits gibt es eigentlich nichts mehr zu tun: Wenn ein DCF77-Bit irgendwo mitten in einer Zelle angekommen ist, muss nur noch der Zähler "Impulse" hochgezählt werden (dcf77_16). Etwas anderes ist es, wenn das LETZTE DCF77-Bit einer Zelle (z.B. 20 oder 41) decodiert wurde: Hier muss entweder nur die Maske in Shifter auf binär 00000001 (Label dcf77_15) zurückgesetzt und der Z-Pointer auf die nächste Zelle ("adiw ZL,1") gesetzt werden, ODER der Inhalt der aktuellen Zelle muss vorher noch (Label dcf77_12 bis dcf77_14) vom BCD- ins Dezimal-Format umgewandelt werden.
Im Rahmen der o.g. Einzeltests werden auch Statusbits beeinflusst: Mit "andi Status,&B11000111" werden die Statusbits 3..5 gelöscht, danach mit "sbr Status,&B00000001" das Statusbit 0 (15. Impuls erreicht) gesetzt. Damit endet die Gültigkeit des alten, und es beginnt die Decodierung eines neuen Telegramms ab dem DCF77-Bit 15.
Die Paritätsbits 28 und 35 sind noch auszuwerten (ab Label dcf77_17): Wenn die Minuten- oder Stunden-Parität UNGERADE ist (Parity.0 = 1), erfolgt der Abbruch. Ist die Parität o.k., wird das zugehörige Statusbit (Bit 1 oder Bit 2) gesetzt.
In diesem Abschnitt 4 hat der Decoder die Hauptarbeit geleistet. Aber wie geht's von hier aus weiter? Das Telegrammende wird in Abschnitt 3 festgestellt, von dort verzweigt der Decoder zum nächsten Abschnitt 5 (Label dcf77_19, dcf77_20)!
- dcf77.lib - Abschnitt 5
dcf77_19: ;Telegramm Ende: inc Parity ;LastBit = 1 dcf77_20: ;LastBit = 0 *sts {DCF77Lastbit},Counter ;LastBit0/1-Pausenlänge für Debug cpi Impulse,58 ;58 DCF77-Bits erfasst? brne dcf77_21 ;unvollständig: weiter sbr Status,&B00010000 ;ja: Status 58 Impulse empfangen setzen dcf77_21: andi Parity,&B10000001 brne dcf77_22 ;Paritäts- oder sonst. Fehler aufgetreten? sbr Status,&B00001000 ;nein: Status Parität OK setzen dcf77_22: mov Counter,Status andi Counter,&B00011111 ;Status Bits 0..4? cpi Counter,&B00011111 ;alle 5 Bits = 1? breq dcf77_24 dcf77_23: clr Shifter ;Validitäts-Zähler zurücksetzen rjmp dcf77_25 ;Fehler: Softclock NICHT stellen! dcf77_24: ;Plausibilitäts-Check: ld Counter,Z ;Jahr (0..99) cpi Counter,100 brsh dcf77_23 ;> 99: Nicht plausibel! ld Counter,-Z ;Monat (1..12) tst Counter breq dcf77_23 cpi Counter,13 brsh dcf77_23 ld Counter,-Z ;Wochentag (1..7) tst Counter breq dcf77_23 cpi Counter,8 brsh dcf77_23 ld Counter,-Z ;Tag (1..31) tst Counter breq dcf77_23 cpi Counter,32 brsh dcf77_23 ld Counter,-Z ;Stunde (0..23) cpi Counter,24 brsh dcf77_23 ld Counter,-Z ;Minute (0..59) cpi Counter,60 brsh dcf77_23 adiw ZL,5 ;Pufferzeiger auf Jahr zurücksetzen sbr Status,&B00100000 ;Status Gültiges DCF77-Telegramm setzen *lds Shifter,{DCF77Val_cnt} ;Validitäts-Zähler laden inc Shifter ;Zähler erhöhen cpi Shifter,Val_Limit ;Stand Validitäts-Zähler? brlo dcf77_25 ;< Val_Limit: Softclock NICHT stellen! ldi Shifter,Val_Limit ;Zähler wird nicht > Val_Limit! sbrs Status,7 ;Status DCF-Decoder EIN/AUS? rjmp dcf77_25 ;AUS: Softclock NICHT stellen! ;Softclock nach DCF stellen: loadadr _year,X ;Zeiger auf Softclock-Variablen ld Counter,Z ;Jahr = DCF77Buffer(9) st X,Counter ;in _year sichern ld Counter,-Z ;Monat = DCF77Buffer(8) st -X,Counter ;in _month sichern ld Counter,-Z ;Wochentag = DCF77Buffer(7) *sts {_dayofweek},Counter ;in _dayofweek sichern ld Counter,-Z ;Tag = DCF77Buffer(6) st -X,Counter ;in _day sichern ld Counter,-Z ;Stunde = DCF77Buffer(5) st -X,Counter ;in _hour sichern ld Counter,-Z ;Minute = DCF77Buffer(4) st -X,Counter ;in _min sichern clr Counter st -X,Counter ;_sec zurücksetzen *sts {Dcf77hsec},Counter ;Sek.-Bruchteile zurücksetzen ld Counter,-Z ;DCF77-Bits 15..20 = DCF77Buffer(3) *sts {Dcfflags},Counter ;in Dcfflags sichern ld Counter,-Z ;DCF77-Bits 8..14 = DCF77Buffer(2) *sts {Dcf77bits8_14},Counter ;in Dcf77bits8_14 sichern ld Counter,-Z ;DCF77-Bits 0..7 = DCF77Buffer(1) *sts {Dcf77bits0_7},Counter ;in Dcf77bits0_7 sichern sbr Status,&B01000000 ;Status Softclock nach DCF gestellt setzen dcf77_25:
Dieser Abschitt 5 wird nur dann aufgerufen, wenn ein LastBit decodiert wurde (s. Abschnitt 3!). Damit ist das vollständige Minutentelegramm empfangen worden und muss jetzt noch geprüft werden. In der Regel handelt es sich bei diesem letzten Bit ja um das DCF77-Bit 58 (P3). Wenn es ein LastBit1 ist (Label dcf77_19), wird zunächst mit "inc Parity" die Datumsparität vervollständigt. Dann wird getestet, ob es sich tatsächlich um das DCF77-Bit 58 handelt. Wenn ja, wird mit "sbr Status,&B00010000" das Bit 4 im Statusregister gesetzt. Dann erfolgt mit "andi Parity,&B10000001" der letzte Fehler- und Paritätstest. Er ist bestanden, wenn das Ergebnis Null ist. Dann wird Bit 3 im Statusregister gesetzt. Jetzt (Label dcf77_22) sind alle Tests vollständig, um das DCF77-Telegramm als intakt oder fehlerhaft bewerten zu können. Wenn alle Statusbits 0..4 gesetzt sind, ist die Decodierung ohne Fehler verlaufen: Das Telegramm ist intakt! Die Verzweigung erfolgt zu Label dcf77_24.
Wenn das Telegramm nicht intakt ist (Label dcf77_23), wird der Validitäts-Zähler (dazu später mehr) gelöscht. Die weitere Prüfung wird abgebrochen (dcf77_25).
Wenn die Verarbeitung das Label dcf77_24 erreicht, dann ist das Telegramm intakt und wird auf Plausibilität geprüft. Wenn der Test erfolgreich ist, wird mit "sbr Status,&B00100000" das Bit 5 im Statusregister (Gültiges DCF77-Telegramm) gesetzt.
Danach (ab "*lds Shifter,{DCF77Val_cnt}") entscheidet sich, ob dieses gültige Telegramm zum Stellen der Softclock verwendet wird. Dazu verfügt der Decoder über einen Validitäts-Zähler, der nach jedem gültigen Telegramm hochgezählt wird. Wenn sein Stand kleiner als die festgelegte Grenze (Konstante "Val-Limit", s. Abschnitt 1!) ist, wird die Softclock nicht gestellt. Val-Limit ist aktuell auf "1" eingestellt, so dass jedes gültige Telegramm genutzt wird.
Zuletzt wird noch (durch "sbrs Status,7") geprüft, ob überhaupt ein Stellen der Softclock gewünscht wird: Ist Status.7 nicht gesetzt, wird die Softclock nie auf die aktuelle DCF-Zeit gestellt. Das Bascom Hauptprogramm muss also das Stellen bewusst einschalten:
Dcfstatus.7 = 1 'DCF-Decoder EIN
Jetzt erfolgt endlich das Stellen der Softclock. Danach wird Bit 6 im Statusregister gesetzt (Softclock nach DCF gestellt).
Zum Schluss noch ein Wort zum Sonderfall "Schaltsekunde": In diesem Abschnitt 5 ist dafür keinerlei Sonderbehandlung zu sehen. Das ist auch nicht erforderlich: Das DCF77-Bit 59 ist ja ein LastBit0. Dadurch ist die Datumsparität fehlerfrei (Bit 59 wird quasi als Paritätsbit für die DCF77-Bits 36..58 angesehen und muss dann ja immer "0" sein. Wenn es ein LastBit1 sein sollte, wird das als Fehler erkannt.).
- dcf77.lib - Abschnitt 6
dcf77_25: andi Status,&B11111000 ;Statusbits 0..2 löschen rjmp dcf77_26 ;------------------------------------------------------------------------------- [Dcf77_init] $EXTERNAL Dcf77_soft Dcf77_init: ;DCF77-Variablen Reset clr Shifter ;Validitätszähler zurücksetzen andi Status,&B11000000 ;Statusbits 0..5 löschen dcf77_26: *sts {Dcfstatus},Status ;Status speichern *sts {DCF77Val_cnt},Shifter ;Validitäts-Zähler speichern loadadr DCF77Buffer(1),Z ;Adresse DCF77Buffer mov Parity,ZL mov Impulse,ZH loadadr DCF77Counter,Z ;Adresse DCF77Counter ldi Counter,12 clr Shifter dcf77_27: ;DCF77Counter..DCF77Buffer = 0 st Z+,Shifter dec Counter brne dcf77_27 inc Counter st Z+,Counter ;DCF77Shifter = 1 st Z+,Parity ;Adresse DCF77Buffer -> DCF77TAL/AH st Z,Impulse ret
Der Abschnitt 6 ist das 3. Unterprogramm "Dcf77_init" in der dcf77.lib. In diesen Programmteil wird über Label dcf77_25 eingesprungen, wenn das Ende eines Telegramms im Abschnitt 5 festgestellt worden war. In diesem Fall werden nur die Bits 0..2 des Statusregisters gelöscht.
In Dcf77_init werden alle Variablen wieder initialisiert, so dass ein neues Telegramm decodiert werden kann. Die Statusbits 0..5 werden gelöscht. Das Statusbit 6 (Softclock nach DCF gestellt) bleibt jedoch unverändert. Im Bascom-Programm ist durch dieses Flag also dauerhaft nachweisbar, dass die Softclock MINDESTENS EINMAL nach DCF gestellt wurde. Zurückgesetzt wird dieses Bit nur durch bestimmte seltene Bedingungen in der Softclock (s. Abschnitt 7!) oder bei Bedarf durch das Bascom-Programm selbst (Dcfstatus.6 = 0).
- dcf77.lib - Abschnitt 7
;------------------------------------------------------------------------------- Softclock: *lds Counter,{Dcf77hsec} inc Counter ;Sek.-Bruchteile erhöhen cpi Counter,ISR_freq ;1000ms = 1s erreicht? breq Soft_10 ;ja: weiter *sts {Dcf77hsec},Counter ret ;sonst Ende Soft_10: clr Counter ;Sek.-Bruchteile löschen *sts {Dcf77hsec},Counter *lds Counter,{_sec} inc Counter ;Sekunde erhöhen cpi Counter,60 ;60 Sekunden erreicht? breq Soft_20 ;ja: weiter *sts {_sec},Counter ret Soft_20: clr Counter ;Sekunde löschen *sts {_sec},Counter *lds Counter,{_min} inc Counter ;Minute erhöhen cpi Counter,60 ;60 Minuten erreicht? breq Soft_30 ;ja: weiter *sts {_min},Counter ret Soft_30: clr Counter ;Minute löschen *sts {_min},Counter *lds Counter,{_hour} inc Counter ;Stunde erhöhen cpi Counter,24 ;24 Stunden erreicht? breq Soft_40 ;ja: weiter *sts {_hour},Counter ret Soft_40: clr Counter ;Stunde löschen *sts {_hour},Counter *lds Counter,{_dayofweek} inc Counter ;Wochentag erhöhen cpi Counter,8 ;letzter Wochentag erreicht? brne Soft_50 ;nein: weiter ldi Counter,1 ;Wochentag auf "1" (Montag) Soft_50: *sts {_dayofweek},Counter *lds Counter,{_day} ;Tag holen *lds Shifter,{_month} ;Monat holen ldi zl,low(Daysofmonth*2) ldi zh,high(Daysofmonth*2) ;Anzahl Tage pro Monat holen add zl,Shifter ;Zeiger auf aktuellen Monat lpm ;Anzahl Tage holen cp Counter,r0 ;Monatsende erreicht? brne Soft_90 ;nein: weiter cpi Shifter,2 ;Monatsende Februar (wg. Schaltjahr)? brne Soft_60 ;nein: weiter cbr Status,&B01000000 ;Status Softclock nach DCF gestellt löschen Soft_60: cpi Shifter,6 ;Monatsende Juni (wg. Schaltsekunde)? brne Soft_70 ;nein: weiter cbr Status,&B01000000 ;Status Softclock nach DCF gestellt löschen Soft_70: ldi Counter,1 ;Tag auf 1 cpi Shifter,12 ;Jahresende (+ wg. Schaltsekunde)? brne Soft_100 ;nein: weiter cbr Status,&B01000000 ;Status Softclock nach DCF gestellt löschen *lds Shifter,{_year} ;Jahr holen inc Shifter ;Jahr erhöhen cpi Shifter,100 ;Jahr 100 erreicht? brne Soft_80 ;nein: Ende clr Shifter ;Jahr 00 setzen Soft_80: *sts {_year},Shifter ;speichern ldi Shifter,1 ;Monat auf 1 rjmp Soft_110 Soft_90: inc Counter ;Tag erhöhen rjmp Soft_110 Soft_100: inc Shifter ;Monat erhöhen Soft_110: *sts {_day},Counter ;Datum speichern *sts {_month},Shifter ret Daysofmonth: .db 00, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 [end]
Dies ist die Softclock. Sie verwendet (mit Ausnahme der Sekunden-Bruchteile und des Wochentages) die Variablen der Bascom-Softclock, so dass die Uhrzeit im Hauptprogramm wie gewohnt dargestellt werden kann. Die Funktion muss hier nicht detailliert beschrieben werden. Ein Punkt soll aber noch erwähnt werden:
In der Softclock wird das Statusbit 6 (Softclock nach DCF gestellt) unter folgenden Bedingungen gelöscht:
- Monatsende Februar (ggf. Schaltjahr mit 29.2.)
- Jahresmitte (ggf. Schaltsekunde)
- Jahresende (ggf. Schaltsekunde)
Das Statusbit 6 bleibt also normalerweise nach dem ERSTEN Stellen der Uhr nach DCF dauerhaft gesetzt. Das Bit wird nur dann "0", wenn die Softclock evtl. wegen Schaltsekunde/-jahr nicht mehr aktuell ist.
Hinweis: Wenn ihr den Quellcode der lib aus den einzelnen Abschnitten koppiert, bitte aufpassen da sind einige Label doppelt! dcf77_25: , dcf77_7: , dcf77_start: jeweils einmal rauslöschen.
Alternative: DCF77 Seriell Wandler Chip
Statt mit Controller lässt sich das DCF Signal auch mittels eines kleinen Decoderchips in ein serielles Signal (RS232 mit 5V oder 3V Pegel) wandeln und einfach einlesen. Vorteil: Keine Belegung von Timer oder Interrupt und eine interne Uhr die auch bei DCF-Ausfall Zeit- und Datum-Informationen auf Abruf liefert.
http://www.shop.robotikhardware.de/shop/catalog/images/artikelbilder/dcfrs1/chip.jpg http://www.shop.robotikhardware.de/shop/catalog/images/artikelbilder/dcfrs1/schaltbild_dcfrs1_390px.png
Übertragung erfolgt über einen Port
Wahlweise über Pin-DOUT, wo die Ausgabe durch einen Low-Impuls angestossen wird (siehe Bild), oder über den TX-Pin im Minutentakt.
Chip Ausgabe erfolgt als Text oder wahlweise Binär
Beispielcode zum Abrufen der Zeit per Chip
'############################################################## 'DCF_Decoder3.bas ' 'Das Programm liest die Uhrzeit aus dem digetalen DCF-Decoder aus 'und gibt die Zeit über RS232 aus 'Es muss lediglich PIN PD7 mit dem DOUT Pin des Chips DCF-RS1 verbunden werden 'Natürlich auch GND und VCC ' ' (c) Frank roboternetz.de 'Weitere Beispiele auf DVD oder im www.Roboternetz.de und rn-wissen.de '###################################################################### Declare Sub DCF_UhrzeitLesen() $programmer = 12 'MCS USB (Zeile weglassen wenn anderer Programmer) $prog &HFF , &HFF , &HD9 , &HFE 'Fusebits richtig programmieren (Quarz ein,Jtag aus) $regfile = "m644pdef.dat" $framesize = 32 $swstack = 32 $hwstack = 64 $crystal = 8000000 'Quarzfrequenz $baud = 9600 'Baudrate (Übertragungsgeschwindigkeit) Baud = 9600 Config Pind.6 = Output 'LED Led1 Alias Portd.6 'Diese Variablen enthalten nach dem Abruf die Uhrzeit und Datum Informationen Dim uhr_sek As byte Dim uhr_min As byte Dim uhr_std As byte Dim uhr_tag As byte Dim uhr_monat As byte Dim uhr_jahr As byte Dim uhr_wochentag As byte Dim uhr_sommerzeit As byte Dim uhr_zeitumschaltung As byte Dim uhr_sync As byte dim bstart as byte 'Enthaelt Byte Zeilenanfang immer 126 dim bende as byte 'Enthaelt Byte Zeilenanfang immer 127 dim zeitgueltig as byte wait 1 zeitgueltig=0 Do DCF_UhrzeitLesen if zeitgueltig=1 then Print "Heute haben wir den: "; uhr_tag;".";uhr_monat;".";uhr_jahr;" ";uhr_std;":"; uhr_min;":";uhr_sek;" Wochentag:";uhr_wochentag if uhr_sommerzeit=1 then print "Es gilt Sommerzeit!" print "Die Uhr wurde von "; uhr_sync; " Minuten per Funk abgeglichen" print endif Wait 5 toggle led1 Loop 'Die eigentliche Abruffunktion: 'Diese Funktion ruft der Zeit im binären Modus ab 'und uerbergibt diese den globalen Uhr Variablen Sub DCF_UhrzeitLesen() Config Pind.7 = Input if Pind.7 =1 then 'DCF Uhrzeit bereits vorhanden? 'Kurz auf GND setzen um DCF Decoderzeit ausgeben zu lassen Config PORTD.7 = output PORTD.7=0 waitus 1 Config Pind.7 = Input Open "comd.7:2400,8,n,1" For input As #1 Inputbin #1,bstart,uhr_tag,uhr_monat,uhr_jahr,uhr_std, uhr_min,uhr_sek,uhr_wochentag,uhr_sommerzeit,uhr_zeitumschaltung,uhr_sync,bende Close #1 Config Pind.7 = Input uhr_tag=uhr_tag-65 uhr_monat =uhr_monat-65 uhr_jahr =uhr_jahr-65 uhr_std=uhr_std-65 uhr_min =uhr_min-65 uhr_sek =uhr_sek-65 uhr_wochentag = uhr_wochentag-65 uhr_sommerzeit=uhr_sommerzeit-65 uhr_zeitumschaltung=uhr_zeitumschaltung-65 uhr_sync=uhr_sync-65 zeitgueltig=1 else zeitgueltig=0 Print "Zeit noch nicht verifiziert!" endif end sub
Autor/Autoren
- Dirk 19:49, 12. Dez 2006 (CET)
- --Frank 20:05, 16. Feb 2007 (CET) (Kleine Wetter Ergänzung)
- ---Dirk 22:50, 23. Jan 2010 (CET) (Library Version 4.11 Update)
Quellen
- Quelle1
- Piester, Hetzel, Bauch; Zeit- und Normalfrequenzverbreitung durch DCF77; PTB-Mitteilungen 114, Heft 4; 2004
- Quelle2
- Wikipedia
- Quelle3
- DCF77
- Quelle4
- Wolfgang Back