Saturday, August 31, 2013

Arduino powered RGB LED strip controller

One of my coworkers brought one of his creations in to show off a few weeks ago, and it sparked my creativity a bit. He had built a mobile sound system out of an Igloo Ice Cube cooler, four marine speakers, a small automotive amp, and a riding lawnmower battery. He had also upgraded the wheels to 10" pneumatic tires.

Think this, with bigger, knobbier tires, two speakers on the front, and one speaker on either side:


I had recently seen this article on Hack a Day, which lead me to this article, which lead down a rabbit hole of researching the MSGEQ7, RGB LED strip control, code examples, etc.

So I got some money from the coworker to order some parts and start development. Still have to move it to a PCB, figure out an enclosure, get a panel mount switch and pot, probably some screw terminals for power in and LED lead out, and mount everything to the cooler.

Ran into a couple challenges, mainly related to noise from the PWM interfering with the audio ground, causing a feedback loop which kept one of the LEDs lit. Ended up eliminating that by altering the timers on the Arduino to move the PWM frequency out of the range of audible frequencies. Also learned a bit about ground loops and how to eliminate them.

From the youtube video, a description on what everything does:

Arduino controlling an analog 5050 RGB LED strip, using one button and one pot for manual control, and a MSGEQ7 to input the audio. Controller has 7 modes, which are cycled through by pressing the one button.

1) Audio visualization. Uses audio input from a headphone jack to change the value of the red, green and blue LEDs based upon low, mid, and high EQ values.
2) Solid color. (2:25) Uses pot to cycle through colors, varying RGB values based upon position.
3) Pulse. (2:47) Pulses last color chosen during setting 2, at a speed based upon pot position.
4) Color cycle. (3:12) Automatically cycles between red, green, and blue, and all the colors in between. Speed of cycle is based upon pot position.
5) Random color cycle. (3:35) "Random" values of RGB are chosen, then transitioned to.
6) Campfire mode. (3:53) Color and flicker programmed to simulate campfire. I still think it's a little too orange, but still tweaking.
7) White. (4:05) Sets red, green and blue to the same value to generate "white" light. Brightness is set by pot position.
8) Off. (4:22) All LEDs set to zero.

Once the button is pushed again, the controller cycles back to the first option.


A bad fritzing diagram of how everything is hooked up. Links to sources for each part are in the code block below.


