Software Clock Counters

Date:2017-11-08

Intro

It was kindly pointed out to me that what I did in the first version (Keeping Track of Time), namely

  • creating an array:

    variable tu.counts #7 cells allot
    
  • giving individual names to the fields:

    ... constant tick
    ... constant sec
    ... constant min
    ...
    
  • adding some index magic to make it look like ordinary variables:

    tu.counts            constant tick
    tu.counts #1 cells + constant sec
    tu.counts #2 cells + constant min
    ...
    

is the predominant use case for structures, provided in Forth in general, and in AmForth, too. So a part of the code mentioned above was changed and moved to a new file. While being at it, I added a field __tz to hold a pointer to a string stored in flash. This pointer can be used to print the label of a time zone. If only one set of these variables is needed, the effort reduces to knowing an interesting feature. If several sets are in use at the same time, this might be useful.

Code Details

Creating a clock amounts to allot-ing an array of counters in RAM. Each of these counters has a meaning like minute or hour. When dealing with one of several such clocks, we need to know the base address of the array (distinct for each set of counters) and the offsets into the array (distinct for each field, but identical across each set of counters). The available defining words begin-structure and end-structure will help us:

#include structures.frt

begin-structure _clock_t
  field: __tick
  field: __sec
  field: __min
  field: __hour
  field: __day
  field: __month
  field: __year
  field: __tz
end-structure

This provides the definition of the array and the meaning of its fields. Normally one would create an instance of this structure and use it by its name:

#include buffer.frt

_clock_t buffer: myclock

To read and display the value of a particular field we can write

myclock __min @ .

So the phrase myclock __min actually places the address of this field on the stack, just like an ordinary variable does. In particular, myclock places the base address of its associated array on the stack, and __min adds the correct offset to it. Several instances just use different names (myclock_2), but the code of __min remains unchanged.

When toying with the idea of using multiple such clock instances, I did not want to explicitly prepend their name to every access of their fields. But instead of using the name of the instance (myclock), it does not matter how the base address is entered on the stack before a call to __min.

I decided to create variable (a user variable, see below) named _clock, which will hold a valid base address at any time. So the old definition of min

variable min    \ old code

now becomes

: min   ( -- addr ) _clock @ __min   ;

This hides the complexity of dealing with possibly different instances. min can be used like a variable, code like : .time ... min   @    #2 u0.r [char] : emit ... ; remains unchanged.

So in order to create an instance of such a clock, including a pointer to a label about its time zone name, a defining word is needed: clock:

\ define a clock data array including
\ a pointer to a string holding its name
\ time zone, really.
: clock: ( s" tz_label" <clock_name> -- )
  dp >r s, r>                   \ -- flash-p
  create
  here dup   ( ram-p )   ,      \ -- flash-p ram-p
  over       ( flash-p ) ,
  _clock_t allot                \ -- flash-p ram-p
  _clock !
  tz !
does>
  dup @i _clock !
  1+  @i     tz !
;

Please note that allot is called directly. buffer: is not offering an advantage here, since there is a little more to do in the runtime portion of this definition. The runtime part fetches the base address of the associated array (in RAM) from flash and stores this value in the variable _clock already mentioned. Additionally the address of the time zone label is fetched and stored in the field __tz. This may not be needed every time, but avoids keeping track of a valid state.

Creating a clock instance then reduces to one line:

s" UTC" clock:  MasterClock

There is one more thing to deal with. If the multitasker is running, and if more than one task are accessing different clocks defined in this way, they actually share a global variable _clock unless we define it to be task-local:

#36 user _clock

The exact reason for #36 is not obvious at all: The task control block currently uses 36 Bytes (SYSUSERSIZE) to store all neccessary information belonging to a task, such as the location of its stacks, how much of them are used, which task follows this one, the current numerical base and so forth. 36 currently is the next unused location, and there are currently 10 Bytes (APPUSERSIZE) reserved in the task control block for such use.

Unfortunately user is defined in several different ways in different Forths and we just have to deal with it. See:

To make life more convenient for the user of all this, three more functions are defined: .date, .time to display the date or time on the serial connection, and .tz to print the label pointed to by field __tz.

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
\ 2017-07-14 ewlib/clocks_v0.3.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/>.
\
\
\ create an infrastructure to accomodate more than one
\ set of counters for a clock.
\
\ this attempts to spend RAM space and trade it for
\ simpler code. Try to avoid converting times ...
\
\ RAM array of counters (cells)
\ optional: uptime or epochseconds (double)
\ optional: EsecOffset (double)
\ 0: tick (optional)!
\ 1: second
\ 2: minute
\ 3: hour
\ 4: day (offset by 1)
\ 5: month (offset by 1)
\ 6: year
\ 7: pointer to TZ string (flash)

\ include lib/forth2012/facility/structures.frt
#include structures.frt
\ include lib/forth2012/core/buffer.frt

begin-structure _clock_t
  field: __tick
  field: __sec
  field: __min
  field: __hour
  field: __day
  field: __month
  field: __year
  field: __tz
end-structure

\ variable _clock
#36 user _clock
: tick  ( -- addr ) _clock @ __tick  ;
: sec   ( -- addr ) _clock @ __sec   ;
: min   ( -- addr ) _clock @ __min   ;
: hour  ( -- addr ) _clock @ __hour  ;
: day   ( -- addr ) _clock @ __day   ;
: month ( -- addr ) _clock @ __month ;
: year  ( -- addr ) _clock @ __year  ;
: tz    ( -- addr ) _clock @ __tz    ;


: .date  ( -- )
  year  @    #4 u0.r  \ [char] - emit
  month @ 1+ #2 u0.r  \ [char] - emit
  day   @ 1+ #2 u0.r
;
: .time  ( -- )
  hour  @    #2 u0.r [char] : emit
  min   @    #2 u0.r [char] : emit
  sec   @    #2 u0.r
;
: .tz ( -- )
  tz    @    icount itype
;


\ define a clock data array including
\ a printing label for its time_zone.
: clock: ( s" tz_label" <clock_name> -- )
  dp >r s, r>                   \ -- flash-p
  create
  here dup   ( ram-p )   ,      \ -- flash-p ram-p
  over       ( flash-p ) ,
  _clock_t allot                \ -- flash-p ram-p
  _clock !
  tz !
does>
  dup @i _clock !
  1+  @i     tz !
;

\ s" UTC" clock:  MasterClock