empowering creative people

Infinity Mirror Kit V2 - Assembly and Tutorials

The Infinity Mirror Kit V2 is a desktop display that creates a dazzling optical illusion - a tunnel of light that tears through space!

The Infinity Mirror Kit V2 entered production in October 2018, and now ships with an Adafruit Trinket to control the lights! If you are cool enough to own the original version follow this link to find the Assembly Instructions for the Infinity Kit Version 1.

Ok, back to the latest version! Here are the guides for V2:


Assembly

This video will guide you through assembling your Infinity Kit. If you prefer working off paper, you can Download the Infinity Mirror V2 Assembly Instructions.

Nothing beats the feeling of a completed project! Your Infinity Mirror Kit will serve as a mesmerising piece of desk-top tech with its default program. Check out the following tutorials If you'd like to learn how to upload your own code to your Infinity Kit.


Operation Instructions

The Infinity Mirror V2 comes already flashed with code to play five different animations. The potentiometer is used to change between the different modes. Some of the modes aren't just nice to look at, but they change the settings for the other modes! Here is a full explanation of the different mode types:

Off: This mode isn't truly off, but the LEDs are turned off, and all user settings are reset to the default values (brightness returns to full, colour returns to green).

Rainbow: This mode plays a rotating rainbow animation

Colour: Colour mode cycles through every colour. The colour shown when you change modes is saved for use in the other animations.

Comet: The mode a comet effect races around the mirror. The colour of the comet is set with Colour Mode

Solid: This mode is solid on, the colour is the same as whatever colour was last shown in colour mode.

Brightness: This mode slowly fades the LEDs up and down. The colour is set with colour mode, and the brightness is saved for all the other animations when you change modes.

mode-options-default-infinity-mirror-kit


Tutorial - Editing Your Code

The Trinket comes with this sketch already flashed to the board. If you would like to change the animations in the Infinity Kit, we'll walk you through the code to make it easy!

Installing Drivers

If you are using a Windows machine you will need to install some drivers for your Trinket to be recognized. If you are using Mac or Linux there are no drivers required!

To install the latest drivers for the Adafruit Trinket. Follow this link to find the Latest Windows Drivers for the Adafruit Trinket and How to Install.

Setting up the Arduino IDE

The next thing we need to do before reprogramming your Infinity Mirror Kit V2 is to set up the Arduino IDE. We need to add the Trinket board to the IDE so we can connect to it properly. We need to go to file>preferences and paste this link: https://adafruit.github.io/arduino-board-index/package_adafruit_index.json into the additional boards manager.

additional-board-manager-arduino-ide

The next step is to install the Trinket board package in the board manager.

Arduino-ide-boards-manager

Search for "Trinket" and install the Adafruit AVR Boards Package.

 Board-manager-installer-arduino-ide

Uploading Code

You should now see Adafruit boards in the Arduino IDE, including the Trinket 5V/16MHz USB. When the Trinket is reset it enters "Bootloader Mode" for 10 seconds. The reset button is located on the top of the board opposite the USB port. While in bootloader mode the red light will pulse. When you upload your sketch onto the Trinket you need to time everything right so the uploading begins while the Trinket is still in Bootloader Mode. We found that the best way is to press the Trinket reset button just before or after clicking upload in the IDE depending on how fast your computer is.

The Trinket uses some pretty slick programming to communicate over USB without having serial support. This means that it is pretty particular about where its plugged into your computer. The Trinket may not be able to handle the timing with the Arduino IDE while connected through a USB hub, or connected to a USB 3.0 port. Connect your Trinket directly to a USB 2.0 port if possible.

If you are using a Linux machine, or a Raspberry Pi to upload your code, you will need to run the Arduino IDE with root access to upload your code.

The Code

The Infinity Mirror Kit V2 comes with a Trinket preloaded with the code required to play the animations explained in the operating instructions. If you want to edit or change the animations then go for it! This project is made to be hacked, so here is the code and an explanation about how it works! We even included 

/*
* This program drives the Core Electronics Infinity Kit
* http://coreelec.io/infinitykit
* 2018
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program.  If not, see <http://www.gnu.org/licenses/>.
*
*/

