scaling divisor, the call to the rctime method looks like this:
pot.rctime(0, 1, 0, 1, @potVal)
Note that we’re passing the address of (using the @ operator) the target variable instead of copying the return value
of the rctime method — there’s a very good reason for this.
More on that later.
Now have a look at the rctime method code. We start
by qualifying the pin input; this is a choice, of course, and I
think it’s a good idea not to connect an RC circuit to the
EEPROM I2C lines or Propeller programming pins, so using
0.. 27 with lookdown prevents that. Remember that in Spin,
lookdown returns a 1 to N value, not 0 to N-1 as with
LOOKDOWN in PBASIC (or the Propeller’s lookdownz
method). By using lookdown, any non-zero return means
that pin is within range; this is one of my favorite tricks with
Spin, and it’s certainly more elegant than:
if (pin => 0) and (pin =< 27)
... especially as lookdown (and lookdownz) allows a compound list with comma-delimited elements.
With the pin qualified, the next step is to make sure that
the state, the zero offset, and the divisor are legal. This is
very easily handled with the limit operators.
At this point, we get into the mechanics of the RC measurement. The first step is to clear the work variable — rc_temp
— and then make pin an output at the desired state; this will
charge (1) or discharge (0) the RC circuit. Using a 220-ohm
series resistor and a 0.1 µF cap, the time required for
charge/discharge is 110 µS; we’ll go ahead and use 500 µS
to account for component variations or a larger series resistor. Once the capacitor is charged, we make the pin an input,
copy the system counter (cnt) value into rc_temp, and then
wait for the pin state to change (this happens at 1/2 VDD).
To monitor the state change, we’ll use the waitpne
(wait until pins are not equal) method. This method lets us
hold the program until one or more selected pins are not
equal to the target value given. The syntax for waitpne is:
waitpne(target, mask, 0)
where target is the desired state of the I/O pins, mask is the
value to be AND’d with the Propeller’s inputs (the result will
be compared to target), and 0 is for the port A pins. When
the Propeller’s pins AND’d with mask no longer match the
target value, the method will fall through. Our program is
just looking at one pin, and yet we can monitor as many as
we need, up to 32. If, for example, we wanted to monitor a
four-bit binary switch connected to A3..A0, and wanted to
hold the program when the switch was set to %1010, the
waitpne method would be set up like this:
waitpne(%1010, %1111, 0)
Note that %1111 is used as the mask so that all four pins,
A3..A0, are used in the comparison.
In the rctime method, we only need to watch one pin, so
we set the target by shifting the starting capacitor state left by
the pin number. The pin mask is created with the decode operator (|<) — this works by shifting %1 left the number of bits
specified (again, the pin number). If we were using pin A3 for
the rctime I/O pin, the mask value would end up as %1000.
Okay, after the pin state changes, we’ll subtract the
original value of rc_temp from the system counter to get the
number of counts elapsed during the discharge/charge cycle.
The absolute operator (||) is used to keep the return positive
(when bit 31 of cnt is set, the value is considered negative by
Spin math operations which are all signed). Now we can subtract the zero offset and divide the raw value by the divisor.
The result is moved (as a long) into the target address; this is
kind of like using POKE in some flavors of BASIC. Note that
the target address is in main RAM; this will let us move the
rctime method to its own cog and work in the background,
since all cogs have (shared) access to the main RAM.
Before we get to that, though, you may be wondering
how rctime terminates since all the code is wrapped in a
repeat loop. When rctime gets called manually a global
variable that is part of the object — mode — is left at zero.
At the end of the rctime method, that variable gets
checked; if it’s zero the repeat loop is terminated with quit.
Now for some real fun ... how about we launch the
rctime method into its own cog so that it runs happily “in
the background” and constantly updates our target variable.
Sound like fun? I can tell you that it is.
The start method of the rctime object handles the details:
PUB start(pin, state, zofs, div, rcAddr) : okay
mode := 0
okay := cogon := (cog :=
cognew(rctime(pin, state, zofs, div, rcAddr),
@stack)) > 0
mode := 1
You may find it odd that the first thing the start method
does is call the stop method. What we have to keep straight
is that this code is assigned to a single object (pot, in our
case), and if we restart that object we need to stop it first.
And yes, we can have multiple versions of the same object
in memory at the same time; if we were using them in
“background” mode, each would be in its own cog, and we
could stop or restart one without affecting any other.
The next step is to clear the mode variable — we don’t
want to let the top-object think that rctime is running in its
own cog unless that actually happens. And we make that
happen with the cognew method. With cognew, we can
“launch” rctime into its own cog if a free cog is available.
When that’s the case (most of the time it will be), cog, cogon,
and okay will get set to the cog number used by rctime.
Once we know that rctime is up and running in its own cog,
we can set mode to 1 to keep the method running and automatically updating our target variable. This is why we pass the
target variable’s address: once that’s known to the method, it
June 2006 15