Smart Lights

Since it is Christmas time and the entire city has become more festive, we thought of decorating our lab a little. And what better way to do it than with a “small” and “simple” project?

The goal: to make the prizes shelves as shiny, colorful and smart as possible.

For doing it, we have used the following:

  • Adafruit Neopixel Stips
  • Arduino Nano (seemed like the fasted approach – proven wrong meanwhile)
  • Mini MP3 Player with SD card (add some musical background)
  • Mini speaker to connect to MP3 Player
  • Joystick module
  • ESP8266 (yes, the shelves are going to be “IoT” )
  • Android phone
  • Wires and prototyping board
  • A good 5V power supply @26A

We included 6 strips of Neopixels, each having 42 LEDs. The wiring is straightforward: first strip has data in connected to a timer (PWM) pin of the microcontroller, and the rest have it connected to data out of the previous, just like in the picture below. In our setup, it is wired to pin D6 on the Nano.

The MP3 Player Module is DFPlayer Mini, and it is wired to two digital pins, as in the diagram below Since the commands are received asynchronously using serial communication, at 9600 baud rate, the corresponding microcontroller pins will be configured to use bit banging. The receiver pin on the player has a 1K resistor attached, which eliminates some of the noise. The additional speaker can be connected either between the two SPK pins, or its + to Speaker pin and – to ground.

The next important part of our project is the ESP, which requires an external 3.3V step-down voltage regulator to function properly. Powering it from the 3V3 output of the Arduino Nano would exceed the little board current supply capabilities (around 150mA), thus making the ESP go into reset state (its peak current drawn can reach 320mA). The voltage regulator chosen by us is a Pololu Module, which was laying around the lab.

The ESP is flashed with the software provided below, configured in the Access Point&Station mode. Check this tutorial on how to flash your ESP. Upon startup, it creates a network you will be able to connect to, and then creates a websocket server listening for incoming connections.

On the Android side, we have developed the app in Android Studio, using WebSocketClient package for the connection. It is fairly simple and rapid to use, since all it needs is the IP address of the ESP module. To be noted is that if you want your connection to be safe as well, you should consider adding some security features. Everything is organized in only one activity, and the Java code written is the following. For an even faster development of your project, you can download the apk from here.

Main activity code

package com.example.hal9000.shelflights;

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.CompoundButton;
import android.widget.ImageButton;
import android.widget.SeekBar;
import android.widget.Switch;
import android.widget.TextView;
import java.net.URI;
import java.net.URISyntaxException;

import org.java_websocket.client.WebSocketClient;
import org.java_websocket.handshake.ServerHandshake;


public class MainActivity extends AppCompatActivity {
    int step = 1;
    int max = 255;
    int min = 0;
    int stepVolume = 1;
    int maxVolume = 30;
    int minVolume = 0;

    ImageButton music, wipeOneColor, wipeAllColors, chaseOneColor, chaseAllColors;
    ImageButton rainbowLine, rainbowCircle, shelves, ladder, back;
    ImageButton playMusic, nextTune, stopMusic;
    TextView musicText, wipeOneColorText, wipeAllColorsText, chaseOneColorText, chaseAllColorsText;
    TextView rainbowLineText, sendBeamText, shelvesText, ladderText;
    TextView color1Text, color2Text, color3Text, volumeText;
    SeekBar color1Bar, color2Bar, color3Bar, volume;
    Switch onOff;

    private WebSocketClient webSocket;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        music = (ImageButton) findViewById(R.id.music);
        wipeOneColor = (ImageButton) findViewById(R.id.wipe_one);
        wipeAllColors = (ImageButton) findViewById(R.id.wipe_all);
        chaseOneColor = (ImageButton) findViewById(R.id.chase_one);
        chaseAllColors = (ImageButton) findViewById(R.id.chase_all);
        rainbowLine = (ImageButton) findViewById(R.id.rainbow);
        rainbowCircle = (ImageButton) findViewById(R.id.rainbow_circle); 
        shelves = (ImageButton) findViewById(R.id.shelves);
        ladder = (ImageButton) findViewById(R.id.ladder);
        back = (ImageButton) findViewById(R.id.back);
        playMusic = (ImageButton) findViewById(R.id.play_music);
        stopMusic = (ImageButton) findViewById(R.id.stop_music);
        nextTune = (ImageButton) findViewById(R.id.next_tune);

        musicText = (TextView) findViewById(R.id.musicText);
        wipeOneColorText = (TextView) findViewById(R.id.wipe_one_text);
        wipeAllColorsText = (TextView) findViewById(R.id.wipe_all_text);
        chaseOneColorText = (TextView) findViewById(R.id.chase_one_text);
        chaseAllColorsText = (TextView) findViewById(R.id.chase_all_text);
        rainbowLineText = (TextView) findViewById(R.id.rainbow_text);
        rainbowCircleText = (TextView) findViewById(R.id.rainbow_circle_text); 
        shelvesText = (TextView) findViewById(R.id.shelves_text);
        ladderText = (TextView) findViewById(R.id.options_text);
        onOff = (Switch) findViewById(R.id.on_off);
        color1Bar = (SeekBar) findViewById(R.id.color1_bar);
        color2Bar = (SeekBar) findViewById(R.id.color2_bar);
        color3Bar = (SeekBar) findViewById(R.id.color3_bar);
        volume = (SeekBar) findViewById(R.id.volume_bar);
        color1Text = (TextView) findViewById(R.id.color1_text);
        color2Text = (TextView) findViewById(R.id.color2_text);
        color3Text = (TextView) findViewById(R.id.color3_text);
        volumeText = (TextView) findViewById(R.id.volume_text);