/*******************************************************************************
* Libraries
* 
******************************************************************************/

#include <Adafruit_NeoPixel.h>
#ifdef __AVR__
  #include <avr/power.h>
#endif


/*******************************************************************************
* Hardware Definitions
* You shouldn't need to change these
******************************************************************************/

// The digital pin that drives the LED strip
const byte strip_pin = 0;                              
// Number of LEDs in the strip. Shouldn't need changing unless you hack the hardware
const byte num_leds = 44;                              
// Potentiometer pin selects the mode
const byte pot = 1;                                    
// Trinkets use 10bit ADCs. If you wish to port to a different platform you might want to redefine the ADC precision
const int ADC_precision = 1023;                        
// This pin to be used as Ground for the Potentiometer
const byte potLow = 1;                                 


/*******************************************************************************
* Global Variables
*
******************************************************************************/

// Number of modes
const byte mode_count = 6;                             

// This is the option that determines the order that settings are available from the user-input pot.    
// Here we name each mode and give it a number designation                                                
const byte mode_off = 1;                               
const byte mode_rainbow = 2;
const byte mode_scroll = 3;
const byte mode_comet = 4;
const byte mode_solid = 5;
const byte mode_brightness = 6;

// 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(num_leds, strip_pin, NEO_GRB + NEO_KHZ800);


/*******************************************************************************
* SETUP
*
******************************************************************************/

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  
  pinMode(potLow, OUTPUT);
// Set Pin #1 to Ground for the Pot
  digitalWrite(potLow, LOW);                                
  strip.begin(); 
// Initialize all pixels to off
  strip.show();                                        
}

/*******************************************************************************
* LOOP
*
******************************************************************************/
void loop()
{
// User-set starting colour used in the wheel to determine color
 static byte userColour = 0;                          

// In this section we detect and set the mode.                                   
  if (mode_check() == mode_off) {
    off();
  } else if (mode_check() == mode_rainbow) {
    rainbow();
  }else if (mode_check() == mode_scroll) {
// The scroll() function returns the new userColour to be used elsewhere
    userColour = scroll();                            
  } else if (mode_check() == mode_comet) {
    comet(userColour);
  } else if (mode_check() == mode_solid) {
    solid(userColour);
  } else if (mode_check() == mode_brightness){
    brightness(userColour);
  }
}


/*******************************************************************************
* Functions
*
******************************************************************************/

// check and set the mode
byte mode_check() {      
// break the pot reading into steps                             
  short mode_step = ADC_precision / mode_count;    
// take a reading from the potentiometer
  short val = analogRead(pot);                        
  byte i; 

// compare the pot reading to the steps
  for (i=1; i<mode_count; i++){                       
    val = val - mode_step;
    if(val <= 0) break;    
  }

// catch any strange errors
  if (i > mode_count) i = mode_count;  
// return i (mode)      
  return i;                                           
}

                                                 
// This is the off mode. Turn all LEDs OFF                                             
void off() { 
  for(byte i=0; i<strip.numPixels(); i++) {
      strip.setPixelColor(i, (0,0,0));
  }
  strip.setBrightness(255);
  strip.show();
  delay(50);
}

                                                      
// First part of comet mode. Moves the head pixel around the strip                                     
void comet(byte userColour){ 
  byte ofs = 15;

  for (int j=0; j<strip.numPixels(); j++){
    strip.clear();
// send position and color to drawComet
    drawComet(j, userColour);                         
    delay(30);
    strip.show();
  }
}

