Introduction: Arduino & Neopixel Coke Bottle Rainbow Party Light

About: Technology is subversive.

So my son Doon spots a very cool party light made of old coke bottles and the gooey innards of Glow Sticks, and asks if we can make one for his upcoming School Exams Are Over Blowout PartAYYY!!! I say sure, but wouldn't you rather have some of those spiffy Adafruit Neopixel Rings we've been reading about... He gives me a blank stare. Because fact is he doesn't know what I'm talking about, but Dad has spotted an opportunity to play with those Neopixel rings HE's been reading about, and we all know one of the top 10 reasons that geek dads procreate is to have an excuse to play with cool gadgets they tell everyone are for their kids.

This is a super-simple project that looks really great. We built ours of 3 old coke bottles, a wooden plate, and a playground post bracket -- stuff lying around in the basement -- combined with an Arduino (Leonardo in our case, but any Genuino board will do!) and three Neopixel rings. I ordered a 9-LED ring, but ended up with a 12-LED ring for the same price. Which was sweet, but meant a do-over on the well-holes -- the 12-LED rings are 35mm wide, as opposed to 23mm. What you'll need:

  • Genuino/Arduino board (We used a Leonardo, but nearly any board will do)
  • 3 Neopixel rings (12 LEDs each): get them from Adafruit, and support those fine folks
  • 1000 µf 6.3v or better capacitor
  • 300-500 ohm resistor
  • A wooden plate, or a square of scrapwood, or anything you can set the neopixels into and sit the coke bottles on top
  • Some form of mount for the plate -- a playground post bracket worked great for us
  • 9v wall wart
  • 40mm hole-borer
  • Bolts, Nuts, Washers, Spacers
  • Solid core wire
  • A soldering iron and solder
  • Breadboard
  • A plastic case for the Arduino. You can go out and buy really nice perfectly fitting plastic case made from million-year old petroleum drilled out of the ground in some fragile environment and manufactured on the other side of the planet and shipped in a container to a warehouse near you with all the ports cut out in perfect alignment and have it delivered to your door by a van spewing carbon dioxide into the atmosphere. Or you can do what I did and use an old discarded plastic box.. in this case a Madagascar band aid box lying around in the medicine cabinet... and drill a few holes in it. Here endeth the lecture. Let's MAKE...

Step 1: Make the Base

You can improvise your base from whatever junk you've got in your own basement, or even just use a wooden box or anything that will hide your electronics.

First we drillled three holes, evenly spaced on the wooden plate, big enough for the Neopixel rings to sit in. In the image the holes are wells drilled with a spade drill. In the end, because of the larger size of the 12-LED rings, we had to drill holes with a borer bit. This meant going all the way through the plate, and instead of snugging the rings nicely into their finely crafted little 2mm-deep wells with a center hole for a neat wire run I ended up securing the rings with... ahem... Duct tape across the bottom of the plate. Don't judge. You can't see the bottom of the plate in my design anyway. And it's dark when it's on. And besides -- what's wrong with duct tape?

I needed clearance between the plate and the bracket for a breadboard on the bottom of the plate and one component -- the capacitor, and for the wire runs that would have to go from breadboard to Arduino, which I planned to put inside the bracket. So I put a set of makeshift spacers on the bolt shafts to give enough clearance -- about 3cm, the height of the breadboard and a bit so you don't crush the wiring. I used two wooden anchor bolts per corner because they were the right height and lying around in the man drawer... that box of loose screws, bolts, nails, rusty chain-links, hose couplings, old coins, unexpectedly sharp objects, and all manner of bits and bobs that can magically save you a trip to the hardware store by offering up, if not the exact thing you need, something that will do just fine.

Happy accident about the playground post I found in the basement was it had holes running through the plate already. No need to drill iron! The base had four bolt holes, and we drilled four counter-sunk holes in the wooden plate to match.

We then spray-painted the whole thing Gothic Black.

Step 2: Preparing the Neopixel Rings