        color1Bar.setMax( (max - min) / step );
        color2Bar.setMax( (max - min) / step );
        volume.setMax( (maxVolume - minVolume) / stepVolume );

        onOff.setChecked(true);
        back.setVisibility(View.INVISIBLE);
        color1Bar.setVisibility(View.INVISIBLE);
        color2Bar.setVisibility(View.INVISIBLE);
        color3Bar.setVisibility(View.INVISIBLE);
        color1Text.setVisibility(View.INVISIBLE);
        color2Text.setVisibility(View.INVISIBLE);
        color3Text.setVisibility(View.INVISIBLE);
        playMusic.setVisibility(View.INVISIBLE);
        stopMusic.setVisibility(View.INVISIBLE);
        nextTune.setVisibility(View.INVISIBLE);
        volume.setVisibility(View.INVISIBLE);
        volumeText.setVisibility(View.INVISIBLE);

        connectWebSocket();

        music.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                music.setVisibility(View.INVISIBLE);
                wipeOneColor.setVisibility(View.INVISIBLE);
                wipeAllColors.setVisibility(View.INVISIBLE);
                chaseOneColor.setVisibility(View.INVISIBLE);
                chaseAllColors.setVisibility(View.INVISIBLE);
                rainbowLine.setVisibility(View.INVISIBLE);
                rainbowCircle.setVisibility(View.INVISIBLE); 
                shelves.setVisibility(View.INVISIBLE);
                ladder.setVisibility(View.INVISIBLE);
                musicText.setVisibility(View.INVISIBLE);
                wipeOneColorText.setVisibility(View.INVISIBLE);
                wipeAllColorsText.setVisibility(View.INVISIBLE);
                chaseOneColorText.setVisibility(View.INVISIBLE);
                chaseAllColorsText.setVisibility(View.INVISIBLE);
                rainbowLineText.setVisibility(View.INVISIBLE);
                rainbowCircleText.setVisibility(View.INVISIBLE); 
                shelvesText.setVisibility(View.INVISIBLE);
                ladderText.setVisibility(View.INVISIBLE);
                onOff.setVisibility(View.INVISIBLE);
                back.setVisibility(View.VISIBLE);
                playMusic.setVisibility(View.VISIBLE);
                stopMusic.setVisibility(View.VISIBLE);
                nextTune.setVisibility(View.VISIBLE);
                volume.setVisibility(View.VISIBLE);
                volumeText.setVisibility(View.VISIBLE); 
            }
        });

        playMusic.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                sendMessage("m:p\n");//music - play
            }
        });

        stopMusic.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                sendMessage("m:s\n");//music - stop
            }
        });

        nextTune.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                sendMessage("m:n\n");//music - next
            }
        });

        wipeOneColor.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                sendMessage("e:wo\n");//effects - wipe one
            }
        });
        wipeAllColors.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                sendMessage("e:wa\n");//effects - wipe all
            }
        });

        chaseOneColor.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                sendMessage("e:co\n");//effects - chase one
            }
        });

        chaseAllColors.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                sendMessage("e:ca\n");//effects - chase all
            }
        });

        rainbowLine.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                sendMessage("e:ro\n");//effects - rainbow Line
            }
        });

        rainbowCircle.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                sendMessage("e:rc\n");//effects - rainbow circle
            }
        });

        ladder.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                sendMessage("e:ll\n");//effects - light ladder
            }
        });

        shelves.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                music.setVisibility(View.INVISIBLE);
                wipeOneColor.setVisibility(View.INVISIBLE);
                wipeAllColors.setVisibility(View.INVISIBLE);
                chaseOneColor.setVisibility(View.INVISIBLE);
                chaseAllColors.setVisibility(View.INVISIBLE);
                rainbowLine.setVisibility(View.INVISIBLE);
                rainbowCircle.setVisibility(View.INVISIBLE); 
                shelves.setVisibility(View.INVISIBLE);
                ladder.setVisibility(View.INVISIBLE);
                musicText.setVisibility(View.INVISIBLE);
                wipeOneColorText.setVisibility(View.INVISIBLE);
                wipeAllColorsText.setVisibility(View.INVISIBLE);
                chaseOneColorText.setVisibility(View.INVISIBLE);
                chaseAllColorsText.setVisibility(View.INVISIBLE);
                rainbowLineText.setVisibility(View.INVISIBLE);
                rainbowCircleText.setVisibility(View.INVISIBLE); 
                shelvesText.setVisibility(View.INVISIBLE);
                ladder.setVisibility(View.INVISIBLE);
                ladderText.setVisibility(View.INVISIBLE);
                onOff.setVisibility(View.INVISIBLE);
                back.setVisibility(View.VISIBLE);
                color1Bar.setVisibility(View.VISIBLE);
                color2Bar.setVisibility(View.VISIBLE);
                color1Text.setVisibility(View.VISIBLE);
                color2Text.setVisibility(View.VISIBLE);
                color3Bar.setVisibility(View.VISIBLE);
                color3Text.setVisibility(View.VISIBLE); 
            }
        });

        back.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                music.setVisibility(View.VISIBLE);
                wipeOneColor.setVisibility(View.VISIBLE);
                wipeAllColors.setVisibility(View.VISIBLE);
                chaseOneColor.setVisibility(View.VISIBLE);
                chaseAllColors.setVisibility(View.VISIBLE);
                rainbowLine.setVisibility(View.VISIBLE);
                rainbowCircle.setVisibility(View.VISIBLE); 
                shelves.setVisibility(View.VISIBLE);
                ladder.setVisibility(View.VISIBLE);
                musicText.setVisibility(View.VISIBLE);
                wipeOneColorText.setVisibility(View.VISIBLE);
                wipeAllColorsText.setVisibility(View.VISIBLE);
                chaseOneColorText.setVisibility(View.VISIBLE);
                chaseAllColorsText.setVisibility(View.VISIBLE);
                rainbowLineText.setVisibility(View.VISIBLE);
                rainbowCircleText.setVisibility(View.VISIBLE); 
                shelvesText.setVisibility(View.VISIBLE);
                ladderText.setVisibility(View.VISIBLE);
                onOff.setVisibility(View.VISIBLE);
                back.setVisibility(View.INVISIBLE);
                color1Bar.setVisibility(View.INVISIBLE);
                color2Bar.setVisibility(View.INVISIBLE);
                color1Text.setVisibility(View.INVISIBLE);
                color2Text.setVisibility(View.INVISIBLE);
                color3Bar.setVisibility(View.INVISIBLE);
                color3Text.setVisibility(View.INVISIBLE);
                playMusic.setVisibility(View.INVISIBLE);
                stopMusic.setVisibility(View.INVISIBLE);
                nextTune.setVisibility(View.INVISIBLE);
                volume.setVisibility(View.INVISIBLE);
                volumeText.setVisibility(View.INVISIBLE);
            }
        });

        color1Bar.setOnSeekBarChangeListener(
                new SeekBar.OnSeekBarChangeListener()
                {
                    @Override
                    public void onStopTrackingTouch(SeekBar color1Bar) {}

                    @Override
                    public void onStartTrackingTouch(SeekBar scolor1Bar) {}

                    @Override
                    public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser)
                    {
                        int value = min + (progress * step);
                        sendMessage("r:"+value+"\n"); // red color value
                    }
                }
        );

        color2Bar.setOnSeekBarChangeListener(
                new SeekBar.OnSeekBarChangeListener()
                {
                    @Override
                    public void onStopTrackingTouch(SeekBar color1Bar) {}

                    @Override
                    public void onStartTrackingTouch(SeekBar scolor1Bar) {}

                    @Override
                    public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser)
                    {
                        int value = min + (progress * step);
                        sendMessage("g:"+value+"\n"); //green color value
                    }
                }
        );

        color3Bar.setOnSeekBarChangeListener(
                new SeekBar.OnSeekBarChangeListener()
                {
                    @Override
                    public void onStopTrackingTouch(SeekBar color1Bar) {}

                    @Override
                    public void onStartTrackingTouch(SeekBar scolor1Bar) {}

                    @Override
                    public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser)
                    {
                        int value = min + (progress * step);
                        sendMessage("b:"+value+"\n"); //blue color value
                    }
                }
        );

        volume.setOnSeekBarChangeListener(
                new SeekBar.OnSeekBarChangeListener()
                {
                    @Override
                    public void onStopTrackingTouch(SeekBar color1Bar) {}

                    @Override
                    public void onStartTrackingTouch(SeekBar scolor1Bar) {}

                    @Override
                    public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser)
                    {
                        int value = minVolume + (progress * stepVolume);
                        sendMessage("v:"+value+"\n");//new volume value
                    }
                }
        );

        onOff.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
            @Override
            public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
                if (isChecked) {
                    sendMessage("p:o\n");//power - on
                } else {
                    sendMessage("p:f\n");//power - off
                }
            }
        });

    }

    private void connectWebSocket() {
        URI uri;
        try {
            uri = new URI("ws://192.168.4.1:80/");
        } catch (URISyntaxException e) {
            e.printStackTrace();
            return;
        }

        webSocket = new WebSocketClient(uri) {
            @Override
            public void onOpen(ServerHandshake serverHandshake) {
                Log.i("Websocket", "Opened");
                //webSocket.send("Hello from " + Build.MANUFACTURER + " " + Build.MODEL);
            }

            @Override
            public void onMessage(String s) {
                final String message = s;
                runOnUiThread(new Runnable() {
                    @Override
                    public void run() {

                    }
                });
            }

            @Override
            public void onClose(int i, String s, boolean b) {
                Log.i("Websocket", "Closed " + s);
            }

            @Override
            public void onError(Exception e) {
                Log.i("Websocket", "Error " + e.getMessage());
            }
        };
        webSocket.connect();
    }

    public void sendMessage(String message) {
        webSocket.send(message);
    }
}

 

