\documentclass[11pt,a4paper]{article} % 2006-07-07 EW Adventures-1.tex % 2006-07-22 EW Adventures-2.tex % 2006-08-14 EW Adventures-2a.tex % 2007-01-10 EW Adventures-3.tex % 2007-01-13 EW Adventures-4.tex % % Aufgebessert nachdem ich 'ne Reihe Optimierungen gemacht hatte % Und dann nochmal komplett umgeräumt! % % language support \usepackage[german]{babel} %\usepackage{german} %\usepackage[latin1]{inputenc} % can use Umlauts now "u instead of "u %\usepackage{lmodern} % better fonts %\usepackage{pslatex} % use native PostScript fonts \usepackage{cm-super} \usepackage{url} % \url{} \path{} with correct "hyphenation" %\usepackage{fancyvrb} % for code examples % \voffset-10mm % \pagestyle{empty} % \pagestyle{plain} % \pagestyle{headings} % \renewcommand{\baselinestretch}{1.08} %\usepackage{xspace} \parindent=0pt %\newcommand{\Forth}{\textsc{forth}\xspace} \begin{document} \title{Adventures in Forth 4} \author{Erich W"alde} \maketitle \begin{multicols}{2} % ==================================================================== \section{Eine Funkuhr} \label{sec:dcfclock} Im zweiten Teil (\ref{Adv2}) dieser kleinen Artikelserie wurde eine Uhr f"ur Steuerungsaufgaben realisiert (\texttt{timeup}). Diese Uhr wollte ich nun mit einem DCF-77 Empf"anger ausr"usten. Allerdings zeigte sich beim Programmieren, dass diese Aufgabe mehr zu einer \textit{Studie f"ur Designentscheidungen} geriet, als ich guthei"sen kann. Ich hatte noch einen arbeitslosen DCF-77 Empf"anger herumliegen. Den invertierten Signalpin des Empf"angers verband ich mit dem Pin 5 von Port D. Dieser Pin des Renesas R8C/13--Kontrollers kann auch Interrupts ausl"osen. Das DCF-Signal ist recht einfach aufgebaut. Jede Sekunde wird ein Bit "ubertragen. Der Wert des Bits wird durch die Dauer kodiert: ein Signal von 100\,ms wird als Null, ein Signal von 200\,ms wird als Eins interpretiert. In der 59.\ Sekunde wird kein Bit "ubertragen. Die f"uhrende Flanke des n"achsten Bits markiert den Beginn der neuen Minute. Die Daten sind als \textit{binary coded decimal} (BCD) Werte kodiert und das niedrigstwertige Bit wird zuerst "ubertragen. BCD bedeutet, dass in jeder H"alfte des Bytes eine Ziffer "ubertragen wird, also etwa \texttt{0x16} f"ur 16 und nicht etwa \texttt{0x10}. Die Bedeutung der DCF-Bits sei hier aufgelistet, soweit f"ur das Programm von Bedeutung (\ref{dcf}). \begin{tabular}[h]{r|l} Bit Nr. & \\ \hline 0 & Beginn der Minute \\ 17 & Sommerzeit \\ 21--27 & Minute \\ 28 & Parit"at Minute \\ 29--34 & Stunde \\ 35 & Parit"at Stunde \\ 36--41 & Tag \\ 42--44 & Wochentag \\ 45--49 & Monat \\ 50--57 & Jahr$-2000$ \\ 58 & Parit"at \\ 59 & kein Signal \\ \end{tabular} \section{Entscheidungen} \subsection{Eine oder zwei Uhren?} Ich hatte so eine DCF-Uhr vor ein paar Jahren schon einmal in 8051-Assembler geschrieben. Damals fand ich heraus, dass das DCF-Signal durchaus ohne Vorwarnung \textit{ausbleibt}, und das f"ur mehrere Minuten. Das mag am Wetter, der Geographie oder den Unzul"anglichkeiten des Empf"angers liegen. Bei Gewittern ist das Signal au"serdem stark gest"ort. Ich will aber eine Uhr, die korrekt weiterl"auft, wenn das Signal ausbleibt. Also nehme ich \texttt{timeup} als Grundlage. Ganz grob will ich zwei Funktionen in \texttt{timeup} einh"angen. Eine Funktion soll \textit{oft} das Signal der DCF-Uhr abtasten und auswerten, die andere soll \textit{selten} die \texttt{timeup}-Uhr mit der DCF-Uhr synchronisieren. \textbf{Entscheidung 1:} Die \texttt{timeup}-Uhr l"auft unabh"angig; sie wird mit der DCF-Uhr gelegentlich synchronisiert. \subsection{Abtasten oder Interrupts?} Es gibt zwei M"oglichkeiten, das DCF-Signal auszuwerten. Entweder man tastet das Signal regelm"a"sig ab oder man l"asst jede Flanke einen Interrupt erzeugen, welcher die Auswertung des Signals ausl"ost. Die zweite Variante hat den Vorteil, dass das Programm nur dann das Signal auswertet, wenn es wirklich was zu tun gibt. Die erste Variante hat den Vorteil, dass sie einfach zu durchschauen ist. Erschwerend kam hinzu, dass ich bislang nicht herausgefunden habe, wie ich einem Interrupt aus Forth heraus eine \textit{interrupt service routine} verpasse --- die G"otter und die Gurus m"ogen mir verzeihen. \textbf{Entscheidung 2:} Das Signal wird abgetastet. Daran schlie"st sich sofort die Frage an, wie oft das Signal abgetastet wird. F"ur eine Null dauert das Signal $1/10$\,s. In dieser Zeit m"ochte ich das Signal vielleicht 10--mal abtasten. Dann erh"alt man normalerweise Z"ahlwerte von 8 oder 9. Wenn au"serdem eine Abtastung durch Rauschen falsch ist, dann ist das immer noch von Eins (16-19 Z"ahler) oder Kein-Signal (0-1 Z"ahler) gut zu unterscheiden. \textbf{Entscheidung 3:} Die Abtastung erfolgt mit 100\,Hz. Das l"asst sich sehr einfach realisieren, indem man der \texttt{timeup}-Uhr die entsprechenden Konstanten verpasst. \begin{verbatim} 10 Constant cycles.tick \ timerC cycles/tick 100 Constant ticks.sec \ ticks/second \end{verbatim} Die Abtastung des Signals ruft man dann in \texttt{job.tick} auf. \subsection{Synchronisieren auf die Sekunde} In Sekunde 59 bleibt das Signal aus, die neue Minute f"angt an der Flanke des n"achsten Bitsignals an. Man k"onnte versucht sein, auf diese Flanke zu warten. Was, aber wenn diese Flanke dann ausbleibt oder durch Rauschen zu fr"uh vorget"auscht wird? Das hat mir auch nicht so recht gefallen. Also habe ich beschlossen, die \texttt{timeup}-Uhr anders an das Signal anzugleichen. Das Signal wird also 100--mal in der Sekunde gemessen. Ist der Pegel 0\,V (aktiv), dann wird der Pulsz"ahler erh"oht, andernfalls der Pausenz"ahler: \begin{verbatim} : dcf.readPin ( -- t/f ) \ pin is on "active low" connection -> invert pinDCF portDCF btst invert ; : dcf.tick ( -- ) dcf.readPin \ count up Pulse/Pause counters IF 1 dcfPulse +! ELSE 1 dcfPause +! ENDIF ... ; \end{verbatim} Zum Erkennen von Null, Eins und der 59.\ Sekunde reicht das aus. Allerdings k"onnte es sein, dass der Bitpuls so sp"at in der \texttt{timeup}-Sekunde losgeht, dass sein Ende bereits in der n"achsten Sekunde liegt. Das kann dazu f"uhren, dass "uber lange Zeit kein g"ultiges DCF-Telegram empfangen wird, vor allem dann, wenn eine Null noch innerhalb der \texttt{timeup}-Sekunde liegt, eine Eins aber in die n"achste Sekunde hineinreicht. Um das zu vermeiden, will ich feststellen, ob der Puls des Bits komplett im ersten Viertel der \texttt{timeup}-Sekunde liegt. Wenn nicht, dann kann ich das Auswerten des Bits "uber ein Offset etwas verschieben. Damit wird die Verschiebung der beiden Uhren zumindest gemessen. Dieses Verfahren ist auch einigerma"sen robust gegen St"orungen des Signals. \textbf{Entscheidung 4:} Die \texttt{timeup}-Uhr auf wenige ticks (10\,ms/tick) mit der DCF-Uhr synchronisiert. \subsection{Auswertung fortlaufend oder am Ende?} Eine weitere Frage ist die, ob man zuerst alle Bits einsammelt und die Information dann am Ende der Minute am St"uck auswertet oder ob man nach jedem Bit die Information auswertet. Auch das ist eher Geschmacksache. \textbf{Entscheidung 5:} Die Bits werden fortlaufend ausgewertet und nicht erst am Ende der Minute. \section{Code 1: DCF-Signal abtasten} Als Grundlage dient der Code aus dem 2.\ Teil: eine \texttt{timeup}--Uhr, bei der alle Teile, die mit dem i2c--Bus zu tun haben, herausgenommen wurden. Die Worte f"ur die DCF-Uhr befinden sich in der Datei \texttt{adv4\_dcf.fs}. Diese erwartet ein paar definierte Konstanten und die Worte \texttt{led2\_0} und \texttt{led2\_1}, um das DCF-Signal direkt auf LED2 anzuzeigen. \begin{verbatim} $e8 Constant portDCF \ port-D $ea Constant pddrDCF \ pddr-D 5 Constant pinDCF : led2_0 ( -- ) 2 port1 bclr ; : led2_1 ( -- ) 2 port1 bset ; include adv4_dcf.fs \end{verbatim} \texttt{adv4\_dcf.fs} enth"alt zun"achst drei simple Worte: \begin{verbatim} : dcf.readPin \ pin is on "active low" connection \ --> invert pinDCF portDCF btst invert ; : dcf.init pinDCF pddrDCF bclr \ set pinDCF input ; : dcf.tick dcf.readPin \ show DCF-Signal "active" on led2 IF led2_1 ELSE led2_0 ENDIF ; \end{verbatim} Zwei dieser Worte werden in den Innereien der \texttt{timeup}--Uhr aufgerufen: \texttt{dcf.init} in \texttt{loop.init} und \texttt{dcf.tick} in \texttt{job.tick}. Wenn man das Programm l"adt und mit \texttt{run} die Uhr startet, dann blinken die beiden LEDs Nr.\,2 und 3. LED Nr.\,3 blinkt im Sekundentakt, Puls und Pause sind gleich lang. LED Nr.\,2 blinkt wie das DCF--Signal. Man kann recht bald Null und Eins (leuchtet l"anger) durch blo"ses Hinsehen unterscheiden. Ebenso ist die Pause bei Sekunde 59 (kein Signal) bald gefunden. \section{Code 2: Bits vermessen} Im n"achsten Schritt werden zwei Variablen \texttt{dcfPulse} und \texttt{dcfPause} definiert. Mit diesen werden die L"angen von Puls (aktives DCF-Signal) und Pause vermessen. Au"serdem verwende ich die Ausgabeumleitung aus dem dritten Teil (\ref{Adv3}) f"ur die Ausgaben. \begin{verbatim} Variable dcfPulse Variable dcfPause : dcf.tick dcf.readPin \ show pin "active" on led2 dup IF led2_1 ELSE led2_0 ENDIF \ count up Pulse/Pause counters IF 1 dcfPulse +! ELSE 1 dcfPause +! ENDIF \ reload counters if tick == 0 tick @ 0 = IF to-stdout cr dcfPulse @ s>d 3 d.r space dcfPause @ s>d 3 d.r dcf.reload ENDIF ; \end{verbatim} Wenn \texttt{tick} aus \texttt{adv4\_timeup.fs} gleich null ist, dann werden die Z"ahlerst"ande ordentlich formatiert ausgegeben und die Z"ahler zur"uckgesetzt. Diese Aufgaben w"urde man eigentlich in einem Wort \texttt{dcf.sec} erwarten, welches von \texttt{job.sec} aufgerufen wird. Allerdings habe ich in \texttt{dcf.tick} die M"oglichkeit, die Auswertung des DCF-Signals innerhalb der Minute der \texttt{timeup}-Uhr zu verschieben, falls das n"otig wird. \texttt{dcf.tick} produziert etwa folgende Ausgabe: \begin{verbatim} include adv4_main.fs ok run ok ... 8 92 9 91 8 92 <== 0 bit 18 82 <== 1 bit 0 100 <== sync! ... \end{verbatim} Die Bitfolge des Zeitsignals ist mit dieser Ausgabe leicht zu sehen. Der gezeigte Abschnitt zeigt ein makelloses DCF--Signal. Aber ich brauche auf gest"orte Signale nie sehr lange zu warten: \begin{verbatim} 10 90 9 91 26 74 <== Störungen! 21 79 . 43 57 . 17 83 . 7 93 . 0 100 . 0 100 . 10 90 . 1 99 . \end{verbatim} \section{Code 3: Bits auswerten} Unter der Annahme, dass \texttt{dcf.tick} tats"achlich regelm"a"sig aufgerufen wird, kann man aus dem Wert in \texttt{dcfPulse} auf den Wert des Bits schlie"sen. Au"ser den Werten 0 und 1 f"ur die Bits sollen noch -1 f"ur Fehler und -2 f"ur die Erkennung der 59.\ Sekunde verwendet werden. Bei dieser Gelegenheit soll auch ein Bit eingef"uhrt werden, welches das Auftreten eines Fehlers innerhalb eines Telegramms (eine Minute) anzeigt. Au"serdem wird in der neuen Variablen \texttt{dcfPos} die Position innerhalb des Datentelegramms gespeichert. \begin{verbatim} Variable dcfFlags $00 Constant dcfError Variable dcfPos : dcf.error.set dcfError dcfFlags bset ; : dcf.error.clr dcfError dcfFlags bclr ; : dcf.error? dcfError dcfFlags btst ; : dcf.init ... dcf.error.set \ error unless proven ok. ; : dcf.bit ( pulse -- bit/error ) dup 2 < IF ( sync59 ) -2 ELSE dup 6 < IF dcf.error.set -1 ELSE dup 11 < IF ( bit:0 ) 0 ELSE dup 16 < IF dcf.error.set -1 ELSE dup 21 < IF ( bit:1 ) 1 ELSE dcf.error.set -1 ENDIF ENDIF ENDIF ENDIF ENDIF swap drop ; : .3r s>d 3 d.r space ; : dcf.tick ... tick @ 0 = IF dcfPulse @ dcf.bit to-stdout cr dcfPulse @ .3r dcfPause @ .3r dcfPos @ .3r dup .3r \ bit value dcf.error? .3r dcf.reload -2 = IF \ sync detected dcf.error.clr 0 dcfPos ! ELSE 1 dcfPos +! ENDIF ENDIF ; \end{verbatim} Die Funktion \texttt{dcf.bit} bewertet den Z"ahlerstand aus \texttt{dcfPulse}. Wurde die Pause in der 59.\ Sekunde gefunden, dann wird das Fehlerbit gel"oscht und \texttt{dcfPos} zur"uckgesetzt. Die Ausgabe sieht dann etwa so aus. \begin{verbatim} 8 92 0 0 -1 18 82 1 1 -1 18 82 2 1 -1 ... 8 92 17 0 -1 9 91 18 0 -1 0 100 19 -2 -1 <== 1. sync 10 90 0 0 0 19 81 1 1 0 19 81 2 1 0 ... 8 92 56 0 0 9 91 57 0 0 9 91 58 0 0 0 100 59 -2 0 <== sync 9 91 0 0 0 9 91 1 0 0 19 81 2 1 0 ... \end{verbatim} \section{Code 4: Bits zu Zahlen schmieden} Soweit habe ich erreicht, dass das DCF-Signal abgetastet und jede Sekunde ein Bit ausgewertet wird. Die Sekunde 59 wird ebenfalls erkannt und \texttt{dcfPos} z"ahlt die Bitpositionen des Daten-Telegramms. Im n"achsten Schritt geht es darum, den Bitstrom in Zahlen umzuwandeln. Daf"ur k"onnte man einen einfachen, aber daf"ur gro"sen \texttt{case}-Block erstellen, der das erledigt: \begin{verbatim} dcf.bit \ bit: 0 or 1 dcfPos @ CASE 0 OF 0 minute ! ... ENDOF 21 OF IF 1 minute +! Ptoggle ENDIF ENDOF 22 OF IF 2 minute +! Ptoggle ENDIF ENDOF 23 OF IF 4 minute +! Ptoggle ENDIF ENDOF 24 OF IF 8 minute +! Ptoggle ENDIF ENDOF 25 OF IF 10 minute +! Ptoggle ENDIF ENDOF 26 OF IF 20 minute +! Ptoggle ENDIF ENDOF 27 OF IF 40 minute +! Ptoggle ENDIF ENDOF 28 OF Ptoggle Pset? IF dcf.error.set ENDIF ENDOF ... ( default: tue nichts ) ENDCASE \end{verbatim} Allerdings sieht man an diesem kurzen St"uck schon, dass sich die Anweisungen ziemlich eint"onig wiederholen. Das hat mir nicht gefallen. Eigentlich wird jedes Bit in eine Variable \textit{rotiert}, und wenn man f"ur eine Angabe im Telegramm gen"ugend Bits zusammen hat, dann wird der Wert abgespeichert und das Spielchen beginnt von vorne. Um die Bitfolge korrekt auszuwerten, schreibe ich eine Tabelle. Diese enth"alt die Bitpositionen im DCF-Telegramm, an denen ein neues Feld beginnt: etwa den Wert 21 f"ur den Beginn der Minute. \begin{verbatim} create dcfFields 0 c, \ .start 8 c, \ 16 c, \ leading flags 17 c, \ daylight savings time 18 c, \ 21 c, \ Minute 28 c, \ MinuteParity 29 c, \ Hour 35 c, \ HourParity 36 c, \ Day 42 c, \ DayOfWeek [1: Mo ... 7:So] 45 c, \ Month 50 c, \ Year-2000 58 c, \ Parity 59 c, \ .fin 16 Constant dcfFieldsN Variable dcfFieldsI Variable dcfCurr ram create dcfValues dcfFieldsN allot rom : dcf.NextField? ( dcfPos -- t/f ) dcfFieldsI @ dcfFields + c@ 1- = ; \end{verbatim} Die Variable \texttt{dcfFieldsI} enth"alt die Nummer des momentan aktiven Felds (beginnt mit 1), also etwa 6 am Beginn des Minutenfelds, und dient als Index. Das Wort \texttt{dcf.FieldComplete?} berechnet, ob das aktuelle Feld vollst"andig ist. Wenn ja, dann kann ich den bislang aufgesammelten Wert speichern, und zwar im Feld \texttt{dcfValues} unter dem gleichen Index. Das verplempert am Anfang ein paar Byte, aber die will man vielleicht sp"ater mal auswerten. Die Felder 0 und 1 mit den Werten 8 und 16 dienen lediglich dem Schutz von \texttt{dcfValues}, welches als Bytearray definiert wurde, und nicht als Array von Worten (\texttt{cells}). \begin{verbatim} 6 dcfFieldsI ! ok dcfFields dcfFieldsI @ + c@ . 28 ok 25 dcf.FieldComplete? . 0 ok 26 dcf.FieldComplete? . 0 ok 27 dcf.FieldComplete? . -1 ok 7 dcfFieldsI ! ok 28 dcf.FieldComplete? . -1 ok \end{verbatim} \texttt{dcfFields} enth"alt die Anfangspositionen der Felder, weil ich dann die relativen Bitposition in diesem Feld einfach ausrechnen kann (beim Setzen der Bits in \texttt{dcfCurr}). Au"serdem wird auf das Feld mit dem Index \texttt{dcfFieldsI-1} zugegriffen, daher setze ich konsequenterweise den Startwert auf 1. Das Resultat des Wortes \texttt{dcf.bit} wird in einem neuen Wort \texttt{dcf.usebit} verwendet: der Bitwert wird an die richtige Bitposition in \texttt{dcfCurr} kopiert. Wenn ein Feld vollst"andig ist, wird der Wert von BCD in dezimal gewandelt und abgespeichert. Diese beiden Werte werden auch an der Stelle ausgegeben, bevor \texttt{dcfCurr} gel"oscht wird. \begin{verbatim} : dcf.usebit ( b -- ) CASE -2 OF ( sync59, reset Fields Index ) 1 dcfFieldsI ! ENDOF -1 OF dcf.error.set ( error ) ENDOF 0 OF ( bit value 0, do nothing ) ENDOF 1 OF ( bit value 1, use it ) \ get position of current bit in dcfCurr \ B: minute ones \ dcfPos = 21..24, dcfFieldsI = 6 \ dcfFields[5] = 21 \ bit pos in dcfCurr = 0..3 dcfPos @ dcfFields dcfFieldsI @ 1- + c@ - dcfCurr bset ENDOF ( default: ) dcf.error.set ENDCASE to-stdout space space dcfCurr @ dup .3r bcd>dec .3r dcfPos @ dcf.FieldComplete? IF dcfCurr @ bcd>dec dcfValues dcfFieldsI @ 1- + c! 0 dcfCurr ! 1 dcfFieldsI +! ENDIF ; \end{verbatim} Zur Belohnung erhalten wir ein komplettes Datentelegramm, hier noch von Hand kommentiert. Alle Felder wurden richtig ausgewertet. Seit 2003 wird in den ersten 15 Bits des Datentelegramms zus"atzliche Information "ubertragen, davor waren dort alle Bits auf null gesetzt. {\small \begin{verbatim} 0 100 59 -2 0 15 0 0 0 Sync 9 91 0 0 0 1 0 0 0 18 82 1 1 0 1 0 2 2 18 82 2 1 0 1 0 6 6 17 83 3 1 0 1 0 14 14 8 92 4 0 0 1 0 14 14 9 91 5 0 0 1 0 14 14 8 92 6 0 0 1 0 14 14 9 91 7 0 0 1 -1 14 14 18 82 8 1 0 2 0 1 1 18 82 9 1 0 2 0 3 3 18 82 10 1 0 2 0 7 7 17 83 11 1 0 2 0 15 15 8 92 12 0 0 2 0 15 15 9 91 13 0 0 2 0 15 15 9 91 14 0 0 2 0 15 15 9 91 15 0 0 2 -1 15 15 9 91 16 0 0 3 -1 0 0 9 91 17 0 0 4 -1 0 0 18 82 18 1 0 5 0 1 1 8 92 19 0 0 5 0 1 1 18 82 20 1 0 5 -1 5 5 8 92 21 0 0 6 0 0 0 9 91 22 0 0 6 0 0 0 18 82 23 1 0 6 0 4 4 8 92 24 0 0 6 0 4 4 9 91 25 0 0 6 0 4 4 18 82 26 1 0 6 0 36 24 9 91 27 0 0 6 -1 36 24 24 Minuten 9 91 28 0 0 7 -1 0 0 P.ok 18 82 29 1 0 8 0 1 1 19 81 30 1 0 8 0 3 3 8 92 31 0 0 8 0 3 3 9 91 32 0 0 8 0 3 3 8 92 33 0 0 8 0 3 3 19 81 34 1 0 8 -1 35 23 23 Stunden 19 81 35 1 0 9 -1 1 1 P.ok 8 92 36 0 0 10 0 0 0 8 92 37 0 0 10 0 0 0 8 92 38 0 0 10 0 0 0 8 92 39 0 0 10 0 0 0 19 81 40 1 0 10 0 16 10 17 83 41 1 0 10 -1 48 30 30 Tag 8 92 42 0 0 11 0 0 0 19 81 43 1 0 11 0 2 2 8 92 44 0 0 11 -1 2 2 2 Dienstag 19 81 45 1 0 12 0 1 1 8 92 46 0 0 12 0 1 1 9 91 47 0 0 12 0 1 1 8 92 48 0 0 12 0 1 1 8 92 49 0 0 12 -1 1 1 1 Monat 19 81 50 1 0 13 0 1 1 18 82 51 1 0 13 0 3 3 17 83 52 1 0 13 0 7 7 8 92 53 0 0 13 0 7 7 8 92 54 0 0 13 0 7 7 9 91 55 0 0 13 0 7 7 8 92 56 0 0 13 0 7 7 8 92 57 0 0 13 -1 7 7 07 Jahr 19 81 58 1 0 14 -1 1 1 P.ok 0 100 59 -2 0 15 0 0 0 Sync \end{verbatim} } Ergebnis: Dienstag 2007-01-30 23:24 h. Das war jetzt ein ordentlicher Brocken, und daran habe ich auch einige Zeit gegr"ubelt. Und beim Schreiben des Artikels nochmal. Eine Konsequenz des Schreibens ist, dass man sich nochmal richtig Gedanken machen muss: Warum habe ich das jetzt so gel"ost und nicht anders? Und gelegentlich stolpert man so auch "uber eine bessere L"osung. \section{Code 5: Parit"atsbits auswerten} Um die Parit"atsbits auszuwerten, brauchen wir ein paar Worte, die das Bit setzen, l"oschen, abfragen und umschalten. Bei jedem Datenbit, welches den Wert 1 hat, wird das interne Parit"atsbit umgeschaltet. Wenn das zugeh"orige Parit"atsbit aus dem Telegramm 1 ist, dann wird das interne Parit"atsbit ebenfalls umgeschaltet. Danach muss das interne Parit"atsbit zwangsl"aufig den Wert 0 haben. Die Positionen der Parit"atsbits im Datentelegramm habe ich im nachfolgenden Block hart kodiert. Wenn das interne Parit"atsbit zu den angegebenen Stellen nicht 0 ist, wird \texttt{dcfError} gesetzt. \begin{verbatim} Variable dcfFlags ... $01 Constant dcfParity : dcf.par.set dcfParity dcfFlags bset ; : dcf.par.clr dcfParity dcfFlags bclr ; : dcf.par.tgl dcfParity dcfFlags 2dup btst IF bclr ELSE bset ENDIF ; : dcf.par? dcfParity dcfFlags btst ; \ set dcf.error bit if parity bit is set : dcf.err.if.par dcf.par? IF dcf.error.set ENDIF dcf.par.clr ; : dcf.usebit ... CASE 1 OF ... dcf.par.tgl ENDOF ( default: ) dcf.error.set ENDCASE dcfPos @ CASE 20 OF dcf.par.clr ENDOF 28 OF dcf.err.if.par ENDOF 35 OF dcf.err.if.par ENDOF 58 OF dcf.err.if.par ENDOF ENDCASE ... ; \end{verbatim} Jetzt ist es an der Zeit, die empfangenen Werte anzuzeigen. \begin{verbatim} : get.DCF ( -- S M H d m Y ) 0 \ fake second dcfValues 5 + c@ \ minute dcfValues 7 + c@ \ hour dcfValues 9 + c@ \ day dcfValues 11 + c@ \ month dcfValues 12 + c@ 2000 + \ year ; \end{verbatim} Das Wort \texttt{get.DCF} liest die Werte aus dem \texttt{dcfValues}-array und legt sie auf den Stapel. Zur Ausgabe benutze ich das schon vorhandene Wort \texttt{show.DT}. Die Ausgabe wird in dcf.tick gemacht, in dem Block \texttt{tick @ 0 = IF ... ENDIF}. Ein fehlerfreies Telegramm vorausgesetzt, erscheint jetzt auch ein ordentlich formatierter Zeitstempel in den Ausgaben: {\small \begin{verbatim} ... 9 91 57 0 0 13 -1 7 7 0 9 91 58 0 0 14 -1 0 0 0 0 100 59 -2 0 15 0 0 0 0 20070201-224700 9 91 0 0 0 1 0 0 0 0 9 91 1 0 0 1 0 0 0 0 ... \end{verbatim} } Danach kann man sich von den l"anglichen Debugausgaben verabschieden. Die habe ich mit einem weiteren Bitflag verziert. So kann man das bequem am laufenden Programm ein- und ausschalten. \begin{verbatim} Variable dcfFlags ... $0f Constant dcfDebug : dcf.D? dcfDebug dcfFlags btst ; ... dcf.D? IF to-stdout ... ENDIF ... \end{verbatim} \begin{verbatim} dcf error dcf error 20070201-225100 20070201-225300 20070201-225400 20070201-225500 20070201-225600 ... \end{verbatim} \section{Code 6: \texttt{timeup}-Uhr stellen} Jetzt sind alle Zutaten bereit, um die vom DCF-Signal gewonnene Zeit auf die \texttt{timeup}-Uhr zu "ubertragen. Dazu f"uhre ich ein weiteres Bit namens \texttt{dcfCommit} ein. Dieses wird von \texttt{dcf.init} und danach jede Stunde von \texttt{job.hour} gesetzt. Am Ende von \texttt{dcf.tick} wird die Zeit aus dem DCF-Signal auf die Z"ahler von \texttt{timeup} kopiert, wenn Sekunde 59 erkannt wurde und wenn \texttt{dcfCommit} gesetzt ist und wenn das Telegramm fehlerfrei war. Dabei muss man bedenken, dass die \texttt{timeup}-Uhr die Z"ahler f"ur Tag und Monat um Eins erniedrigt benutzt. Danach wird \texttt{dcfCommit} gel"oscht. \begin{verbatim} : dcf.commit ( -- ) get.DCF year ! 1- month ! 1- day ! hour ! min ! sec ! \ zero ; : dcf.init ... dcfCommit dcfFlags bset \ commit requested ; : dcf.tick ... tick @ 0 = IF ... -2 = IF \ sync detected to-stdout cr dcf.error? invert IF get.DCF show.DT dcfCommit dcfFlags btst IF dcfCommit dcfFlags bclr dcf.commit ENDIF ELSE ." dcf error " ENDIF dcf.error.clr 0 dcfPos ! 0 dcfCurr ! ELSE 1 dcfPos +! ENDIF ENDIF ; ... : job.hour dcfCommit dcfFlags bset ; \end{verbatim} Damit haben wir eine einigerma"sen funktionierende DCF-Uhr. Der Code belegt etwa 3520 Bytes (nach savesystem). \textit{Einigerma"sen} bedeutet, dass unter ung"unstigen Bedingungen das DCF-Signal nicht korrekt lesen kann und die Uhr lange Zeit braucht, bis sie sich gestellt hat. \section{Code 7: \texttt{dcfOffset}} Wenn man zu lang das Blinken der LEDs und die Anzeige der Uhrzeit anstarrt, dann findet man, dass die \texttt{timeup}-Uhr zu langsam (oder zu schnell, abh"angig vom Quarz) l"auft. Die Fabrikationstoleranz der Quarze zeigt sich hier deutlich. Daher habe ich ein Offset eingef"uhrt, welches die Verschiebung des DCF-Signals gegen die \texttt{timeup}-Uhr beinhaltet (maximal 100 ticks, also 1 Sekunde). Die Verschiebung betr"agt bei mir ca.\ 0.15 Sekunden in der Stunde. Beim "Ubertragen der DCF-Zeit wird diese Verschiebung ausgeglichen, siehe Entscheidung 4. Der Aufwand ist betr"achtlich. Zun"achst sind 2 Variablen zu definieren: \texttt{dcfOffset} enth"alt die Verschiebung der DCF-Sekunde gegen die \texttt{timeup}-Sekunde. Die erlaubten Werte sind \texttt{0} bis \texttt{ticks.sec-1}. Alle Abfragen von der Bauart \verb|tick @ [Zahl] = IF ...| werden ge"andert in \verb|tick @ [Zahl] dcfOffset @ + ticks.sec mod =| Wichtig ist dabei die Modulo--Operation, denn negative oder zu gro"se Werte erreicht \texttt{tick} nun mal nicht. In \texttt{dcf.tick} wird nach dem Hochz"ahlen der Wert von \texttt{dcfPulse} in \texttt{dcfPulse1} gespeichert, falls \texttt{dcfOffset1} ticks verstrichen sind (Aufruf von \texttt{dcfCountPP}). Damit kann ich feststellen, ob der DCF-Puls am Anfang der mit \texttt{dcfOffset} festgelegten Sekunde liegt. Falls nicht, wird \texttt{dcfOffset} angepasst (Aufruf von \texttt{dcf.check.offset}). Wenn man \texttt{dcfOffset} um \texttt{1} erh"oht, dann wird der mit \verb|tick @ dcfOffset @ ticks.sec mod = IF ... ENDIF| eingefasste Block beim n"achsten tick wieder ausgef"uhrt, obwohl nur ein einziger tick verstrichen ist. Um das zu vermeiden, bekommt \texttt{dcf.bit} einen weiteren R"uckgabewert, welcher diesen Fall anzeigt. In \texttt{dcf.tick} wird alles nach dem Aufruf von \texttt{dcf.bit} entsprechend abgesichert: \verb|dup -3 <> IF ... ELSE drop ENDIF|. \begin{verbatim} Variable dcfOffset Variable dcfPulse1 21 Constant dcfOffset1 : dcf.commit ( -- ) 0 tick ! 0 dcfOffset ! get.DCF ... ." commited " ; : dcf.reload ... 0 dcfPulse1 ! ; : dcf.bit ( pulse pause -- bit/error ) \ return values: \ -3 interval too short \ -2 sync detected \ -1 error \ 0 bit value 0 \ 1 bit value 1 \ limits hardcoded assuming 10 ms per count 2dup + 97 > IF drop dup 2 < IF ( ." sync59 " ) -2 ELSE dup 6 < IF dcf.error.set -1 ELSE dup 11 < IF ( ." bit:0 " ) 0 ELSE dup 16 < IF dcf.error.set -1 ELSE dup 21 < IF ( ." bit:1 " ) 1 ELSE dcf.error.set -1 ENDIF ENDIF ENDIF ENDIF ENDIF swap drop ELSE 2drop -3 ENDIF ; : dcf.usebit ( b -- ) CASE ... 1 OF ( bit value 1, use it ) dcf.error? invert IF \ position of current bit in dcfCurr ... dcf.par.tgl ENDIF ENDOF ... ENDCASE dcfPos @ dcf.FieldComplete? IF dcfCurr @ bcd>dec dcfValues dcfFieldsI @ 1- + c! 0 dcfCurr ! dcfFieldsI @ 1+ dup dcfFieldsN < invert IF drop 1 ENDIF dcfFieldsI ! ENDIF ; : dcfCountPP tick @ dcfOffset1 dcfOffset @ + ticks.sec mod = IF dcfPulse @ dcfPulse1 ! ENDIF ; : dcf.tick dcf.readPin \ show pin "active" on led2 dup IF led2_1 ELSE led2_0 ENDIF \ count up Pulse/Pause counters IF 1 dcfPulse +! ELSE 1 dcfPause +! ENDIF dcfCountPP \ reload counters if tick == 0 tick @ ( 0 ) dcfOffset @ ( + ) ticks.sec mod = IF dcfPulse @ dcfPause @ dcf.bit dup -3 <> IF \ unless interval too short dup dcf.dbg.out \ print counters dup dcf.usebit \ print dcfCurr bcd, dec dup 0 >= IF \ check dcfOffset if valid dcf.check.offset ENDIF dcf.reload \ clear counters -2 = IF \ sync detected to-stdout cr dcf.error? invert IF get.DCF show.DT dcfPos @ 59 = dcfCommit dcfFlags btst and IF dcfCommit dcfFlags bclr dcf.commit ENDIF ELSE ." dcf error " ENDIF dcf.error.clr 0 dcfPos ! 0 dcfCurr ! 1 dcfFieldsI ! ELSE \ 1 dcfPos +! \ assert 0 .. 59! dcfPos @ 1+ 60 mod dcfPos ! ENDIF ELSE drop ENDIF ENDIF ; \end{verbatim} Das ist nochmal ein ordentlicher Brocken. Man kann das bestimmt auch anders machen, wie alles. Aber so hat es mir am besten gefallen. Das Programm belegt 3722 Byte und nach \verb|rom ' run is bootmessage ram savesystem| 4006 Byte. Wenn die Uhr noch irgendetwas anderes k"onnen soll, dann ist wohl erst mal Code aufr"aumen angesagt. \section{Ausblick} Ein paar Dinge k"onnte man jetzt noch tun: \begin{itemize} \item die Debugausgaben aus dem Programm entfernen, um Platz zu schaffen \item man k"onnte den Zustand von \texttt{dcfError} auf dem LCDisplay anzeigen \item Geschickt w"are auch ein Zeichen, welches die Sommerzeit anzeigt \item Schaltsekunden werden derzeit nicht ber"ucksichtigt \item dcfOffset auf dem LCDisplay anzeigen\\ \verb+lcdpos 1 0 dcfOffset @ s>d 4 d.r+ in \texttt{job.sec} \end{itemize} M"oglicherweise k"onnte man dem Kontroller einen externen Uhrenquarz oder Oszillator spendieren, um das Problem mit der Drift zu l"osen. Diese Uhr ist \textit{nur eine Uhr} --- keine DCF-synchronisierte Hauszentrale. Dazu reicht leider der Platz nicht so recht, selbst wenn man die nicht mehr ben"otigten Teile des Programms entfernt. Auch die Geschichte mit dem Sonnenscheinsensor muss noch etwas warten. Erg"anzungen, Kommentare, Korrekturen sind ausdr"ucklich erw"unscht. Sie erreichen mich unter \url{ew.forth@nassur.net} \section{Referenzen} \begin{enumerate} \item \label{adv4:Adv1} E. W"alde, Adventures in Forth, Die 4. Dimension 3/2006, Jahrgang 22 \item \label{adv4:Adv2} E. W"alde, Adventures in Forth 2, Die 4. Dimension 4/2006, Jahrgang 22 \item \label{adv4:Adv3} E. W"alde, Adventures in Forth 3, Die 4. Dimension 1/2007, Jahrgang 23 %\item \label{adv4:Adv4} E. W"alde, Adventures in Forth 4, Die 4. Dimension 1/2007, Jahrgang 23 \item \label{r8c-ds} Renesas R8C/13 datasheet auf \url{www.renesas.com} %\item \label{Koenig} A.\ K"onig und M.\ K"onig, Das PICmicro Profi Buch, Franzis % Verlag 1999, ISBN 3-7723-4284-1 %\item \label{SPelc} Stephen Pelc, Programming Forth, \\ \url{http://www.mpeforth.com/arena/ProgramForth.pdf} %\item \label{Deliano} \url{http://www.embeddedforth.de/emb3.pdf} S.\ 9 \item \label{dcf} \url{http://de.wikipedia.org/wiki/DCF77} und Verweise darin \end{enumerate} \end{multicols} \section{Listings} \begin{quote} \begin{small} \begin{multicols}{2} \listinginput[1]{1}{2007-01/adv4_main.fs} % main loop, jobs \listinginput[1]{1}{2007-01/adv4_dcf.fs} % . dcf Uhr \listinginput[1]{1}{2007-01/adv4_timeup.fs} % . timeup Uhr \listinginput[1]{1}{2007-01/adv4_lcd.fs} % . Formatierung fürs LCD \listinginput[1]{1}{2007-01/adv4_redir.fs} % . output redirection \listinginput[1]{1}{2007-01/adv4_tasker.fs} % . tasker \end{multicols} \end{small} \end{quote} \end{document}