Generating Ticks from the main crystal¶
- Date:
 2017-08-08
Intro¶
Since your favourite microcontroller board most likely has a crystal as clock source already, we might just as well use it to generate ticks.
Design Decisions¶
We always have to accept or make such decisions:
The main crystal has a baud rate compatible frequency of 11.059200 MHz — this is my favourite choice
For no particular reason, I decided to have
32ticks/second. For reasons not yet apparent, a power of2is more suitable than other values.We use timer/counter1, which is a 16-bit counter, to generate the ticks as timer overflow interrupts
We use the interrupt service routine to increment a variable
Any of the above values could be chosen otherwise.
Setting up Timer1 for overflow interrupts¶
First we decompose the crystal frequency into its prime factors:
11059200 = 2^14 * 3^3 * 5^2
Timer/Counter1 has a prescaler, which I chose to set to 256:
11059200 = 256 * 2^6 * 3^3 * 5^2 = 256 * 43200
In order to have 32 timer overflows per second, we need to count this number of cycles:
11059200 = 256 * 32 * 2^1 * 3^3 * 5^2 = 256 * 32 * 1350
Timer/Counter1 will be used in CTC-Mode (clear on compare match). The
compare match value needs to be set to 1350-1 then. With only one
addition (toggle pin OC1A) these things are to be found in the
function +ticks.
: +ticks
  ...
  \ --- timer1 ---
  [ %00000000                 \ WGM1[10] CTC mode
    %01000000 or              \ COM1A[10] toggle OC1A on compare match
  ] literal TCCR1A c!
  #1350 1-  OCR1A   !         \ TOP or compare match value rather
  DDRD c@ $80 or DDRD c!      \ pin OC2A output
  [ %00000100                 \ CS1[210] clock_ts2/256
    %00001000 or              \ WGM1[32] CTC mode
  ] literal TCCR1B c!
  ...
;
Toggling pin OC1A on timer/counter1 overflow is a means of
debugging: You can attach something (LED, buzzer, oscilloscope,
frequency counter, logic analyzer) to this pin in order to verify the
timers operation.
This exercise should hopefully motivate you to reread the data sheet
about timer/counter1. You may find that the prescaler does not provide
all powers of 2, just some. You may find quite a number of
different modes of operation. We chose clear on compare match mode,
which provides timer overflows after counting 1350 cycles. By
changing this compare value we could alter the length of the ticks.
Of course, one could choose 100 ticks/second, or something else. But the crystal frequency, prescaler, and compare value need to be chosen in such a way, that a number of ticks result in exactly one second (we ignore the accuracy of the crystal at this stage).
Serving the Timer1 Overflow Interrupt¶
The timer/counter1 overflow interrupts do nothing so far, so we want
to add some effect to their occurance. This effect should be visible
in the running code somehow. The effect will be connected to the timer
overflow via the interrupt service routine tick_isr.
: tick_isr
  \ do something here
;
: +ticks
  ...
  \ register ISR and enable OCIE1A interrupt
  ['] tick_isr TIMER1_COMPAAddr int!
  TIMSK1 c@ $02 or TIMSK1 c!
;
Now the do something here part needs to be considered. One could
set a bit flag in a variable, and upon serving this event in the main
program, the flag is being cleared. This would work in principle, but
we have not addressed possible race conditions. If the interrupt
setting the flag is called again before the last event has been
serviced, the second event is simply lost. If we spend only 1 bit of
information, we cannot count these events. So a variable as counter
may look more promising.
Incrementing a counter may not lose events, however, care must be taken that the main programm and the interrupt do not produce undesirable effects. Always keep in mind that the main program may be interrupted at any time, even between reading two bytes of one variable. So maybe its a bad idea, if the main program tries to decrement the counter, unless we disable interrupts during that operation.
Or maybe the main programm should only read the variable and never write to it — would that be better? But how about variable size and the number of instructions needed to read its complete content? What happens if the variable sooner or later wraps around?
Welcome to the heart and core of making a software clock!
Well, you can choose to abandon this project right here and run. That would actually be ok!
Still reading? Well …
I chose to increment variable ct.ticks from within the ISR. I also
think that we can’t get it much simpler.
: tick_isr   ( -- )      1 ct.ticks +! ;
The main program will use another variable ct.ticks.follow to keep
track of how many events it has serviced. Ideally the difference
should be 0 most of the time, and 1 after one interrupt has
occured. The main loop will check this difference as often as it can.
The variables will be 16 bit long for now. In order to deal with the
inevitable overflow a not so obvious comparison (- 0<) is used.
: tick.over? ( -- t/f )  ct.ticks.follow @  ct.ticks @  - 0< ;
The difference between ct.ticks.follow and ct.ticks is
compared less-or-equal-to-zero as a signed quantity.
Assignment #1: Verify that this code actually works (on paper, for
8 bit variables). Check that 0< does not work correctly on 8 bit
variables (on the controller). Any idea why?
The main programm acknowledges handling one event by incrementing
ct.ticks.follow.
: tick.over! ( -- )      1 ct.ticks.follow +! ;
Putting it all together¶
We should find the above code snippets used in the main program
somehow like this (see e.g. Model 1: The Fairly Minimal Clock).
Note that ticks is a separate variable in the main programm.
include ewlib/clockticks_main_crystal.fs
variable ticks
: init
  ...
  0 ticks !
  +ticks
