cogpntr (the local
array pointer) to the
instruction so we can
work on an element
of that array.
The first time
through the loop,
we're writing the cog
address of the channel
zero accumulator to
those instructions.
After reading the
channel duty cycle
from the hub, we add
that into the current
accumulator, then test
the accumulator carry
bit by comparing it to
a mask. In doing this,
we will set or clear the
Propeller's Carry flag.
After clearing the
accumulator carry bit, we update the output by using the
muxc instruction to copy the Propeller Carry flag to the
current output pin. In short, when adding the duty cycle
to the accumulator causes a carry, the output for that
channel will be on. The larger the duty cycle, the more
frequently this will happen.
The end of the inner loop updates the hub pointer. As
we're using bytes, we add one to point to the next. The
following line updates the cog pointer. You may be
wondering why we're using one when all cog variables are
longs (four bytes). Well, here’s why: We can only treat cog
variables as longs, so adding one to cogpntr will point to
the next accumulator address. The channel mask is
updated by shifting it to the left.
After all the channels have been processed, we start
over. The code runs constantly — as fast as it can — given
the crystal input, PLL setting, and number of channels
used. To give you an idea of the frequency variability,
the worst case on the low end is when the duty cycle
is set to one. With a typical system ( 80 MHz), I
measured the modulation frequency at about 575 Hz.
The fastest frequency comes when the modulation is
set to 128 because this will cause the output to toggle
every cycle. On the same board, I measured the
modulation frequency at about 73 kHz for a duty cycle
setting of 128.
There are two lines of code that I skipped over, so I'll
go back to those now. What I don't like about general
accumulator-divider code is that there is a periodic low
output — even when the input is set and maintained at the
maximum level. To save paper, let's look at a three-bit level
with the duty cycle set to seven (maximum value for three
bits):
■ FIGURE 1.
Acc = %0_000 + %111 = %0_111 : Carry = 0
Acc = %0_111 + %111 = %1_110 : Carry = 1
Acc = %0_110 + %111 = %1_101 : Carry = 1
16 November 2011
Acc = %0_101 + %111 = %1_100 : Carry = 1
Acc = %0_100 + %111 = %1_011 : Carry = 1
Acc = %0_011 + %111 = %1_010 : Carry = 1
Acc = %0_010 + %111 = %1_001 : Carry = 1
Acc = %0_001 + %111 = %1_000 : Carry = 1
Acc = %0_000 + %111 = %0_111 : Carry = 0
As you can see, we get a zero output every
2^channels bit, even when we maintain the maximum
duty cycle input. This is easy to correct in code. After
reading the channel duty cycle, we compare it to the
maximum for the values we're using (255 for a byte in our
case). If this is true, then we preset the modulation carry
bit by writing the carry value into the working duty cycle
variable. When this is added to the accumulator, it will
force the modulation carry to be set and the output to be
on — without periodic dips — when the duty cycle is set to
maximum.
Okay, we have another way to control LEDs, so let's
put it to work. As a kid, one of my favorite ‘70s TV shows
was Battlestar Galactica. As an adult, I like the new version
even more. An icon of BSG (and later, Knight Rider, also
created by Glen Larson) is what's known as the "Larson
Scanner" — a string of lights/LEDs where one lit element is
"ping-ponged" back and forth in a scanning motion. The
Cylon eye from BSG is a Larson scanner.
Most of the time we simply turn on an output and use
left and right shifts inside a time loop. Yes, everybody has
done this with every processor you can imagine. While
working with an FX crew over the summer, I was
approached about building a circuit for a Cylon helmet
from the original series. Having recently written the object
we just worked through, I decided to use that instead of
taking the easy route. For convenience, we'll use just eight
elements in the demo program which will let you run the
program (jm_cylon_8x.spin) on a QuickStart board, a
demo board, or the Propeller PDB. If you don't have one
of these, connect eight LEDs to your Propeller as shown in
Figure 1.
I enjoy small lighting projects, and something I often
do is embed lighting sequences right into the code. We
can do this with the Propeller by using a DAT section; this
allows us to embed raw data into the program that we can
access at will. Here's the data for the Cylon eye animation:
cylon byte
byte
byte
byte
byte
byte
byte
byte
byte
$00, $00, $01, $08, $FF, $00, $00,
$00
$00, $00, $00, $01, $08, $FF, $00,
$00
$00, $00, $00, $00, $01, $08, $FF,
$00
$00, $00, $00, $00, $00, $01, $08,
$FF
$00, $00, $00, $00, $00, $00, $FF,
$08
$00, $00, $00, $00, $00, $FF, $08,
$01
$00, $00, $00, $00, $FF, $08, $01,
$00
$00, $00, $00, $FF, $08, $01, $00,
$00
$00, $00, $FF, $08, $01, $00, $00,
$00