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
.shortversions use cached values for some epoch, e.g. 2017new: 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.
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).
Schematic for one 7-segment digit¶
Prototype Board manually worked¶
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;