Reading the DCF77 Signal¶
Date: | 2017-10-01 |
---|
- Intro
- Design Decisions
- Structure
- Code 0: Pin and LED
- Code 1: dcf.tick — sampling the radio signal
- Code 2: dcf.bit — producing one bit value
- Code 3: dcf.sec — processing one bit value
- Code 4: calling into the jump table
- Code 5: dcf.min — processing a telegram
- Code 6: Some Debugging
- Code 7: resyncing hourly
- Putting it all together
- Leftovers
- The Code
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.tick
of theMasterClock
- The leading edge is detected if the last 16 readings lead to
$000F
- pin readings of high are simply added up in counter
dcfPulse
—dcfPause
remained unused for too long, so I removed it- the end of a second is at
dcfTick == 127
. This is not neccessarily in sync with the ticks of theMasterClock
- At the end of a second these counters are evaluated. If
dcfPulse
is within9
to12
, a valid bit0
has been seen. If it is within20
to25
, a valid bit1
has been seen. If its value is below2
, async59
has 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
MasterClock
at 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/sec
itself, sinceticks/sec
is 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.tick
in 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 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 | \ 2017-07-23 dcf_01.fs
\
\ Written in 2017 by Erich Wälde <erich.waelde@forth-ev.de>
\
\ To the extent possible under law, the author(s) have dedicated
\ all copyright and related and neighboring rights to this software
\ to the public domain worldwide. This software is distributed
\ without any warranty.
\
\ You should have received a copy of the CC0 Public Domain
\ Dedication along with this software. If not, see
\ <http://creativecommons.org/publicdomain/zero/1.0/>.
\
\ functions to sample dcf signal
\ extract bit values,
\ extract values from bits,
\ verify parity
\ provide debug output
\
\ needs
\ #2000 constant Century
\ PORTD 7 portpin: _dcf
\ PORTB 2 portpin: led.0
\ : led_dcf [: led.0 ;] execute ;
\
\ words
\ +dcf -dcf
\ f.dcf.insync
\ f.dcf.commit
\ +DD +DRX
variable dcfFlags
dcfFlags 0 flag: f.dcf.err \ any error, telegram invalid
\ Flags 1 flag: f.dcf.??? \
dcfFlags #2 flag: f.dcf.commit \ commit dcf -> masterclock wanted
dcfFlags #3 flag: f.dcf.dbg \ debug output active
dcfFlags #4 flag: f.dcf.nosignal \
dcfFlags #5 flag: f.dcf.insync \ tell the world, dcf time is ok
dcfFlags #6 flag: f.dcf.CET \ timezone bit
dcfFlags #7 flag: f.dcf.CEDT \ daylight saving time bit
dcfFlags #8 flag: f.dcf.parerr.min \ parity error minute
dcfFlags #9 flag: f.dcf.parerr.hour \ parity error hour
dcfFlags #10 flag: f.dcf.parerr.date \ parity error date
dcfFlags #11 flag: f.dcf.parerr \ any parity error
dcfFlags #12 flag: f.dcf.dbg.rx \ debug output per received bit active
\ Flags #13 flag: f.dcf.???
\ Flags #14 flag: f.dcf.???
\ Flags #15 flag: f.dcf.???
variable dcfPulse \ sample count, reduced tx power
: dcfPulse.clr ( -- ) 0 dcfPulse ! ;
: dcfPulse++ ( -- ) 1 dcfPulse +! ;
variable dcfBit \ value of last received bit
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 ! ;
variable dcfPos \ position in telegram, == dcf second
: dcfPos++ ( -- ) dcfPos @ 1+ #60 mod dcfPos ! ;
variable dcfPar \ collect parity
: dcfPar++ ( -- ) 1 dcfPar +! ;
: dcfPar? ( -- t/f ) dcfPar @ $01 and 0= ; \ even parity
variable dcfErrCnt \ counter (down) of passed checks
: dcfErrCnt-- ( -- ) -1 dcfErrCnt +! ;
: dcfErrCnt.set ( -- ) #7 dcfErrCnt ! ;
\ these collect the bits in the DCF bit stream
variable dcfTMin
variable dcfTHour
variable dcfTDay
variable dcfTWday
variable dcfTMonth
variable dcfTYear
: +DD f.dcf.dbg fset ;
: -DD f.dcf.dbg fclr ;
: .DD
." dcf.error: " f.dcf.err fset? if 1 else 0 then . cr
." dcf.parity: " f.dcf.parerr fset? if 1 else 0 then . cr
." dcf.commit: " f.dcf.commit fset? if 1 else 0 then . cr
." dcf.debug: " f.dcf.dbg fset? if 1 else 0 then . cr
." dcf.dbg.rx: " f.dcf.dbg.rx fset? if 1 else 0 then . cr
;
: +DRX f.dcf.dbg.rx fset ;
: -DRX f.dcf.dbg.rx fclr ;
\ create temporary RAM table
variable dcf.tmp.table #60 cells allot \ only temporary really!
\ fill RAM table with ' drop \ noop -> drop: remove argument!
' drop dcf.tmp.table #60 ramtable.init
\ dcf.tmp.table #60 ramtable.dump \ DEBUG
: >rt ( xt idx -- ) dcf.tmp.table >ramtable ;
\ code snippet XTs to ram table
\ structure of functions
\ :noname ( 0|1 -- )
\ if ( bit:1 ) ...
\ else ( bit:0 ) ...
\ then ( always ) ...
\ ; #idx >rt
\ :noname
\ drop ( always ) ...
\ ; #idx >rt
\ #0 always 0!
:noname
0= if ( bit:0 ) dcfErrCnt--
then
; #0 >rt
\ #1 .. #14 civil warning bits, MeteoTime data bits
\ #15 call bit
\ #16 1: day light savings time switch at the end of this hour
\ #17 0 CET, 1 CEST
:noname
if ( bit:1 ) f.dcf.CEDT fset
else ( bit:0 ) f.dcf.CEDT fclr
then
; #17 >rt
\ #18 1 CET, 0 CEST
:noname
if ( bit:1 ) f.dcf.CET fset f.dcf.CEDT fclr? if dcfErrCnt-- then
else ( bit:0 ) f.dcf.CET fclr f.dcf.CEDT fset? if dcfErrCnt-- then
then
; #18 >rt
\ #19 1: leap second announcement
\ #20 always 1: start of encoded time
:noname
if ( bit:1 ) dcfErrCnt--
then
f.dcf.parerr fset
f.dcf.parerr.min fset
f.dcf.parerr.hour fset
f.dcf.parerr.date fset
0 dcfPar !
; #20 >rt
\ #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
\ #29 .. #35: hour
\ hour ones
:noname if ( bit:1 ) #1 dcfTHour +! dcfPar++ then ; #29 >rt
:noname if ( bit:1 ) #2 dcfTHour +! dcfPar++ then ; #30 >rt
:noname if ( bit:1 ) #4 dcfTHour +! dcfPar++ then ; #31 >rt
:noname if ( bit:1 ) #8 dcfTHour +! dcfPar++ then ; #32 >rt
\ hour tens
:noname if ( bit:1 ) #10 dcfTHour +! dcfPar++ then ; #33 >rt
:noname if ( bit:1 ) #20 dcfTHour +! dcfPar++ then ; #34 >rt
\ hour parity bit
:noname
if ( bit:1 ) dcfPar++ then
( always )
dcfPar? if f.dcf.parerr.hour fclr dcfErrCnt-- then
0 dcfPar !
; #35 >rt
\ #36 .. #41: day
\ day ones
:noname if ( bit:1 ) #1 dcfTDay +! dcfPar++ then ; #36 >rt
:noname if ( bit:1 ) #2 dcfTDay +! dcfPar++ then ; #37 >rt
:noname if ( bit:1 ) #4 dcfTDay +! dcfPar++ then ; #38 >rt
:noname if ( bit:1 ) #8 dcfTDay +! dcfPar++ then ; #39 >rt
\ day tens
:noname if ( bit:1 ) #10 dcfTDay +! dcfPar++ then ; #40 >rt
:noname if ( bit:1 ) #20 dcfTDay +! dcfPar++ then ; #41 >rt
\ #42 .. #44: Wochentag
\ day of week
:noname if ( bit:1 ) #1 dcfTWday +! dcfPar++ then ; #42 >rt
:noname if ( bit:1 ) #2 dcfTWday +! dcfPar++ then ; #43 >rt
:noname if ( bit:1 ) #4 dcfTWday +! dcfPar++ then ; #44 >rt
\ #45 .. #49: Monat
\ month ones
:noname if ( bit:1 ) #1 dcfTMonth +! dcfPar++ then ; #45 >rt
:noname if ( bit:1 ) #2 dcfTMonth +! dcfPar++ then ; #46 >rt
:noname if ( bit:1 ) #4 dcfTMonth +! dcfPar++ then ; #47 >rt
:noname if ( bit:1 ) #8 dcfTMonth +! dcfPar++ then ; #48 >rt
\ month tens
:noname if ( bit:1 ) #10 dcfTMonth +! dcfPar++ then ; #49 >rt
\ #50 .. #57: Jahr % 100
\ year ones
:noname if ( bit:1 ) #1 dcfTYear +! dcfPar++ then ; #50 >rt
:noname if ( bit:1 ) #2 dcfTYear +! dcfPar++ then ; #51 >rt
:noname if ( bit:1 ) #4 dcfTYear +! dcfPar++ then ; #52 >rt
:noname if ( bit:1 ) #8 dcfTYear +! dcfPar++ then ; #53 >rt
\ year tens
:noname if ( bit:1 ) #10 dcfTYear +! dcfPar++ then ; #54 >rt
:noname if ( bit:1 ) #20 dcfTYear +! dcfPar++ then ; #55 >rt
:noname if ( bit:1 ) #40 dcfTYear +! dcfPar++ then ; #56 >rt
:noname if ( bit:1 ) #80 dcfTYear +! dcfPar++ then ; #57 >rt
\ date parity bit
:noname
if ( bit:1 ) dcfPar++ then
( always )
dcfPar? if f.dcf.parerr.date fclr dcfErrCnt-- then
f.dcf.parerr.min fclr?
f.dcf.parerr.hour fclr? and
f.dcf.parerr.date fclr? and if f.dcf.parerr fclr then
; #58 >rt
\ dcf.tmp.table #60 ramtable.dump \ DEBUG
\ copy RAM table to FLASH
dcf.tmp.table #60 >flashtable pos_cmd_map
\ release RAM
dcf.tmp.table to here \ fixme: possibly bad???
: pos.cmd ( index -- )
dup 0 #60 within if
( position ) pos_cmd_map + @i execute
else
drop
then
;
-2 constant dcf.bit:sync59
-1 constant dcf.bit:error
0 constant dcf.bit:0
1 constant dcf.bit:1
: dcf.pulse>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
;
: dcf.led ( t/f -- )
if led_dcf on else led_dcf off then
;
: dcf.dbg.minute.over ( -- )
space
f.dcf.err fclr? if
[char] O emit
else
[char] F emit
then
cr
;
: 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.tick: 128/s, sample signal ----
Rdefer dcf.tick
Rdefer dcf.sec
Rdefer dcf.min
: (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
\ it is unclear whether deriving the dcf-second is
\ ok from dcfTick, or whether ist must be
\ dcfClock tick
\ (dcf.tick) does both increments
\ dcfTick is clear when calling dcf.sec
\ dcf.sec clears dcfClock tick
;
\ --- dcf.sec: 1/s, book keeping ---------
: dcf.tmp.counter.reset
#59 dcfPos !
0 dcfTMin !
0 dcfTHour !
0 dcfTDay !
0 dcfTWday !
0 dcfTMonth !
0 dcfTYear !
0 dcfPar !
;
: dcfTemp>dcfClock
dcfClock
0 tick !
0 sec !
dcfTMin @ min !
dcfTHour @ hour !
dcfTDay @ 1- day !
\ dcfTWday @ ?
dcfTMonth @ 1- month !
dcfTYear @ Century + year !
;
: dcf.dbg.sec ( -- ) f.dcf.dbg fset? if dcf.dbg.second.over then ; \ debug
: dcf.dbg.min ( -- ) f.dcf.dbg fset? if dcf.dbg.minute.over then ; \ debug
: (dcf.sec)
dcfClock timeup
0 tick !
\ evaluate pulse
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
dcfPos++
;
: (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
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
;
\ --- init: enable DCF clock -------------
: +dcf
_dcf pin_input
['] (dcf.tick) to dcf.tick
['] (dcf.sec) to dcf.sec
['] (dcf.min) to dcf.min
0 dcfPos ! \ assume we are at 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
;
: -dcf
['] noop to dcf.tick
['] noop to dcf.sec
;
|