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 .short versions use cached values for some epoch, e.g. 2017

  • shift 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 masterclock during startup, after valid telegrams have been received

  • new: 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;