// Draw a comet on the strip and handle wrapping gracefully.                    
void drawComet(int pos, byte userColour) {
// Brightness of the first LED in the comet is taken from universal brightness setting  
// Length of comet tail                                                    
  byte len = 40;             
// Parameter that effects how quickly the comet tail dims                         
  float lambda = 0.001;                                 
  
// Get colour of comet head, and prepare variables to use for tail-dimming 
// Convert to RGB for dimming                                                    
  uint8_t R, G, B;                                    
  R = (Wheel(userColour) >> 16) & 0xFF;
  G = (Wheel(userColour) >> 8) & 0xFF;
  B = Wheel(userColour) & 0xFF;

// Head of comet
  strip.setPixelColor(pos, Wheel(userColour));        

// Figure out if the current pixel is wrapped across the strip ends or not, light that pixel
  for(int i=1; i<len; i++){   
// Wrapped                       
    if( pos - i < 0 ){                                
      strip.setPixelColor(strip.numPixels()+pos-i, strip.Color(R,G,B));
//while (true) {}
// strip.setPixelColor(44+pos-i, strip.Color(R,G,B));
// Not wrapped
    } else {                                          
      strip.setPixelColor(pos-i, strip.Color(R,G,B));
    }

// Exponential decay function to dim tail LEDs
    R = uint8_t(R * exp(-lambda));                    
    G = uint8_t(G * exp(-lambda));
    B = uint8_t(B * exp(-lambda));
    lambda += lambda;
  }
}


// Rainbow function                                         
void rainbow() {
  byte i, j;

  for(j=0; j<256; j++) {
    for(i=0; i<strip.numPixels(); i++) {
      strip.setPixelColor(i, Wheel(((i * 256 / strip.numPixels()) + j) & 255));
    }
    strip.show();
// Break out of loop if mode is changed
    if(mode_check() != mode_rainbow) return;          
    delay(40);
  }
}

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


// Display a single colour on all LEDs. While accessible from the state machine, this function does not set/check                        
// the state variables, because it is called by other states.                                  
void solid(byte userColour){                            
  short i;
  for(i=0; i<strip.numPixels(); i++){
// Color set by user variable userColour and that color's position on the wheel
    strip.setPixelColor(i, Wheel(userColour));        
  }
  strip.show();
  delay(50);
}


// Scroll through the colour wheel for all LEDs. Also allows user to set a desired colour for other modes.                         
uint16_t scroll() {
// Initialize userColour. 1-255 number to be used in Wheel()
  byte userColour;                                    
  byte i, j;

// Transition through all colors of the wheel
  for(j=0; j<256; j++) {                              
    for(i=0; i<strip.numPixels(); i++) {
      strip.setPixelColor(i, Wheel((j) & 255));
    }
// Store last wheel position as userColour
    userColour = j;                                   
    strip.show();

// Return the set colour as 1-255 Wheel number for use in other sequences
    if(mode_check() != mode_scroll) return userColour;
    delay(40);
  }
}


// Fade the brightness up  down and update a brightness parameter for other modes.                          
void brightness(byte userColour) {
  byte i;

// Increases brightness
  for (i=0; i<255; i++){                              
    strip.setBrightness(i);
// Slows brightness change when dim
    if (i<50){                                        
      delay(50); 
  }

// Updates changes through solid()
  solid(userColour);  
// Checks to see mode hasn't been changed                                
  if(mode_check() != mode_brightness) return;         
  }

// Decreases Brightness
  for (i=255; i>1; i--){                              
    strip.setBrightness(i);    
    if (i<50){
      delay(50); 
  }

// Updates changes through solid()
  solid(userColour);   
// Checks to see mode hasn't been changed                               
  if(mode_check() != mode_brightness) return;         
  }
}

Let's break it down into parts. The code is organized by the different modes, so let's talk about mode control.

Mode Control

To control the various modes of the Infinity Mirror V2, we first define the constant variables that will be used to control the modes. We define how many modes we will have and give each a number. These are used later in the sketch. To add an additional mode, increase the mode_count variable. 

// Number of modes
const byte mode_count = 6;

// This is the option that determines the order that settings are available from the user-input pot.
// Here we name each mode and give it a number designation
const byte mode_off = 1;
const byte mode_rainbow = 2;
const byte mode_scroll = 3;
const byte mode_comet = 4;
const byte mode_solid = 5;
const byte mode_brightness = 6;

void setup(){
}

The next portion of the code we call our mode check function (up next) and assign the mode based on the readings.