The single RGB LED represents the strip, source voltage to the top rail is 12v, bottom rail is 5v. 


  /*   
   RGB LED music controller  
   8 modes: music reaction, color select, color pulse, R>G>B cycle, random cycle, campfire simulation, all white, and off  
   By Russell Milling 2013-08-31  
     
   Large portions of code, wiring diagrams, and other help borrowed from:  
   Markus Ulfberg   
   http://genericnerd.blogspot.com/2011/12/arduino-mood-light-controller-v3.html  
   n00bsys0p   
   http://www.n00bsys0p.co.uk/blog/2012/07/09/arduinomsgeq7-audio-spectrum-led-display  
   J. Skoba   
   http://nuewire.com/info-archive/msgeq7-by-j-skoba/  
   Tyler Cooper   
   http://learn.adafruit.com/rgb-led-strips  
   Those who edit articles on the Arduino Playground   
   http://playground.arduino.cc/Main/TimerPWMCheatsheet  
   The DIY & Hobbies forum on SomethingAwful   
   http://forums.somethingawful.com/showthread.php?threadid=2734977&pagenumber=223#post418931935  
     
     
     
   Thanks to: Ladyada, Tom Igoe and   
   everyone at the Arduino forum for excellent   
   tutorials and everyday help.   
    
  */  
    
  /* stuff to smooth out the pot readings  
    
    Define the number of samples to keep track of. The higher the number,  
    the more the readings will be smoothed, but the slower the output will  
    respond to the input. Using a constant rather than a normal variable lets  
    use this value to determine the size of the readings array.  
  */  
    
    
  const int numReadings = 10;  
    
  int readings[numReadings];   // the readings from the analog input  
  int index = 0;         // the index of the current reading  
  int total = 0;         // the running total  
  int average = 0;        // the average  
    
  // set the ledPins  
  int ledRed = 9;  
  int ledGreen = 10;  
  int ledBlue = 11;  
    
  // color selector pin  
  int potPin = 0;  
     
  // lightMode selector switch pin  
  int switchPin = 2;  
   
  // light mode variable  
  // initial value 0 = music  
  int lightMode = 0;  
    
  // LED Power variables  
  byte redPwr = 0;  
  byte greenPwr = 0;  
  byte bluePwr = 0;  
  byte ledPwr = 0;  
    
  // Variables for lightMode 2  
  // variables for keeping pulse color  
  byte redPulse;  
  byte greenPulse;  
  byte bluePulse;  
  int pulseSpeed;   
    
  // Set pulse to down initially  
  byte pulse = 0;  
    
  // floating variables needed to be able to pulse a fixed color   
  float redFloat;  
  float greenFloat;  
  float blueFloat;  
    
  // the amount R,G & B should step up/down to display an fixed color  
  float redKoff;  
  float greenKoff;  
  float blueKoff;  
    
  // Variables for lightMode 3  
  // set the initial random colors  
  byte redNew = random(255);  
  byte greenNew = random(255);  
  byte blueNew = random(255);  
    
  // Variables for cycleColor  
  int truColor = 0;  
    
  // misc interface variables  
  // potVal store the value of the potentiometer for various needs   
  int potVal;  
  // value from the button (debounce)  
  int switchVal;  
  int switchVal2;  
  // buttonState registers if the button has changed  
  int buttonState;  
    
  // MSGEQ7 stuff for music  
    
  int analogPin = 3; // MSGEQ7 OUT  
  int strobePin = 4; // MSGEQ7 STROBE  
  int resetPin = 7; // MSGEQ7 RESET  
  int spectrumValue[7]; // store the 7 eq band values  
     
  // MSGEQ7 OUT pin produces values around 50-80  
  // when there is no input, so use this value to  
  // filter out a lot of the chaff.  
  int filterValue = 80;  
    
  // variable to control the max intensity of the LEDs when in music mode  
  int filterMax = 255;  
    
    
  void setup()  
  {  
   pinMode(ledRed, OUTPUT);  
   pinMode(ledGreen, OUTPUT);  
   pinMode(ledBlue, OUTPUT);  
     
   pinMode(potPin, INPUT);  
     
   pinMode(switchPin, INPUT);  
   buttonState = digitalRead(switchPin);   
     
     
     
   // serial for debugging purposes only  
   // Serial.begin(9600);  
     
   // pot smoothing  
   for (int thisReading = 0; thisReading < numReadings; thisReading++)  
     readings[thisReading] = 0;   
       
     // Read from MSGEQ7 OUT  
   pinMode(analogPin, INPUT);  
   // Write to MSGEQ7 STROBE and RESET  
   pinMode(strobePin, OUTPUT);  
   pinMode(resetPin, OUTPUT);  
     
   // Set analogPin's reference voltage  
   analogReference(DEFAULT); // 5V  
     
   // Set startup values for pins  
   digitalWrite(resetPin, LOW);  
   digitalWrite(strobePin, HIGH);  
    
   // set the Arduino timers to crazyfast values for pins 3, 9, 10, and 11 so that PWM doesn't cause audible hum or feedback in the audio circuit.   
   //If changing code to use a second LED strip on the other 3 PWM pins, make sure to set TCCR0B as well, but be mindful that it will effect system timer values  
   // such as delay(), millis(), micros(), etc  
   // See: http://playground.arduino.cc/Main/TimerPWMCheatsheet for more information  
   TCCR1B = TCCR1B & 0b11111000 | 0x01;  
   TCCR2B = TCCR2B & 0b11111000 | 0x01;  
  }  
    
  void loop()  
  {  
   switchVal = digitalRead(switchPin);   // read input value and store it in val  
   delay(10);             // 10 milliseconds is a good amount of time  
      
   switchVal2 = digitalRead(switchPin);   // read the input again to check for bounces  
   if (switchVal == switchVal2) {         // make sure we got 2 consistant readings!  
    if (switchVal != buttonState) {     // the button state has changed!  
     if (switchVal == LOW) {        // check if the button is pressed  
      switch (lightMode) {     // light is initially in music react mode  
       case 0:  
        lightMode = 1;      // light is on and responds to pot to choose color  
        break;   
       case 1:  
        lightMode = 2;      // light pulsates in the latest color from pot  
        break;   
       case 2:     
        lightMode = 3;      // light cycles thru colors  
        break;  
       case 3:  
        lightMode = 4;      // light changes randomly  
        break;  
       case 4:  
        lightMode = 5;      // simulated fire  
        break;   
       case 5:     
        lightMode = 6;       // all white      
        break;   
       case 6:  
        lightMode = 7;      // lights off  
        break;  
       case 7:  
        lightMode = 0;      // music react  
        break;                       
      } // END switch (lightMode)    
     } // END if (switchVal == LOW)  
    } // END if (switchVal != buttonState)   
       
    buttonState = switchVal;         // save the new state in our variable  
   } // END if (switchVal == switchVal2)  
    
     
   // Debug  
   //Serial.print("lightMode: ");  
   //Serial.println(lightMode);  
     
     
   switch (lightMode) {  
    case 0:  
     musicEQ();  
     break;  
    case 1:  
     colorControl();  
     break;  
    case 2:  
     pulsateColor();  
     break;  
    case 3:   
     cycleColor();  
     break;  
    case 4:  
     randomColor();  
     break;  
    case 5:  
     lightMyFire();  
     break;   
    case 6:  
     allWhite();  
     break;  
    case 7:  
     lightsOff();  
     break;  
   }  
    
  } // END loop()  
    
    
  // lightMode 7  
  void lightsOff() {  
   redPwr = 0;  
   greenPwr = 0;  
   bluePwr = 0;  
   colorDisplay();  
  }  
    
  // lightMode 1   
  void colorControl() {  
    
    // read the potentiometer position  
    getPot();  
    potVal = average;   
     
   // RED > ORANGE > YELLOW  
    if (potVal > 0 && potVal < 170) {  
     redPwr = 255;  
     bluePwr = 0;  
     greenPwr = map(potVal, 0, 170, 0, 255);  
    }  
     
    // YELLOW > LIME?? > GREEN   
    if (potVal > 170 && potVal < 341) {  
     greenPwr = 255;  
     bluePwr = 0;  
     redPwr = map(potVal, 341, 170, 0, 255);  
    }  
    
    // GREEN > TURQOUISE  
    if (potVal > 341 && potVal < 511) {  
     greenPwr = 255;  
     redPwr = 0;  
     bluePwr = map(potVal, 341, 511, 0, 255);  
    }  
     
    // TURQOUISE > BLUE   
    if (potVal > 511 && potVal < 682) {  
     bluePwr = 255;  
     redPwr = 0;  
     greenPwr = map(potVal, 682, 511, 0, 255);  
    }  
     
    // BLUE > PURPLE   
    if (potVal > 682 && potVal < 852) {  
     bluePwr = 255;  
     greenPwr = 0;  
     redPwr = map(potVal, 682, 852, 0, 255);  
    }  
     
    // PURPLE > RED  
    if (potVal > 852 && potVal < 1023) {  
     redPwr = 255;  
     greenPwr = 0;  
     bluePwr = map(potVal, 1023, 852, 0, 255);  
    }   
      
    redFloat = float(redPwr);  
    greenFloat = float(greenPwr);  
    blueFloat = float(bluePwr);  
      
    redKoff = redFloat / 255;  
    greenKoff = greenFloat / 255;  
    blueKoff = blueFloat / 255;  
      
    redPulse = redPwr;  
    greenPulse = greenPwr;  
    bluePulse = bluePwr;   
      
   /*  
   // Debug   
   Serial.print("redFLoat: ");  
   Serial.print(redFloat, DEC);  
   Serial.print(" redPwr: ");  
   Serial.print(redPwr, DEC);  
   Serial.print(" greenFloat: ");  
   Serial.print(greenFloat, DEC);  
   Serial.print(" greenPwr: ");  
   Serial.print(greenPwr, DEC);  
   Serial.print(" blueFloat: ");  
   Serial.print(blueFloat, DEC);  
   Serial.print(" bluePwr: ");  
   Serial.println(bluePwr, DEC);  
   // End debug  
   */  
     
   // Display colors   
   colorDisplay();  
  }      
    
  // lightMode 2  
  void pulsateColor() {  
     
    // get colors from colorControl  
    redPwr = int(redFloat);  
    greenPwr = int(greenFloat);  
    bluePwr = int(blueFloat);  
       
    // Read speed from potentiometer   
    getPot();  
    potVal = average;   
    pulseSpeed = map(potVal, 0, 1023, 0, 200);  
     
    //display the colors  
    colorDisplay();  
      
    // set speed of change  
    delay(pulseSpeed);  
      
    // pulse down  
    if (pulse == 0) {  
     if (redFloat > 10) {  
      redFloat = redFloat - redKoff;  
     }   
     if (greenFloat > 10) {  
      greenFloat = greenFloat - greenKoff;  
     }   
     if (blueFloat > 10) {  
      blueFloat = blueFloat - blueKoff;  
     }   
    
    // If all xFloat match 10 get pulse up  
    if (byte(redFloat) <= 10) {  
     if (byte(greenFloat) <= 10) {  
     if (byte(blueFloat) <= 10) {  
      pulse = 1;  
     }  
     }  
    }  
   }  
   // Pulse up  
   if (pulse == 1) {  
    if (redFloat < redPulse) {  
     redFloat = redFloat + redKoff;  
    }   
    if (greenFloat < greenPulse) {  
     greenFloat = greenFloat + greenKoff;  
    }   
    if (blueFloat < bluePulse) {  
     blueFloat = blueFloat + blueKoff;  
    }  
    // If all Pwr match Pulse get pulse down  
     
    if (byte(redFloat) == redPulse) {  
     if (byte(greenFloat) == greenPulse) {  
     if (byte(blueFloat) == bluePulse) {  
      pulse = 0;  
     }  
     }  
    }  
   }  
     
   /*  
   // Debug   
   Serial.print("redFloat: ");  
   Serial.print(redFloat, DEC);  
   Serial.print(" redPulse: ");  
   Serial.print(redPulse, DEC);  
   Serial.print(" greenFloat: ");  
   Serial.print(greenFloat, DEC);  
   Serial.print(" greenPulse: ");  
   Serial.print(greenPulse, DEC);  
   Serial.print(" blueFloat: ");  
   Serial.print(blueFloat, DEC);  
   Serial.print(" bluePulse: ");  
   Serial.print(bluePulse, DEC);  
   Serial.print(" pulse: ");  
   Serial.println(pulse, DEC);  
   // End debug  
   */  
     
  } // pulsateColor END   
    
  // lightMode 3  
  void cycleColor() {  // Cycles through colors  
    
   switch(truColor) {  
   // RED > ORANGE > YELLOW    
    case 0:  
     redPwr = 255;  
     bluePwr = 0;  
     greenPwr++;  
     if (greenPwr > 254) {  
      truColor = 1;  
     }  
     break;  
      
    // YELLOW > LIME?? > GREEN   
    case 1:  
     greenPwr = 255;  
     bluePwr = 0;  
     redPwr--;  
     if (redPwr < 1) {  
      truColor = 2;  
     }  
     break;  
    
    // GREEN > TURQOUISE  
    case 2:  
     greenPwr = 255;  
     bluePwr++;  
     redPwr = 0;  
     if (bluePwr > 254) {  
      truColor = 3;  
     }    
    break;  
      
    // TURQOUISE > BLUE   
    case 3:  
     greenPwr--;  
     bluePwr = 255;  
     redPwr = 0;  
     if (greenPwr < 1) {  
      truColor = 4;  
     }  
     break;  
       
    // BLUE > PURPLE   
    case 4:  
     greenPwr = 0;  
     bluePwr = 255;  
     redPwr++;  
     if (redPwr > 254) {  
      truColor = 5;  
     }  
     break;  
       
    // PURPLE > RED  
    case 5:  
     greenPwr = 0;  
     bluePwr--;  
     redPwr = 255;  
     if (bluePwr < 1) {  
      truColor = 0;  
     }    
     break;  
   }  
   // START SPEED   
   getPot();  
   potVal = average;   
   pulseSpeed = map(potVal, 0, 1023, 0, 200);  
     
   //display the colors  
   colorDisplay();  
   // set speed of change  
   delay(pulseSpeed);  
   // END SPEED  
     
  } // END cycleColor   
    
    
  // lightMode 4   
  void randomColor() {   // randomize colorNew and step colorPwr to it  
                 
   if (redPwr > redNew) {  
    redPwr--;  
   }   
   if (redPwr < redNew) {  
    redPwr++;  
   }  
   if (greenPwr > greenNew) {  
    greenPwr--;  
   }   
   if (greenPwr < greenNew) {  
    greenPwr++;  
   }  
   if (bluePwr > blueNew) {  
    bluePwr--;  
   }   
   if (bluePwr < blueNew) {  
    bluePwr++;  
   }  
    
  // If all Pwr match New get new colors  
     
   if (redPwr == redNew) {  
    if (greenPwr == greenNew) {  
    if (bluePwr == blueNew) {  
     redNew = random(254);  
     greenNew = random(254);  
     blueNew = random(254);  
    }  
    }  
   }  
     
   getPot();  
   potVal = average;   
   pulseSpeed = map(potVal, 0, 1023, 0, 200);  
     
   //display the colors  
   colorDisplay();  
   // set speed of change  
   delay(pulseSpeed);  
    
  } // END randomColor   
    
  // lightMode 5  
  void lightMyFire() {  
     
   // Flicker will determine how often a fast flare will occur  
   int flicker;  
    
   // set flicker randomness  
   flicker = random(1800);   
    
   // Set random colors,   
   // constrain green to red and blue to green  
   // in order to stay within a red, blue, white spectrum  
   redPwr = random(220, 240);  
   greenPwr = random(20, 40);  
   // when flicker occur, the colors shine brighter  
   // adding blue creates a white shine   
   if (flicker > 1750) {  
    redPwr = 254;  
    greenPwr = random(60, 80);   
    bluePwr = random(10, 40);    
   } else {  
    bluePwr = 0;  
   }  
     
   // display Colors  
   colorDisplay();  
     
   // Set speed of fire  
   delay(80);   
    
  } // END lightMyFire  
    
  // lightMode 6  
  void allWhite() {  
      
   // Smoothing out the pot readings. Will replace later with getPot()  
     
   getPot();  
     
   // map the smoothed pot value to LED pwm  
   ledPwr = map(average, 0, 1023, 0, 255);  
     
   redPwr = ledPwr;  
   greenPwr = ledPwr;  
   bluePwr = ledPwr;  
     
   colorDisplay();  
  } // End allWhite  
    
  // Begin lightMode 0   
  void musicEQ() {  
     
   getPot();  
     
   filterMax = map(average, 0, 1023, 0, 255);  
     
   // Set reset pin low to enable strobe  
   digitalWrite(resetPin, HIGH);  
   digitalWrite(resetPin, LOW);  
     
   // Get all 7 spectrum values from the MSGEQ7  
   for (int i = 0; i < 7; i++)  
   {  
    digitalWrite(strobePin, LOW);  
    delayMicroseconds(30); // Allow output to settle  
     
    spectrumValue[i] = analogRead(analogPin);  
     
    // Constrain any value above 1023 or below filterValue  
    spectrumValue[i] = constrain(spectrumValue[i], filterValue, 1023);  
     
    // Remap the value to a number between 0 and 255  
    spectrumValue[i] = map(spectrumValue[i], filterValue, 1023, 0, filterMax);  
     
    // Remove serial stuff after debugging  
    //Serial.print(spectrumValue[i]);  
    //Serial.print(" ");  
     
    digitalWrite(strobePin, HIGH);  
    }  
     
    //Serial.println();  
       
    /* Spectrum Value Frequencies:  
     0: 63 Hz  
     1: 160 Hz  
     2: 400 Hz  
     3: 1000 Hz  
     4: 2500 Hz  
     5: 6250 Hz  
     6: 16000 Hz  
       
     1, 4, and 6 seem to offer best combination using only 3 LEDs  
     If using 6 LEDs, 1-6 or 1-3 and 4-6 depending on music.  
    */  
      
   redPwr = spectrumValue[1];  
   greenPwr = spectrumValue[4];  
   bluePwr = spectrumValue[6];  
     
   colorDisplay();  
  }  
    
  // Displays the colors when called from other functions  
  void colorDisplay() {  
   analogWrite(ledRed, redPwr);  
   analogWrite(ledGreen, greenPwr);  
   analogWrite(ledBlue, bluePwr);  
  }  
    
    
  // Smooths out the pot readings.   
  void getPot() {  
   // subtract the last reading:  
   total= total - readings[index];       
   // read from the sensor:   
   readings[index] = analogRead(potPin);   
   // add the reading to the total:  
   total= total + readings[index];      
   // advance to the next position in the array:   
   index = index + 1;            
    
   // if we're at the end of the array...  
   if (index >= numReadings)         
    // ...wrap around to the beginning:   
    index = 0;                
    
   // calculate the average:  
   average = total / numReadings;       
   // send it to the computer as ASCII digits  
   // Serial.println(average);    
   delay(1);    // delay in between reads for stability        
  }  

