Model 4: The Radio Controlled UTC Clock¶
- Date:
2018-11-11
Intro¶
Design Decisions¶
128 ticks/sec
uptime counter
multitasker
a battery backed real time clock is connected via i2c
start time is read from RTC
RTC generates 32768 Hz signal to drive clock ticks
run epoch seconds as additional software clock
use epoch seconds to derive times in 3 different time zones, read desired zone from 2 input pins
conversion to and from epoch seconds: additional
.shortversions use cached values for some epoch, e.g. 2017shift register drives 7-segment digits as display
new: DCF77 receiver connected and sampled
new: additional software clock to track DCF77 time
new: sync: DCF77 time is written to
masterclockduring startup, after valid telegrams have been receivednew: another sync is requested every hour.
Description¶
This clock uses the same hardware as
Model 3 except for one change: the external
DCF77 receiver signal is connected to Pin D.7.
The code below is a complete, working example, tested on an atmega644p
controller. The syntax for the includes is such that
amforth-shell.py will upload the programm and resolve all
#include file directives.
Connecting to DCF77¶
The additions to the main program are fairly straight forward. We need to define the pin to which the receiver is connected. And while we are at it, we also define an alias name for an led to indicate dcf77 activity.
\ dcf77 RX
: led_dcf [: led.0 ;] execute ;
PORTD 7 portpin: _dcf
We then need some more functions to control everything.
\ --- DCF77 receiver
Rdefer dcfClock>MasterClock
s" DCF" clock: dcfClock
#include ramtable_to_flash.fs
#include dcf_01.fs
: dcf.resync ( -- )
\ request update of masterclock from dcf
;
\ copy time from dcfClock to MasterClock
: (dcfClock>MasterClock)
\ update masterclock from dcfclock
\ take care of time zones
;
Running dcfClock is hooked into job.tick.
: job.tick
dcf.tick
;
Updating the counters of MasterClock needs some additional
handling at the start of a minute. Please note that dcf.min is
not hooked into job.min, because the end of the minute may
differ between dcfClock and MasterClock.
: job.min
f.mc.insync fclr? if
f.dcf.insync fset? if
\ dcfClock>MasterClock
clock>hwclock
-DD -DRX
f.mc.insync fset
then
then
_tz.set cd.localtime
MasterClock min @ #58 = if dcf.resync then ;
;
And not surpisingly, all of this code needs to be activated at startup of the system:
: init
\ ...
['] (dcfClock>MasterClock) to dcfClock>MasterClock
+dcf
\ f.dcf.dbg fset \ debug dcf
\ f.dcf.dbg.rx fset
f.dcf.commit fset
led.3 on \ commit pending
led.2 on \ dcf error unless proven otherwise
+DD +DRX
;
The Code¶
1\ 2017-09-24 main-30-utc-dcf-wallclock.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\ include syntax for upload with amforth-shell.py
15\
16\ 11.059200 MHz main crystal
17\ 32768 Hz square signal on pin T0
18\ timer/counter0
19\ 128 ticks/second
20\
21\ minimal clock
22\ plus i2c, i2c RTC (ds3231)
23\ display: shift registers (TPIC 6B595) and 4 7-segment digits
24\ MasterClock in UTC, display in 2 other timezones
25\ 2 pins for selection of timezone
26\ plus
27\ stationID, redefined ready-prompt
28
29\ _include builds.frt
30#include erase.frt
31#include dot-base.frt
32\ _include imove.frt
33#include bitnames.frt
34#include marker.frt
35#include environment-q.frt
36#include dot-res.frt
37#include avr-values.frt
38#include is.frt
39\ _include dumper.frt
40\ _include interrupts.frt
41\ these definitions are resolved by amforth-shell.py as needed
42\ include atmega644p.fs
43
44#include flags.frt
45#include 2variable.frt
46#include 2constant.frt
47#include 2-fetch.frt
48#include 2-store.frt
49#include m-star-slash.frt
50#include quotations.frt
51#include avr-defers.frt
52#include defers.frt
53#include eallot.frt
54#include 2evalue.frt
55#include case.frt
56
57marker --start--
58
59$006f Evalue stationID
60
61PORTA $03 bitmask: _tz
62
63\ PORTB 0 portpin: T0
64\ PORTB 1 portpin: T1
65PORTB 2 portpin: led.0
66PORTB 3 portpin: led.1
67PORTB 4 portpin: led.2
68PORTB 5 portpin: led.3
69: led_dcf [: led.0 ;] execute ;
70: led_utc [: led.1 ;] execute ;
71: led_mez [: led.2 ;] execute ;
72: led_mesz [: led.3 ;] execute ;
73
74PORTC 0 portpin: i2c_scl
75PORTC 1 portpin: i2c_sda
76
77\ abakus/4x7seg display
78PORTD 4 portpin: sr_data
79PORTD 5 portpin: sr_clock
80PORTD 6 portpin: sr_latch
81\ dcf77 RX
82PORTD 7 portpin: _dcf
83
84\ --- famous includes and other words
85: ms ( n -- ) 0 ?do pause 1ms loop ;
86: u0.r ( u n -- ) >r 0 <# r> 0 ?do # loop #> type ;
87: odd? ( x -- t/f ) $0001 and 0= 0= ;
88: even? ( x -- t/f ) $0001 and 0= ;
89
90: .stationID_ready
91 cr
92 [char] ~ emit
93 base @ $10 base ! stationID 2 u0.r base !
94 [char] > emit space
95;
96
97\ --- driver: status leds
98#include leds.fs
99
100\ --- driver: time zone switch
101: +sw ( -- ) _tz pin_input ;
102
103\ --- driver: i2c rtc clock
104: bcd>dec ( n.bcd -- n.dec )
105 $10 /mod #10 * + ;
106: dec>bcd ( n.dec -- n.bcd )
107 #100 mod #10 /mod $10 * + ;
108
109#include i2c-twi-master.frt
110#include i2c.frt
111#include i2c-detect.frt
112: +i2c ( -- )
113 i2c_scl pin_pullup_on
114 i2c_sda pin_pullup_on
115 i2c.prescaler/1
116 #6 \ bit rate --- 400kHz @ 11.0592 MHz
117 i2c.init
118;
119
120: i2c.scan
121 base @ hex
122 $79 $7 do
123 i i2c.ping? if i 3 .r then
124 loop
125 base !
126 cr
127;
128$68 constant i2c_addr_rtc
129#2000 constant Century
130#include i2c_rtc_ds3231.fs
131
132\ --- master clock
133#include clocks_v0.3.fs
134 \ newfangled clock:
135 \ data as structure
136 \ sec min hour day month year
137 \ .date .time .tz
138s" UTC" clock: MasterClock
139 \ create the "master" clock
140variable mcFlags
141mcFlags 0 flag: f.mc.insync
142
143\ --- timeup
144#include timeup_v1.0.fs
145 \ last_day_of_month ( year month -- last_day )
146 \ timeup.init
147 \ timeup
148 \ tu.upd.limits ( Y m -- )
149
150\ --- uptime
1512variable uptime
152: ++uptime ( -- ) 1. uptime d+! ;
153: .uptime ( -- ) uptime 2@ decimal ud. [char] s emit ;
154: du.i ( d -- ) <# #s #> type ;
155: ud.dhms ( d:T/sec -- )
156 2dup decimal du.i [char] s emit space
157 &60 ud/mod &60 ud/mod &24 ud/mod
158 du.i [char] d emit space \ days
159 2 u0.r [char] : emit \ hours
160 2 u0.r [char] : emit \ minutes
161 2 u0.r \ seconds
162;
163: .uptime.dhms uptime 2@ ud.dhms ;
164
165\ --- timer0 clock tick
166\ 128 ticks/sec
167\ timer_0_ overflow
168\ clock source pin T0 @ 32768 Hz (from ds3231)
169#include clock_tick0_external.fs
170
171 \ +ticks
172 \ tick.over? ( -- t/f )
173 \ tick.over!
174 \ half.second.over? ( -- 0|1|2 )
175: clock.set ( Y m d H M S -- )
176 sec ! min ! hour !
177 1- day !
178 over over
179 1- month ! year !
180 ( Y m ) tu.upd.limits
181;
182: clock.get ( -- S M H d m Y )
183 sec @ min @ hour @
184 day @ 1+ month @ 1+ year @
185;
186: clock.dot ( S M H d m Y -- )
187 #4 u0.r [char] - emit #2 u0.r [char] - emit #2 u0.r [char] _ emit
188 #2 u0.r [char] : emit #2 u0.r [char] : emit #2 u0.r space .tz
189;
190: clock.show ( -- )
191 clock.get
192 clock.dot
193;
194
195: .date
196 year @ 4 u0.r
197 month @ 1+ 2 u0.r
198 day @ 1+ 2 u0.r
199;
200: .time
201 hour @ 2 u0.r [char] : emit
202 min @ 2 u0.r [char] : emit
203 sec @ 2 u0.r
204;
205
206: hwclock>clock ( -- )
207 rtc.get \ -- sec min hour wday day month year
208 year !
209 1- month !
210 1- day !
211 ( wday ) drop
212 hour !
213 min !
214 sec !
215 year @ month @ 1+ tu.upd.limits
216;
217: clock>hwclock ( -- )
218
219 year @ month @ 1+ day @ 1+
220 1 \ sunday ":-)
221 hour @ min @ sec @
222 ( Y m d wday H M S ) rtc.set
223;
224
225#include shiftregister.fs
226#include 7seg_1.fs
227
228\ --- epoch seconds, timezones
229: u>= ( n n -- t/f ) u< invert ;
230: d>s ( d -- n ) drop ;
231 variable _last_epoch
232 2variable _last_esec
233
234#2017 Evalue EE_last_epoch
235#1483228800. 2Evalue EE_last_esec \ 2017
236
237#include epochseconds.fs
2382variable Esec
239: ++Esec ( -- ) 1. Esec d+! ;
240: .Esec ( -- ) Esec 2@ ud. ;
241
2422variable EsecOffset
243: UTC ( -- ) 0. EsecOffset 2! ;
244: MEZ ( -- ) 3600. EsecOffset 2! ;
245: MESZ ( -- ) 7200. EsecOffset 2! ;
246: _tz.set
247 _tz pin@
248 dup 0 = if
249 UTC
250 led_utc on led_mez off led_mesz off
251 then
252 dup 1 = if
253 MEZ
254 led_utc off led_mez on led_mesz off
255 then
256 dup 2 = if
257 MESZ
258 led_utc off led_mez off led_mesz on
259 then
260 dup 3 = if
261 UTC
262 led_utc on led_mez off led_mesz off
263 then
264 drop
265;
266
267: local.dt ( -- S M H d m Y )
268 Esec 2@ EsecOffset 2@ d+ s>dt.short
269;
270: cd.localtime
271 local.dt \ -- S M H d m Y
272 drop drop drop \ -- S M H
273 rot drop swap \ -- H M
274 >r #10 /mod swap \ -- H.10 H.1
275 r> #10 /mod swap \ -- H.10 H.1 M.10 M.1
276 #4 type.7seg \ --
277;
278\ --- DCF77 receiver
279Rdefer dcfClock>MasterClock
280
281s" DCF" clock: dcfClock
282#include ramtable_to_flash.fs
283#include dcf_01.fs
284
285: dcf.resync ( -- )
286 f.dcf.insync fclr \ resync dcf time
287 f.dcf.commit fset \ sync dcf -> MasterClock wanted
288 f.dcf.dbg fset \ dbg.min on
289 f.dcf.dbg.rx fset \ dbg.sec on
290;
291
292\ copy time from dcfClock to MasterClock
293: (dcfClock>MasterClock)
294 dcfClock
295 sec @ min @ hour @
296 day @ 1+ month @ 1+ year @
297 MasterClock
298 \ -- sec min hour day month year
299 \ convert "local time" to epoch seconds
300 ut>s.short \ -- T/sec
301 \ adjust local time zone
302 f.dcf.CEDT fset? if
303 #3600. d-
304 then
305 #3600. d-
306 2dup Esec 2! \ copy to Epoch seconds!
307
308 \ convert back to "UTC date time" format
309 s>dt.short \ -- sec min hour day month year
310
311 MasterClock
312 year ! 1- month ! 1- day !
313 hour ! min ! sec !
314
315 \ fixme: might cause wreaking havoc?
316 dcfClock tick @
317 dup MasterClock tick !
318 ct.ticks.follow !
319;
320
321\ --- multitasker
322#include multitask.frt
323: +tasks multi ;
324: -tasks single ;
325
326
327\ --- timeup jobs ---------------------------
328: job.tick
329 dcf.tick
330;
331: job.sec
332 ++uptime
333 ++Esec
334;
335: job.min
336 f.mc.insync fclr? if
337 f.dcf.insync fset? if
338 \ dcfClock>MasterClock
339 clock>hwclock
340 -DD -DRX
341 f.mc.insync fset
342 then
343 then
344 _tz.set cd.localtime
345 MasterClock min @ #58 = if dcf.resync then ;
346;
347: job.hour ;
348: job.day ;
349: job.month
350 \ update length of month in tu.limits
351 MasterClock
352 year @ month @ 1+ tu.upd.limits
353;
354: job.year ;
355
356create Jobs
357 ' job.tick ,
358 ' job.sec , ' job.min , ' job.hour ,
359 ' job.day , ' job.month , ' job.year ,
360
361variable jobCount
362: jobCount++
363 jobCount @
364 6 < if
365 1 jobCount +!
366 then
367;
368
369\ --- task 2 --------------------------------
370: run-masterclock
371 ['] tx-poll to emit \ add emit to run-masterclock
372 begin
373
374 tick.over? if
375 tick.over!
376 MasterClock
377 1 tick +!
378 job.tick
379 then
380
381 half.second.over?
382 dup 0<> if
383 dup odd? if \ half second
384 led.1 off
385 else \ second
386 led.1 on
387 MasterClock
388 timeup
389 0 tick !
390 1 jobCount !
391 then
392 then
393 drop
394
395 \ run one job per loop, not all at once
396 jobCount @
397 bv tu.flags fset?
398 if
399 jobCount @ dup
400 Jobs + @i execute
401 bv tu.flags fclr
402 then
403 jobCount++
404
405 pause
406 again
407;
408$80 $80 $80 task: task-masterclock \ create task space
409: start-masterclock
410 task-masterclock tib>tcb
411 activate
412 \ words after this line are run in new task
413 run-masterclock
414;
415: starttasker
416 task-masterclock task-init \ create TCB in RAM
417 start-masterclock \ activate tasks job
418
419 onlytask \ make cmd loop task-1
420 task-masterclock tib>tcb alsotask \ start task-2
421 multi \ activate multitasking
422;
423
424\ --- main ----------------------------------
425: init
426 +sr
427 $00 byte>sr $00 byte>sr $00 byte>sr $00 byte>sr
428 sr_latch low sr_latch high
429 +sw
430 +leds leds-intro
431 ['] .stationID_ready is .ready
432
433 0. uptime 2!
434 0. Esec 2!
435 EE_last_epoch _last_epoch !
436 EE_last_esec _last_esec 2!
437
438 MasterClock
439 UTC
440 #2017 1 1 0 0 0 clock.set
441 timeup.init
442 +ticks
443
444 +i2c
445 i2c_addr_rtc i2c.ping? if
446 hwclock>clock
447 clock.get ut>s.short Esec 2!
448 else
449 _last_epoch @ 1 1 0 0 0 clock.set
450 _last_esec 2@ Esec 2!
451 then
452 _tz.set cd.localtime
453
454 ['] (dcfClock>MasterClock) to dcfClock>MasterClock
455 +dcf
456 \ f.dcf.dbg fset \ debug dcf
457 \ f.dcf.dbg.rx fset
458 f.dcf.commit fset
459 led.3 on \ commit pending
460 led.2 on \ dcf error unless proven otherwise
461 +DD +DRX
462;
463: run
464 init
465 starttasker
466;
467: run-turnkey
468 applturnkey
469 init
470 starttasker
471;
472\ ' run-turnkey to turnkey
473
474: .dcf.diff
475 \ 1 \ daylight saving time
476 0 \ not daylight saving time
477 dcfClock clock.get MasterClock
478 dt>s 2dup ud.
479 Esec 2@ 2dup ud.
480 d- d.
481;
482
483: .d ( -- )
484 decimal
485 .uptime space space
486 clock.show space
487 tick @ . space
488 ct.ticks.follow @ . space space
489 .Esec space
490 Esec 2@ EsecOffset 2@ d+
491 s>dt.short clock.dot space
492 f.dcf.commit fclr? if .dcf.diff then
493 cr
494;