Back to the hardware side, the role of the Joystick is to switch between effects and turn music on and off when this cannot be achieved through the phone.

All that is left to do now is to wire all components according to the schematic, either on a breadboard or prototyping board and glue your Neopixel strips tight.

The commands send from the Android are received by the ESP and then send to the microcontroller using UART at 115200 baud rate. Since we do not want to lose any valuable information, the receiving on the Arduino needs to be implemented with interrupts.

This is where the code became a little more challenging, since the Neopixels have quite strict timing requirements and the library offered by the producer (Adafruit_Neopixel) disables all interrupts while sending commands to the strip, which was interfering with our implementation. Luckily enough, it seems that the LED drivers inside the Neopixels can be more flexible when it comes to timing, and you can find more details here (this post was our inspiration, too).

With the MP3Player module, we have kept from the library provided by DFRobot only the play, stop and next commands, which are nothing but a 10 character string, just as the website indicates.

This is the final Arduino code:

#include <avr/delay.h>
#include <avr/power.h>
#include <avr/interrupt.h>


#define F_CPU 16000000UL   
#define BAUDRATE 115200
#define BAUD_PRESCALLER (((F_CPU / (BAUDRATE * 8UL))) - 1)


// WS2812 Strip configuration
#define STRIP_PIXEL_NUMBER        252