void loop()
{
// User-set starting colour used in the wheel to determine color
static byte userColour = 0;

// In this section we detect and set the mode.
if (mode_check() == mode_off) {
off();
} else if (mode_check() == mode_rainbow) {
rainbow();
}else if (mode_check() == mode_scroll) {
// The scroll() function returns the new userColour to be used elsewhere
userColour = scroll();
} else if (mode_check() == mode_comet) {
comet(userColour);
} else if (mode_check() == mode_solid) {
solid(userColour);
} else if (mode_check() == mode_brightness){
brightness(userColour);
}
}
}

This is the function where we check the position of the potentiometer to change the mode. We will call this throughout the sketch to check to see if the mode has changed.

// check and set the mode
byte mode_check() {      
// break the pot reading into steps                             
  short mode_step = ADC_precision / mode_count;    
// take a reading from the potentiometer
  short val = analogRead(pot);                        
  byte i; 

// compare the pot reading to the steps
  for (i=1; i<mode_count; i++){                       
    val = val - mode_step;
    if(val <= 0) break;    
  }

// catch any strange errors
  if (i > mode_count) i = mode_count;  
// return i (mode)      
  return i;                                           
}

The Modes

Off - Turns off all the LEDs and resets the user settings to default

// This is the off mode. Turn all LEDs OFF                                             
void off() { 
  for(byte i=0; i<strip.numPixels(); i++) {
      strip.setPixelColor(i, (0,0,0));
  }
  strip.setBrightness(255);
  strip.show();
  delay(50);
}

Comet - This animation makes a comet effect that spins around the mirror. You can edit the style of this animation by changing len and lambda

// First part of comet mode. Moves the head pixel around the strip                                     
void comet(byte userColour){ 
  byte ofs = 15;

  for (int j=0; j<strip.numPixels(); j++){
    strip.clear();
// send position and color to drawComet
    drawComet(j, userColour);                         
    delay(30);
    strip.show();
  }
}

// Draw a comet on the strip and handle wrapping gracefully.                    
void drawComet(int pos, byte userColour) {
// Brightness of the first LED in the comet is taken from universal brightness setting  
// Length of comet tail                                                    
  byte len = 40;             
// Parameter that effects how quickly the comet tail dims                         
  float lambda = 0.001;                                 
  
// Get colour of comet head, and prepare variables to use for tail-dimming 
// Convert to RGB for dimming                                                    
  uint8_t R, G, B;                                    
  R = (Wheel(userColour) >> 16) & 0xFF;
  G = (Wheel(userColour) >> 8) & 0xFF;
  B = Wheel(userColour) & 0xFF;

// Head of comet
  strip.setPixelColor(pos, Wheel(userColour));        

// Figure out if the current pixel is wrapped across the strip ends or not, light that pixel
  for(int i=1; i<len; i++){   
// Wrapped                       
    if( pos - i < 0 ){                                
      strip.setPixelColor(strip.numPixels()+pos-i, strip.Color(R,G,B));
//while (true) {}
// strip.setPixelColor(44+pos-i, strip.Color(R,G,B));
// Not wrapped
    } else {                                          
      strip.setPixelColor(pos-i, strip.Color(R,G,B));
    }

// Exponential decay function to dim tail LEDs
    R = uint8_t(R * exp(-lambda));                    
    G = uint8_t(G * exp(-lambda));
    B = uint8_t(B * exp(-lambda));
    lambda += lambda;
  }
}

 

Rainbow - This animation is a rainbow effect that appears to rotate in within the mirror. It's important to note the mode check portion of this function. Every 40ms we check to see if the knob has been moved into a different mode.

// Rainbow function                                         
void rainbow() {
  byte i, j;

  for(j=0; j<256; j++) {
    for(i=0; i<strip.numPixels(); i++) {
      strip.setPixelColor(i, Wheel(((i * 256 / strip.numPixels()) + j) & 255));
    }
    strip.show();
// Break out of loop if mode is changed
    if(mode_check() != mode_rainbow) return;          
    delay(40);
  }
}

Wheel - This function is used in all the others to create a colour wheel. It's best not to touch it!

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

Solid - This sets the LEDs to one colour. The colour is set by using Scroll mode.