A partial parts list for what I used other than the Arduino:

220k ohm resistor
http://www.taydaelectronics.com/resistors/1-4w-metal-film-resistors/220k-ohm-1-4w-1-metal-film-resistor.html
.01uf capacitor
http://www.taydaelectronics.com/capacitors/ceramic-disc-capacitors/10-x-0-01uf-50v-ceramic-disc-capacitor-pkg-of-10.html
(2) .1uf capacitor
http://www.taydaelectronics.com/capacitors/ceramic-disc-capacitors/10-x-0-1uf-50v-ceramic-disc-capacitor-pkg-of-10.html
33pf capacitor
http://www.taydaelectronics.com/capacitors/ceramic-disc-capacitors/10-x-33pf-50v-ceramic-disc-capacitor-pkg-of-10.html

3x N channel mosfets:
http://www.mouser.com/ProductDetail/STMicroelectronics/STP16NF06L/?qs=RC432zO33OqodrhO5g7gPg%3d%3d
2x
Headphone jack
http://www.mouser.com/ProductDetail/Kobiconn/161-3402-E/?qs=%2fha2pyFaduiyB%252bLCpU7TZoJ3cfVHPgazskZ0wf2sV%252bg%3d
A push button. Can be as simple as this, or as fancy as you want. Make sure to get normally open, single pole, single throw in a panel mount package.
http://www.taydaelectronics.com/electromechanical/switches-key-pad/push-button/pb-11d02-push-button-panel-mount-spst-no-pb-11d02-th1r00.html
A 1K ohm linear potentiometer. Again, get a panel mount part, and you can go fancy or simple with a knob for it if you want.
http://www.taydaelectronics.com/potentiometer-variable-resistors/rotary-potentiometer/linear/1k-ohm-linear-taper-potentiometer-with-solder-lugs.html
http://www.ebay.com/sch/i.html?_odkw=1k+linear+potentiometer&_fspt=1&_sop=12&_osacat=0&_mPrRngCbx=1&_trksid=p2045573.m570.l1313.TR12.TRC2.A0.Xpotentiometer+knob&_nkw=potentiometer+knob&_sacat=0&_from=R40

RGB LED strip. This is the one I ordered. Comes with a controller, but not a power brick. It's not hard to find a 12v power brick that you can use with these laying around with old electronics.
http://www.ebay.com/itm/290954092203?ssPageName=STRK:MEWNX:IT&_trksid=p3984.m1497.l2649

MSGEQ7, which is the chip that takes the audio in and turns it into something the microcontroller can use:
http://www.ebay.com/sch/items/?_nkw=msgeq7&_sacat=&_ex_kw=&_mPrRngCbx=1&_udlo=&_udhi=&_sop=12&_fpos=&_fspt=1&_sadis=&LH_CAds=