You'll need to solder wires onto your neopixel rings: a Data-In wire for all of them, an Data-Out wire for two of them, and power and ground for each. Whatever length you think you need, add some. You can always cut off excess wire, you can't stretch one that's too short. And be aware of the warning from Adafruit:

When soldering wires to these rings, you need to be extra vigilant about solder blobs and short circuits. The spacing between components is very tight! It’s often easiest to insert the wire from the front and solder on the back.

I wish I read that before I soldered to the front. I managed not to burn out any of my LEDs, but I scorched the edge of one in a way that had me sweating until I turned it on. Also, had I read the fine manual I'd have also read the warning not to put an alligator clip on the LED. Let my near-shipwrecks be your lighthouse.

Neopixel rings daisy-chain, which means you can control all of their LEDs simultaneously from an Arduino by connecting a wire from the OUT of one ring to the IN of another. Each ring needs power and ground wires too.

Step 3: The Wiring

Wire it up as in he Fritzing above -- pin 6 of the Arduino takes the data to the first ring, the Data-out from that ring goes to the Data-in of the next, the Data-out of that one goes to the Data-in of the last ring. You don't need the data-out wire of the final ring.

The 1000 µf capacity goes between the positive and negative rails of the breadboard. This cap protects the rings from power spikes and is recommended by the Adafruit NeoPixel Uberguide's best-practice section. The resistor on the Data in to the first neopixel is also recommended by Adafruit -- it's 1K in the Fritzing but recommended resistance is 300-500 ohms.

In my build, I ran the wires from the Neopixels across the back of the plate to a breadboard fixed in the centre. That way you only have to run three long wires down into the base unit: power, ground, and data. I made these wires super-long -- there's plenty of storage space in the base, and it makes it convenient to be able to pull the board out for reprogramming.

Step 4: The Code

Our original ambition was to make the light music-sensitive. But between widely varying noise levels, the need for a potentiometer to adjust the mic sensitivity, and the fact that the party was THAT NIGHT, we abandoned attempts to get a Fast Fourier Transform library and the Neopixel rings talking to each other*, and just went with the Neopixel library's very pretty strandtest routine:

<p>#include <br>#ifdef __AVR__
  #include 