// Display a single colour on all LEDs. While accessible from the state machine, this function does not set/check                        
// the state variables, because it is called by other states.                                  
void solid(byte userColour){                            
  short i;
  for(i=0; i<strip.numPixels(); i++){
// Color set by user variable userColour and that color's position on the wheel
    strip.setPixelColor(i, Wheel(userColour));        
  }
  strip.show();
  delay(50);
}

Scroll - This mode transitions all the LEDs through each colour. When we change modes the last colour is saved to be used in other animations. 

// Scroll through the colour wheel for all LEDs. Also allows user to set a desired colour for other modes.                         
uint16_t scroll() {
// Initialize userColour. 1-255 number to be used in Wheel()
  byte userColour;                                    
  byte i, j;

// Transition through all colors of the wheel
  for(j=0; j<256; j++) {                              
    for(i=0; i<strip.numPixels(); i++) {
      strip.setPixelColor(i, Wheel((j) & 255));
    }
// Store last wheel position as userColour
    userColour = j;                                   
    strip.show();

// Return the set colour as 1-255 Wheel number for use in other sequences
    if(mode_check() != mode_scroll) return userColour;
    delay(40);
  }
}

Brightness - This mode is pretty self-explanatory. It fades through brightnesses, and when you change mode the brightness level is set for all other animations. 

// Fade the brightness up  down and update a brightness parameter for other modes.                          
void brightness(byte userColour) {
  byte i;

// Increases brightness
  for (i=0; i<255; i++){                              
    strip.setBrightness(i);
// Slows brightness change when dim
    if (i<50){                                        
      delay(50); 
  }

// Updates changes through solid()
  solid(userColour);  
// Checks to see mode hasn't been changed                                
  if(mode_check() != mode_brightness) return;         
  }

// Decreases Brightness
  for (i=255; i>1; i--){                              
    strip.setBrightness(i);    
    if (i<50){
      delay(50); 
  }

// Updates changes through solid()
  solid(userColour);   
// Checks to see mode hasn't been changed                               
  if(mode_check() != mode_brightness) return;         
  }
}

Alternative Modes

If you decide to change the modes on your Infinity Mirror V2, keep in mind that there is very little memory on the Trinket. This program uses 91% of the available memory and quite a bit of time was spent shrinking it to fit. That said, you can change all you like, but you will probably need to replace one of the existing modes instead of just adding more. We recommend removing the Comet mode first, as it uses 34% of the total available memory. That way you can even add a couple additional modes. Save space by using the smallest variable types possible! Here are some animations that you could try out on your Infinity Mirror V2!

We've created this animation packed version of the code to help you get started!

/*
* This program drives the Core Electronics Infinity Kit
* http://coreelec.io/infinitykit
* 2018
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program.  If not, see <http://www.gnu.org/licenses/>.
*
*/

/*******************************************************************************
* Libraries
* 
******************************************************************************/

#include <Adafruit_NeoPixel.h>
#ifdef __AVR__
  #include <avr/power.h>
#endif


/*******************************************************************************
* Hardware Definitions
* You shouldn't need to change these
******************************************************************************/

// The digital pin that drives the LED strip
const byte strip_pin = 0;                              
// Number of LEDs in the strip. Shouldn't need changing unless you hack the hardware
const byte num_leds = 44;                              
// Potentiometer pin selects the mode
const byte pot = 1;                                    
// Trinkets use 10bit ADCs. If you wish to port to a different platform you might want to redefine the ADC precision
const int ADC_precision = 1023;                        
// This pin to be used as Ground for the Potentiometer
const byte potLow = 1;                                 


/*******************************************************************************
* Global Variables
*
******************************************************************************/

// Number of modes
const byte mode_count = 6;                             

// This is the option that determines the order that settings are available from the user-input pot.    
// Here we name each mode and give it a number designation                                                
const byte mode_off = 1;                               
const byte mode_rainbow = 2;
const byte mode_scroll = 3;
const byte mode_comet = 4;
const byte mode_solid = 5;
const byte mode_brightness = 6;

