each LED, and we do need a 50 microsecond reset period
before each frame. In practical terms, this means that we
could update a 16x16 array of WS2812 LEDs in under
eight milliseconds (~125 Hz). That's pretty zippy.
:loop test nbits, #%111 wz
if_z rol colorbits, nbits
rol colorbits, #1 wc
if_c mov bittimer, bit1hi
if_nc mov bittimer, bit0hi
or outa, txmask
add bittimer, cnt
if_c waitcnt bittimer, bit1lo
if_nc waitcnt bittimer, bit0lo
andn outa, txmask
waitcnt bittimer, #0
djnz nbits, #:loop
Let's look at the working code:
rgbmain mov bittimer, resettix
add bittimer, cnt
waitcnt bittimer, #0
mov addr, hubpntr
mov nleds, ledcount
frameloop rdlong colorbits, addr
add addr, #4
At rgbmain, we copy resettix into bittimer, then
synchronize the timer by adding the system counter (cnt)
to it. The waitcnt instruction takes care of the reset timing.
At the top, we're going to rotate to the right (ror) the
value in color bits. Rotate differs from shift in that bits
move from one end to the other (through the carry bit) —
they're not lost to the bit bucket. With ror, bit0 will be
rotated into bit31. In this case, we start out with
$00_RR_GG_BB and end up with $BB_00_RR_GG (okay,
how many geeks like me see BORG?).
Boom! Easy! Next, we move the value in hubpntr (hub
address of the color array) into addr. We move the
number of LEDs in the string to nleds.
Now we're off! At frameloop, we read an RGB color
value from the hub into colorbits. The hub address in addr
is advanced to the next long by adding four.
The next section of code is not too hard, but does
employ Tony's trick to swap the transmission order of the
red and green bytes:
shiftout ror colorbits, #8
mov nbits, #24
I know what you're thinking ... "Hey, you said we have
to shift out the green channel first!" You're right. We do,
and we will. After setting the value of nbits to 24 (three
bytes times eight bits per byte), we drop down the code at
:loop which tests the value of nbits. What Tony came up
with here is really clever. When nbits is 24, 16, or eight,
we're going to rotate the value left (rol) by nbits. On our
first time through, then, nbits will be 24 which will set the
zero flag and cause the next line to run. This will modify
the value from $BB_00_RR_GG to $GG_BB_00_RR. Note
that we have the correct color element in the correct
position for output.
The next line rotates the value by one to the left and
captures the value of bit31
in the C flag. This flag is
used to load the appropriate
value into bittimer. After that,
we start the bit by taking the
TX line high. The C flag is
used again to select a line to
run waitcnt; you'll remember
that the second parameter of
waitcnt is the reload value.
This structure lets us do the
delay and reload the timer
based on what's required for
the low side of the bit.
At the end of the high
period, the TX line is taken
low and waitcnt runs the
low-side timing. Finally, we
check to see if there are any
bits left to transmit. If there
are, jump back to :loop.
Let me show how Tony's
trick works to re-arrange the
bytes for correct transmission
output to the WS2812 color
registers. On going in, we have ■ FIGURE 6. Running NeoPixels.
54 September 2013