// light ladder effect pixels
#define LIGHTLADDER2_FLOORLEDS    42
#define LIGHTLADDER2_FLOORS       6

// Change this to be at least as long as your pixel string (too long will work fine, just be a little slower)
#define PIXELS STRIP_PIXEL_NUMBER  // Number of pixels in the string

#define PIXEL_PORT  PORTD  // Port of the pin the pixels are connected to
#define PIXEL_DDR   DDRD   // Port of the pin the pixels are connected to
#define PIXEL_BIT   6      // Bit of the pin the pixels are connected to

// These are the timing constraints taken mostly from the WS2812 datasheets 
// These are chosen to be conservative and avoid problems rather than for maximum throughput 
#define T1H  800    // Width of a 1 bit in ns
#define T1L  500    // Width of a 1 bit in ns
#define T0H  250    // Width of a 0 bit in ns
#define T0L  800    // Width of a 0 bit in ns

#define RES 6000    // Width of the low gap between bits to cause a frame to latch

// Here are some convience defines for using nanoseconds specs to generate actual CPU delays
#define NS_PER_SEC (1000000000L)          // Note that this has to be SIGNED since we want to be able to check for negative values of derivatives
#define CYCLES_PER_SEC (F_CPU)
#define NS_PER_CYCLE ( NS_PER_SEC / CYCLES_PER_SEC )
#define NS_TO_CYCLES(n) ( (n) / NS_PER_CYCLE )
#define DELAY_CYCLES(n) ( ((n)>0) ? __builtin_avr_delay_cycles( n ) : __builtin_avr_delay_cycles( 0 ) ) // Make sure we never have a delay less than zero
 

// MP3 Player Configuration
#define PLAYER_SELECTED_LED_PIN 5
#define PLAYER_ON_LED_PIN 4
#define PLAYER_RX   10
#define PLAYER_TX   11

// Joystick configuration
#define Ox_PIN A0
#define Oy_PIN A1
#define BTN 2
int ox_val = 0.0, oy_val = 0.0;
uint8_t button_flag = 0;


/**
 * Color effects
 */
#define PLAY_MUSIC 0
#define WIPE_ONE_COLOR 1
#define WIPE_ALL_COLORS 2
#define CHASE_ONE_COLOR 3
#define CHASE_ALL_COLORS 4
#define RAINBOW_LINE 5
#define RAINBOW_CYCLE 6
#define SEND_BEAM 7
#define LIGHT_LADDER 8
#define POWEROFF 9

// number of effects
const int mode_count = POWEROFF;
// default effect
volatile int mode = LIGHT_LADDER;

/**
 * Generic effect color configuration, joystick control
 */
#define NONE_VAR  0
#define RED_VAR   1
#define BLUE_VAR  2
#define GREEN_VAR 3
int red_value = 200, green_value = 200, blue_value = 0;
int selected_variable = 0;
const int parameters_number = 4; // volume, red, green, blue
uint8_t first_wipe = 1;


/**
 * Light ladder effect configuration
 */
uint8_t lightLadder2_color1[ 3 ] = { 255, 255, 16 };
uint8_t lightLadder2_color2[ 3 ] = { 32, 128, 178 };
uint8_t lightLadder2_speed = 6;
uint32_t lightLadder2_steps;
uint8_t lightLadder2_floorMap[ LIGHTLADDER2_FLOORS ][ 3 ];
int32_t lightLadder2_floorTransition[ 3 ];

// effect roll configuration
int start_next_effect, next_effect_in_series = 0;

/**
 * Serial interrupt cmmands
 */
volatile uint8_t int_rx_new = 0;
volatile char int_rx_cmd = 0;
volatile char int_rx_value[ 4 ] = "";

/**
 * MP3 Player control
 */
#define MP3_SEND_LENGTH 10
#define STX_PORT  PORTB
#define STX_BIT   3

uint8_t mp3_sendVect[MP3_SEND_LENGTH] = {0x7E, 0xFF, 06, 00, 01, 00, 00, 00, 00, 0xEF};
uint8_t mp3_sendFrame[MP3_SEND_LENGTH];

// disable unnecessary interrupts
void disableTimerInt( void )
{
  cli();
  ADCSRA &= ~(1 << ADEN);    //  Disable the analog comparator.
  TIMSK0 &= ~(1 << TOIE0);   //  Disable Timer 0.
  TIMSK1 &= ~(1 << TOIE1);   //  Disable Timer 1 - Turns Off PWM.
  TIMSK2 &= ~(1 << TOIE2);   //  Disable Timer 2.  
  sei();
}

void sputchar( uint8_t c )
{
  c = ~c;
  STX_PORT &= ~(1<<STX_BIT);            // start bit
  for( uint8_t i = 10; i; i-- ){        // 10 bits
    _delay_us( 1e6 / 9600 );            // bit duration
    if( c & 1 )
      STX_PORT &= ~(1<<STX_BIT);        // data bit 0
    else
      STX_PORT |= 1<<STX_BIT;           // data bit 1 or stop bit
    c >>= 1;
  }
}

void mp3_init()
{
  pinMode( 11, OUTPUT );  
  digitalWrite( 11, HIGH );
}

void mp3_sendCommand( void )
{
   uint8_t i;
   for( i=0; i<MP3_SEND_LENGTH; i++ )
    sputchar( mp3_sendFrame[ i ] );
}