// 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(num_leds, strip_pin, NEO_GRB + NEO_KHZ800);


/*******************************************************************************
* SETUP
*
******************************************************************************/

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  
  pinMode(potLow, OUTPUT);
// Set Pin #1 to Ground for the Pot
  digitalWrite(potLow, LOW);                                
  strip.begin(); 
// Initialize all pixels to off
  strip.show();                                        
}

/*******************************************************************************
* LOOP
*
******************************************************************************/
void loop()
{
// User-set starting colour used in the wheel to determine color
 static byte userColour = 0;                          

// In this section we detect and set the mode.                                 
  if (mode_check() == mode_off) {
    off();
  }else if (mode_check() == mode_rainbow) {
    //rainbow();
// This is an optional pattern, try commenting rainbow() and put this in!
    theaterChaseRainbow(50);                        
  }else if (mode_check() == mode_scroll) {
// The scroll() function returns the new userColour to be used elsewhere
    //userColour = scroll();    
// This optional pattern replaces scroll() and changes the colors with a wipe                           
    userColour = scrollWipe();                           
  }else if (mode_check() == mode_comet) {
    //comet(userColour);
// This is an optional pattern, try commenting comet() and put this in!
    Sparkle(0xff, 0xff, 0xff, 10);                    
  }else if (mode_check() == mode_solid) {
    //solid(userColour);
// This is an optional pattern, try commenting solid() and put this in!
    theaterChase(userColour, 50);       
  }else if (mode_check() == mode_brightness){
//brightness(userColour);
    colorWipe(strip.Color(64, 0, 0), 50); // Red
    colorWipe(strip.Color(0, 64, 0), 50); // Green
    colorWipe(strip.Color(0, 0, 64), 50); // Blue
    colorWipe(strip.Color(0,0,0), 25); // Black
  }
}


/*******************************************************************************
* Functions
*
******************************************************************************/

// check and set the mode
byte mode_check() {      
// break the pot reading into steps                             
  short mode_step = ADC_precision / mode_count;    
// take a reading from the potentiometer
  short val = analogRead(pot);                        
  byte i; 

// compare the pot reading to the steps
  for (i=1; i<mode_count; i++){                       
    val = val - mode_step;
    if(val <= 0) break;    
  }

// catch any strange errors
  if (i > mode_count) i = mode_count;  
// return i (mode)      
  return i;                                           
}

                                                 
// This is the off mode. Turn all LEDs OFF                                             
void off() { 
  for(byte i=0; i<strip.numPixels(); i++) {
      strip.setPixelColor(i, (0,0,0));
  }
  strip.setBrightness(255);
  strip.show();
  delay(50);
}

                                                      
// First part of comet mode. Moves the head pixel around the strip                                     
void comet(byte userColour){ 
  byte ofs = 15;

  for (int j=0; j<strip.numPixels(); j++){
    strip.clear();
// send position and color to drawComet
    drawComet(j, userColour);                         
    delay(30);
    strip.show();
  }
}

// Draw a comet on the strip and handle wrapping gracefully.                    
void drawComet(int pos, byte userColour) {
// Brightness of the first LED in the comet is taken from universal brightness setting  
// Length of comet tail                                                    
  byte len = 40;             
// Parameter that effects how quickly the comet tail dims                         
  float lambda = 0.001;                                 
  
// Get colour of comet head, and prepare variables to use for tail-dimming 
// Convert to RGB for dimming                                                    
  uint8_t R, G, B;                                    
  R = (Wheel(userColour) >> 16) & 0xFF;
  G = (Wheel(userColour) >> 8) & 0xFF;
  B = Wheel(userColour) & 0xFF;

// Head of comet
  strip.setPixelColor(pos, Wheel(userColour));        

// Figure out if the current pixel is wrapped across the strip ends or not, light that pixel
  for(int i=1; i<len; i++){   
// Wrapped                       
    if( pos - i < 0 ){                                
      strip.setPixelColor(strip.numPixels()+pos-i, strip.Color(R,G,B));
//while (true) {}
// strip.setPixelColor(44+pos-i, strip.Color(R,G,B));
// Not wrapped
    } else {                                          
      strip.setPixelColor(pos-i, strip.Color(R,G,B));
    }

// Exponential decay function to dim tail LEDs
    R = uint8_t(R * exp(-lambda));                    
    G = uint8_t(G * exp(-lambda));
    B = uint8_t(B * exp(-lambda));
    lambda += lambda;
  }
}


