Shop OBEX P1 Docs P2 Docs Learn Events
Propeller RGB VU Meter — Parallax Forums

Propeller RGB VU Meter

xanaduxanadu Posts: 3,347
edited 2014-04-02 22:35 in Robotics
  • Propeller Microcontroller
  • MCP3002 ADC
  • WS8211 72/m RGB LED strip
  • Logic level shifter
  • 4 resistors
  • 2 capacitors
I currently have one channel working and will add the second channel next. I also have some better effects to integrate. Also I think my output is linear and needs to be logarithmic.

[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&#61609;&#9472;&#9523;&#9472;&#9472;&#9523;&#9472;&#61629;&#61630;&#9472;&#61627;input                                                         *
''*       C1&#61612;&#61613;&#61614; &#61628;R2                                                                 *
''*          &#61464;  &#61464;                                                                   *
''* 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

  • xanaduxanadu Posts: 3,347
    edited 2014-02-13 17:14
    The code runs like this:
     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
    
    
  • mindrobotsmindrobots Posts: 6,506
    edited 2014-02-13 19:19
    This is really cool! Nice integration of pieces parts!
  • kwinnkwinn Posts: 8,697
    edited 2014-02-14 21:38
    You are right, it does need to be logarithmic. Looks good now, and should look even better with log response.
  • xanaduxanadu Posts: 3,347
    edited 2014-02-18 10:53
    Thanks! Adding channel two as we type!
  • xanaduxanadu Posts: 3,347
    edited 2014-02-18 11:06
    kwinn wrote: »
    You are right, it does need to be logarithmic. Looks good now, and should look even better with log response.

    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.
  • kwinnkwinn Posts: 8,697
    edited 2014-02-18 21:23
    I thought there was a log table in prop rom. If so you might be able to use that. If not a case statement might be more readable even though it would do the same thing.
  • xanaduxanadu Posts: 3,347
    edited 2014-02-19 08:52
    Thanks kwinn. I found the log table in the Prop manual, will try to use that, if not case.
  • xanaduxanadu Posts: 3,347
    edited 2014-02-28 17:04
    I added the second channel and also a three point logarithmic response curve to the output. It looks better but since it has so long to go I thought I'd share an update.

    [video=youtube_share;i0-VT5XDB2E]
  • xanaduxanadu Posts: 3,347
    edited 2014-03-25 23:22
    Good day, ladies and gentlemen. I bring to you project #522 (unfinished project #217 and #305).

    [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.
  • kwinnkwinn Posts: 8,697
    edited 2014-03-26 18:27
    Impressive. Like your choice of music as well.
  • xanaduxanadu Posts: 3,347
    edited 2014-03-27 18:40
    Thanks, I almost always get negativity from techno-ish music but I've been finding some good in-betweens.

    It looks a lot better without the white strips. Some hot glue a frame, and notes in my code and this puppy is done!
  • WildManDanWildManDan Posts: 7
    edited 2014-03-30 22:17
    Xanadu,

    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
  • xanaduxanadu Posts: 3,347
    edited 2014-03-31 09:58
    Thanks!

    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 ;)


  • xanaduxanadu Posts: 3,347
    edited 2014-04-02 22:35
    When it comes to music, the audio spectrum is full. To allow for a graphical representation of this spectrum would require many individual channels. More than I wish to make. With only two channels to spare, I had to improvise.

    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.
Sign In or Register to comment.