WARNUNG
! Die hier
beschriebene Schaltung ist mit dem 230V-Stromnetz verbunden
und bei
unsachgemäßer
Handhabung lebensgefährlich! Diese Schaltung darf
daher nur von Personen
aufgebaut und in Betrieb genommen werden, die eine entsprechende
Ausbildung
haben und genau wissen, was sie tun. |
Das DCF77-Signal |
Der
Langwellensender DCF 77 sendet
Zeitsignale, die von einer Funkuhr
entschlüsselt und sekundengenau
angezeigt werden können. Solche Funkuhren gibt es,
für kleines Geld, käuflich
zu erwerben. Auch herrscht im
Internet kein Mangel an Programmieranleitungen solcher Funkuhren
für die Atmel
AVR's. Sogar eine fertige Library für den BASCOM gibt es. Doch
ich wollte es
einmal, von ganz unten beginnend, selbst programmieren. So entstand
dieses
Projekt.
Beginnen wir mit den Grundlagen. Der Sender sendet auf 77,5 kHz Zeitsignale im Sekundentakt. Der Grundzustand ist ein Signal mit 100% Sendeleistung. Dieses wird jede Sekunde auf 15% Sendeleistung abgesenkt. Die Dauer der Absenkung beträgt entweder 100 mS oder 200 mS. Dabei gilt: 100 mS entsprechen einer logischen Null und 200 mS entsprechen einer logischen Eins. Innerhalb einer Minute wird so ein komplettes Zeittelegramm übermittelt. Insgesamt ist die Information 59 Bit lang. In der 59. Sekunde erfolgt keine Absenkung. Dies dient der Synchronisation auf die volle Minute. Die 60. Sekunde enthält immer die logische Null. Nicht alle so übertragenen Bits werden benötigt, bzw. können genutzt werden. So sind die Bits 1 bis 14 nicht für das Datum und die Uhrzeit relevant. Sie enthalten Wetterinformationen, die verschlüsselt und nicht frei zugänglich sind. Bit 15 ist ebenfalls nicht wichtig. Es gibt Auskunft über welche Antenne der Sender gerade sendet. Die Bits 16 bis 18 geben Auskunft ob wir gerade Sommer- oder Winterzeit haben. Mit Bit 19 wird eine Zeitkorrektur um eine Sekunde angekündigt. Bit 20 ist ein Startbit und immer logisch Eins. Ab Bit 21 wird es für unsere Uhr wirklich spannend. Die Bits 21 bis 27 übertragen die Minuten im BCD-Code. Dabei hat Bit 21 die Wertigkeit 1, Bit 22 die Wertigkeit 2, Bit 23 die Wertigkeit 4, Bit 24 die Wertigkeit 8, Bit 25 die Wertigkeit 10. |
Die fertige Funkuhr. |
usw.
Bit 28 ist ein Prüfbit (Paritätsbit) für die
Minuten. Eine logische Null entspricht dabei einer geraden
Parität, eine logische Eins einer ungeraden Parität.
Die Bits 29 bis 34 übertragen in gleicher Weise die Stunden,
Bit 35 ist wieder ein Paritätsbit. Es folgen: Bit 36 bis 41
für den Kalendertag, Bit 42 bis 44 für den Wochentag,
Bit 45 bis 49 für den Kalendermonat und Bit 50 bis 57
für das Kalenderjahr. Bit 58 ist wieder ein
Paritätsbit. Diesmal jedoch zusammen für die Bits 36
bis 57.
Wikipedia Ein Artikel von Wolfgang Back Die Seite der PTB
Die Hardware: |
definierter
Nullpegel an dem Eingang des 4069. Durch die zweifache Invertierung
liegt das Signal damit wieder nichtinvertiert an. Es geht einmal
über einen 1,5 K Ohm Widerstand an eine Low-Current LED, die
im Betrieb dann, bei Empfang, im Sekundentakt aufblinkt und des weitern
an den Port PD5 des eingesetzten ATMega88. Der Mikrocontroller mit Oszillator:
Damit sind wir auch schon bei dem Mikrocontroller. Warum ein ATMega88?
Weil ich den gerade da hatte! Es geht aber auch ein ATMega8, der immer
noch sehr beliebt ist. Die beiden Controller sind pinkompatibel und
unterscheiden sich im Wesentlichen in den Adressen verschiedener
Register. Ein Unterschied, der bei der Verwendung des BASCOMs
egalisiert wird. Da der DCF77 nicht immer zu empfangen ist, muss der
Controller über eine zweite Zeitbasis
verfügen. Damit diese hinreichend genau läuft setzte
ich einen externen quarzgesteuerten Oszillator ein. Er wird an Pin 9,
XTAL1 angeschlossen. Das Eingangssignal vom DCF77 Empfänger
kommt, wie schon gesagt, auf Pin11, PD5.
Die Anzeige erfolg auf einem LCD-Display, das an die Pins 15 bis 18,
entsprechend PB1 bis PB4 und an Pin 23 entsprechend PC0 und Pin 25,
entsprechend PC2 angeschlossen ist. Ferner sind, wie üblich,
die Stromversorgung mit den entsprechenden Kapazitäten und der
Power-On-Reset angeschlossen. Auch ARef habe ich mit 100nF an Masse
angebunden, obwohl hier die Analogeingänge nicht genutzt
werden.
Das
LCD-Display:
Als LCD-Display wählte ich eins mit zwei Zeilen a 16 Zeichen und einem HD44780 kompatiblen Controller. Da die fertige Funkuhr später als Wandschmuck dienen sollte, wählte ich ein Edelteil, positiv-weiß mit Hintergrundbeleuchtung. Es kann natürlich auch ein Display ohne Hintergrundbeleuchtung gewählt werden, wichtig ist nur, dass es pinkompatibel bleibt, sonst muss das Layout und die Programmierung geändert werden. Das Netzteil:
Da der Stromverbrauch der konzipierten Schaltung bei etwa
100mA liegt, kommt ein sinnvoller Batteriebetrieb nicht in
Frage. Ein 9V-Block mit 500mAh wäre schon nach 5 Stunden leer
und selbst eine Batterie mit 2500 mAh nach etwas mehr als einem Tag
erschöpft. Ich entschloss mich daher ein Netzteil einzusetzen.
Da die Uhr stationär betrieben wird, ist das auch kein
Nachteil. Ein Printtransformator mit 10VA lag noch in der Bastelkiste.
Der Trafo ist zwar deutlich überdimensioniert, war aber halt
kostenfrei verfügbar. Das gleiche gilt für den
eingesetzten Brückengleichrichter B80C1500. Auch hier
hätte eine kleinere Ausführung gereicht. Zur
Spannungsstabilisierung kommt ein gewöhnlicher 7805 zum
Einsatz. Bei den Elkos ist auf eine ausreichende Spannungsfestigkeit zu
achten. Mit 35V lag ich auf der sicheren Seite.
Wer sich mit den Sicherheitsregeln für das Arbeiten mit dem 230V Netz nicht so gut auskennt, dem empfehle ich stattdessen die Verwendung eines fertigen Steckernetzteils. Z.B. Steckernetzteil 5V/DC/1200mA, C- Best.-Nr.: 512683. In dem Platinenlayout spart man damit sowohl den Printtrafo als auch die folgende Gleichrichtung und Spannungsstabilisierung ein. Das Platinenlayout ist dann natürlich entsprechend zu ändern. |
Display und Hauptplatine. Dabei ordnete ich auf der Platine eine
Steckerleiste und
auf dem LCD-Display eine Buchsenleiste an. Der Printtransformator wurde
mit der 230V Seite auf der Innenseite der Platine angeordnet, so dass
die Netzspannung möglichst weit von den Rändern
entfernt ist. Um die beiden 230V führenden Leiterbahnen wurden
im Abstand von zwei Lochrasterpunkten die Leiterbahnen entfernt. Die
230V führenden Leiterbahnen wurden
großzügig mit gewebeverstärktem Isolierband
abgeklebt. Weiterhin kam unter die Platine eine weitere
Lochrasterplatine (diesmal ohne Kupfer), um einen weitgehenden
Berührungsschutz zu gewährleisten.
Neben dem Printtransformator ordnete ich eine Zugentlastung
für die 230V Zuleitung an. Der Printtrafo wurde
sekundärseitig mit 500mA abgesichert. |
Anz. |
Bezeichnung |
Symb. |
C - Best.-Nr. |
1 | Lötstreifenrasterplatine 160 * 100 mm | - | 529506 |
1 | Lochrasterplatine 160 * 100 mm | - | 528455 |
4 | Zylinderkofschraube
M3x10, mit je 2 Unterlegscheiben und 2 Muttern |
- | - |
1 | LCD-Display 2 Zeilen, a 16 Zeichen | U2 | 181664 |
1 | C-Control DCF-Empfängerplatine | U1 | 641138 |
1 | Mikrocontroller AVM ATMega88 oder ATMega8 | IC2 | - |
1 | Quarzoszillator 8 MHz | IC1 | 158119 |
1 | CMOS - IC 4069 | IC4 | 172952 |
1 | IC-Fassunung 14pol. | - | 189618 |
1 | Trimpotentiometer 10 kOhm, liegend | P1 | 422444 |
1 | Mini Impulstaster T602 | S1 | 700460 |
1 | Brückengleichrichter B80C1500 | Br1 | 501441 |
1 | Spannungskonstanthalter 7805 | IC3 | 179205 |
1 | Printtransformator 10 VA , 2 x 9 Volt | Tr1 | 710554 |
1 | Netzkabel schwarz, 2 x 0,75 | - | 611984 |
1 | Zugentlastung | - | 531375 |
1 | Zylinderkopfschraube M3 x 16, mit Unterlegscheibe und Mutter | - |
- |
1 | low Current LED, rot | D1 | 146005 |
1 | Elektrolytkondensator 2200 uF, 35V | C1 | 472557 |
4 | Kondensator, 100 nF | C2,C3,C5,C6 | 453358 |
1 | Elektrolytkondensator 220 uF, 35V | C4 | 472522 |
1 | Kondensator, 47 nF | C7 | 453080 |
2 | Kohleschichtwiderstand, 1/4 W, 5,6 kOhm | R1,R2 | 403342 |
1 | Kohleschichtwiderstand, 1/4 W, 100 kOhm | R3 | 403290 |
2 | Kohleschichtwiderstand, 1/4 W, 1,5 kOhm | R4,R6 |
403270 |
1 | Kohleschichtwiderstand, 1/4 W, 10 kOhm | R5 | 403377 |
1 | Stiftleiste RM 2,54, 36 pol. | - | 741119 |
1 | Buchsenleiste RM 2,54, 36 pol. | - |
741120 |
1 | Feinsicherung 0,5A | F1 | 533467 |
1 | Sicherungshalter 5x20 | - | 533866 |
Messung
der Signallängen verwendet. Da die Signale entweder 100 mS
oder 200 mS lang sind, ist der Timer0 so konfiguriert, dass
er jede Hundertstelsekunde einen Interrupt auslöst. Dieser
Interrupt wird von der Interruptroutine "Kurzzeit:" bedient. Hier wird
die Variable "Zaehler" hochgezählt und der Timer0 mit dem Wert
von "Preload0" neu gesetzt. Der Wert von "Zaehler" dient dem Vergleich
der verstrichenen Signalzeit in Hundertstelsekunden.
An dieser Stelle einmal eine Anmerkung, die dem Verständnis
der weiteren Erläuterungen dient. Ich nutze von der DCF77
Empfangsplatine das invertierte Signal. D. h. eine Absenkung des
Sendesignals entspricht einem aktiven Signal und steht an dem Port des
ATMegas als eine log. Eins an.
Sekundentakt:
Wird, wie schon gesagt, durch einen Interrupt von Timer1, jede Sekunde
aufgerufen. Der Timer1 wird hier durch den Wert von
"Preload1" neu gesetzt. Ferner wird die Variable "Sekunden"
hochgezählt. Hat diese einen höheren Wert als 59 wird
sie auf Null gesetzt und die Variable "Minuten" um eins
hochgezählt. Ist diese größer als 59 wird
sie auf Null gesetzt und die Variable "Stunden" um eins
erhöht. Analog verfahre ich mit den Variablen "Tag", "Monat"
und "Jahr". Diese Variablen kommen auch auf dem LCD-Display
mittelbar zur Anzeige. Zur richtigen Formatierung wird das
Unterprogramm "Form:" aufgerufen. Dies gibt die formatierten Werte in
der Variablen "H" zurückt, die dann an der jeweiligen Position
im LCD-Display angezeigt werden. Eine Ausnahme bildet der
Wochentag. Seine Darstellungsform ist in dem Stringfeld
"Wota( )" gespeichert. Dabei ist das erste Element der Montag
(Mo) und das siebte Element der Sonntag (So). Die Variable "Wtag" steht
für die Wochentage 1 bis 7.
Form:
Sowohl die Zeit als auch das Datum haben in der Darstellung auf dem
Display ein ähnliches Format. So wird die Zeit als hh:mm:ss
und das Datum als tt.mm.jj dargestellt. Deshalb wird an das
Unterprogramm "Form:" immer die Variablen "A" für Stunde, "B"
für Minute und "C" für die Sekunde bei der
Zeitdarstellung und "A" für den Kalendertag, "B" für
den Kalendermonat und "C" für das Kalenderjahr
übergeben. Die Übergabevariable "Sep" legt fest ob
der Separator für die Zeit, also ":" oder
für das Datum "." gemeint ist.
Die Stringvariable "H" übergibt, wie schon gesagt, die fertig
formatierte Anzeige.
Das Hauptprogramm ist schnell besprochen. Es ruft zunächst eine Initialisierungsroutine "Init:" auf und läuft dann in einer Endlosschleife, die immer wieder das Unterprogramm "Stellen:" aufruft, das wie der Name vermuten lässt, die Quarzuhr mit der Zeit der Funkuhr synchronisiert. Init:
ist eine Initialisierungsroutine. Sie legt die Werte für das
Stringfeld "Wota( )" fest, setzt den Wochentag zunächst
willkürlich auf 1 (Montag) und legt die Preloadwerte
für den Timer0 und den Timer1 fest. Weiterhin
ermittelt sie den Wert für die Variable "V". "V" ist ein
Vergleichswert zur Bestimmung der Signalzeiten. Eigentlich
könnte man ihn konstant auf 11 setzen (> 100mS = 11
"Zaehler"- Einheiten). Jedoch laufen die Mikrocontroller nicht alle
gleich schnell. Es gibt da von Baustein zu Baustein fertigungsbedingt
Unterschiede. Außerdem geht es, unter BASIC, mit
dem Messen von Hundertstelsekunden schon nahe an die Grenze des Machbaren.
Es kann gut sein, dass da schon mal eine Hundertstelsekunde verschluckt
wird. Um solchen Schwierigkeiten aus dem Weg zu gehen testet "Init:"
wie viele "Zaehler" - Werte für ein 100 mS Signal gebraucht
werden und verwendet den so ermittelten Wert der Variablen "V"
später für die Erkennung der Null- und
Einszustände des Zeittelegramms. "V" ist immer
1,5-mal so lang wie das kürzeste Signal.
Synchro: Diese
Routine dient dem Erkennen der 59. Sekunde, sprich des folgenden
Minutenwechsels. Das Programm startet zwei ineinander
geschachtelte Schleifen. Die äußere
Schleife setzt die Variable "Zaehler" auf Null und startet
den Timer0. Das Programm durchläuft die
äußere Schleife solange, bis die Variable "Zaehler"
einen größeren Wert als 130 hat. Mit anderen Worten:
bis eine längere Zeit als 1,3 Sekunden verstrichen ist. Da in
der 0. bis 58. Sekunde immer ein Signal kommt, nur in der 59.
Sekunde nicht, ist damit die 59. Sekunde eindeutig ermittelt.
Die
innere Schleife läuft solange, wie das Signal auf Null
liegt. Sobald das Signal auf Eins wechselt wird die innere Schleife
nicht mehr durchlaufen. Das Programm verlässt die innere
Schleife ebenfalls, wenn die Variable "Zaehler" einen
größeren Stand als 1000 erreicht hat. Dann sind Zehn
Sekunden verstrichen ohne dass ein Signalwechsel eintrat. Ein Zeichen
dafür, dass der Sender z. Zt. nicht empfangen werden kann. In
diesem Fall wird das Fehlerflag "Fehler" gesetzt.
Dadurch
erreichen wir, dass das Programm solange in diesen beiden
Schleifen bleibt, bis entweder das Synchronsignal empfangen wurde oder
ein Timeout stattfindet. Erst danach wird "Synchro:" wieder verlassen.Stellen:
ist das Herzstück des Programms. Hier wird ein komplettes
Zeittelegramm erfasst und
entschlüsselt. Das Programm wartet zunächst bis das
Signal log. Eins wird,
setzt dann die Variable "Zaehler" auf
Null und startet den Timer0. Dann wartet das
Programm bis das Signal
wieder Null ist und speichert anschließend den Wert von
"Zaehler" in
die Variable "AZaehler" zwischen (denn "Zaehler" läuft ja
interruptgesteuert weiter). Wir haben damit die Dauer des Eins-
Signals. Diese
wird nun, mit Hilfe der Variablen "V",
ausgewertet. (Wir erinnern uns, "V"
stellt die 1,5-fache Zeitdauer der kürzesten Signalzeit dar.) Ist "Azaehler"
kürzer als
"V", so haben wir eine log. Null entschlüsselt, ist
"AZaehler" größer oder gleich "V" so haben wir eine
log.
Eins entschlüsselt. Das
Ergebnis dieser
Entschlüsselung speichern wir in der Variablen "Einbit". Der
Vorgang
wird mit Hilfe einer FOR/NEXT- Schleife
59-mal wiederholt und mit der Laufvariablen
"Bitnummer"
gezählt. "Bitnummer" gibt uns also die Stelle in dem
Zeittelegramm
an. Weiterhin legen wir mit "Bitnummer" innerhalb der FOR/NEXT-
Schleife fest, was nun mit "Einbit" passiert. Dies geschieht mit
einer SELECT CASE - Anweisung.
Betrachten wir einmal beispielhaft die Bits 21 bis
27. Zunächst wird der
Wert von "Einbit" in der Variablen "Paritaet" aufaddiert.
Bei Bit 21 mit einer Zuweisung, ab Bit 22 durch Addition. Weiterhin
wird mit
Hilfe von "Einbit" der Wert für die Minuten
mit der Variablen "PMinuten" gebildet. Dies
geschieht durch
beaufschlagen von "Einbit" mit einer Wertigkeit. Bei Bit 21 ist diese
1, bei Bit 22 ist sie 2, bei Bit 23 4, usw. Mit dieser Wertigkeit
multiplizieren wir den Wert von "Einbit". Da "Einbit" nur
Null oder Eins sein kann, ist das Ergebnis der Multiplikation Null oder
eben
die Wertigkeit. Jeweils wird das Ergebnis der Multiplikation mit dem
Ergebnis
des vorherigen Bits hinzu addiert. (Außer bei Bit 21 .) Damit haben wir dann mit
Bit 27 den
kompletten Wert für die Minute.
Weiterhin haben wir in der Variablen "Paritaet" die
Summe der
Werte aller Bits von Bit 21 bis einschließlich Bit 27. Dieser
Wert kann nun geradzahlig
(z.B. 4) oder Ungeradzahlig (z.B. 7) sein. Bit 28 gibt nun Auskunft
darüber,
wie das Ergebnis sein SOLLTE; nämlich geradzahlig, dann ist
Bit 28 Null oder ungeradzahlig,
dann ist Bit 28 Eins. Dies prüfen wir mit dem Unterprogramm
"Ptest:".
Stimmt die errechnete Parität in der Variablen "Paritaet" mit
der
erwarteten Parität aus Bit 28 überein, ist alles in
Ordnung, wenn nicht, dann
war die Übertragung fehlerhaft.
Analog
verfahren wir mit den anderen Inhalten des Zeittelegramms. Zu Bemerken
ist
noch, dass "Minuten" hier "PMinuten",
"Stunden" hier "PStunden"
usw. heißen. Diese Doppelung von Variablen ist notwendig, da
zu diesem
Zeitpunkt noch gar nicht feststeht, ob diese Werte auch gültig
und nicht eventuell
durch Übertragungsfehler verfälscht sind. Da
"Minuten", "Stunden",
usw. ja interruptgesteuert, und damit unabhängig von dem Stand
der
Entschlüsselung des Zeittelegramms und der folgenden
Kausalitätskontrolle, jede
Sekunde neu angezeigt werden, ist damit
sichergestellt, dass trotzdem keine falschen oder 'halbfertige' Werte
zur
Anzeige kommen.
Ptest:
Wie
testet man ob eine Zahl gerade oder ungerade ist? Nun, wenn man diese
Zahl durch Zwei teilt und das Ergebnis keine Nachkommastellen hat, dann
ist diese Zahl gerade. Eine ungerade Zahl hat, teilt man sie
durch Zwei, immer Nachkommastellen. Genauso verfahren wir mit der
Eingangsvariablen "Paritaet". Die Funktion Frac() unter BASCOM
gibt die Nachkommastellen einer Zahl zurück. Hat die Zahl keine
Nachkommastellen gibt Frac() den Wert Null zurück. Wir weisen der
Singlevariablen "Paritaet" das Ergebnis von Frac() zu und
prüfen anschließend ob "Paritaet" gleich Null ist. Ist
"Paritaet" gleich Null so wird der Variablen "Pbit" der Wert Null
zugewiesen, andernfalls weisen wir "Pbit" eine Eins zu. Damit
enthält jetzt "Pbit" die Aussage, ob "Paritaet" eine gerade oder
ungerade Zahl enthielt. Und zwar in der gleichen Form die auch der
Paritätssollwert "Einbit" enthält. Ergibt ein Vergleich der
beiden, dass sie identisch sind, so bedeutet das: die Parität ist
ok. Falls nicht, ist etwas mit der Übertragung schief gegangen und
wir setzen das Fehlerflag "Fehler" gleich Eins.
Das Programm steht hier zum Download bereit |