// Rainbow function                                         
void rainbow() {
  byte i, j;

  for(j=0; j<256; j++) {
    for(i=0; i<strip.numPixels(); i++) {
      strip.setPixelColor(i, Wheel(((i * 256 / strip.numPixels()) + j) & 255));
    }
    strip.show();
// Break out of loop if mode is changed
    if(mode_check() != mode_rainbow) return;          
    delay(40);
  }
}

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


// Display a single colour on all LEDs. While accessible from the state machine, this function does not set/check                        
// the state variables, because it is called by other states.                                  
void solid(byte userColour){                            
  short i;
  for(i=0; i<strip.numPixels(); i++){
// Color set by user variable userColour and that color's position on the wheel
    strip.setPixelColor(i, Wheel(userColour));        
  }
  strip.show();
  delay(50);
}


// Scroll through the colour wheel for all LEDs. Also allows user to set a desired colour for other modes.                         
uint16_t scroll() {
// Initialize userColour. 1-255 number to be used in Wheel()
  byte userColour;                                    
  byte i, j;

// Transition through all colors of the wheel
  for(j=0; j<256; j++) {                              
    for(i=0; i<strip.numPixels(); i++) {
      strip.setPixelColor(i, Wheel((j) & 255));
    }
// Store last wheel position as userColour
    userColour = j;                                   
    strip.show();

// Return the set colour as 1-255 Wheel number for use in other sequences
    if(mode_check() != mode_scroll) return userColour;
    delay(40);
  }
}


// Fade the brightness up  down and update a brightness parameter for other modes.                          
void brightness(byte userColour) {
  byte i;

// Increases brightness
  for (i=0; i<255; i++){                              
    strip.setBrightness(i);
// Slows brightness change when dim
    if (i<50){                                        
      delay(50); 
  }

// Updates changes through solid()
  solid(userColour);  
// Checks to see mode hasn't been changed                                
  if(mode_check() != mode_brightness) return;         
  }

// Decreases Brightness
  for (i=255; i>1; i--){                              
    strip.setBrightness(i);    
    if (i<50){
      delay(50); 
  }

// Updates changes through solid()
  solid(userColour);   
// Checks to see mode hasn't been changed                               
  if(mode_check() != mode_brightness) return;         
  }
}
/*******************************************************************************
* Optional Light Effects
* Replace the existing light effects by calling these instead!
******************************************************************************/
//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) {
//turn every third pixel on
        strip.setPixelColor(i+q, Wheel(((i * 256 / strip.numPixels()) + j) & 255));    
      }
      strip.show();
// Break out of loop if mode is changed
      if(mode_check() != mode_rainbow) return;          

      delay(wait);

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