void mp3_copyFrame( void ) 
{
  uint8_t i;
  for( i=0; i<MP3_SEND_LENGTH; i++ )
    mp3_sendFrame[ i ] = mp3_sendVect[ i ];
}
void mp3_updateChecksum( void )
{
  uint16_t sum = 0;
  for (int i=1; i<7; i++) {
    sum += mp3_sendFrame[i];
  }
  sum = -sum;
  mp3_sendFrame[ 7 ] = (uint8_t)(sum>>8);
  mp3_sendFrame[ 8 ] = (uint8_t)(sum);
}

void mp3_volume( uint8_t volume )
{
  mp3_copyFrame();
  mp3_sendFrame[ 3 ] = 0x06;
  mp3_sendFrame[ 6 ] = volume;
  mp3_updateChecksum();
  mp3_sendCommand();
}

void mp3_next( void )
{
  mp3_copyFrame();
  mp3_sendFrame[ 3 ] = 0x01;
  mp3_updateChecksum();
  mp3_sendCommand();
}

void mp3_play( uint8_t track )
{
  mp3_copyFrame();
  mp3_sendFrame[ 3 ] = 0x03;
  mp3_sendFrame[ 6 ] = track;
  mp3_updateChecksum();
  mp3_sendCommand();
}

void mp3_stop( void )
{
  mp3_copyFrame();
  mp3_sendFrame[ 3 ] = 0x16;
  mp3_updateChecksum();
  mp3_sendCommand();
}


/**
 * Fast ISR friendly WS2812 led driver
 * https://wp.josh.com/2014/05/13/ws2812-neopixels-are-not-so-finicky-once-you-get-to-know-them/
 */
void sendBit(bool) __attribute__ ((optimize(0)));
void sendBit( bool bitVal ) { 
    if (  bitVal ) {       // 0 bit
      cli();
      asm volatile (
        "sbi %[port], %[bit] \n\t"        // Set the output bit
        ".rept %[onCycles] \n\t"                                // Execute NOPs to delay exactly the specified number of cycles
        "nop \n\t"
        ".endr \n\t"
        "cbi %[port], %[bit] \n\t"                              // Clear the output bit
        ".rept %[offCycles] \n\t"                               // Execute NOPs to delay exactly the specified number of cycles
        "nop \n\t"
        ".endr \n\t"
        ::
        [port]    "I" (_SFR_IO_ADDR(PIXEL_PORT)),
        [bit]   "I" (PIXEL_BIT),
        [onCycles]  "I" (NS_TO_CYCLES(T1H) - 2),    // 1-bit width less overhead  for the actual bit setting, note that this delay could be longer and everything would still work
        [offCycles]   "I" (NS_TO_CYCLES(T1L) - 2)     // Minimum interbit delay. Note that we probably don't need this at all since the loop overhead will be enough, but here for correctness
  
      );
      sei();                  
    } else {          // 1 bit

    // **************************************************************************
    // This line is really the only tight goldilocks timing in the whole program!
    // **************************************************************************


      asm volatile (
        "sbi %[port], %[bit] \n\t"        // Set the output bit
        ".rept %[onCycles] \n\t"        // Now timing actually matters. The 0-bit must be long enough to be detected but not too long or it will be a 1-bit
        "nop \n\t"                                              // Execute NOPs to delay exactly the specified number of cycles
        ".endr \n\t"
        "cbi %[port], %[bit] \n\t"                              // Clear the output bit
        ".rept %[offCycles] \n\t"                               // Execute NOPs to delay exactly the specified number of cycles
        "nop \n\t"
        ".endr \n\t"
        ::
        [port]    "I" (_SFR_IO_ADDR(PIXEL_PORT)),
        [bit]   "I" (PIXEL_BIT),
        [onCycles]  "I" (NS_TO_CYCLES(T0H) - 2),
        [offCycles] "I" (NS_TO_CYCLES(T0L) - 2)
  
      );
      
    }
}

void sendByte( unsigned char byte ) 
{ 
    for( unsigned char bit = 0 ; bit < 8 ; bit++ ) {
      sendBit( bitRead( byte , 7 ) ); // Neopixel wants bit in highest-to-lowest orde                                           // so send highest bit (bit #7 in an 8-bit byte since they start at 0)
      byte <<= 1; // and then shift left so bit 6 moves into 7, 5 moves into 6, etc
    }
}

void ledSetup() {  
  bitSet( PIXEL_DDR , PIXEL_BIT );  
}

inline void sendPixel( unsigned char r, unsigned char g , unsigned char b )  {  
  sendByte(g); 
  sendByte(r);
  sendByte(b);
}

void stripShow() {
  _delay_us( (RES / 1000UL) + 1);       // Round up since the delay must be _at_least_ this long (too short might not work, too long not a problem)
}

// Display a single color on the whole string
void showColor( unsigned char r , unsigned char g , unsigned char b ) 
{   
  for( int p=0; p<PIXELS; p++ ) {
    sendPixel( r , g , b );    
  }
  stripShow();
}


/**
 * Music control
 */
void play_music()
{
  digitalWrite( PLAYER_ON_LED_PIN, HIGH );
  mp3_play( 2 );
}

void stop_music()
{
  digitalWrite( PLAYER_ON_LED_PIN, LOW );
  mp3_stop();
}

void next_track()
{
  mp3_next();
}

void set_volume( int volume )
{
  mp3_volume( volume );
}



