settings. Since constants are pre-calculated by the
compiler, this adds no overhead to program at run time.
Now, we can create a method to emulate PAUSE:
PUB pause(ms) | t
t := cnt
repeat ms
waitcnt(t += MS_001)
On entry, we immediately capture the value of the
system counter — this is the starting point for the delay.
Next, we drop into a REPEAT loop for the desired number
of milliseconds. In the WAITCNT instruction, we add the
number of ticks per millisecond to the starting value. What
this does for us is account for the time used to run the
loop and set up the WAITCNT instruction each time
through. Remember, WAITCNT is looking for a specific
target and the system counter is always running as we’re
working our way through the code.
If we re-read the system counter in the WAITCNT
instruction, several microseconds would be added to each
loop — for long delays, this could become a problem. By
reading CNT before the loop, we ensure that each time
through runs exactly 1 ms. The Propeller manual has a
great description of this process called synchronized
delays; we’ll put this strategy to use often.
HOW LONG?
About 10 years ago, I created an alarm product that
used the BASIC Stamp 2 microcontroller. The product
didn’t need much in the way of resolution (100 ms per
“tick”), but a tick needed to be 100 ms no matter what
path the program took. To make this happen, I had to
measure the various paths in the code and pad them to
get to a consistent 100 milliseconds per path.
This process was tedious, to say the least. I used a
spare output pin, setting it high (at the start of a code
section) and low (at the end of a code section), and
monitoring it with an oscilloscope to measure code
execution time. Boy, was I glad to get that project finished.
It took as long to tune the program timing as it did to
write the baseline code!
With multiple cogs and the WAITCNT instruction, it’s
not likely we’d ever go through this process when using
the Propeller, but we might want to measure a bit of code
to check for performance. This is really helpful when
experimenting with variations on code. With the Propeller,
we don’t need a spare pin or an oscilloscope. We can do
this with a couple variables and a terminal program
(connected to the programming port). The ability to time a
code segment requires just a few lines to be added to our
template:
PUB main | t0, t1
term.start(31, 30, %0000, 115_200)
pause(1)
term.tx(CLS)
repeat
t0 := cnt
‘ code to test goes here
t1 := cnt
t1 := ||(t1 - t0)
term.tx(HOME)
term.dec(t1)
term.tx(CLREOL)
pause(1_000)
After starting a serial object so that we can send the
timing result to a terminal, the program drops into a
REPEAT loop where we set t0 to the present system
counter. After this, we’ll insert a bit of code to test (not
yet, though). After the code, we grab the system counter
again and put it into variable t1. Now we can take the
difference between the two. Since CNT is a free-running
32-bit counter, we need to use the absolute (||) operator
on the difference between t1 and t0. Note, too, that this
process should only be used on short-term events.
When I run the program on my Propeller platform, I
get 368 counts with no code inserted between the t0 and
t1 checkpoints. Knowing this, we can update the
calculation as follows:
t1 := (||(t1 - t0) - 368) #> 0
The subtraction of 368 is obvious; the rest of the line
ensures that we do not dip below zero. Another point:
Since we’re working with system clock ticks, the value
does not change with frequency,
For fun, I tested the pause method and found that —
at 80 MHz ( 80,000 ticks per ms) — I got a value of 81,088
for one millisecond. This means that there is an overhead
of 1,088 clock ticks for the call. At 80 MHz, this is about
13. 6 microseconds. This is the time required to set up the
instruction, jump to the pause method, and then return to
the program.
Can we account for this overhead? Sure; knowing the
value, we can update the pause method like this:
PUB pause(ms) | t
t := cnt - 1088
repeat ms
waitcnt(t += MS_001)
Now, this isn’t perfect. A delay of one millisecond
comes back at exactly 80,000 counts (okay, that’s
perfect), but a 1,000 ms delay comes back at 80,000,048
counts. Remember, we are dealing with an interpreted
language; I think that a 0.6 microsecond error on an inline
delay of one second is probably okay.
TICK TOCK
July 2010 15
I mentioned earlier that my friend Wayne was working
on a timer project. Originally, we was taking the high and
low cycle times — which could be expressed in seconds,