\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}