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.