Model 3: The UTC Wall Clock

Date:

2017-09-18

Design Decisions

  • 128 ticks/sec, generated from external RTC clock (32768 Hz)

  • uptime counter

  • multitasker

  • a battery backed real time clock is connected via i2c

  • start time is read from RTC

  • new: RTC generates 32768 Hz signal to drive clock ticks

  • new: run epoch seconds as additional software clock

  • new: use epoch seconds to derive times in 3 different time zones, read desired zone from 2 input pins

  • new: conversion to and from epoch seconds: additional .short versions use cached values for some epoch, e.g. 2017

  • new: shift register drives 7-segment digits as display

Description

The code included 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.

This clock is a more traditional design with 4 large 7-Segment digits and a good RTC (battery backed). Placed in a nice housing it can be used standalone.

../../_images/i_model3_2.jpg

Model 3: Four 7-segment LED digits to indicate local time.

The Pinout section should be familiar by now. Using quotations ([: ... ;]) the LED definitions can have alias names, which might be more useful in the given context.

The Display is driven by shift registers as before. These are connected to 7-segment digits, not individual LEDs.

Time zones are selected by reading 2 pins.

The Real Time Clock is a different chip (DS3231). It needs somewhat adapted functions to read and set the time counters. The chip is much more accurate than the clock sources I have used before.

The counters of the master clock are unchanged, uptime is counted as before. The source of the clock tick has changed. The 32768 Hz square wave signal is driving timer/counter0 which overflows 128 times per second. The corresponding interrupt service routine increments a counter, the main loop checks whether a half second has passed.

Functions to set/read/display the counters of the master clock are available. Functions to copy time counter values from between the master clock and the RTC follow.

Handling of time zones, epoch seconds, and the display of a local time are handled as described in section Time Zones.

Multitasking, periodic jobs, a background task to run the main loop of the master clock — everything is as described before (Model 2).

../../_images/p_display_wallclock2.png

Schematic for one 7-segment digit

../../_images/i_model3_1.jpg

Prototype Board manually worked

../../_images/i_model3_3.jpg

Controller Board and display

The Code

  1\ 2017-08-30  main-20-utc-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
 27#include erase.frt
 28#include bitnames.frt
 29#include marker.frt
 30\ these definitions are resolved by amforth-shell.py as needed
 31\ include atmega644p.fs
 32
 33#include flags.frt
 34#include 2variable.frt
 35#include 2constant.frt
 36#include 2-fetch.frt
 37#include 2-store.frt
 38#include m-star-slash.frt
 39#include quotations.frt
 40#include avr-defers.frt
 41#include defers.frt
 42#include eallot.frt
 43#include 2evalue.frt
 44
 45marker --start--
 46
 47PORTA $03 bitmask: _tz
 48
 49\ PORTB 0 portpin: T0
 50\ PORTB 1 portpin: T1
 51PORTB 2 portpin: led.0
 52PORTB 3 portpin: led.1
 53PORTB 4 portpin: led.2
 54PORTB 5 portpin: led.3
 55: led_dcf  [: led.0 ;] execute ;
 56: led_utc  [: led.1 ;] execute ;
 57: led_mez  [: led.2 ;] execute ;
 58: led_mesz [: led.3 ;] execute ;
 59
 60PORTC 0 portpin: i2c_scl
 61PORTC 1 portpin: i2c_sda
 62
 63\ abakus/4x7seg display
 64PORTD 4 portpin: sr_data
 65PORTD 5 portpin: sr_clock
 66PORTD 6 portpin: sr_latch
 67\ --- famous includes and other words
 68: ms   ( n -- )       0 ?do pause 1ms loop ;
 69: u0.r ( u n -- )     >r 0 <# r> 0 ?do # loop #> type ;
 70: odd?  ( x -- t/f )  $0001 and 0= 0= ;
 71: even? ( x -- t/f )  $0001 and 0= ;
 72
 73\ --- driver: status leds
 74#include leds.fs
 75
 76\ --- driver: time zone switch
 77: +sw ( -- )          _tz pin_input ;
 78
 79\ --- driver: i2c rtc clock
 80: bcd>dec  ( n.bcd -- n.dec )
 81  $10 /mod  #10 * + ;
 82: dec>bcd  ( n.dec -- n.bcd )
 83  #100 mod  #10 /mod  $10 * + ;
 84
 85#include i2c-twi-master.frt
 86#include i2c.frt
 87#include i2c-detect.frt
 88: +i2c  ( -- )
 89  i2c_scl pin_pullup_on
 90  i2c_sda pin_pullup_on
 91  i2c.prescaler/1
 92  #6 \ bit rate --- 400kHz @ 11.0592 MHz
 93  i2c.init
 94;
 95
 96: i2c.scan
 97  base @ hex
 98  $79 $7 do
 99    i i2c.ping? if i 3 .r then
