a sneaky way. To illustrate, a forum member had a bit of
code that looked something like this:
I tend to use loops like this when I absolutely need
the loop to run at a fixed frequency — things like motor
PWM or servo drivers are good examples. When things
are a little more forgiving, I tend to use my timer object
and fix any variations using the adjust() methods.
repeat
waitcnt(cnt + clkfreq)
if (++seconds 60)
seconds : 0
He started his program and synchronized with
another timing device. A few days later, he noticed that his
Propeller clock was off by more than one would expect
for crystal accuracy.
Here's the problem: The way the loop is constructed,
the waitcnt itself takes a fraction more than a second; the
interpreter is reading the cnt register and the clkfreq
register, adding them, and then running waitcnt. It's
microseconds per iteration, but after enough runs through
the loop this extra time will become noticeable.
I have a friend named Brian who is a prop builder in
the Halloween industry, and he recently asked for help
with a count-down timer for a zombie escape room. This
was the perfect application for my little timer object. Brian
is not the only person requesting count-down; I helped
another guy with a paratrooper training device, and my
friend, Matt with a timer for a Propeller-powered arcade
game he created.
We're not done. It takes time to read the seconds
variable, increment it, compare it to 60, and then change
it to zero if necessary. Finally, the code has to jump back
to the waitcnt line. All of these things pad the loop,
causing it to run fractionally longer than one second. The
fix is very easy, and only requires one variable (a long):
My timer object calculates the difference between
successive reads of the cnt register and accumulates
milliseconds. So, how do we count down? Simple: We
preset the milliseconds to a negative value. Internally, the
timer is counting up toward zero; by removing the sign,
we have a value that appears to be counting down.
In my timing-oriented projects, I tend to have a
method like this:
t : cnt
repeat
waitcnt(t + clkfreq)
if (++seconds 60)
seconds : 0
pub reset time
if (state S HOLD)
time.hold
time.set secs(-TIME SET)
This is a very small change with a very big impact.
This version of the code will update the seconds variable
dead-on every second (plus or minus the crystal accuracy).
Here's why: Before we enter the loop, we establish an
initial sync point by reading the cnt register there (note
that this is the only place where the cnt register is read).
In the escape room and paratrooper training
programs, there are hold and run modes; I only allow the
time to be reset when the clock is not actively running.
When that's the case, I put the timer on hold, preset it to
the negative value of the starting time (TIME_SET), and
then set a flag (last) to a value that differs from the initial
time setting.
The waitcnt instruction updates the sync point variable t
(note the += operator) with the number of ticks in one
second (clkfreq). When that value is reached, the code
falls through to dealing with the seconds variable. After
that, we go back to the top of the loop and update the
sync variable again while doing the next waitcnt.
Let's look at some numbers so this is absolutely clear.
When starting the timer, I ran into a problem:
Displaying the time in whole seconds caused the display
to immediately drop one second. The reason is that the
timer object does everything in milliseconds. Let's say we
preset the timer to - 30 seconds and release it to run. One
millisecond later, the time will be - 29.999. When we
divide by 1,000 to get seconds, we get - 29 — this makes
our display jump time right at the beginning.
We'll imagine that we got very lucky when reading the cnt
register into t; it was 0. Now, we add clkfreq and the sync
point becomes 80,000,000 (for a typical system). The next
time through, the sync point will be set to 160,000,000,
and so on. The waitcnt delays are all fixed from the line
before entering the loop; hence, the loop timing stays solid.
Here's how I solved that problem:
pub check time | nowms
nowms : time.millis
now : -nowms / 1000
if (nowms // 1000)
We call this a synchronized loop, and it will run at the
desired speed as long as the code inside the loop
consumes less time than the waitcnt is set for. If we
violate the waitcnt timing, our loop will appear to hang
because waitcnt has to wait for the 32-bit cnt register to
go all the way around to the original target — this can take
about 54 seconds in an 80 MHz system.
if (now <> last)
show time
last : now
if (now 0)
state : S HOLD
We start by capturing the current value of the timer
14 October 2015