September 2015 29
Bill of Materials
Perfboard All Electronics #SB- 37
FIGURE 4.
Propeller Mini Parallax #32150
12-pin strip socket (x2) Digi-Key 929974E-01-12-ND
WS2812 24-pixel ring Adafruit #1586
Adjustable DC power supply Amazon #B008BHAOQO
12V wall wart EFX-TEK 750-00007
2.1 mm pigtail jack All Electronics #CB-209
330 ohm resistor All Electronics #291-330
It's that easy. In the actual Tibbers code, this happens
four times as there are two methods generating flame
levels, and two more generating ember levels; the latter
uses lower high-side values and much slower timing.
Let's get back to Matt's project and how we can put it
to use in a simple Halloween prop. WS2812s require a 24-
bit color value. What we need to do is convert an eight-bit
heat value into a 24-bit color value. I do this with a
method called morph() which uses a scaling code from
the WS2812 object:
A quick note on style: Using a PRIvate method in the
top object file doesn't do anything special; I use this as a
marker to remind myself not to call this method in the
usual way; cognew must be used to launch this method
into its own cog.
At the top, we initialize mode to zeroes to force each
channel into brighten mode; this will cause the heat level
to increment to the target, which we initialize into the high
range. The rest of this code loops through the channels,
incrementing toward the target when the mode bit is 0
and decrementing to the target when the channel mode
bit is 1. When a new target is set, the speed is updated as
well. I tend to go with a smaller value (faster) when
incrementing than decrementing, but this is purely an
aesthetic choice.
bytemove(dmx.address+0, @heat1, 8)
pub morph(c1, c2, phase) | newcolor
if (phase =< 0)
newcolor := c1
elseif (phase => 255)
newcolor := c2
Finally, when choosing a high-side target, the code
does a random test and will occasionally use the 25%-
50% range. Again, the technique I used for the
randomized selection (lower two bits or new random
value are set) is arbitrary; this is about art and aesthetics,
and will take the most time to tune to your liking (art is
always more time-consuming than technology).
else
c1 := strip.scale_rgb(c1, 255-phase)
c2 := strip.scale_rgb(c2, phase)
newcolor := c1 + c2
return newcolor
What we have now is an array of values that are
randomly bouncing between two points, and if we run
these values through a PWM driver into LEDs, we can
suggest flames. For the Tibbers project, I created duplicate
sets of these algorithms with slightly different values. By
using byte arrays, I was able to plug the heat values
directly into the DMX stream.
This method expects two 24-bit colors and a [DMX
compatible] phase value that is between 0-255. If the
phase is between 1 and 254, we use it as a color mixer;
below 128, the color will be biased toward c1; above 127,
the color will be biased toward c2. For Matt and me, this
simple two-point ramp works well. For more complex
ramps, this method could be called from a case structure
that divides the heat value into three or more ranges.
Any of my objects that use an output buffer have a
method called .address() which provides the hub address
of the output buffer. With bytemove, we can copy the
heat array directly into the DMX stream:
To keep the code simple for Matt's project, I also
created a scrambled array of heat indexes. This allows me
to have more than eight randomized WS2812s without
multiple heat generators. The reason I go to this trouble
gets back to the human mind: Just as it's open to
suggestion, it's even better at pattern recognition.
If we have a string of 24 LEDs that is divided into
three groups of eight, and each group is doing the same
thing, the mind will catch the duplication and suspension
of disbelief will be interrupted. We may not even be
conscious of this; we simply know that something is “not
right.”
So, why don't I manually scramble the array? Well,