//Single-color theatre-style crawling lights
void theaterChase(uint32_t c, uint8_t wait) {
//do 10 cycles of chasing
  for (int j=0; j<10; j++) {  
    for (int q=0; q < 3; q++) {
      for (uint16_t i=0; i < strip.numPixels(); i=i+3) {
//turn every third pixel on
        strip.setPixelColor(i+q, Wheel(c));    
      }
      strip.show();

      delay(wait);

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

// Fill the dots one after the other with a color
void colorWipe(uint32_t c, uint8_t wait) {
  for(uint16_t i=0; i<strip.numPixels(); i++) {
      strip.setPixelColor(i, c);
      strip.show();
      if(mode_check() != mode_brightness) return;          // Break out of loop if mode is changed
      delay(wait);
  }
}

// Fill the dots one after the other with a color
uint16_t scrollWipe() {
  byte userColour;                                    // Initialize userColour. 1-255 number to be used in Wheel()
  byte i, j;

  for(j=0; j<256; j= j+16) {                              // Transition through all colors of the wheel
    for(uint16_t i=0; i<strip.numPixels(); i++) {
        strip.setPixelColor(i, Wheel((j) & 255));
    
    userColour = j; 
    strip.show();
    if(mode_check() != mode_scroll) return userColour;// Return the set colour as 1-255 Wheel number for use in other sequences
    delay(40);
    }
  }  
}

void Sparkle(byte red, byte green, byte blue, byte SpeedDelay) {
byte Pixel = random(strip.numPixels());
strip.setPixelColor(Pixel,red,green,blue);
strip.show();
delay(SpeedDelay);
strip.setPixelColor(Pixel,0,0,0);
}

Changing Modes

To change to one of our provided alternate modes, you just need to change the functions called in the main loop. Just comment out the animation being used and uncomment the one you want. Mix and match however you like, and add your own! If you create a great animation for the Infinity Kit, please share it on our Forum!

Here is an explanation of the alternate animations we provided.

Color Wipe -  Replaces all the colours with what is called out when you start the function:

void loop() {
  // Some example procedures showing how to display to the pixels:
  colorWipe(strip.Color(0,0,0), 25); // Black
  colorWipe(strip.Color(64, 0, 0), 100); // Red
  colorWipe(strip.Color(0, 64, 0), 100); // Green
  colorWipe(strip.Color(0, 0, 64), 100); // Blue
} 

// Fill the dots one after the other with a color
void colorWipe(uint32_t c, uint8_t wait) {
  for(uint16_t i=0; i<strip.numPixels(); i++) {
      strip.setPixelColor(i, c);
      strip.show();
 // Break out of loop if mode is changed
      if(mode_check() != mode_brightness) return;         
      delay(wait);
  }
}

Theater Chase - Gives an illusion that the lights are crawling around in a circle. You could use your selected colour rather than entering it each time.

void loop() {
  // Send a theater pixel chase in...
  theaterChase(strip.Color(userColour, 50); 
}

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, Wheel(c));    //turn every third pixel on
      }
      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
      }
    }
  }
}

Rainbow Theater Chase - The same as theater chase but rather than selecting the color its rainbow!

void loop() {
  theaterChaseRainbow(50);
}

//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) {
 //turn every third pixel on
        strip.setPixelColor(i+q, Wheel(((i * 256 / strip.numPixels()) + j) & 255));   
      }
      strip.show();
 // Break out of loop if mode is changed. CHANGE THIS TO WHATEVER MODE YOU USE
      if(mode_check() != mode_rainbow) return;         

      delay(wait);

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

Scroll Wipe - Change through the colors with a wipe effect!

uint16_t scrollWipe() {
 // Initialize userColour. 1-255 number to be used in Wheel()
  byte userColour;                                   
  byte i, j;

// Transition through all colors of the wheel
  for(j=0; j<256; j= j+16) {                              
    for(uint16_t i=0; i<strip.numPixels(); i++) {
        strip.setPixelColor(i, Wheel((j) & 255));
    
    userColour = j; 
    strip.show();
// Return the set colour as 1-255 Wheel number for use in other sequences
    if(mode_check() != mode_scroll) return userColour;
    delay(40);
    }
  }  
}
 

Sparkle - Sparkling colour!

void Sparkle(byte red, byte green, byte blue, byte SpeedDelay) {
byte Pixel = random(strip.numPixels());
strip.setPixelColor(Pixel,red,green,blue);
strip.show();
delay(SpeedDelay);
strip.setPixelColor(Pixel,0,0,0);
}

Going Further

There we have it! A complete Infinity Mirror Kit V2, and possibly even customised. We designed this project to be hacked, don't be scared to make this project your own by adding sensors or other interfaces! If you want to learn more about Arduino and coding, check out our Arduino Tutorials and our Arduino Workshop. If you have any questions about this kit, please reach out on the forum! We are here to help!

Attachment: V2-assembly-instructions-compressed.pdf

The Infinity Mirror Kit V2 is a desktop display that creates a dazzling optical illusion - a tunnel of light that t...

Have a question? Ask the Author of this guide today!