void serial_init() 
{
  power_usart0_enable();

  UCSR0A = _BV( U2X0 );
  
  // configure the ports speed
  UBRR0H = (uint8_t)(BAUD_PRESCALLER>>8);
  UBRR0L = (uint8_t)(BAUD_PRESCALLER);
  
  // asynchronous, 8N1 mode
  UCSR0C |= 0x06;
  
  // rx/tx enable
  UCSR0B |= _BV(RXEN0);
  UCSR0B |= _BV(TXEN0);
  
  UCSR0B |= (1 << RXEN0) | (1 << TXEN0) | (1 << RXCIE0);   // Turn on the transmission, reception, and Receive interrupt      
  interrupts();
}

/**
 * Initializations
 */
void setup( ) 
{
  disableTimerInt();
  pinMode( BTN, INPUT_PULLUP );
  pinMode( PLAYER_SELECTED_LED_PIN, OUTPUT );
  pinMode( PLAYER_ON_LED_PIN, OUTPUT );  
  digitalWrite( PLAYER_SELECTED_LED_PIN, LOW );
  digitalWrite( PLAYER_ON_LED_PIN, LOW );
  
  attachInterrupt( 0, ButtonISR, FALLING ); 
 
  ledSetup();
  
  mp3_init();
  mp3_volume( 15 );
  
  serial_init();
  
  lightLadder2_Init();
}



/**
 * Refresh loop
 */
void loop( ) 
{
  // button was pressed => change mode, clear flag
  if( button_flag ) 
  {
    button_flag = 0; 
    showColor( 0, 0, 0 );
    if( mode == WIPE_ALL_COLORS ) first_wipe = 1;
  }

  // process incoming commands
  if( int_rx_new ) {
    interpret_cmd( int_rx_cmd, int_rx_value );
    int_rx_new = 0; 
    if( mode == WIPE_ALL_COLORS ) first_wipe = 1;     
  }

  if( mode == PLAY_MUSIC ) digitalWrite( PLAYER_SELECTED_LED_PIN, HIGH );
  else digitalWrite( PLAYER_SELECTED_LED_PIN, LOW );

  
  switch( mode )
  {
    case WIPE_ONE_COLOR: 
      colorWipe( red_value, green_value, blue_value, 0, 0, 0 );
      colorWipeInverse( 0, 0, 0, red_value, green_value, blue_value );
      break;
      
    case WIPE_ALL_COLORS:
      if( first_wipe ) {
        colorWipe( 255, 0, 0, 0, 0, 0 );        // Red
        first_wipe = 0;
      } else {
        colorWipe( 255, 0, 0, 255, 0, 255 );
      }
      colorWipeInverse( 0, 255, 0, 255, 0, 0 ); //Green 
      colorWipe( 0, 0, 255, 0, 255, 0 );        // Blue
      colorWipeInverse( 255, 0, 255, 0, 0, 255);//Purple
      break;
      
    case CHASE_ONE_COLOR:
      theaterChase( red_value, green_value, blue_value, 5 );
      break;
      
    case CHASE_ALL_COLORS:
      theaterChase( 127, 127, 127, 10 ); // White
      theaterChase( 127, 0, 0, 10 ); // Red
      theaterChase( 0, 0, 127, 10 ); // Green
      theaterChase( 0, 127, 0, 10 ); // Blue
      break;
      
    case RAINBOW_LINE:
      rainbow( 5 );
      break;
   
    case RAINBOW_CYCLE:
      rainbowCycle(1000 , 20 , 5 );
      break;
      
    case SEND_BEAM:
      detonate( 255, 255, 255, 1000 );
      break;
  
    case LIGHT_LADDER:
      lightLadder2_CalculateColors();
      break;
  
    case PLAY_MUSIC:
      selected_variable = NONE_VAR;
      static unsigned long timer = millis();
      ox_val = analogRead( Ox_PIN );
      
      if( ox_val > 800 ) {
        play_music();    
      }
      else if( ox_val < 100 ) {
        stop_music();
      }
      break;

    case POWEROFF:
      stop_music();
      showColor( 0, 0, 0 );      
      break;
  }
}

/**
 * Light ladder two color initialization
 */
void lightLadder2_Init( void )
{
  double cf = 1.00F / ( LIGHTLADDER2_FLOORS - 1 );
  double colorDiff[ 3 ];
  uint8_t i, j;

  // first and last floor fill
  for( i=0; i<3; i++ ) {
    lightLadder2_floorMap[ 0 ][ i ] = lightLadder2_color1[ i ];
    lightLadder2_floorMap[ LIGHTLADDER2_FLOORS - 1 ][ i ] = lightLadder2_color2[ i ];
    // floor and step differences
    colorDiff[ i ] = (( double ) lightLadder2_color2[ i ] ) - (( double ) lightLadder2_color1[ i ] );
    lightLadder2_floorTransition[ i ] = ( int32_t )( colorDiff[ i ] * cf );
  }

  // intermediate fill
  for( i=1; i<(LIGHTLADDER2_FLOORS-1); i++ )
    for( j=0;j<3;j++ )
      lightLadder2_floorMap[ i ][ j ] = (( double ) lightLadder2_color1[ j ]) + (((double) i) * cf ) * colorDiff[ j ];

  lightLadder2_steps = ((uint32_t) lightLadder2_speed ) * ((LIGHTLADDER2_FLOORS - 1 )* 2);
}

