Die Aufgabe der PIC-Software besteht bei der DCF-Dekodierung aus mehreren Teilaufgaben:
Der PIC-I/O-Eingang, an dem das DCF-Empfangsmodul (mit einer Anpass-Schaltung) angeschlossen ist, wird zyklisch (ca. alle 4 ms) vom Unterprogramm DCF_Routine abgefragt. Als Zeitbasis für den 4-ms-Takt dient der Timer-0-Interrupt. Dieser kann je nach Anwendung entweder so eingestellt werden, dass er alle 4 ms einen Interrupt auslöst, oder er wird so eingestellt dass er z. B. alle 500µs oder alle 1ms einen Interrupt erzeugt und ein Zählregister zählt die notwendige Anzahl an Interruptaufrufen bis zu einer Zeitbasis von 4 ms. Hier in dieser Dokumentation wird der Einfachheit halber die erstere Variante verwendet. Der Aufruf des Unterprogramms DCF_Routine erfolgt hier aber nicht von der ISR (Interrupt-Service-Routine), sondern vom Hauptprogramm. Die ISR setzt nur alle 4 ms ein Flag zur Kennzeichnung, dass das Hauptprogramm das Unterprogramm DCF_Routine aufrufen soll.
Die Aufgabe des Unterprogramms DCF_Routine besteht nun darin, aus dem vom
DCF-Empfangsmodul empfangenen Datenstrom die Informationen LOW, HIGH und den Minutenwechsel (also das Ausbleiben
der 59. Sekunde) gemäß dem im Abschnitt 2 (Grundlagen zu DCF) gewonnen
Erkenntnisse zu dekodieren. Für diese Informationen befinden sich im DCF-Statusregister
(cDCF_Status) entsprechende Flags. Siehe
Abschnitt 4.2 (Benötigte Register, Konstanten und Portdefinition für die DCF-Dekodierung)
Ein weiteres Register (cDCF_Hilfsregister) beinhaltet den Zustand des
DCF-Eingangs 4 ms vor dem Aufruf des Unterprogramms DCF_Routine. Mit Hilfe des
aktuellen Zustands und des Zustands vor 4 ms kann nun erkannt werden, ob sich der Pegel am DCF-Eingang geändert
hat.
Das Hauptprogramm prüft nun ständig die Flags bDCF_Neue_Sekunde (wird zusätzlich
zu bDCF_Low bzw. bDCF_High gesetzt, wenn eine
gültige Sekunde empfangen wurde) und bDCF_Neue_Minute, und ruft die Unterprogramme
DCF_UP_Sekunde bzw. DCF_UP_Minute auf. Die Aufgabe
des Unterprogramms DCF_UP_Sekunde ist es, die empfangenen Low- und High-Pegel in
den Register cDCF_Telegramm1 bis cDCF_Telegramm8
zwischenzuspeichern.
Die Aufgabe des Unterprogramms DCF_UP_Minute ist etwas umfangreicher:
Zunächst die Zeit und das Datum aus den in den Registern cDCF_Telegramm1 bis
cDCF_Telegramm8 zwischengespeicherten Werten entsprechend Tabelle 4 zusammensetzen.
Anschließend die soeben gewonnen Zeit- und Datumsinformationen mit den Zeit- und Datumsinformationen der
vorhergehenden Minute vergleichen. Die Zeit- und Datumswerte der soeben empfangenen Minute sind nur dann gültig,
wenn sich die Minute um maximal 1 von der vorhergehenden Minute unterscheidet, während sich die restlichen Zeit-
und Datumsinformationen mit denen der vorhergehenden Minute nicht unterscheiden. Dies ist nur eine einfache
Überprüfung, die aber manche richtigen Telegramme als falsch auswertet. Siehe
Abschnitt 4.3 (Unterprogramme für die DCF-Dekodierung)
Parallel zur Dekodierung des DCF-Eingangs erzeugt das Unterprogramm DCF_Innere_Uhr eine reine Softwareuhr. Für diesen Zweck ist eine genaue 1-Sekunden-Zeitbasis notwendig. Diese Zeitbasis wird von der schon erwähnten Timer-0-ISR (Interrupt Service Routine) zusätzlich zur 4ms-Zeitbasis erzeugt. Auch für diese Zeitbasis wird in der ISR ein entsprechendes Flag gesetzt, und das Hauptprogramm ruft das Unterprogramm DCF_Innere_Uhr auf, wenn dieses Flag gesetzt ist. Diese zusätzliche Softwareuhr mag auf dem ersten Blick unnötig erscheinen. Es hat sich aber gezeigt, dass ein DCF-Empfang oft auch über eine längere Zeit nicht möglich ist. In diesem Fall würde die Uhr "stehen" bleiben. Diese Zeit wird mit einer zusätzlichen Softwareuhr so überbrückt, dass der Betrachter der Uhr davon nichts bemerkt.
Register:
Für die DCF-Dekodierung sind neben einigen internen Registern (SFR, Spezielle Funktions-Register)
noch eine ganze Menge eigener Register notwendig:
Konstanten:
Bei einem LOW dauert die Absenkung des Trägers zwischen 80ms und 120ms, für ein HIGH dauert die Absenkung des
Trägers zwischen 160ms und 240ms. Die Konstanten KONSTDCFLMIN,
KONSTDCFLMAX, KONSTDCFHMIN und
KONSTDCFHMAX dienen zur Ermittlung eines LOW oder eines HIGH.
Das Unterprogramm DCF_Routine wird alle 4ms aufgerufen, daher ergeben sich
für diese Konstanten folgende Werte:
KONSTDCFLMIN: 20 (20 x 4ms = 80ms)
KONSTDCFLMAX: 30 (30 x 4ms = 120ms)
KONSTDCFHMIN: 40 (40 x 4ms = 160ms)
KONSTDCFHMAX: 60 (60 x 4ms = 240ms)
Es kann erforderlich sein, dass bei einer Anwendung die PIC-Taktfrequenz erhöht werden muss. In diesem Fall kann es notwendig sein, diese Konstanten anzupassen.
Die Konstante KONSTDCFNEUEMIN wird wür die Erkennung einer neuen Minute benötigt. Zur Erinnerung: Eine neue Minute wird im DCF-Telegramm durch das Ausbleiben der 59. Sekunde definiert.
Die Konstanten sind in der Datei DCF.H definiert (Listing 1):
Portdefinition:
Im Allgemeinen wird bei jeder Anwendung der Eingangspin für die DCF-Dekodierung an einem anderen Portpin
verwendet. Damit dies in der Software nur an einer Stelle berücksichtigt werden muss befindet sich in der
Software eine Portdefinition für den DCF-Eingang (Datei: DCF.H, Listing 2).
|
Achtung:
Beim Initialisieren des PIC-Mikrocontroller (bei mir im Unterprogramm Init) muss dieser Portpin als Eingang konfiguriert werden. Siehe Demonstrationsbeispiel (Abschnitt 5) |
Zur DCF-Dekodierung sind 4 Unterprogramme notwendig:
Weiters das Unterprogramm DCF_Innere_Uhr. Dieses Unterprogramm sorgt dafür, dass, wenn kein gültiges DCF-Telegramm empfangen werden kann, die Uhrzeit trotzdem jede Sekunde aktualisiert wird. Ist für eine längere Zeit kein korrekter DCF-Empfang möglich, so würden die Minuten, die Stunden, die Tage usw. "stehen" bleiben, da im Unterprogramm DCF_UP_Minute nur gültige DCF-Telegramme übernommen werden.
Ein großes Plus des mikroC-Compilers ist, dass die globalen Register, die in einer C-Datei definiert sind,
nicht von Unterprogrammen (oder vom Hauptprogramm) aus anderen C-Dateien benutzt werden können. Dadurch entsteht
eine Art Datenkapselung, und "ungewollte" Registermanipulationen werden dadurch vermieden. Der
mikroC-Compiler erzeugt eine Fehlermeldung, falls versucht wird ein globales Register aus einer anderen C-Datei
anzusprechen.
Bei der DCF-Dekodierung sind aber die Uhrzeit und das Datum, sowie andere nützliche Flags und Variablen in
solchen Registern abgelegt. Damit vom Hauptprogramm oder von Unterprogrammen der Inhalt dieser Register gelesen
werden kann, sind eine Reihe von Unterprogrammen notwendig, die als Rückgabeparameter den Inhalt des gewünschten
Registers an das aufrufende Programm zurückgeben. Die folgende Liste zeigt diese Unterprogramme.
Unterprogramm DCF_Init:
Das Unterprogramm DCF_Init hat die Aufgabe die für die DCF-Dekodierung
notwendigen globalen Register zu initialisieren.
Listing 3 zeigt die Realisierung dieses Unterprogramms.
Unterprogramm DCF_Routine:
Das Unterprogramm DCF_Routine ist für die DCF-Dekodierung die wichtigste
Komponente und muss ca. alle 4 ms aufgerufen werden.
Die Beschreibung dieses Unterprogrammes erfolgt mit Hilfe eines Flussdiagrammes (Abbildung 7). Die Aufgabe
des Unterprogramms DCF_Routine besteht darin aus den Abtastungen des
DCF-Eingangspins, die alle 4ms erfolgen, herauszufinden, ob ein Low, ein High oder ein Minutenwechsel gesendet
wurde. Dazu wird bei jedem Aufruf des Unterprogramms DCF_Routine entweder das
statische Zählregister cDCF_Puls oder das statische Zaehlregister
cDCF_Pause um 1 erhöht (inkrementiert), wenn sich der Pegel vom DCF-Eingang zum
vorhergehenden Aufruf dieses Unterprogramms nicht veraendert hat. Ist der DCF-Eingang Low (also logisch 0), so
wird das Zählregister cDCF_Puls um 1 erhöht, ist der DCF-Eingang High (also
logisch 1), so wird das Zählregister cDCF_Pause um 1 erhöht. Beide Zählregister
können maximal den Wert 255 aufnehmen, was einer Zeit von 1020 ms (oder 1,02 Sekunden) entspricht (= 255 x 4ms).
Unterscheidet sich der aktuelle Pegel des DCF-Eingangs mit dem Pegel vom vorhergehenden Aufruf, so spricht
man von einer Flanke, wobei hier zwischen einer fallenden und einer steigenden Flanke unterschieden werden muss.
Bei einer steigenden Flanke (der aktuelle Pegel des DCF-Eingangs ist logisch 1), wird das statische Zählregister
cDCF_Pause gelöscht. Anschließend erfolgt eine Auswertung des statischen Zählregisters
cDCF_Puls. Beinhaltet dieses Zählregister einen Wert zwischen den Konstanten
KONSTDCFHMIN und KONSTDCFHMAX, so wurde ein High
des DCF-Telegramms ermittelt. Im DCF-Statusregister (cDCF_Status) werden daher die
Flags bDCF_High und bDCF_Neue_Sekunde gesetzt.
Das Flag bDCF_Neue_Sekunde dient als Zeichen einer neu empfangenen und gültigen
Sekunde. Beinhaltet das Zählregister cDCF_Puls einen Wert zwischen den Konstanten
KONSTDCFLMIN und KONSTDCFLMAX, so wurde ein Low des
DCF-Telegramms ermittelt. Im DCF-Statusregister (cDCF_Status) werden daher die Flags
bDCF_Low und bDCF_Neue_Sekunde gesetzt. Das Flag
bDCF_Neue_Sekunde dient auch hier als Zeichen einer neu empfangenen und gültigen
Sekunde. Beinhaltet das statische Zählregister cDCF_Puls einen Wert der weder
zwischen den Konstanten KONSTDCFHMIN und KONSTDCFHMAX
noch zwischen den Konstanten KONSTDCFLMIN und KONSTDCFLMAX
liegt, so handelt es sich um einen Übertragungsfehler. In diesem Fall werden die Fehlerflags
bDCF_Fehler und bDCF_Bitfehler im DCF-Statusregister
(cDCF_Status) gesetzt. Das Flag bDCF_Neue_Sekunde
wird hier aber nicht gesetzt, da es sich in diesem Fall um keine gültige Sekunde handelt. Das Fehlerflag
bDCF_Fehler wird erst bei der Erkennung einer neuen Minute wieder gelöscht.
Bei einer fallenden Flanke (der aktuelle Pegel des DCF-Eingangs ist logisch 0) wird nur das statische
Zählregister cDCF_Puls gelöscht.
Entsprechend dem im Abschnitt 2 (Grundlagen zu DCF) beschriebenen Protokoll
erfolgt in der 59. Sekunde keine Absenkung des Trägers. Da es also in der 59. Sekunde keine fallende Flanke gibt,
gibt es auch keine steigende Flanke. Das Zählregister cDCF_Pause "läuft
über", da es nur einen Zählbereich von 0 bis 255 besitzt. Dieser Überlauf wird hier ausgenützt. Zur
Bestimmung einer neuen Minute bzw. zur Bestimmung des Telegrammbeginns. Ist das Fehlerflag bDCF_Fehler
gesetzt, so wird es nun gelöscht. War es nicht gesetzt, so wird das Flag bDCF_Neue_Minute
im DCF-Hilfsregister (cDCF_Hilfsregister) gesetzt.
Im Flussdiagramm (Abbildung 7) erfolgt in den grau hinterlegten Bereichen das Setzen oder Rücksetzen der zur weiteren Verwendung notwendigen Übergabeflags im DCF-Statusregister (cDCF_Status). Die Unterprogramme DCF_UP_Sekunde und DCF_UP_Minute greifen auf diese Flags zu.
Listing 4 zeigt die Realisierung dieses etwas umfangreicheren Unterprogramms.
Unterprogramm DCF_UP_Sekunde:
Das Unterprogramm DCF_UP_Sekunde hat die Aufgabe, je nachdem ob das Bit
bDCF_Low oder das Bit bDCF_High gesetzt ist
dieses Bit dem Telegramm (Register cDCF_Telegramm1 bis cDCF_Telegramm8)
hinzuzufügen.
Vorgehensweise:
Ist das Flag bDCF_Low (im Register cDCF_Status)
gesetzt das Carryflag (im SFR5 STATUS)
löschen. Ist das Flag bDCF_High (ebenfalls im Register cDCF_Status)
gesetzt, das Carryflag setzten. Dieses Carryflag nun dem Telegramm hinzufügen. Dieser Vorgang erfolgt mit einem
so genannten Schiebebefehl. Dabei werden alle Bits des Registers cDCF_Telegramm1
auf die nächst höhere Position verschoben. (Bit 6 wandert ins Bit 7, Bit 5 wandert ins Bit 6 usw. Bit 0 wandert
ins Bit 1). Der Inhalt vom Carryflag wandert ins Bit 0. Der Inhalt von Bit 7 wird ins Carryflag geschoben. Bei
den Register cDCF_Telegramm2 bis cDCF_Telegramm6
erfolgt der selbe Vorgang. (Der Inhalt von Bit 7 des Registers cDCF_Telegramm1
wird ins Carry geschoben, und das Carry aber weiter in das Bit 0 des Registers cDCF_Telegramm2).
Achtung: Diese Schiebebefehle sind nur als Assembler-Anweisungen verfügbar. Daher sind diese Befehle in
einem asm-Block (Zeilen 306 bis 315).
Anmerkung:
Die beiden Flags bDCF_Low und bDCF_High können
nie gleichzeitig gesetzt sein. Es ist nur möglich, dass entweder nur bDCF_Low oder
nur bDCF_High gesetzt ist, aber nie beide gleichzeitig.
|
Wichtig:
Dieses Unterporgramm wird durch ein gesetztes Botschaftsflag aufgerufen. Dieses Botschaftsflag muss daher wieder gelöscht werden (Zeile 319). |
Listing 5 zeigt die Realisierung dieses Unterprogramms.
Unterprogramm DCF_UP_Minute:
Das Unterprogramm DCF_UP_Minute hat folgende Aufgaben:
|
Wichtig:
Dieses Unterporgramm wird durch ein gesetztes Botschaftsflag aufgerufen. Dieses Botschaftsflag muss daher wieder gelöscht werden (Zeile 455). |
Listing 6 zeigt die Realisierung dieses etwas umfangreicheren Unterprogramms.
Unterprogramm DCF_Innere_Uhr:
Für den Fall dass kein gültiges DCF-Telegramm empfangen werden kann sorgt das Unterprogramm
DCF_Innere_Uhr dafür, dass die Uhrzeit trotzdem jede Sekunde aktualisiert wird.
Ist für eine längere Zeit kein korrekter DCF-Empfang möglich, so würden die Minuten, die Stunden, die Tage usw.
"stehen" bleiben, da ja im Unterprogramm DCF_UP_Minute nur gültige
DCF-Telegramme übernommen werden. Dieses Unterprogramm wird also parallel zu den Unterprogrammen für die
DCF-Dekodierung aufgerufen.
Vorgehensweise:
Die Sekunden (Register cUhr_Sekunde) um 1 erhöhen. Beinhaltet dieses
Register nun den Wert 60, so beginnt eine neue Minute. Daher die Sekunden löschen und die Minuten (Register
cUhr_Minute) um 1 erhöhen. Beinhaltet dieses Register nun den Wert 60, so
beginnt eine neue Stunde. Daher die Minuten löschen und die Stunden (Register cUhr_Stunde)
um 1 erhöhen. Beinhaltet dieses Register nun den Wert 24, so beginnt ein neuer Tag. Daher die Stunden löschen.
Ist das Mitzählen des Datums notwendig, so muss nun ein Register für den Tag (z.B. cDatum_Tag)
um 1 erhöht werden und dieses überprüft werden, wobei diese Prüfung nun nicht mehr so einfach ist, da ja jeder
Monat unterschiedlich viele Tage besitzt. Erschwerend kommt auch noch hinzu, dass auch die Schaltjahre
miteinbezogen werden müssen!
|
Wichtig:
Dieses Unterprogramm muss jede Sekunde (z.B. vom Hauptprogramm) aufgerufen werden. |
Listing 7 zeigt die Realisierung dieses Unterprogramms.
Unterprogramme zum Lesen von Uhrzeit, Datum und diversen Zusatzinformationen:
Die Uhrzeit, das Datum, sowie andere nützliche Informationen sind in globalen Registern gesichert. Diese
globalen Register können nur vom Quellcode in der Datei DCF.C verändert und
gelesen werden. Das Hauptprogramm, oder andere Unterprogramme benötigen aber diese Informationen. Daher sind
einige Unterprogramme notwendig, die als Rückgabeparameter nur den Inhalt dieser Register beinhalten. Durch
diesen Mechanismus wird verhindert, dass Programmteile Datenregister (ungewollt) verändern. Dieser Mechanismus
wir in der Informatik auch Datenkapselung genannt.
Die Listings 8 bis 26 zeigen diese sehr einfachen Unterprogramme.
Unterprogramme zum Lesen der Botschaftsflags für die Abarbeitung der Unterprogramme
DCF_UP_Sekunde und DCF_UP_Minute:
Die für die DCF-Dekodierung wichtigen Unterprogramme DCF_UP_Sekunde (Listing 6)
und DCF_UP_Minute (Listing 6) werden nur bei einer vollen Sekunde bzw. bei einer
vollen Minute abgearbeitet. Die zwei Botschaftsflags bDCF_Neue_Sekunde und
bDCF_Neue_Minute müssen daher mit Hilfe der Unterprogramme
DCF_Is_Neue_Sekunde (Listing 27) und DCF_Is_Neue_Minute
(Listing 28) im Hauptprogramm abgefragt werden. Denn auch hier gilt das Prinzip der Datenkapselung, da diese zwei
Botschaftsflags im Unterprogramm DCF_Routine (Listing 4, in der Datei
DCF.C) gesetzt werden. Anmerkung: Diese beiden Botschaftsflags werden automatisch
in den Unterprogrammen DCF_UP_Sekunde bzw. DCF_UP_Minute
zurückgesetzt.
Das Demonstrationsbeispiel (im Abschnitt 5) zeigt dies an einem konkreten, praktischen Beispiel.
5 SFR steht für Special Function Register. Diese Register haben vom Mikrocontroller-Hersteller klar definierte Aufgaben, und sind im Datenblatt des Mikrocontrollers beschrieben