■ BY JON MCPHALEN THE SPIN ZONE
ADVENTURES IN PROPELLER PROGRAMMING
When we think of multitasking with a Propeller, we tend to think about running multiple
cogs — it's easy to launch a new cog when we need to run another task. With a bit of
effort, we can run multiple tasks in the same cog. We've done this in Spin using the
system counter to determine the need to handle a process. The same strategy applies in
PASM. That said, what about complex tasks like full-duplex serial communications? Well,
the Propeller has a special instruction to facilitate what is usually called “ping-pong
multitasking.” I'm going to be honest here ... I avoided it for a long time because it can be
a little mind-numbing at first. Still, as necessity is the mother of invention, it is also the
mother of education. I needed a specialty UART, so I forced myself to come to grips with
ping-pong multitasking. I'm going to share my experiences here.
In essence, what’s happening here is a non-blocking
version of waitcnt, though it’s not quite as precise
because it cannot put everything else on hold while
waiting for a specific value in the cnt register. This is why
we do not test for equality to zero. If other loop processes
caused us to miss this specific point in time, we’d be
forced to wait for the system counter to roll all the way
over. That takes on the order of 53. 7 seconds at 80 MHz.
Okay, ready for fun? Load up jm_pasmblink_demo.
spin available at the article link. This version duplicates
what we just did in Spin, and also demonstrates how I do
object development. When starting out with a new object,
I don’t want to be bothered with multiple files. What I do,
then, is stick my object development at the end of a
standard template file, and I give my interface methods
names that start with what I’d normally call an instance of
the object. The method names are separated from the
object name with an underscore.
For example, in a ready-to-run object which I
instantiate as blinker, I will have a method called start(). In
my development code, that method will be called
blinker_start(). When the development is complete, I will
remove “blinker_” from the method names and then save
the object code to its own file. I find this development
process useful; perhaps you will too.
An upgrade to the PASM version is the ability to call
the blinker_start() method with the pins and timing (ms).
To keep things simple — since the goal here is to learn
PASM cooperative tasking, not find another way to blink
LEDs — the method expects two valid pin numbers and a
valid half-period time for each.
Normal convention is to call an object’s stop()
method before starting it, as it may already be running.
For our blinker demo, then, we have these two interface
Let’s start with familiar ground: Spin! Here’s a little code loop that causes LEDs on the Parallax Activity board to
blink at different rates (jm_spinblink_demo.spin):
led0tix := 100 MS_001
led0tgt := led0tix + cnt
led1tix := 1000 MS_001
led1tgt := led1tix + cnt
if ((led0tgt - cnt) < 0)
led0tgt += led0tix
if ((led1tgt - cnt) < 0)
led1tgt += led1tix
We start by defining the pins as outputs. For each
LED, we’ll need two variables: the first is the half-period
timing in system ticks; the other is a target point (in the
system counter register) at which we want to toggle the
LED. It is initialized to the current system counter plus the
half-period duration. This second variable will be updated
every time we toggle the LED.
Inside the loop, we check to see if the difference
between the target value and the current system counter
is less than zero. This value will be positive or zero until
the system counter runs past our target. At that point, we
update the target and toggle the LED.
pub blinker_start(pin0, ms0, pin1, ms1)
12 May 2017