Propeller RGB VU Meter
- Propeller Microcontroller
- MCP3002 ADC
- WS8211 72/m RGB LED strip
- Logic level shifter
- 4 resistors
- 2 capacitors
[video=youtube_share;dqCtIZwMXPw]

The camera doesn't work so well with all the flashing lights, but my next routines help a lot.
Big thanks to JonnyMac and whoever wrote the demo code for the ADC. It's great to be able to control a lot of LEDs without having to do a lot of soldering, the WS8211 strips are great for that and the Propeller handles them with ease.
The code below is a different effect than the video, and needs some work.
CON '' MCP3208 - ADC code snippet demo **********************************
'>>> READ THE MCP3208 DATA SHEET CAREFULLY for correct SPI timing pulses
'' Declare Objects & Variables
_clkmode = xtal1 + pll16x 'PLL Multiplier
_xinfreq = 5_000_000 'XTAL Frequency
''On Propeller Board On MCP3208 ic
''Symbol Pin I/O Function Symbol Pin I/O Function
sdi = 4 'Data in <-- SDO 2 Out Serial Data Output/SPI default
sdo = 5 'Data out --> SDI 4 In Serial Data Input /SPI default
clk = 6 'Clock out --> CLK 5 In SerialClock Input /SPI default
''cs0 xxx = 7 'CS out --> CS0 X In Chip Select Input /other ic (not used here)
cs1 = 8 'CS out --> CS1 10 In Chip Select Input /Analog-Digital Converter
CLK_FREQ = ((_clkmode - xtal1) >> 6) * _xinfreq ' system freq as a constant
MS_001 = CLK_FREQ / 1_000 ' ticks in 1ms
US_001 = CLK_FREQ / 1_000_000 ' ticks in 1us
RX1 = 31 ' programming / terminal
TX1 = 30
SDA = 29 ' eeprom / i2c
SCL = 28
LEDS = 1 ' LED tx pin
OBJ
DEBUG : "FullDuplexSerial" 'add this in the same directory
lcd : "SparkFun_serial_lcd"
strip: "jm_ws2812" ' WS2812 LED driver
VAR
LONG ii, jj, command, channel, ADCvalue
byte left
byte right
word addr
byte ledp
byte ledpb
dat
Chakras long strip#RED, strip#ORANGE, strip#YELLOW
long strip#GREEN, strip#BLUE, strip#INDIGO
PUB Main | posa,color1,color2,color3
strip.start(LEDS, 32)
waitcnt(clkfreq/100 + cnt)
addr := strip.address
dira[sdi] := 0 'setSDI pin = input
outa[sdo] := 0 'setSDO output = low
dira[sdo] := 1 'setSDO pin = output
outa[clk] := 0 'setCLK output = low
dira[clk] := 1 'setCLK pin = output
outa[cs1] := 1 'setCS output = high = deselect chip
dira[cs1] := 1 'setCS pin = output ADC chip CS
waitcnt(clkfreq + cnt)
repeat
left := adc3208(1)
strip.fill(0, left/6, strip#blue)
strip.set(left/6, $FF_00_00)
pause(1)
strip.fill(left/6, 32, 0_0_0)
PUB ADC3208(chan) 'get ADC voltage value
channel := chan 'save "channel"variable
outa[cs1] := 1 'Set CS1 output= high - preset value
outa[clk] := 1 'Set CLK output= high - preset clock pulse bf CS2
outa[cs1] := 0 'CS1 output low - start ADC
outa[clk] := 0 '>>> READ THE MCP3208 DATA SHEET CAREFULLY <<<-----------------------
command :=(000 + chan) << (32-5) 'command Start, Single, Chan(xx)
repeat 5 'send 5 command bits - per Specification
outa[sdo] := (command <-= 1) & 1
outa[clk] := 1 'clock the command bit
outa[clk] := 0
outa[clk] := 1 'set CLK output= high - one clock pulse after Command = 5th Clock pulse
outa[clk] := 0 'set CLK output= low - set sample and hold ADC value
outa[clk] := 1 'set CLK output= high - one clock pulse after Command = 6th Clock pulse
outa[clk] := 0 'set CLK output= low - end conversion = Null bit
ADCvalue := 0 'clear work variable =0
repeat 10 'read conversion 12 data bits '***10 bit
ADCvalue := (ADCvalue << 1) | ina[sdi] 'input ADC data bit
outa[clk] := 1 'clock ADC data bit
outa[clk] := 0
outa[clk] := 0 'just to be sure = 0
outa[cs1] := 1 'CS1 output high - end ADC
outa[clk] := 0
return ADCvalue
' End of demo
pub color_chase(p_colors, len, ms) | base, idx, ch
'' Performs color chase
repeat base from 0 to len-1 ' do all colors in table
idx := base ' start at base
repeat ch from 0 to strip.num_pixels-1 ' loop through connected leds
strip.set(ch, long[p_colors][idx]) ' update channel color
if (++idx == len) ' past end of list?
idx := 0 ' yes, reset
pause(ms) ' set movement speed
con
' Routines ported from C code by Phil Burgess (www.paintyourdragon.com)
pub color_wipe(rgb, ms) | ch
'' Sequentially fills strip with color rgb
'' -- ms is delay between pixels, in milliseconds
repeat ch from 0 to strip.num_pixels-1
strip.set(ch, rgb)
pause(ms)
pub rainbow(ms) | pos, ch
repeat pos from 0 to 255
repeat ch from 0 to strip.num_pixels-1
strip.set(ch, wheel((pos + ch) & $FF))
pause(ms)
pub rainbow_cycle(ms) | pos, ch
repeat pos from 0 to (255 * 5)
repeat ch from 0 to strip.num_pixels-1
strip.set(ch, wheel(((ch * 256 / strip.num_pixels) + pos) & $FF))
pause(ms)
pub wheel(pos)
'' Creates color from 0 to 255 position input
'' -- colors transition r->g->b back to r
if (pos < 85)
return strip.color(255-pos*3, pos*3, 0)
elseif (pos < 170)
pos -= 85
return strip.color(0, 255-pos*3, pos*3)
else
pos -= 170
return strip.color(pos*3, 0, 255-pos*3)
pub wheelx(pos, level) ' update by JonnyMac
'' Creates color from 0 to 255 position input
'' -- colors transition r-g-b back to r
'' -- level is brightness, 0 to 255
if (pos < 85)
return strip.colorx(255-pos*3, pos*3, 0, level)
elseif (pos < 170)
pos -= 85
return strip.colorx(0, 255-pos*3, pos*3, level)
else
pos -= 170
return strip.colorx(pos*3, 0, 255-pos*3, level)
con
{ ------------- }
{ B A S I C S }
{ ------------- }
pub pause(ms) | t
'' Delay program in milliseconds
if (ms < 1) ' delay must be > 0
return
else
t := cnt - 1776 ' sync with system counter
repeat ms ' run delay
waitcnt(t += MS_001)
pub high(pin)
'' Makes pin output and high
outa[pin] := 1
dira[pin] := 1
pub low(pin)
'' Makes pin output and low
outa[pin] := 0
dira[pin] := 1
pub toggle(pin)
'' Toggles pin state
!outa[pin]
dira[pin] := 1
pub input(pin)
'' Makes pin input and returns current state
dira[pin] := 0
return ina[pin]
dat
{{
Terms of Use: MIT License
Permission is hereby granted, free of charge, to any person obtaining a copy of this
software and associated documentation files (the "Software"), to deal in the Software
without restriction, including without limitation the rights to use, copy, modify,
merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to the following
conditions:
The above copyright notice and this permission notice shall be included in all copies
or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE
OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
}}
The ADC is connected like this:
''* Example wiring would be as follows: *
''* R1 *
''* ADC─┳──┳──input *
''* C1 R2 *
''*   *
''* R1: 10K (high impedance input is helpful, 10K should be the minimum) *
''* R2: 100K (this effectively creates a voltage divider, but also drives input to *
''* zero volts when not in use) *
''* C1: 0.01µF (10000pF is the about the maximum you would want to use. The *
''* capacitor reduces jitter or spikes but also reduces resolution).
Comments
repeat left := adc3208(1) 'read ADC and return variable strip.fill(0, left/6, strip#blue) 'fill the strip to match variable divided by 6 for scale strip.set(left/6, $FF_00_00) 'add a red dot to the current meter position pause(1) 'makes it look better strip.fill(left/6, 32, 0_0_0) 'turn off all LEDs past the current meter position
I was able to implement a logarithmic response by using horrible coding instead of proper math. I used a bunch of IF statements. For example-
IF left =< 9
'turn first LED on
IF left => 10 AND left < 20
'turn second LED on
IF left => 20 AND left < 30
'turn third LED on
Effectively the power has to double to light up the next LED. I'm assuming that is how standard log taper works but working on that aspect as well.
If anyone has any comments on how to do that better I'm all ears.
[video=youtube_share;i0-VT5XDB2E]
[video=youtube_share;fJ3FbF0lbqA]
^^Please watch this at 1080 or it will seem out of foocus.
Still working out some kinks. Still waiting for parts for Xanadu Industrial Aēchos Rover 1! In the meantime all other aspects of life have been calling.
I'm going to black out the LED strips next.
It looks a lot better without the white strips. Some hot glue a frame, and notes in my code and this puppy is done!
This thing is awesome! I'm so glad I stumbled into it, as I am starting to venture into the realm of sound processing, and adding LEDs sounds like a logical next step. Do you have any plans to try out something with frequency analysis on those strips?
WMD
LEDs sell, that is for sure. Things that look good don't need a proper function these days, or maybe since the beginning of the human race. I think the LM3915 and that 10 segment LED bar is the first one I built, around 30 years ago. Since then I've always been a little obsessed with interactive music. Music is the best sensor source to drive just about anything entertaining.
For stereo you need two channels of ADC for each range of frequency you want. I ordered some MCP3008 awhile back to start playing with spectrum/EQ style display. Eight channel ADC divided by two channel audio give four different sets of inputs you can drive from the same two channel source. I am pretty sure all of this needs to be done on the analog input side of the ADC. I don't think there is any other way, at least using what I have to accomplish it.
On the input side of my two channel ADC I have toyed around with using RC filters to isolate low, medium and high freqs, it seems to work well. When I start to add all of this to an already busy WS8211 strip it starts to go south. I know a lot of that problem lies within my code, and what I'm working on now is this:
Low pass = red; band pass = blue; high pass = white. There is a lot of tinkering... er.. calibration in getting them equal, for instance the low pass doesn't give me close to the ADC value as the high pass.
I think I will post that version next. I just need to close out a few hundred trouble tickets first
I cut my project down to monaural for now, as a proof of concept. The red channel represents a 6 dB/o roll off above 160Hz and the green represents a 6dB/o roll down to 482Hz. The values are as close as I have parts as of now. You’d think all those capacitor and resistor assortment packs would get you somewhere with audio, but they don’t, I have drawers of components and unless I’m missing something I still don’t have what I would need.
I’ve included a very long and drawn out sweep from 20Hz to 20,000Hz as well, feel free to skip around to see the LEDs transition.
Easy RC Filter Calc = http://www.muzique.com/schem/filter.htm
[video=youtube_share;beJn1NAre8M]
Remember, audio is about proper reproduction, without the right equipment you're missing out.
[video=youtube_share;YwHSANdFwU8]
You need some good speakers, or headphone to hit the full sweep.
The screaming kid in the background is not mine, if it isn't the dogs bumping the desk with the infinity mirror you can be sure some other random effect to include laser printer calibration will impose.