want to set the on side of the cycle to 90 seconds (in
seconds), and have the off side of the cycle set to 210
seconds (total cycle time is 300 seconds; five minutes).
Okay, it’s just SMOP (a small matter of programming).
I copied the softrtc method and created a new one called
freerun. This version is a free-running timer with very little
bounds checking:
PRI freerun | t0
t0 := cnt
repeat
if (Clock[REG_RST] < 0)
longfill(@Clock, 0, 6)
waitcnt(t0 += MS_001)
if (++Clock[REG_MS] == 1_000)
Clock[REG_MS]~
if (Clock[REG_SC] < posx)
if ((++Clock[REG_SC] // 60) == 0)
if ((++Clock[REG_MN] // 60) == 0)
if ((++Clock[REG_HR] // 24) == 0)
++Clock[REG_DY]
else
Clock[REG_RST]~~
This method is structurally similar to softrtc but we
don’t do the same bounds checking on the seconds,
minutes, and hours registers. We do, of course, check the
milliseconds register and treat it the same way. Seconds,
however, has a new boundary: POSX. This is an internal
value that is the largest positive value in the Propeller’s 32-
bit integer system — it’s just a hair over 2.1 billion (the
32nd bit is the sign bit).
The reason for this limit is that we’re going to use the
modulus operator (//) to check for updates to the other
registers when the seconds register changes. Since //
treats integers as signed, we have to limit the seconds
register to 31 bits. Don’t be concerned about this
limitation. We could let the timer run for 68 years before
the seconds register forces the timer to reset itself.
When I showed Wayne this code, he was happy
about the flexibility, but concerned about the accuracy
with all that math when value changes are cascading;
modulus relies on division which is notoriously slow. I did
a quick check using the time tester we played with earlier
and determined that even under the worst case conditions
— when every register updated on a given cycle — the
whole works took about 1/8 of a millisecond. That leaves
us plenty of time to get back to and be sitting on the
WAITCNT for the next 1 ms tick.
I also checked the stack — no change there; still uses
just seven longs. After I was happy with the routines, I
folded them into an object that I could use in other
programs, and adjusted the stack requirement down to 16
(still safe, but not wasteful). You’ll find this code in
jm_softtiming.spin of the downloads.
ARE WE DONE?
In early May, I was wrapping up a program for a
commercial product and had a situation where I wanted
to alert the user of an error condition (using a new variant
SPIN ZONE
of my bi-color LED object), but I didn’t want it to remain
static; after a short period — say two seconds — I wanted
the LED to return to the normal program state. As the
program is already using several cogs and I wanted to
leave the others free for future updates and features, what
I needed was a way to kill the error LED without using
another cog.
Here’s what I came up with: Since my mainline code
is running in a loop about every 50 ms, I decided I could
just set an error timer and decrement it each time through
the loop. Once it reached zero, I would return the LED to
the normal state.
To keep things easy, I decided to use milliseconds as
my timing unit. This meant I needed a way to determine
the elapsed milliseconds since the last check. You know
where this is going, right? We’ve done this with the code
timer. Here’s the method that returns the number of
milliseconds elapsed since some starting point:
PUB elapsedms(tstart)
return ||(cnt - tstart) / MS_001
There is a caveat here: the limit (at 80 MHz) is about
26. 8 seconds. If we wait longer than that between tstart
and the call to elapsedms, we’ll get a bogus return value (I
ran into this the hard way when trying to using this
method with another feature in my product).
To use this method, I set up two global variables:
LedT0 and Led Timer. The first is set to the system counter
to create the tstart checkpoint; the second is the number
of milliseconds I want the LED to be in the error state.
Here’s a bit of code that demonstrates the use of
elapsedms (see jm_et_demo.spin):
repeat
c := term.rxtime(50)
if (c => 0)
LedT0 := cnt
case c
“““1” : led.red
LetTimer := 1_000
“2” : led.green
LetTimer := 2_000
“3” : led.yellow
LetTimer := 3_000
other : if (c => 0)
led.set2phase(RED,100,YEL,100)
LetTimer := 1_000
‘ check led
if (LedTimer > 0)
etms := elapsedms(LedT0)
if (etms < LedTimer)
LedTimer -= etms
LedT0 := cnt
else
LedTimer := 0
led.off
July 2010 17
The REPEAT loop will run about every 50 ms if no key
is pressed; this is controlled by using the rxtime method