#endif</p><p>#define PIN 6</p><p>// Parameter 1 = number of pixels in strip
// Parameter 2 = Arduino pin number (most are valid)
// Parameter 3 = pixel type flags, add together as needed:
//   NEO_KHZ800  800 KHz bitstream (most NeoPixel products w/WS2812 LEDs)
//   NEO_KHZ400  400 KHz (classic 'v1' (not v2) FLORA pixels, WS2811 drivers)
//   NEO_GRB     Pixels are wired for GRB bitstream (most NeoPixel products)
//   NEO_RGB     Pixels are wired for RGB bitstream (v1 FLORA pixels, not v2)
//   NEO_RGBW    Pixels are wired for RGBW bitstream (NeoPixel RGBW products)
Adafruit_NeoPixel strip = Adafruit_NeoPixel(36, PIN, NEO_GRB + NEO_KHZ800);</p><p>// IMPORTANT: To reduce NeoPixel burnout risk, add 1000 uF capacitor across
// pixel power leads, add 300 - 500 Ohm resistor on first pixel's data input
// and minimize distance between Arduino and first pixel.  Avoid connecting
// on a live circuit...if you must, connect GND first.</p><p>void setup() {
  // This is for Trinket 5V 16MHz, you can remove these three lines if you are not using a Trinket
  #if defined (__AVR_ATtiny85__)
    if (F_CPU == 16000000) clock_prescale_set(clock_div_1);
  #endif
  // End of trinket special code</p><p>  strip.begin();
  strip.show(); // Initialize all pixels to 'off'
}</p><p>void loop() {
  // Some example procedures showing how to display to the pixels:
  colorWipe(strip.Color(255, 0, 0), 50); // Red
  colorWipe(strip.Color(0, 255, 0), 50); // Green
  colorWipe(strip.Color(0, 0, 255), 50); // Blue
//colorWipe(strip.Color(0, 0, 0, 255), 50); // White RGBW
  // Send a theater pixel chase in...
  theaterChase(strip.Color(127, 127, 127), 50); // White
  theaterChase(strip.Color(127, 0, 0), 50); // Red
  theaterChase(strip.Color(0, 0, 127), 50); // Blue</p><p>  rainbow(20);
  rainbowCycle(20);
  theaterChaseRainbow(50);
}</p><p>// Fill the dots one after the other with a color
void colorWipe(uint32_t c, uint8_t wait) {
  for(uint16_t i=0; i</p><p>void rainbow(uint8_t wait) {
  uint16_t i, j;</p><p>  for(j=0; j<256; j++) {
    for(i=0; i</p><p>// Slightly different, this makes the rainbow equally distributed throughout
void rainbowCycle(uint8_t wait) {
  uint16_t i, j;</p><p>  for(j=0; j<256*5; j++) { // 5 cycles of all colors on wheel
    for(i=0; i< strip.numPixels(); i++) {
      strip.setPixelColor(i, Wheel(((i * 256 / strip.numPixels()) + j) & 255));
    }
    strip.show();
    delay(wait);
  }
}</p><p>//Theatre-style crawling lights.
void theaterChase(uint32_t c, uint8_t wait) {
  for (int j=0; j<10; j++) {  //do 10 cycles of chasing
    for (int q=0; q < 3; q++) {
      for (uint16_t i=0; i < strip.numPixels(); i=i+3) {
        strip.setPixelColor(i+q, c);    //turn every third pixel on
      }
      strip.show();</p><p>      delay(wait);</p><p>      for (uint16_t i=0; i < strip.numPixels(); i=i+3) {
        strip.setPixelColor(i+q, 0);        //turn every third pixel off
      }
    }
  }
}</p><p>//Theatre-style crawling lights with rainbow effect
void theaterChaseRainbow(uint8_t wait) {
  for (int j=0; j < 256; j++) {     // cycle all 256 colors in the wheel
    for (int q=0; q < 3; q++) {
      for (uint16_t i=0; i < strip.numPixels(); i=i+3) {
        strip.setPixelColor(i+q, Wheel( (i+j) % 255));    //turn every third pixel on
      }
      strip.show();</p><p>      delay(wait);</p><p>      for (uint16_t i=0; i < strip.numPixels(); i=i+3) {
        strip.setPixelColor(i+q, 0);        //turn every third pixel off
      }
    }
  }
}</p><p>// Input a value 0 to 255 to get a color value.
// The colours are a transition r - g - b - back to r.
uint32_t Wheel(byte WheelPos) {
  WheelPos = 255 - WheelPos;
  if(WheelPos < 85) {
    return strip.Color(255 - WheelPos * 3, 0, WheelPos * 3);
  }
  if(WheelPos < 170) {
    WheelPos -= 85;
    return strip.Color(0, WheelPos * 3, 255 - WheelPos * 3);
  }
  WheelPos -= 170;
  return strip.Color(WheelPos * 3, 255 - WheelPos * 3, 0);
}</p>

*We've since added sound sensitivity with an auto-gain mic chip. If there's interest in that fork of this project let us know in the comments and we'll write it up.

Step 5: Put It All Together...

Fill the coke bottles with water. Alternatively, you can add a drop of milk to get a cloudy effect -- it makes the liquid translucent and gives the impression of solid light, but you loose the little bubble and scratch effects that clear water highlights, which we thought was cooler.

We put a black Sugru cap on the bottles to avoid a drunken accident involving water and electronics. And filled the countersunk bolt holes with Sugru to hide the bolt heads as well.

I attached the plastic arduino casing with double-sided velcro to the base unit for easy removal. It'd look nicer if I'd put the whole unit inside the base, but as we're planning to pull the board out and modify for music sensitivity, I opted for convenience over beauty. If anybody has any cool Arduino code that gets an Adafruit auto-gain mic and neopixels jamming via FFT, I'd love to see it. There are some great color organ projects out there, but I can't seem to find this particular combo. So we may have to write it ourselves!

Plug the wall wart into the Arduino and BOOM. You got yourself a classic party light that's mesmerizing to look at, throws patterns on the ceiling that look like Hubble images of distant nebulae, a fine conversation piece, and a bunch of cast off junk transformed into a thing of beauty.

I'm not sure, but I think this has something to do with art.

Step 6: Music Sensitivity Mod

I mentioned my son wanted a music-reactive version of this. Took til his 18th birthday to get around to it, but here it is!

Additional pieces of equipment:

1 Single pole, Double Throw Switch
1 Automatic Gain Control Mic (I used AdaFruit's MAX9184)
1 1uF-100uF capacitor (Any value)

The microphone really has to have Automatic Gain Control for this to work properly. AGC will constantly sample the ambient noise and raise and lower the threshold that it considers background, so your light will respond to spikes against that background. AdaFruit's mic is brilliant: you can go from a silent room in which the sound of a single voice will trigger it to full-on party mode with a room full of teenagers and music blaring, and it will pick up the beat of the music just fine. The alternative, an Adjustable Gain Mic, has a tiny potentiometer on the board which is impossibly delicate and fiddly. It doesn't take much change in the ambient sound to render the unit useless: lights on constantly or dark constantly. AGC works like magic.

I wanted the option to use the swirl test pattern or music, so I wired the center lead of a switch to VIN and one lead to pin 4 the other to pin 8 of the Leonardo. By testing those pins for HIGH or LOW we can know which state the switch is in, and branch code accordingly.

Step 7: Wiring Up the Microphone

Feed the Mic input, via that 1-100µF capacitor, into Analogue Pin 0. If your capacitor is polarized, the out pin goes to the positive side (Green wire).

Thanks to CodeGirlJP for her Trinket-Color-by-Sound routine, which I adapted below:

<p>// Sound activated LEDs with the Arduino and NeoPixels </p><p>#include </p><p>#define MIC_PIN    A0  // Microphone is attached to  pin a0 on the Leonardo
#define LED_PIN    6  // NeoPixel LED strand attached to pin 6 on the Leonardo
#define N_PIXELS   36  // number of pixels in LED strand !!!!!! Adjust to number of pixels in your setup. This is correct for 3 Neopixel rings !!!!!!
#define N          100  // Number of samples to take each time readSamples is called
#define fadeDelay  5  // delay time for each fade amount
#define noiseLevel 30  // slope level of average mic noise without sound </p><p>//Initialize the NeoPixel strip with the defined values above:</p><p>Adafruit_NeoPixel  strip = Adafruit_NeoPixel(N_PIXELS, LED_PIN, NEO_GRB + NEO_KHZ800);</p><p>int samples[N];        // storage for a sample collection set
int periodFactor = 0;  // keep track of number of ms for period calculation
int t1 = -1;           // times of slope > 100 detected.
int T;                 // period between times scaled to milliseconds
int slope;             // the slope of two collected data sample points
byte periodChanged = 0;
const int SwitchPinMusic = 4; // Pin for switch position music-sensitivity 
const int SwitchPinSwirl = 8; // Pin for switch position Test Pattern (swirl)
int MusicbuttonState = 0; // On off logic variable for music sensitivity </p><p>// Arduino setup Method
void setup() 
{</p><p>    strip.begin();
    ledsOff();
    delay(500);
    
    displayColor(Wheel(100)); 
    strip.show();
    delay(500);
    
    oddWheel(Wheel(100));
    strip.show();
    delay(500);
     pinMode(SwitchPinMusic, INPUT);
     pinMode(SwitchPinSwirl, INPUT);
     //attachInterrupt(4, Switched, FALLING);</p><p>}</p><p>// Arduino loop Method
void loop() 
{  
  
 SwirlbuttonState = digitalRead(SwitchPinSwirl); //HIGH if switch set to Music sensitivity
 MusicbuttonState = digitalRead(SwitchPinMusic); //HIGH if switch set to Test pattern 
 while (SwirlbuttonState == LOW) {
    readSamples(); //Run the music sampling routine
    SwirlbuttonState = digitalRead(SwitchPinSwirl); //Check if the switch was changed
}
 SwirlbuttonState = digitalRead(SwitchPinSwirl);
 MusicbuttonState = digitalRead(SwitchPinMusic);
 while (SwirlbuttonState == HIGH) {
  Dance(); //Run the swirly test pattern routine
 SwirlbuttonState = digitalRead(SwitchPinSwirl); //Check if the switch was changed</p><p>}
}</p><p>void Dance() {  
  while (SwirlbuttonState == HIGH) {
  colorWipe(strip.Color(255, 0, 0), 50); // Red
  SwirlbuttonState = digitalRead(SwitchPinSwirl);
  colorWipe(strip.Color(0, 255, 0), 50); // Green
  SwirlbuttonState = digitalRead(SwitchPinSwirl);
  colorWipe(strip.Color(0, 0, 255), 50); // Blue
  SwirlbuttonState = digitalRead(SwitchPinSwirl);
  //colorWipe(strip.Color(0, 0, 0, 255), 50); // White RGBW
  // Send a theater pixel chase in...
  SwirlbuttonState = digitalRead(SwitchPinSwirl);
  theaterChase(strip.Color(127, 127, 127), 50); // White
  SwirlbuttonState = digitalRead(SwitchPinSwirl);
  theaterChase(strip.Color(127, 0, 0), 50); // Red
  SwirlbuttonState = digitalRead(SwitchPinSwirl);
  theaterChase(strip.Color(0, 0, 127), 50); // Blue
  SwirlbuttonState = digitalRead(SwitchPinSwirl);
  rainbow(20);
  SwirlbuttonState = digitalRead(SwitchPinSwirl);
  rainbowCycle(20);
  SwirlbuttonState = digitalRead(SwitchPinSwirl);
  theaterChaseRainbow(50);
  SwirlbuttonState = digitalRead(SwitchPinSwirl);
}
}
// Read  and Process Sample Data from Mic
void readSamples()
{  
  for(int i=0; i0)
      {
        slope = samples[i] - samples[i-1];
      }
      else
      {
        slope = samples[i] - samples[N-1];
      }
    
      // Check if Slope greater than noiseLevel  - sound that is not at noise level detected
      if(abs(slope) > noiseLevel)
      {
        if(slope < 0)
        {
             calculatePeriod(i);
             
             if(periodChanged == 1)
             {
               displayColor(getColor(T));
             }
        }
      }
      else
      {
             ledsOff();
//             theaterChaseRainbow(50);
             
      }
     
    periodFactor += 1;
    delay(1);
  }
}</p><p>void calculatePeriod(int i)
{
        if(t1 == -1)
        {
            // t1 has not been set 
            t1 = i; 
        }
        else
        { 
             // t1 was set so calc period
             int period = periodFactor*(i - t1);
             periodChanged = T==period ? 0 : 1;
             T = period;
             //Serial.println(T);
             
             // reset t1 to new i value
             t1 = i; 
             periodFactor = 0;
        }
}</p><p>uint32_t getColor(int period)
{
  if(period == -1) 
     return Wheel(0);
  else if(period > 400)
     return Wheel(5);
  else 
    return Wheel(map(-1*period, -400, -1, 50, 255));
}</p><p>void fadeOut()
{
  for(int i=0; i<5; i++)
  { 
    strip.setBrightness(110 - i*20);
    strip.show(); // Update strip
    delay(fadeDelay);
    periodFactor +=fadeDelay;
  }
}</p><p>void fadeIn()
{
  
  strip.setBrightness(100);
  strip.show(); // Update strip
  
    // fade color in 
  for(int i=0; i<5; i++)
  {
    //strip.setBrightness(20*i + 30);
    //strip.show(); // Update strip
    delay(fadeDelay);
    periodFactor+=fadeDelay;
  }
}</p><p>void ledsOff()
{ 
  fadeOut();
  
  for(int i=0; i</p><p>void displayColor(uint32_t color)
{
  
  for(int i=0; i</p><p>  void oddWheel(uint32_t color)
{
  for (int j=0; j < 256; j++) {     // cycle all 256 colors in the wheel
    for (int q=0; q < 3; q++) {
      for (uint16_t i=24; i < 36; i=i+3) {
        strip.setPixelColor(i+q, Wheel( (i+j) % 255));    //turn every third pixel on
      }
      strip.show();</p><p>      delay(1 );</p><p>      for (uint16_t i=24; i < 36; i=i+3) {
        strip.setPixelColor(i+q, 0);        //turn every third pixel off
      }
    }
  }
  fadeIn();
}</p><p>// Fill the dots one after the other with a color
void colorWipe(uint32_t c, uint8_t wait) {
  for(uint16_t i=0; i</p><p>void rainbow(uint8_t wait) {
  uint16_t i, j;</p><p>  for(j=0; j<256; j++) {
    for(i=0; i</p><p>// Slightly different, this makes the rainbow equally distributed throughout
void rainbowCycle(uint8_t wait) {
  uint16_t i, j;</p><p>  for(j=0; j<256*5; j++) { // 5 cycles of all colors on wheel
    for(i=0; i< strip.numPixels(); i++) {
      strip.setPixelColor(i, Wheel(((i * 256 / strip.numPixels()) + j) & 255));
    }
    strip.show();
    delay(wait);
  }
}</p><p>//Theatre-style crawling lights.
void theaterChase(uint32_t c, uint8_t wait) {
  for (int j=0; j<10; j++) {  //do 10 cycles of chasing
    for (int q=0; q < 3; q++) {
      for (uint16_t i=0; i < strip.numPixels(); i=i+3) {
        strip.setPixelColor(i+q, c);    //turn every third pixel on
      }
      strip.show();</p><p>      delay(wait);</p><p>      for (uint16_t i=0; i < strip.numPixels(); i=i+3) {
        strip.setPixelColor(i+q, 0);        //turn every third pixel off
      }
    }
  }
}</p><p>//Theatre-style crawling lights with rainbow effect
void theaterChaseRainbow(uint8_t wait) {
  for (int j=0; j < 256; j++) {     // cycle all 256 colors in the wheel
    for (int q=0; q < 3; q++) {
      for (uint16_t i=0; i < strip.numPixels(); i=i+3) {
        strip.setPixelColor(i+q, Wheel( (i+j) % 255));    //turn every third pixel on
      }
      strip.show();</p><p>      delay(wait);</p><p>      for (uint16_t i=0; i < strip.numPixels(); i=i+3) {
        strip.setPixelColor(i+q, 0);        //turn every third pixel off
      }
    }
  }
}</p><p>// Input a value 0 to 255 to get a color value.
// The colours are a transition r - g - b - back to r.
uint32_t Wheel(byte WheelPos) {
  WheelPos = 255 - WheelPos;
  if(WheelPos < 85) {
    return strip.Color(255 - WheelPos * 3, 0, WheelPos * 3);
  }
  if(WheelPos < 170) {
    WheelPos -= 85;
    return strip.Color(0, WheelPos * 3, 255 - WheelPos * 3);
  }
  WheelPos -= 170;
  return strip.Color(WheelPos * 3, 255 - WheelPos * 3, 0);
}</p><p>void Switched(){
  strip.show();
 readSamples(); 
}</p>

Before I get slaughtered in the comments (remember the Be Nice policy!!) I realised after I'd uploaded this how sloppy some of my code is. There's no need to constantly test both Pin 4 and Pin 8 for HIGH. As the switch is Single pole double throw, the value of one can be inferred from the other: you only need to test one. So you could go through and remove every reference to reading and writing the MusicButtonState and simply run the entire thing more efficiently by testing SwirlButtonState, if you are low on memory or extending with other routines. But the code above works.

And if anyone wants to tweak those audio routines to sense not just noise levels but frequency as well, and write some smooth code to slide up and down the light spectrum in response to moves along the audio spectrum, do drop a link in the comments to how you did it.

Enjoy!