Reading the DCF77 Signal¶
- Date:
2017-10-01
Intro¶
Adding a DCF77 radio receiver to a clock is a fairly obvious feature, in Europe at least. So here we go: the details of how the information is transferred over the course of a minute is given e.g. on wikipedia https://en.wikipedia.org/wiki/DCF77#Time_code_details Please note that DCF77 is not the only time signal available. For a list consult https://en.wikipedia.org/wiki/Radio_clock
The DCF77 signal is modulated by amplitude shift keying. Full power
of the carrier signal indicates one state, reduced power (ca. 15%)
indicates another state. Power is reduced at the beginning of each
second. A low state lasting 0.1 s indicates a bit value of 0. A
low state of 0.2 s indicates a bit value of 1. In second 59 no
power reduction occurs. It serves as a sync pattern to indicate the
start of the next minute at the beginning of the next falling edge.
The signal is generally ok to receive in large parts of Europe, however, the signal might fade at any moment. Moreover the signal might be disturbed in any way at any time, e.g. by lightning or interference.
The DCF77 receiver chosen provides a TTL level signal, which is high in idle, and goes low during the low power state of the carrier signal.
Design Decisions¶
The signal of the receiver is sampled during each tick of the MasterClock, 128 times per second. In my experience it is not a good idea to trigger interrupts at each edge of the receiver signal and try to measure the length of a pulse using a timer/counter
(dcf.tick)is doing the sampling and some book keeping. It is called viadcf.tick(a deferred word) which in turn is called injob.tickof theMasterClockThe leading edge is detected if the last 16 readings lead to
$000Fpin readings of high are simply added up in counter
dcfPulse—dcfPauseremained unused for too long, so I removed itthe end of a second is at
dcfTick == 127. This is not neccessarily in sync with the ticks of theMasterClockAt the end of a second these counters are evaluated. If
dcfPulseis within9to12, a valid bit0has been seen. If it is within20to25, a valid bit1has been seen. If its value is below2, async59has been seen, i.e. the last second before the start of the next minute. Every other value is regarded to be an error!the extracted bit values are evaluated at the end of each second (they are not collected and evaluated together at the end of the minute)
a jump table with the current second as index is used to call functions, which evaluate the last bit value
all parity bits are evaluated. If any of them are not correct, the telegram is discarded
the concept of daylight saving time is implemented
the concept of leap seconds is ignored at this stage
if desired, the valid telegram is converted to Unix time, corrected to UTC and fed to the
MasterClockat the end of the minute
This is quite a bit of material, but there is no shorter way, I’m afraid.
Structure¶
sampling the radio signal (128/s) — produces the Pulse Counter
evaluating Pulse (1/s) — produces 1 Bit value
processing one bit (1/s) — produces a data telegram and some flags (f.dcf.err …)
processing one telegram (1/60s) — produces a timestamp valid for the start of the next second. This can be copied over to MasterClock, if desired.
I shall attempt to explain the code starting from the fast part, namely sampling the signal.
Code 0: Pin and LED¶
The DCF77-receiver signal is connected to pin D[7]. There also is
a LED definition, to show the signal or other state.
PORTB 2 portpin: led.0
: led_dcf [: led.0 ;] execute ;
\ dcf77 RX
PORTD 7 portpin: _dcf
Reading the pin takes place in (dcf.tick) (see below) in a block
similar to this
_dcf pin_low? if
\ account low signal level (active)
else
\ account high signal level
then
Code 1: dcf.tick — sampling the radio signal¶
dcf.tick is a deferred word due to code organisation. At runtime
it always points to (dcf.tick) which is defined later in the
source code.
(dcf.tick) is called through job.tick of the MasterClock, so
this routine runs ticks/sec (128) times per second. Its job is to
sample the signal over the course of a second so that at the end of
this second the received bit value is available.
So we declare a variable to count the (active) low samples. Then we define a function to clear the counter, and another one to increment it. These are for better readability only!
variable dcfPulse \ sample count, reduced tx power
: dcfPulse.clr ( -- ) 0 dcfPulse ! ;
: dcfPulse++ ( -- ) 1 dcfPulse +! ;
In order to synchronize the dcf second to the radio signal, we add
some detection of the begining of the second (leading edge of signal).
Every sample is shifted into a 16-bit variable (dcfEdge).
variable dcfTick \ separate tick for dcf
: dcfTick++/mod ( -- ) dcfTick @ 1+ ticks/sec mod dcfTick ! ;
variable dcfEdge \ edge detector
#4 constant dcfEdge:leading.bits
1 dcfEdge:leading.bits lshift 1- constant dcfEdge:leading.mask
: dcfEdge<<1 ( -- ) dcfEdge @ 1 lshift dcfEdge ! ;
: dcfEdge+1 ( -- ) 1 dcfEdge +! ;
: dcfEdge? ( -- ) dcfEdge @ dcfEdge:leading.mask = ;
: dcfTick.set ( -- ) dcfEdge:leading.bits dcfTick ! ;
If the resulting pattern is 0x000f, then I interpret this as a
leading edge, and dcfTick is set to 4. These magic numbers
have been moved into constants. I want to see the 4 low bits set, the
corresponding mask is constructed.
Setting dcfTick to 4 is a brutal thing, and it will be wrong
occasionally, e.g. in an electrically noisy environment. It might then
be needed to add some “window” in the time bookkeeping, where this
edge can be expected. It would be valid only during this window.
So we have all puzzle pieces now to write down the needed word.
: (dcf.tick)
dcfClock 1 tick +! \ keep dcfClock ticking
dcfTick++/mod \ .
dcfEdge<<1 \ . edge detection
_dcf pin_low? if
\ active!
dcfPulse++
dcfEdge+1
-1 dcf.led
else
0 dcf.led
then
dcfEdge? if \ keep dcfClock in sync with radio signal
dcfTick.set \ . this might be bad occasionally
then
\ dcfTick: 0 .. 127
\ 127 indicates the last tick in this second
dcfTick @ ticks/sec 1- = if
dcf.sec \ handle "one second over"
then
;
At the end of the current second we need to decide, what bitvalue was
received, and what to do with it. This work is hidden behind the call
to dcf.sec.
Code 2: dcf.bit — producing one bit value¶
This function is called at the end of the current second to determine
the transmitted bit value. dcf.bit will look at dcfPulse (on
top of the stack) and return an appropriate result. The comparison
values in the nested if-else blocks depend on ticks/sec.
They have been determined by staring at the debug output more than
anything else.
-2 constant dcf.bit:sync59
-1 constant dcf.bit:error
0 constant dcf.bit:0
1 constant dcf.bit:1
: dcf.bit ( pulse -- bit|error )
dup #2 < if dcf.bit:sync59 else
dup #9 < if f.dcf.err fset dcf.bit:error else
dup #13 < if dcf.bit:0 else
dup #20 < if f.dcf.err fset dcf.bit:error else
dup #26 < if dcf.bit:1 else
f.dcf.err fset dcf.bit:error
then then then then then
swap drop
;
I think this function is pretty strict. Of all values 0 .. 127,
only a small portion is valid: 0 .. 1 is regarded as the sync in
second 59, 9 .. 12 is regarded as 0, 20 .. 25 is regarded
as 1, anything else triggers an error and thus an invalid
telegram. Nonetheless I have had only occasional difficulties to
receive a valid telegram.
Code 3: dcf.sec — processing one bit value¶
dcf.sec is a deferred word, again due to code organisation. At
runtime it always points to (dcf.sec) which is defined later in
the code. This function is called at the end of the current second.
The variable dcfPos holds the position in the telegram, i.e. the
current second in fact.
So in this code block we look at the value of dcfPulse, consult
dcf.bit to evaluate the received bit, and then call the
appropriate entry in the call table to process the bit just received.
dcf.dbg.sec hides the details of debugging output, and we ignore
that for now.
#include case.frt
variable dcfPos
: dcfPos++ ( -- ) dcfPos @ 1+ #60 mod dcfPos ! ;
: (dcf.sec)
dcfClock timeup
0 tick !
\ evaluate pulse/pause
dcfPulse @ dcf.pulse>bit dup dcfBit !
( bit ) case
dcf.bit:0 of 0 dcfPos @ pos.cmd dcf.dbg.sec endof
dcf.bit:1 of 1 dcfPos @ pos.cmd dcf.dbg.sec endof
dcf.bit:error of f.dcf.err fset dcf.dbg.sec endof
dcf.bit:sync59 of
dcf.min
dcf.tmp.counter.reset
dcfErrCnt.set
endof
endcase
dcfPulse.clr \ reset pulse/pause
dcfPos++
;
Code 4: calling into the jump table¶
In (dcf.sec) the real work is hidden in the calls into the jump table:
( bit value 0 or 1 ) dcfPos @ pos.cmd
pos.cmd inspects the top of stack item and uses it as the index
into the jump table.
: pos.cmd ( index -- )
dup 0 #60 within if
( index ) pos_cmd_map + @i execute
else
drop
then
;
The function thus called will consume one more item of the stack, namely the bit value.
So the processing is hidden in the functions listed in table
pos_cmd_map. All of these functions have the same structure
:noname ( 0|1 -- )
if ( bit:1 ) ...
else ( bit:0 ) ...
then ( always ) ...
;
So for every position in the telegram something needs to be done. As an example we will look at the handling of Bits 21 to 28, i.e. the minute value and its parity bit.
\ #21 .. #28: Minute
\ minute ones
:noname if ( bit:1 ) #1 dcfTMin +! dcfPar++ then ; #21 >rt
:noname if ( bit:1 ) #2 dcfTMin +! dcfPar++ then ; #22 >rt
:noname if ( bit:1 ) #4 dcfTMin +! dcfPar++ then ; #23 >rt
:noname if ( bit:1 ) #8 dcfTMin +! dcfPar++ then ; #24 >rt
\ minute tens
:noname if ( bit:1 ) #10 dcfTMin +! dcfPar++ then ; #25 >rt
:noname if ( bit:1 ) #20 dcfTMin +! dcfPar++ then ; #26 >rt
:noname if ( bit:1 ) #40 dcfTMin +! dcfPar++ then ; #27 >rt
\ minute parity bit
:noname
if ( bit:1 ) dcfPar++ then
( always )
dcfPar? if f.dcf.parerr.min fclr dcfErrCnt-- then
0 dcfPar !
; #28 >rt
In Bits 21 to 27, if their value is 1, we add the corresponding
decimal value to dcfTMin. Please note that this takes care of the
conversion from BCD to decimal as well. If the received bit value is
0, there is nothing to do, so there is no else clause. If the
received bit value is 1 we increment the parity variable dcfPar as
well.
\ count 1 bits, really; result must be even
: dcfPar++ ( -- ) 1 dcfPar +! ;
: dcfPar? ( -- t/f ) dcfPar @ $01 and 0= ;
At the end, the least significant bit of dcfPar is consulted. If
it is 0, then all is well and we decrement dcfErrCnt. This
variable counts the checks to be done while receiving a telegram and
must be 0 after the last bit of the telegram was processed.
Code 5: dcf.min — processing a telegram¶
(dcf.min) again is called via deferred word dcf.min due to
code organisation. This function is called at the end of the current
minute. We check that we are indeed at the end of second #59. If
so, we decrement dcfErrCnt one more time. Then we check that its
value has indeed dropped to zero — this indicates that all checks to
the data telegram were successful. The result is saved in
f.dcf.err for use elsewhere.
If all is well, and if f.dcf.commit is set, then we proceed to
convert the telegram and update the clocks dcfClock and
MasterClock, respectively. There is a potential problem here, if
updating these clocks takes a noticeable time, they end up a little
late.
The remainder of (dcf.min) is debug output and bookkeeping. There
is another potential problem with the debug output as well, it adds
possibly unexpected delays and the next call to (dcf.tick) might
be late.
: (dcf.min) ( -- )
dcfPos @ #59 = if dcfErrCnt-- then
dcfErrCnt @ 0= if f.dcf.err fclr then
\ this block runs at the *end* of second "59" i.e.
\ at the start of second "00", thus copy counters
f.dcf.err fclr? if
f.dcf.commit fset? if
dcfTemp>dcfClock
dcfClock>MasterClock
space [char] C emit \ FIXME: dbg conditional?
f.dcf.insync fset \ dcfClock is in sync!
f.dcf.commit fclr
then
then
dcf.dbg.sec
dcf.dbg.min
f.dcf.commit fclr? if
\ clear debug flags --- this is only to demonstrate resync/commit
f.dcf.dbg fclr
then
;
Updating the clocks from the DCF77 telegram is done in two steps. The
first function call (dcfTemp>dcfClock) copies the values collected
in the dcfT* counters into the clock counters of dcfClock.
This clock runs independently from the master clock, tick and
sec are just reset to zero. The clock is kept ticking from
(dcf.sec) and it runs in the timezone indicated by DCF77.
: dcfTemp>dcfClock
dcfClock
0 tick !
0 sec !
dcfTMin @ min !
dcfTHour @ hour !
dcfTDay @ 1- day !
\ dcfTWday @ ?
dcfTMonth @ 1- month !
dcfTYear @ Century + year !
;
Updating MasterClock during the call to dcfClock>MasterClock
is more work. As we have seen before, this is another deferred
function pointing to (dcfClock>MasterClock). We now have to take
care about the different time zones. dcfClock and MasterClock
differ by one or two hours.
The new values are read from dcfClock, converted to epoch seconds
(ut>s.short), corrected by one or two hours, converted back to
clock counter values (s>dt.short) and written into the counters of
MasterClock. Along the way Esec is updated, too.
Unfortunately, we once again have the potential problem, that these
steps might introduce more delay than acceptable.
\ copy time from dcfClock to MasterClock
: (dcfClock>MasterClock)
dcfClock
sec @ min @ hour @
day @ 1+ month @ 1+ year @
MasterClock
\ -- sec min hour day month year
\ convert "local time" to epoch seconds
ut>s.short \ -- T/sec
\ adjust local time zone
f.dcf.CEDT fset? if
#3600. d-
then
#3600. d-
2dup Esec 2! \ copy to Epoch seconds!
\ convert back to "UTC date time" format
s>dt.short \ -- sec min hour day month year
MasterClock
year ! 1- month ! 1- day !
hour ! min ! sec !
\ fixme: might cause wreaking havoc?
dcfClock tick @
dup MasterClock tick !
ct.ticks.follow !
;
I have to admit that writing documentation helps to see less than ideal structure or implementation in my projects. So while I was aware about the unwanted delays as pointed out above, this clock is still useful for my daily life. I just would not expect more accuracy than a couple of seconds, which may or may not be good enough for the problem at hand.
Code 6: Some Debugging¶
During development of the dcf part I needed a lot of debugging output. Really a lot. But debugging output does have undesired consequences at times. It can delay the processing of a tick — visible clearly on the traces of the logic analyzer.
dcf.dbg.second.over emits one character per second to indicate the
state of the received bit. . and + indicate values of 0 and 1,
/ indicates an error, and S indicates the detection of the
sync event in second 59.
: dcf.dbg.second.over ( -- )
f.dcf.dbg.rx fset? if
dcfBit @ case
dcf.bit:0 of [char] . emit endof
dcf.bit:1 of [char] + emit endof
dcf.bit:error of [char] / emit endof
dcf.bit:sync59 of [char] S emit endof
endcase
then
;
dcf.dbg.minute.over used to be quite large, but as indicated
above, it started to create problems — which I still have not
understood in detail.
\ short version
: dcf.dbg.minute.over ( -- )
space
f.dcf.err fclr? if
[char] O emit
else
[char] F emit
then
cr
;
Code 7: resyncing hourly¶
I decided to request a new telegram to be committed to the running clock once per hour. It will handle the appropriate flags and enable debug output.
: dcf.resync ( -- )
f.dcf.insync fclr \ resync dcf time
f.dcf.commit fset \ sync dcf -> MasterClock wanted
f.dcf.dbg fset \ dbg.min on
f.dcf.dbg.rx fset \ dbg.sec on
;
: job.min
...
MasterClock min @ #58 = if dcf.resync then ;
;
The result can be seen on the console:
~6F> .uptime.dhms
859562s 9d 22:46:02 ok
~6F> .d
859567 s 2017-12-11_19:55:56 UTC 24 24 1513022156 \
2017-12-11_20:55:56 UTC 1513022156 1513022156 0
ok
~6F> .+....++...+.++...+.++..++.+......+++...+.+...+..++++.+...+ CS O X
.d
860802 s 2017-12-11_20:16:32 UTC 92 93 1513023392 \
2017-12-11_21:16:32 UTC 1513023392 1513023392 0
ok
~6F>
Resyncing should be sufficient once per day, perhaps some time during the night. Debug output is not needed any more, once the clock is in production state. Note that the above output was edited manuallay ant that the seconde “UTC” label above is wrong and should rather be “MEZ”.
Putting it all together¶
Switching the whole wonderstuff on (and off) is all there is left to do:
: +dcf
_dcf pin_input
['] (dcf.tick) to dcf.tick \ register DCF77 functions
['] (dcf.sec) to dcf.sec
0 dcfPos ! \ assume we are at position 0
f.dcf.err fset \ error unless proven otherwise
f.dcf.commit fset \ request to set dcfClock
f.dcf.insync fclr \ not in sync yet!
dcfErrCnt.set \ more errors unless proven otherwise
;
: -dcf
['] noop to dcf.tick \ deregister DCF77 functions
['] noop to dcf.sec
;
This piece of code ist long. And I’m not even convinced it is nice and good and clean code. It is one way to solve the problem, and I’m sure, a few bugs or dragons are luring …
Leftovers¶
While this clock works, there is an uneasy feeling about it. There are still things that could or should be improved. This is the list of things I am aware of, there might be more, of course.
dcf.bit— construct comparison values fromticks/secitself, sinceticks/secis defined at compile time, the comparison values might as well be calculated.
dcf.tick— edge detection might be wrong under certain conditions; perhaps defining a window in time, where the edge is acceptable, could help. However, it is unclear at this point, whether this is a problem that occurs in practice.
dcf.min— copying time counters from dcfClock to MasterClock takes some time, because timezones must be accounted for. So this intruduces a noticeable delay. The must be more clever ways to solve this. One thing could be to move some of the work to be done from the end of the current second to the middle of the second. There is plenty of idle time in this whole game.
dcf.min— debug output over serial possibly introduces more delays thus delayingdcf.tickin possibly unacceptable ways. One could write the output to a larger ringbuffer and create a third task to output these charcters at more convenient times. However, adding debugging output makes the problem disappear or worse is a very common phenomenon in programming. So I’m sure there are better solutions available.
The Code¶
1\ 2017-07-23 dcf_01.fs
2\
3\ Written in 2017 by Erich Wälde <erich.waelde@forth-ev.de>
4\
5\ To the extent possible under law, the author(s) have dedicated
6\ all copyright and related and neighboring rights to this software
7\ to the public domain worldwide. This software is distributed
8\ without any warranty.
9\
10\ You should have received a copy of the CC0 Public Domain
11\ Dedication along with this software. If not, see
12\ <http://creativecommons.org/publicdomain/zero/1.0/>.
13\
14\ functions to sample dcf signal
15\ extract bit values,
16\ extract values from bits,
17\ verify parity
18\ provide debug output
19\
20\ needs
21\ #2000 constant Century
22\ PORTD 7 portpin: _dcf
23\ PORTB 2 portpin: led.0
24\ : led_dcf [: led.0 ;] execute ;
25\
26\ words
27\ +dcf -dcf
28\ f.dcf.insync
29\ f.dcf.commit
30\ +DD +DRX
31
32variable dcfFlags
33dcfFlags 0 flag: f.dcf.err \ any error, telegram invalid
34\ Flags 1 flag: f.dcf.??? \
35dcfFlags #2 flag: f.dcf.commit \ commit dcf -> masterclock wanted
36dcfFlags #3 flag: f.dcf.dbg \ debug output active
37dcfFlags #4 flag: f.dcf.nosignal \
38dcfFlags #5 flag: f.dcf.insync \ tell the world, dcf time is ok
39dcfFlags #6 flag: f.dcf.CET \ timezone bit
40dcfFlags #7 flag: f.dcf.CEDT \ daylight saving time bit
41dcfFlags #8 flag: f.dcf.parerr.min \ parity error minute
42dcfFlags #9 flag: f.dcf.parerr.hour \ parity error hour
43dcfFlags #10 flag: f.dcf.parerr.date \ parity error date
44dcfFlags #11 flag: f.dcf.parerr \ any parity error
45dcfFlags #12 flag: f.dcf.dbg.rx \ debug output per received bit active
46\ Flags #13 flag: f.dcf.???
47\ Flags #14 flag: f.dcf.???
48\ Flags #15 flag: f.dcf.???
49
50variable dcfPulse \ sample count, reduced tx power
51: dcfPulse.clr ( -- ) 0 dcfPulse ! ;
52: dcfPulse++ ( -- ) 1 dcfPulse +! ;
53variable dcfBit \ value of last received bit
54variable dcfTick \ separate tick for dcf
55: dcfTick++/mod ( -- ) dcfTick @ 1+ ticks/sec mod dcfTick ! ;
56variable dcfEdge \ edge detector
57#4 constant dcfEdge:leading.bits
581 dcfEdge:leading.bits lshift 1- constant dcfEdge:leading.mask
59: dcfEdge<<1 ( -- ) dcfEdge @ 1 lshift dcfEdge ! ;
60: dcfEdge+1 ( -- ) 1 dcfEdge +! ;
61: dcfEdge? ( -- ) dcfEdge @ dcfEdge:leading.mask = ;
62
63: dcfTick.set ( -- ) dcfEdge:leading.bits dcfTick ! ;
64
65variable dcfPos \ position in telegram, == dcf second
66: dcfPos++ ( -- ) dcfPos @ 1+ #60 mod dcfPos ! ;
67variable dcfPar \ collect parity
68: dcfPar++ ( -- ) 1 dcfPar +! ;
69: dcfPar? ( -- t/f ) dcfPar @ $01 and 0= ; \ even parity
70variable dcfErrCnt \ counter (down) of passed checks
71: dcfErrCnt-- ( -- ) -1 dcfErrCnt +! ;
72: dcfErrCnt.set ( -- ) #7 dcfErrCnt ! ;
73
74\ these collect the bits in the DCF bit stream
75variable dcfTMin
76variable dcfTHour
77variable dcfTDay
78variable dcfTWday
79variable dcfTMonth
80variable dcfTYear
81
82: +DD f.dcf.dbg fset ;
83: -DD f.dcf.dbg fclr ;
84: .DD
85 ." dcf.error: " f.dcf.err fset? if 1 else 0 then . cr
86 ." dcf.parity: " f.dcf.parerr fset? if 1 else 0 then . cr
87 ." dcf.commit: " f.dcf.commit fset? if 1 else 0 then . cr
88 ." dcf.debug: " f.dcf.dbg fset? if 1 else 0 then . cr
89 ." dcf.dbg.rx: " f.dcf.dbg.rx fset? if 1 else 0 then . cr
90;
91: +DRX f.dcf.dbg.rx fset ;
92: -DRX f.dcf.dbg.rx fclr ;
93
94
95\ create temporary RAM table
96variable dcf.tmp.table #60 cells allot \ only temporary really!
97\ fill RAM table with ' drop \ noop -> drop: remove argument!
98' drop dcf.tmp.table #60 ramtable.init
99\ dcf.tmp.table #60 ramtable.dump \ DEBUG
100: >rt ( xt idx -- ) dcf.tmp.table >ramtable ;
101
102\ code snippet XTs to ram table
103\ structure of functions
104\ :noname ( 0|1 -- )
105\ if ( bit:1 ) ...
106\ else ( bit:0 ) ...
107\ then ( always ) ...
108\ ; #idx >rt
109\ :noname
110\ drop ( always ) ...
111\ ; #idx >rt
112
113\ #0 always 0!
114:noname
115 0= if ( bit:0 ) dcfErrCnt--
116 then
117; #0 >rt
118\ #1 .. #14 civil warning bits, MeteoTime data bits
119\ #15 call bit
120\ #16 1: day light savings time switch at the end of this hour
121\ #17 0 CET, 1 CEST
122:noname
123 if ( bit:1 ) f.dcf.CEDT fset
124 else ( bit:0 ) f.dcf.CEDT fclr
125 then
126; #17 >rt
127\ #18 1 CET, 0 CEST
128:noname
129 if ( bit:1 ) f.dcf.CET fset f.dcf.CEDT fclr? if dcfErrCnt-- then
130 else ( bit:0 ) f.dcf.CET fclr f.dcf.CEDT fset? if dcfErrCnt-- then
131 then
132; #18 >rt
133\ #19 1: leap second announcement
134\ #20 always 1: start of encoded time
135:noname
136 if ( bit:1 ) dcfErrCnt--
137 then
138 f.dcf.parerr fset
139 f.dcf.parerr.min fset
140 f.dcf.parerr.hour fset
141 f.dcf.parerr.date fset
142 0 dcfPar !
143; #20 >rt
144\ #21 .. #28: minute
145\ minute ones
146:noname if ( bit:1 ) #1 dcfTMin +! dcfPar++ then ; #21 >rt
147:noname if ( bit:1 ) #2 dcfTMin +! dcfPar++ then ; #22 >rt
148:noname if ( bit:1 ) #4 dcfTMin +! dcfPar++ then ; #23 >rt
149:noname if ( bit:1 ) #8 dcfTMin +! dcfPar++ then ; #24 >rt
150\ minute tens
151:noname if ( bit:1 ) #10 dcfTMin +! dcfPar++ then ; #25 >rt
152:noname if ( bit:1 ) #20 dcfTMin +! dcfPar++ then ; #26 >rt
153:noname if ( bit:1 ) #40 dcfTMin +! dcfPar++ then ; #27 >rt
154\ minute parity bit
155:noname
156 if ( bit:1 ) dcfPar++ then
157 ( always )
158 dcfPar? if f.dcf.parerr.min fclr dcfErrCnt-- then
159 0 dcfPar !
160; #28 >rt
161
162\ #29 .. #35: hour
163\ hour ones
164:noname if ( bit:1 ) #1 dcfTHour +! dcfPar++ then ; #29 >rt
165:noname if ( bit:1 ) #2 dcfTHour +! dcfPar++ then ; #30 >rt
166:noname if ( bit:1 ) #4 dcfTHour +! dcfPar++ then ; #31 >rt
167:noname if ( bit:1 ) #8 dcfTHour +! dcfPar++ then ; #32 >rt
168\ hour tens
169:noname if ( bit:1 ) #10 dcfTHour +! dcfPar++ then ; #33 >rt
170:noname if ( bit:1 ) #20 dcfTHour +! dcfPar++ then ; #34 >rt
171\ hour parity bit
172:noname
173 if ( bit:1 ) dcfPar++ then
174 ( always )
175 dcfPar? if f.dcf.parerr.hour fclr dcfErrCnt-- then
176 0 dcfPar !
177; #35 >rt
178\ #36 .. #41: day
179\ day ones
180:noname if ( bit:1 ) #1 dcfTDay +! dcfPar++ then ; #36 >rt
181:noname if ( bit:1 ) #2 dcfTDay +! dcfPar++ then ; #37 >rt
182:noname if ( bit:1 ) #4 dcfTDay +! dcfPar++ then ; #38 >rt
183:noname if ( bit:1 ) #8 dcfTDay +! dcfPar++ then ; #39 >rt
184\ day tens
185:noname if ( bit:1 ) #10 dcfTDay +! dcfPar++ then ; #40 >rt
186:noname if ( bit:1 ) #20 dcfTDay +! dcfPar++ then ; #41 >rt
187\ #42 .. #44: Wochentag
188\ day of week
189:noname if ( bit:1 ) #1 dcfTWday +! dcfPar++ then ; #42 >rt
190:noname if ( bit:1 ) #2 dcfTWday +! dcfPar++ then ; #43 >rt
191:noname if ( bit:1 ) #4 dcfTWday +! dcfPar++ then ; #44 >rt
192\ #45 .. #49: Monat
193\ month ones
194:noname if ( bit:1 ) #1 dcfTMonth +! dcfPar++ then ; #45 >rt
195:noname if ( bit:1 ) #2 dcfTMonth +! dcfPar++ then ; #46 >rt
196:noname if ( bit:1 ) #4 dcfTMonth +! dcfPar++ then ; #47 >rt
197:noname if ( bit:1 ) #8 dcfTMonth +! dcfPar++ then ; #48 >rt
198\ month tens
199:noname if ( bit:1 ) #10 dcfTMonth +! dcfPar++ then ; #49 >rt
200\ #50 .. #57: Jahr % 100
201\ year ones
202:noname if ( bit:1 ) #1 dcfTYear +! dcfPar++ then ; #50 >rt
203:noname if ( bit:1 ) #2 dcfTYear +! dcfPar++ then ; #51 >rt
204:noname if ( bit:1 ) #4 dcfTYear +! dcfPar++ then ; #52 >rt
205:noname if ( bit:1 ) #8 dcfTYear +! dcfPar++ then ; #53 >rt
206\ year tens
207:noname if ( bit:1 ) #10 dcfTYear +! dcfPar++ then ; #54 >rt
208:noname if ( bit:1 ) #20 dcfTYear +! dcfPar++ then ; #55 >rt
209:noname if ( bit:1 ) #40 dcfTYear +! dcfPar++ then ; #56 >rt
210:noname if ( bit:1 ) #80 dcfTYear +! dcfPar++ then ; #57 >rt
211\ date parity bit
212:noname
213 if ( bit:1 ) dcfPar++ then
214 ( always )
215 dcfPar? if f.dcf.parerr.date fclr dcfErrCnt-- then
216 f.dcf.parerr.min fclr?
217 f.dcf.parerr.hour fclr? and
218 f.dcf.parerr.date fclr? and if f.dcf.parerr fclr then
219; #58 >rt
220
221
222\ dcf.tmp.table #60 ramtable.dump \ DEBUG
223\ copy RAM table to FLASH
224dcf.tmp.table #60 >flashtable pos_cmd_map
225\ release RAM
226dcf.tmp.table to here \ fixme: possibly bad???
227
228: pos.cmd ( index -- )
229 dup 0 #60 within if
230 ( position ) pos_cmd_map + @i execute
231 else
232 drop
233 then
234;
235
236-2 constant dcf.bit:sync59
237-1 constant dcf.bit:error
238 0 constant dcf.bit:0
239 1 constant dcf.bit:1
240: dcf.pulse>bit ( pulse -- bit|error )
241 dup #2 < if dcf.bit:sync59 else
242 dup #9 < if f.dcf.err fset dcf.bit:error else
243 dup #13 < if dcf.bit:0 else
244 dup #20 < if f.dcf.err fset dcf.bit:error else
245 dup #26 < if dcf.bit:1 else
246 f.dcf.err fset dcf.bit:error
247 then then then then then
248 swap drop
249;
250
251: dcf.led ( t/f -- )
252 if led_dcf on else led_dcf off then
253;
254: dcf.dbg.minute.over ( -- )
255 space
256 f.dcf.err fclr? if
257 [char] O emit
258 else
259 [char] F emit
260 then
261 cr
262;
263: dcf.dbg.second.over ( -- )
264 f.dcf.dbg.rx fset? if
265 dcfBit @ case
266 dcf.bit:0 of [char] . emit endof
267 dcf.bit:1 of [char] + emit endof
268 dcf.bit:error of [char] / emit endof
269 dcf.bit:sync59 of [char] S emit endof
270 endcase
271 then
272;
273
274
275
276\ --- dcf.tick: 128/s, sample signal ----
277Rdefer dcf.tick
278Rdefer dcf.sec
279Rdefer dcf.min
280
281: (dcf.tick)
282 dcfClock 1 tick +! \ keep dcfClock ticking
283 dcfTick++/mod \ .
284 dcfEdge<<1 \ . edge detection
285
286 _dcf pin_low? if
287 \ active!
288 dcfPulse++
289 dcfEdge+1
290 -1 dcf.led
291 else
292 0 dcf.led
293 then
294
295 dcfEdge? if \ keep dcfClock in sync with radio signal
296 dcfTick.set \ . this might be bad occasionally
297 then
298
299 \ dcfTick: 0 .. 127
300 \ 127 indicates the last tick in this second
301 dcfTick @ ticks/sec 1- = if
302 dcf.sec \ handle "one second over"
303 then
304
305 \ it is unclear whether deriving the dcf-second is
306 \ ok from dcfTick, or whether ist must be
307 \ dcfClock tick
308 \ (dcf.tick) does both increments
309 \ dcfTick is clear when calling dcf.sec
310 \ dcf.sec clears dcfClock tick
311;
312
313\ --- dcf.sec: 1/s, book keeping ---------
314: dcf.tmp.counter.reset
315 #59 dcfPos !
316 0 dcfTMin !
317 0 dcfTHour !
318 0 dcfTDay !
319 0 dcfTWday !
320 0 dcfTMonth !
321 0 dcfTYear !
322 0 dcfPar !
323;
324: dcfTemp>dcfClock
325 dcfClock
326 0 tick !
327 0 sec !
328 dcfTMin @ min !
329 dcfTHour @ hour !
330 dcfTDay @ 1- day !
331\ dcfTWday @ ?
332 dcfTMonth @ 1- month !
333 dcfTYear @ Century + year !
334;
335
336: dcf.dbg.sec ( -- ) f.dcf.dbg fset? if dcf.dbg.second.over then ; \ debug
337: dcf.dbg.min ( -- ) f.dcf.dbg fset? if dcf.dbg.minute.over then ; \ debug
338
339: (dcf.sec)
340 dcfClock timeup
341 0 tick !
342
343 \ evaluate pulse
344 dcfPulse @ dcf.pulse>bit dup dcfBit !
345 ( bit ) case
346
347 dcf.bit:0 of 0 dcfPos @ pos.cmd dcf.dbg.sec endof
348
349 dcf.bit:1 of 1 dcfPos @ pos.cmd dcf.dbg.sec endof
350
351 dcf.bit:error of f.dcf.err fset dcf.dbg.sec endof
352
353 dcf.bit:sync59 of
354 dcf.min
355 dcf.tmp.counter.reset
356 dcfErrCnt.set
357 endof
358
359 endcase
360 dcfPulse.clr \ reset pulse
361 dcfPos++
362;
363
364: (dcf.min) ( -- )
365 dcfPos @ #59 = if dcfErrCnt-- then
366 dcfErrCnt @ 0= if f.dcf.err fclr then
367 \ this block runs at the *end* of second "59" i.e.
368 \ at the start of second "00", thus copy counters
369 f.dcf.err fclr? if
370 f.dcf.commit fset? if
371 dcfTemp>dcfClock
372 dcfClock>MasterClock
373 space [char] C emit
374 f.dcf.insync fset \ dcfClock is in sync!
375 f.dcf.commit fclr
376 then
377 then
378 dcf.dbg.sec
379 dcf.dbg.min
380 f.dcf.commit fclr? if
381 \ clear debug flags --- this is only to demonstrate resync/commit
382 f.dcf.dbg fclr
383 then
384;
385
386\ --- init: enable DCF clock -------------
387: +dcf
388 _dcf pin_input
389 ['] (dcf.tick) to dcf.tick
390 ['] (dcf.sec) to dcf.sec
391 ['] (dcf.min) to dcf.min
392
393 0 dcfPos ! \ assume we are at 0
394 f.dcf.err fset \ error unless proven otherwise
395 f.dcf.commit fset \ request to set dcfClock
396 f.dcf.insync fclr \ not in sync yet!
397 dcfErrCnt.set
398;
399: -dcf
400 ['] noop to dcf.tick
401 ['] noop to dcf.sec
402;