void setFloorColors( uint8_t floors[ LIGHTLADDER2_FLOORS ][ 3 ] )
{
  uint8_t i, j;
  uint16_t p;
  
  // display floor colors
  for( i=0; i<LIGHTLADDER2_FLOORS; i++ )
  {
    p = LIGHTLADDER2_FLOORLEDS * i;
    for( j=0; j<LIGHTLADDER2_FLOORLEDS; j++ )
      sendPixel( floors[ LIGHTLADDER2_FLOORS - i - 1 ][ 0 ], floors[ LIGHTLADDER2_FLOORS - i - 1 ][ 1 ], floors[ LIGHTLADDER2_FLOORS - i - 1 ][ 2 ]  );
  }
  stripShow();
}

/**
 * Light ladder two color calculate floor colors
 */
void lightLadder2_CalculateColors( void )
{
  static uint32_t stepIndex = 0;
  uint8_t lightLadder2_floorColors[ LIGHTLADDER2_FLOORS ][ 3 ];
  uint8_t i, j, floorShift, mappedFloor;
  uint8_t transistionIndex;
  int16_t transitionRGB[ 3 ];

  // reset index step
  if( ++stepIndex == lightLadder2_steps ) stepIndex = 0;

  // caclulate floor to floor transition and floor shift
  transistionIndex = stepIndex % lightLadder2_speed;
  floorShift = stepIndex / lightLadder2_speed;

  // calculate transition color
  for( i=0; i<3; i++ )
    transitionRGB[ i ] = ( int16_t )(( lightLadder2_floorTransition[ i ] * transistionIndex ) / ( lightLadder2_speed - 1 ));
  
  // update individual floor colors
  for( i=0; i<LIGHTLADDER2_FLOORS; i++ )
  {
    // map floor i 
    mappedFloor = ( floorShift + i ) % (( LIGHTLADDER2_FLOORS - 1) * 2 );
    
    if( mappedFloor < LIGHTLADDER2_FLOORS ) {
      if( mappedFloor == LIGHTLADDER2_FLOORS - 1 && transistionIndex != 0 ) {
        for( j=0; j<3; j++ )
          lightLadder2_floorColors[ i ][ j ] = (( int16_t ) lightLadder2_floorMap[ mappedFloor ][ j ] ) - transitionRGB[ j ];
      } else {
        for( j=0; j<3; j++ )
          lightLadder2_floorColors[ i ][ j ] = (( int16_t ) lightLadder2_floorMap[ mappedFloor ][ j ] ) + transitionRGB[ j ];
      }
    } else {  
      mappedFloor = ( LIGHTLADDER2_FLOORS - 1 ) - ( mappedFloor % ( LIGHTLADDER2_FLOORS - 1 ));    
      for( j=0; j<3; j++ )
        lightLadder2_floorColors[ i ][ j ] = (( int16_t ) lightLadder2_floorMap[ mappedFloor ][ j ] ) - transitionRGB[ j ];
    }
  }
  
  setFloorColors( lightLadder2_floorColors );
}



// Fill the dots one after the other with a color
void colorWipe(unsigned char r , unsigned char g, unsigned char b, unsigned char r2, unsigned char g2,  unsigned char b2 ) {
  for(unsigned int i=0; i<PIXELS; i+=(PIXELS/60) ) {    
    unsigned int p=0;
    while (p++<=i) {
        sendPixel( r, g, b );
    } 
    while (p++<=PIXELS) {
        sendPixel( r2, g2, b2 );  
    }
    if( button_flag || int_rx_new ) return;
    stripShow();
    _delay_ms(5);
  }
}

void colorWipeInverse(unsigned char r , unsigned char g, unsigned char b, unsigned char r2, unsigned char g2,  unsigned char b2 ) {
  for(int i=PIXELS-1; i>=0; i-=(PIXELS/60) ) {    
    int p=0;
    while (p++<=i) {
        sendPixel(r2, g2, b2);
    } 
    while (p++<=PIXELS) {
        sendPixel(r, g, b);  
    }
    if( button_flag || int_rx_new ) return;
    stripShow();
    _delay_ms(5);
  }
}

void rainbow(uint8_t wait) 
{
  uint16_t i, j;
  byte WheelPos;
  for(j=0; j<256; j++) {
    for(i=0; i<PIXELS; i++) {
      WheelPos = 255 - ((i+j) & 255);
      if(WheelPos < 85) {
        sendPixel(255 - WheelPos * 3, 0, WheelPos * 3);
      }
      else if(WheelPos < 170) {
        WheelPos -= 85;
        sendPixel(0, WheelPos * 3, 255 - WheelPos * 3);
      } else {
      WheelPos -= 170;
      sendPixel(WheelPos * 3, 255 - WheelPos * 3, 0);
      }
      //strip.setPixelColor(i, Wheel((i+j) & 255));
      if( button_flag || int_rx_new  ) return;
    }
    stripShow();
    _delay_ms(wait);
  }
}

void rainbowCycle(unsigned char frames , unsigned int frameAdvance, unsigned int pixelAdvance ) 
{  
  unsigned int firstPixelHue = 0;     // Color for the first pixel in the string
  
  for(unsigned int j=0; j<frames; j++) {                        
    
    unsigned int currentPixelHue = firstPixelHue;
        
    for(unsigned int i=0; i< PIXELS; i++) {
      
      if (currentPixelHue>=(3*256)) {                  // Normalize back down incase we incremented and overflowed
        currentPixelHue -= (3*256);
      }
            
      unsigned char phase = currentPixelHue >> 8;
      unsigned char step = currentPixelHue & 0xff;
                 
      switch (phase) {        
        case 0: 
          sendPixel( ~step , step ,  0 );
          break;          
        case 1: 
          sendPixel( 0 , ~step , step );
          break;
        case 2: 
          sendPixel(  step ,0 , ~step );
          break;          
      }
      currentPixelHue+=pixelAdvance;                                      
    } 
    if( int_rx_new || button_flag ) return;
    
    stripShow();    
    firstPixelHue += frameAdvance;           
  }
}