100  loop
101  base !
102  cr
103;
104$68 constant i2c_addr_rtc
105#2000 constant Century
106#include i2c_rtc_ds3231.fs
107
108\ --- master clock
109\ --- timeup
110#include timeup_v0.0.fs
111                                        \ tu.counts -- fields available as:
112                                        \   tick sec min hour day month year
113                                        \ last_day_of_month ( year month -- last_day )
114                                        \ timeup.init
115                                        \ timeup
116                                        \ tu.upd.limits ( Y m -- )
117
118\ --- uptime
1192variable uptime
120: .uptime  ( -- )  uptime 2@  decimal ud. [char] s emit ;
121: ++uptime ( -- )  1.  uptime 2@  d+  uptime 2! ;
122
123\ --- timer0 clock tick
124\ 128 ticks/sec
125\ timer_0_ overflow
126\ clock source pin T0 @ 32768 Hz (from ds3231)
127#include clock_tick0_external.fs
128
129                                        \ +ticks
130                                        \ tick.over?  ( -- t/f )
131                                        \ tick.over!
132                                        \ half.second.over?  ( -- 0|1|2 )
133: clock.set ( Y m d H M S -- )
134  sec ! min ! hour !
135  1- day !
136  over over
137  1- month ! year !
138  ( Y m ) tu.upd.limits
139;
140: clock.get ( -- S M H d m Y )
141  sec @ min @ hour @
142  day @ 1+ month @ 1+ year @
143;
144: clock.dot ( S M H d m Y -- )
145  #4 u0.r [char] - emit #2 u0.r [char] - emit #2 u0.r  [char] _  emit
146  #2 u0.r [char] : emit #2 u0.r [char] : emit #2 u0.r
147;
148: clock.show ( -- )
149  clock.get
150  clock.dot
151;
152
153: .date
154  year  @    4 u0.r
155  month @ 1+ 2 u0.r
156  day   @ 1+ 2 u0.r
157;
158: .time
159  hour @ 2 u0.r [char] : emit
160  min  @ 2 u0.r [char] : emit
161  sec  @ 2 u0.r
162;
163
164: hwclock>clock ( -- )
165  rtc.get    \ -- sec min hour wday day month year
166     year  !
167  1- month !
168  1- day   !
169  ( wday ) drop
170     hour  !
171     min   !
172     sec   !
173  year @   month @ 1+  tu.upd.limits
174;
175: clock>hwclock ( -- )
176
177  year @   month @ 1+  day @ 1+
178  1 \ sunday ":-)
179  hour @   min   @     sec @
180  ( Y m d wday H M S ) rtc.set
181;
182
183#include shiftregister.fs
184#include 7seg_1.fs
185
186\ --- epoch seconds, timezones
187: u>= ( n n -- t/f ) u< invert ;
188: d>s ( d -- n ) drop ;
189           variable   _last_epoch
190          2variable   _last_esec
191
192#2017        Evalue   EE_last_epoch
193#1483228800. 2Evalue  EE_last_esec \ 2017
194
195#include epochseconds.fs
1962variable Esec
197: ++Esec  ( -- )  Esec 2@  1. d+  Esec 2! ;
198: .Esec   ( -- )  Esec 2@ ud. ;
199
2002variable EsecOffset
201: UTC  ( -- )     0. EsecOffset 2! ;
202: MEZ  ( -- )  3600. EsecOffset 2! ;
203: MESZ ( -- )  7200. EsecOffset 2! ;
204: _tz.set
205  _tz pin@
206  dup 0 = if
207    UTC
208    led_utc on  led_mez off led_mesz off
209  then
210  dup 1 = if
211    MEZ
212    led_utc off led_mez on  led_mesz off
213  then
214  dup 2 = if
215    MESZ
216    led_utc off led_mez off led_mesz on
217  then
218  dup 3 = if
219    UTC
220    led_utc on  led_mez off led_mesz off
221  then
222  drop
223;
224
225: local.dt ( -- S M H d m Y )
226  Esec 2@  EsecOffset 2@  d+  s>dt.short
227;
228: cd.localtime
229  local.dt          \ -- S M H d m Y
230  drop drop drop    \ -- S M H
231  rot drop swap     \ -- H M
232  >r #10 /mod swap  \ -- H.10 H.1
233  r> #10 /mod swap  \ -- H.10 H.1 M.10 M.1
234  #4 type.7seg      \ --
235;
236
237\ --- multitasker
238#include multitask.frt
239: +tasks  multi ;
240: -tasks  single ;
241
242
243\ --- timeup jobs ---------------------------
244: job.tick
245;
246: job.sec
247  ++uptime
248  ++Esec
249;
250: job.min
251  _tz.set cd.localtime
252;
253: job.hour  ;
254: job.day   ;
255: job.month
256  \ update length of month in tu.limits
257  year @  month @ 1+  tu.upd.limits
258;
259: job.year  ;
260
261create Jobs
262  ' job.tick ,
263  ' job.sec , ' job.min ,   ' job.hour ,
264  ' job.day , ' job.month , ' job.year ,
265
266variable jobCount
267: jobCount++
268  jobCount @
269  6 < if
270    1 jobCount +!
271  then
272;
273
274\ --- task 2 --------------------------------
275: run-masterclock
276  ['] tx-poll to emit \ add emit to run-masterclock
277  begin
278
279    tick.over? if
280      tick.over!
281      1 tick +!
282      job.tick
283    then
284
285    half.second.over?
286    dup 0<> if
287      dup odd? if       \ half second
288        led.1 off
289      else              \ second
290        led.1 on
291        timeup
292        0 tick !
293        1 jobCount !
294      then
295    then
296    drop
297
298    \ run one job per loop, not all at once
299    jobCount @
300    bv tu.flags fset?
301    if
302      jobCount @ dup
303      Jobs + @i execute
304      bv tu.flags fclr
305    then
306    jobCount++
307
308    pause
309  again
310;
311$40 $40 0 task: task-masterclock \ create task space
312: start-masterclock
313  task-masterclock tib>tcb
314  activate
315  \ words after this line are run in new task
316  run-masterclock
317;
318: starttasker
319  task-masterclock task-init            \ create TCB in RAM
320  start-masterclock                     \ activate tasks job
321
322  onlytask                              \ make cmd loop task-1
323  task-masterclock tib>tcb alsotask     \ start task-2
324  multi                                 \ activate multitasking
325;
326
327\ --- main ----------------------------------
328: init
329  +sr
330  $00 byte>sr $00 byte>sr $00 byte>sr $00 byte>sr
331  sr_latch low sr_latch high
332  +sw
333  +leds leds-intro
334  #2017 1 1 0 0 0 clock.set
335  0. uptime 2!
336  0. Esec    2!
337  EE_last_epoch _last_epoch  !
338  EE_last_esec  _last_esec  2!
339  +ticks
340  timeup.init
341  +i2c
342  i2c_addr_rtc i2c.ping? if
343    hwclock>clock
344    clock.get ut>s.short Esec 2!
345  else
346    _last_epoch @ 1 1 0 0 0 clock.set
347    _last_esec 2@ Esec 2!
348  then
349  _tz.set cd.localtime
350;
351: run
352  init
353  starttasker
354;
355: run-turnkey
356  applturnkey
357  init
358  starttasker
359;
360\ ' run-turnkey to turnkey
361
362: .d ( -- )
363  decimal
364  .uptime         space space
365  clock.show      space
366  tick            @ . space
367  ct.ticks.follow @ . space space
368  .Esec                space
369  Esec 2@  EsecOffset  2@ d+
370  s>dt.short      clock.dot
371  cr
372;