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
32
ticks/second. For reasons not yet apparent, a power of2
is 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 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 91 92 93 94 95 96 97 | \ 2017-08-13 ewlib/clock_tick1_main.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/>.
\
#32 constant ticks/sec
variable ct.ticks
variable ct.ticks.follow
variable last.tick[4]
variable last.tick[5]
\ overflow interupt service routine
: tick_isr
1 ct.ticks +!
;
: tick.over? ( -- t/f ) ct.ticks.follow @ ct.ticks @ - 0< ;
: tick.over! ( -- ) 1 ct.ticks.follow +! ;
\ enable ticks
\ crystal: 11059200 /sec
\ prescaler: 256
\ 43200 /sec
\ TOP+1: 1350
\ 32 /sec
: +ticks
0 ct.ticks !
0 ct.ticks.follow !
0 last.tick[4] !
0 last.tick[5] !
\ --- 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!
\ register isr
['] tick_isr TIMER1_COMPAAddr int!
TIMSK1 c@ $02 or TIMSK1 c! \ enable OCIE1A interupt
;
: -ticks
TIMSK1 c@
[ $02 invert ] literal
and TIMSK1 c! \ disable OCIE1A interrupt
$00 TCCR1B c! \ disable timer/counter1
$02 TIFR2 c! \ clear interrupt flags
;
\ no phase shift accumulator
\ one second == 32 ticks
\ half second == 16 ticks
\ that is a toggle on bit 4 of ct.ticks.follow
: half.second.over? ( -- 0|1|2 )
\ return: 0 == false
\ 1 == half second over
\ 2 == second over
ct.ticks.follow c@
$0010 and 0= 0= \ extract significant bit as t/f
dup last.tick[4] @ = if
\ no change, done
drop 0
else
dup 0= if
\ falling edge, second over
2
else
\ rising edge, half second over
1
then
swap
( sig.bit-t/f ) last.tick[4] !
then
;
: second.over? ( -- t/f )
ct.ticks.follow c@ $0020 and 0= 0=
dup last.tick[5] @ = if
drop 0
else
last.tick[5] !
-1
then
;
|