//Theatre-style crawling lights.
#define THEATER_SPACING (PIXELS/50)

void theaterChase( unsigned char r , unsigned char g, unsigned char b, unsigned char wait ) 
{  
  for (int j=0; j< 3 ; j++) {  
    for (int q=0; q < THEATER_SPACING ; q++) {      
      unsigned int step=0;
      for (int i=0; i < PIXELS ; i++) {        
        if (step==q) {          
          sendPixel( r , g , b );          
        } else {          
          sendPixel( 0 , 0 , 0 );          
        }        
        step++;        
        if ( step == THEATER_SPACING ) step = 0;
        if( int_rx_new || button_flag )  return;      
      }     
      stripShow();
      _delay_ms(wait);      
    }    
  }  
}

void detonate( unsigned char r , unsigned char g , unsigned char b , unsigned int startdelayms) {
  while (startdelayms) {
    showColor( r , g , b );      // Flash the color 
    showColor( 0 , 0 , 0 );
    _delay_ms( startdelayms );     
    startdelayms =  ( startdelayms * 4 ) / 5 ;           // delay between flashes is halved each time until zero
  }
  
  // Then we fade to black....
  for( int fade=256; fade>0; fade-- ) {
    showColor( (r * fade) / 256 ,(g*fade) /256 , (b*fade)/256 );
    if( button_flag || int_rx_new ) return;
  }
  showColor( 0 , 0 , 0 );
}

//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
        if( button_flag ) return;
      }
      strip.show(); 
      delay(wait);

      for (uint16_t i=0; i < strip.numPixels(); i=i+3) {
        strip.setPixelColor(i+q, 0);        //turn every third pixel off
        if( button_flag )  return;
      }
    }
  }*/
}


/**
 * Button pressed ISR
 */
void ButtonISR() 
{
  if( digitalRead( BTN ) == 0 ) 
  {
    button_flag = 1;
    mode = (( mode + 1 ) % ( mode_count + 1 ));
  }
}


void interpret_cmd( char cmd, char* val )
{
  int value;
  
  // music command
  if( cmd == 'm' ) {
    if( val[ 0 ] == 'p' ) play_music();
    else if( val[ 0 ] == 'n' ) next_track();
    else if( val[ 0 ] == 's' ) stop_music();
  } 
  // volume control
  else if( cmd == 'v' ) {
    sscanf( val, "%d", &value );
    set_volume( value );
  } 
  // power on/off
  else if( cmd == 'p' ) {
    if( val[ 0 ] == 'o' ) mode = LIGHT_LADDER;
    else if( val[ 0 ] == 'f' ) mode = POWEROFF;
  } 
  //effects mode
  else if(cmd == 'e'){
    if( val[0] == 'b' ) mode = SEND_BEAM;
    else if( val[0] == 'w' && val[1] == 'o' ) mode = WIPE_ONE_COLOR;
    else if( val[0] == 'w' && val[1] == 'a' ) mode = WIPE_ALL_COLORS;
    else if( val[0] == 'c' && val[1] == 'o' ) mode = CHASE_ONE_COLOR;
    else if( val[0] == 'c' && val[1] == 'a' ) mode = CHASE_ALL_COLORS;
    else if( val[0] == 'r' && val[1] == 'o' ) mode = RAINBOW_LINE;
    else if( val[0] == 'r' && val[1] == 'c' ) mode = RAINBOW_CYCLE;
    else if( val[0] == 'l' && val[1] == 'l' ) mode = LIGHT_LADDER;
  }
  //colors for one-color effects
  else if( cmd == 'r' ) {
    sscanf( val, "%d", &value );
    red_value = value;
    lightLadder2_color1[0] = value;
    lightLadder2_Init();
  }
  else if( cmd == 'g' ) {
    sscanf( val, "%d", &value );
    green_value = value;
    lightLadder2_color1[1] = value;
    lightLadder2_Init();
  }
  else if( cmd == 'b' ) {
    sscanf( val, "%d", &value );
    blue_value = value;
    lightLadder2_color1[2] = value;
  }

}


/**
 * Serial variable decoding from smartphone
 */
ISR(USART_RX_vect)
{  
  static uint8_t valid_cmd = 0;
  static uint8_t charIndex = 0;
  static char rx_cmd = 0;
  static char rx_value[ 4 ] = "";
  char c;
  
    // get the new byte:
    c = ( char ) UDR0;
    
    // newline, new commands
    if( c == '\n' ) {
      if( valid_cmd ) {
        int_rx_new = 1;
        int_rx_cmd = rx_cmd;
        strncpy( int_rx_value, rx_value, 4 );
      }
      charIndex = 0;
      valid_cmd = 1;
    } else if( valid_cmd ) {
      if( charIndex == 0 ) rx_cmd = c;
      else if( charIndex == 1 ) {
        if( c == ':' ) { rx_value[ 0 ] = rx_value[ 1 ] = rx_value[ 2 ] = rx_value[ 3 ] = 0; }
        else valid_cmd = 0;
      }
      else if( charIndex < 5 ){
        rx_value[ charIndex - 2 ] = c;
      }
      charIndex++;
    }
  
}

Merry Engineering Christmas!!!!

P.S. You can see the final result in action on our YouTube Channel here.