;
: run-loop
  init
  begin
    tick.over? if
      tick.over!
      \ one tick over, do something
      1 ticks +!    \ count ticks
    then
    ticks @ 1+  ticks/sec > if
      ticks @ ticks/sec - ticks !
      \ one second over, do something!
      ...
    then
  again
;
The Code¶
 1\ 2017-08-13 ewlib/clock_tick1_main.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
15#32 constant ticks/sec
16variable ct.ticks
17variable ct.ticks.follow
18variable last.tick[4]
19variable last.tick[5]
20
21\ overflow interupt service routine
22: tick_isr
23  1 ct.ticks +!
24;
25
26: tick.over? ( -- t/f ) ct.ticks.follow @  ct.ticks @  - 0< ;
27: tick.over! ( -- )     1 ct.ticks.follow +! ;
28
29\ enable ticks
30\ crystal:   11059200 /sec
31\ prescaler: 256
32\            43200 /sec
33\ TOP+1:     1350
34\            32 /sec
35: +ticks
36  0 ct.ticks        !
37  0 ct.ticks.follow !
38  0 last.tick[4]    !
39  0 last.tick[5]    !
40
41  \ --- timer1 ! ---
42  [ %00000000                 \ WGM1[10] CTC mode
43    %01000000 or              \ COM1A[10] toggle OC1A on compare match
44  ] literal TCCR1A c!
45  #1350 1-  OCR1A   !         \ TOP or compare match value rather
46  DDRD c@ $80 or DDRD c!      \ pin OC2A output
47  [ %00000100                 \ CS1[210] clock_ts2/256
48    %00001000 or              \ WGM1[32] CTC mode
49  ] literal TCCR1B c!
50                              \ register isr
51  ['] tick_isr TIMER1_COMPAAddr int!
52  TIMSK1 c@ $02 or TIMSK1 c!  \ enable OCIE1A interupt
53;
54
55: -ticks
56  TIMSK1 c@
57  [ $02 invert ] literal
58  and TIMSK1  c!              \ disable OCIE1A interrupt
59  $00  TCCR1B c!              \ disable timer/counter1
60  $02  TIFR2  c!              \ clear interrupt flags
61;
62
63\ no phase shift accumulator
64\ one second == 32 ticks
65\ half second == 16 ticks
66\ that is a toggle on bit 4 of ct.ticks.follow
67: half.second.over? ( -- 0|1|2 )
68  \ return: 0 == false
69  \         1 == half second over
70  \         2 == second over
71  ct.ticks.follow c@
72  $0010 and 0= 0=  \ extract significant bit as t/f
73  dup last.tick[4] @ = if
74    \ no change, done
75    drop 0
76  else
77    dup 0= if
78      \ falling edge, second over
79      2
80    else
81      \ rising edge, half second over
82      1
83    then
84    swap
85    ( sig.bit-t/f ) last.tick[4] !
86  then
87;
88
89: second.over? ( -- t/f )
90  ct.ticks.follow c@  $0020 and 0= 0=
91  dup  last.tick[5] @  = if
92    drop 0
93  else
94    last.tick[5] !
95    -1
96  then
97;