Introduction: Easy Infinity Mirror With Arduino Gemma & NeoPixels

About: Making and sharing are my two biggest passions! In total I've published hundreds of tutorials about everything from microcontrollers to knitting. I'm a New York City motorcyclist and unrepentant dog mom. My wo…

Behold! Look deep into the enchanting and deceptively simple infinity mirror! A single strip of LEDs shine inward on a mirror sandwich to create the effect of endless reflection. This project will apply the skills and techniques from my intro Arduino Class, and put it all together into a final form using a smaller Arduino Gemma board.

Watch a webinar of this project! Check out this webinar I led on June 28th 2017 to see me complete this build!


To keep up with what I'm working on, follow me on YouTube, Instagram, Twitter, Pinterest, and subscribe to my newsletter.

Step 1: Supplies

To follow along with this lesson you will need:

This project walks you through building an electronics enclosure from foamcore board, which requires a protected work surface (cutting mat or multiple layers of scrap cardboard), metal ruler, and sharp utility knife. You can either use a hot glue gun to assemble the pieces, or opt for a craft adhesive like E6000. A round glass mirror is at the center of the infinity mirror, and a piece of see-through mirror plastic is the secret ingredient for the infinity tunnel effect. If you don't have a plastic scoring knife, you can use a pair of sturdy scissors to cut the mirror plastic, but leave a wider margin than you think you'll need, since the mirror film tends to flake a little around scissor-cut edges. Be careful when using sharp tools, keep a bowl of ice water nearby any hot glue project for quick burn treatment, and use proper ventilation for any adhesives.

Arduino Gemma - The infinity mirror project miniaturizes the Arduino circuit by substituting the Arduino Uno with an Arduino Gemma. Gemma is a tiny board built around the ATTiny85 microcontroller, which has less memory and fewer features than the Uno's Atmega328, but it's also smaller and lower cost. The large pads are super easy to solder to (and sew to with conductive thread, but that's a topic for a different class). Gemma uses a micro USB cable to connect to your computer, and has a JST port for connecting a battery. You'll learn how to program Gemma from the Arduino software and build it into the final project. You can also use an Adafruit Gemma instead, but you will need to perform an additional step to configure the Arduino software.


RGBW NeoPixel strip - This digitally addressable strip contains WS2812b chips controlling compound LEDs in red, green, blue, and white. NeoPixel is the Adafruit brand name but you can also find this strip by searching for "WS2812b RGBW strip" on your favorite supplier's site. The sample code provided in this class will not work with RGB (no white) strip, analog LED strip, or with any other kind of digital control chip (like APA104 aka DotStar).

Step 2: Cut Frame Pieces

Get ready for some papercraft! This step involves sharp tools and requires attention to detail, so be sure you're well-rested, but not overly caffeinated either. Use bright lighting and a large, clean work surface protected by a cutting mat or scrap cardboard.

If you're new to cutting and glueing foamcore board, get extra for practice and mistakes— a three-pack of 16x20in boards should suffice (and you can make other projects with it if you have extra left over). To prevent injury, use a sharp blade, a metal ruler, a slow pace, and plenty of caution. It's normal to remake a few pieces due to an errant blade slip or snag.

There are two ways to create the shapes you'll be cutting: print out the template, or draw the shapes with a circle-drawing compass. There is no distinct advantage in either, but your skills and tools might sway you one way or the other. The template is available as a tiled PDF for letter-sized paper, which you'll tape together and use a glue stick to adhere it to your foamcore. There's also an untiled version of the template file in case you want to print it on a large format printer or make changes.

It's really simple to draw the shapes by hand, though, I promise! First draw a circle to match your mirror size by setting the compass to its radius (4" mirror = 2" radius) and drawing a circle on your foamcore at least 5 inches from each edge. Sure, you could just trace the circumference of the mirror, but then you'd have to find and mark the center! The compass makes an indentation at the center point that's handy for making the second concentric circle.

Now widen your compass to 4" and draw out the bigger circle around the first. This is the complete bottom/back of your mirror— label it as such.

The top/front piece needs to be just a bit bigger, so widen your compass to 4 3/16" and draw it out at a safe distance from the bottom piece.

The viewing window should be just slightly smaller than the mirror, though it's not important exactly how much. Set your compass to about 1/8 inch smaller than the mirror radius, and then draw out the circle using the same center point as the larger front/top perimeter.

Label this piece within the smaller circle, which will be cut away in a few moments.

Along one long side of your foamcore, mark and cut one strip at 1/2" wide, and another at 1" wide.

The narrow strip will hug the mirror and support your NeoPixel strip, while the wider one will form the outer wall of the circular frame.

Onto cutting the circles! Some finesse and patience are helpful here. I like to use a smaller craft knife for cutting circles because I feel like I have more control. The particular knife I'm using here takes regular X-acto blades, and I found it in the scrapbooking aisle.

First, lightly drag your knife around the entire circumference of the bottom piece, only piercing the top layer of paper. During this pass you're free to angle the blade however is most comfortable and produces the most precise shape.

Cut around the circle once again, tracing the line you made in the previous pass. This time, pay attention to your blade angle, which should be 90 degrees (straight up and down). Press firmly as you make this cut, and keep your fingers out of the blade path. Pick up your board and check to see if you cut all the way through. Make one more pass with your blade to cut through any remaining spots along the perimeter.

Next up, cut out the top piece, and then cut out its inner circle. This piece is seen more than any other, so give it a little extra cleanup to straighten up any edges that are uneven.

For the curved inner ring, make cross-cuts every 1/4" or so along the thinner foamcore strip, but don't cut all the way through! It's easier than it sounds— just make two light passes and you'll get the hang of it quickly. These cuts allow the piece to curve while providing a smooth interior surface.

The outer frame piece needs to put it's best face outward, so we'll make cross cuts in a slightly different pattern. First prep for the lap joint by scoring a line 3/16" from the edge. Make gentle cross-cuts along the strip, alternating thick and thin sections about 3/8" and 1/8", respectively.

To remove the material where the edge will lap, place the strip along the cutting surface edge and slide your knife horizontally to shed the foam excess, leaving the bottom layer of paper intact.

Now remove the thin sections by yanking them out with a pair of tweezers or pliers. They release with a satisfying popping sound. With that extra space, the strip can now curve in on itself and form the clean outer shell of the project!

Cut a piece of your see-through mirror plastic to be larger than your mirror, but smaller than the outer frame. Don't bother trying to cut it in a circle. If you have a plastic scoring knife, that's best. Drag the gouge along your ruler a few times, then snap the plastic along the score. However a utility knife easily cuts this thin material as well, albeit with some flaking of the mirror material along the cut edge, which will be hidden inside the frame anyway.

Step 3: Assemble Frame

Protect your work surface with some scrap material. Heat up your glue gun and prep a bowl of ice water to keep nearby, in case you burn yourself. You can use different adhesive(s) for this project if you prefer.

Apply a dollop of glue on the center of the bottom circle and stick your mirror to it. Rotate and squish the mirror against the foamcore gently, aligning it with the marked circle. Then glue your thin strip to the perimeter of the mirror and tear off any excess, leaving a small gap for wires to pass through.

Place your front "donut" piece face down on the work surface and glue on the lapped edge. Repeatedly press these pieces together and down on the work surface as you glue around the go, so the front edge turns out nice and clean. The outer rim won't go all the way around and that's ok— you can choose to close up this gap later if you want.

Route the NeoPixel strip's wires through the small gap on the mirror rim, and glue it to the interior. Optionally use a clothespin to clamp the strip while the glue cools. Try to avoid getting hot glue on the mirror, but if you do it's ok! A little rubbing alcohol will release its hold on nonporous surfaces like glass.

Clean your work area to remove dust and bits of foamcore. Use a lint free cloth to wipe the mirror completely clean, then grab your see-through mirror and peel the protective cover from one side. Apply a small amount of glue at four points around the inner wall (keep your glue gun motions from dragging over the mirror to avoid stray strands), and glue the see-through mirror in place. Now your reflective surfaces are sealed and protected from dust.

Bask in the double reflectivity by plugging your NeoPixel strip into your Arduino board running the sample NeoPixel code described in my Arduino Class lesson on the topic.

Step 4: Circuit Diagram & Pseudocode

Although you're welcome to reference the diagram shown here throughout your build, I highly encourage you to draw your own. You'll have an at-a-glance reference as you build your breadboard and final prototypes, and diagramming your circuits will make it easier to design your own projects in the future. The purpose of a circuit diagram is to show all the electrical connections in a circuit, not necessary their physical positions or orientations.

The connections are as follows:

NeoPixel 5V -> Arduino 5V

NeoPixel GND -> Arduino GND

NeoPixel Din (data in) -> Arduino digital I/O pin (configurable)

one side of momentary pushbutton switch -> Arduino digital I/O pin (configurable)

other side of momentary pushbutton switch -> Arduino GND

This circuit combines NeoPixel strip with a pushbutton for triggering different LED animations, and will use an internal pull-up resistor like you saw in the input/output lesson. Using all this information, we can write a human-readable mockup of our Arduino program, called "pseudocode:"

Variables: NeoPixel pin number, button pin number, how many LEDs there are, how bright the LEDs should be

One-time tasks: initialize button pin as input with internal pull-up resistor, initialize NeoPixel strip, describe LED animations

Looping tasks: check to see if button has been pressed and if it has, switch to a different LED animation

It might seem simple, but taking the time to write pseudocode for your project will help you write your final Arduino sketch faster and with less confusion. It functions a bit like a to-do list as well as a reference guide for when you're swimming in code and can't remember what you're trying to accomplish!

Step 5: Breadboard Prototype

Grab your Arduino and breadboard, and make sure the USB cord is unplugged. Are your NeoPixels still plugged in from earlier? Great! If not, connect them up: 5V to power rail, Din to Arduino pin 6, GND to ground rail.

Then add a momentary pushbutton to your breadboard, straddling the center dividing line. Connect one leg to the ground rail, and its neighboring leg to Arduino pin 2. Download the code for this project directly or in the Autodesk Circuits module above, click the "Code Editor" button, then "Download Code" and open the file in Arduino, or copy and paste the code into a new blank Arduino sketch.

Plug in your USB cable and upload the code to your Arduino board. Press the button; it should trigger a new animation to play across the NeoPixels. The 5V rail is sufficient for this few pixels on limited brightness, but for future projects with more LEDs, you will need a separate power supply, as discussed in the skills lesson of my intro Arduino Class.

Step 6: Code

Let's examine the code in more detail:

#define BUTTON_PIN   2    // Digital IO pin connected to the button.  This will be
                          // driven with a pull-up resistor so the switch should
                          // pull the pin to ground momentarily.  On a high -> low
                          // transition the button press logic will execute.

#define PIXEL_PIN    6    // Digital IO pin connected to the NeoPixels.

#define PIXEL_COUNT 19
#define BRIGHTNESS 100    // 0-255

// Parameter 1 = number of pixels in strip
// Parameter 2 = pin number (most are valid)
// Parameter 3 = pixel type flags, add together as needed:
//   NEO_RGB     Pixels are wired for RGB bitstream
//   NEO_GRB     Pixels are wired for GRB bitstream, correct if colors are swapped upon testing
//   NEO_RGBW    Pixels are wired for RGBW bitstream
//   NEO_KHZ400  400 KHz bitstream (e.g. FLORA pixels)
//   NEO_KHZ800  800 KHz bitstream (e.g. High Density LED strip), correct for neopixel stick
Adafruit_NeoPixel strip = Adafruit_NeoPixel(PIXEL_COUNT, PIXEL_PIN, NEO_GRBW + NEO_KHZ800);

bool oldState = HIGH;
int showType = 0;

Similar to the NeoPixel example code, this first section sets up the NeoPixel strip and the variables for the pushbutton pin, pixel control pin, etc.

void setup() {
  pinMode(BUTTON_PIN, INPUT_PULLUP);
  strip.setBrightness(BRIGHTNESS);
  strip.begin();
  strip.show(); // Initialize all pixels to 'off'
}

The setup function sets pin 2 to an input with its internal pull-up resistor activated, sets the global brightness of the pixels, and starts up the pixel data connection.

void loop() {
  // Get current button state.
  bool newState = digitalRead(BUTTON_PIN);

  // Check if state changed from high to low (button press).
  if (newState == LOW && oldState == HIGH) {
    // Short delay to debounce button.
    delay(20);
    // Check if button is still low after debounce.
    newState = digitalRead(BUTTON_PIN);
    if (newState == LOW) {
      showType++;
      if (showType > 6)
        showType=0;
      startShow(showType);
    }
  }

  // Set the last button state to the old state.
  oldState = newState;
}

The loop function first checks the current state of the button and stores it in a boolean variable (can be one of two states: HIGH or LOW). Then it checks and double checks to see if that state goes from HIGH to LOW. If it did, showType is increased by one, and the startShow function is called, with the current showType passed to it as an argument (showType is constrained to 0-6). The variable oldState is updated to reflect what the last button state was.

void startShow(int i) {
  switch(i){
    case 0: colorWipe(strip.Color(0, 0, 0), 50);    // Black/off
            break;
    case 1: colorWipe(strip.Color(255, 0, 0), 50);  // Red
            break;
    case 2: colorWipe(strip.Color(0, 255, 0), 50);  // Green
            break;
    case 3: colorWipe(strip.Color(0, 0, 255), 50);  // Blue
            break;
    case 4: pulseWhite(5); 
            break;
    case 5: rainbowFade2White(3,3,1);
            break;
    case 6: fullWhite();
            break;
  }
}

The startShow function contains a switch/case statement, which is just a fancy fast way to stack a bunch of if/else statements. The switch case compares the variable i to the values of each case, then runs the code in that statement. The keyword break; exits the switch/case statement. This switch/case is used to call different animation functions every time you press the button.

Now that you've got a functional breadboard prototype, it's time to make this into a finished project by using an Arduino Gemma, which is smaller, less fully featured, and lower cost than the Arduino Uno. You can also use an Adafruit Gemma instead, but you will need to perform an additional step to configure the Arduino software.

First, change the NeoPixel pin variable from 6 to 1 in your code:

#define PIXEL_PIN    1    // Digital IO pin connected to the NeoPixels.

Plug your Arduino Gemma into your computer using a USB cable, and select "Arduino Gemma" as your board type in the Arduino Tools menu.

The limited functions of the ATTiny85 microcontroller onboard don't support a serial port in the same way as the Uno, so you don't have to select anything from the Port menu. However, be sure to select "Arduino Gemma" under the Programmer menu item.

The board needs a little help knowing when you're trying to program it, so press the reset button on the board, and while the red LED is pulsing, press the Upload button to load your sketch onto the Gemma. If your red LED does not pulse when you press the reset button, your USB cable may be power-only, and should be swapped out for a USB cable that has power and data connections. Another reason your LED may not pulse is if you are using a USB 3 port (all newer Macs), which has trouble recognizing the Gemma bootloader. Use a USB 2 port on your computer or a USB hub in between your computer and Gemma.

Step 7: Solder Circuit

To run the circuit with your Gemma, we'll solder the wires directly to the pads on the board. Snip off the breadboard connector and strip, twist, and tin the leads of the NeoPixel strip wires. Solder wires onto diagonal leads of a pushbutton in the same manner (you can use the button from the soldering lesson). Twist and solder together the two ground wires.

Gemma's large holes make it easy to assemble this circuit with no additional parts— just thread the tinned wires through the holes and wrap the excess around the solder pad. The connections are as follows:

  • NeoPixel 5V -> Gemma Vout
  • NeoPixel Din -> Gemma 1~ (digital pin 1)
  • NeoPixel GND -> one side of pushbutton -> Gemma GND
  • other side of pushbutton -> Gemma 2 (digital pin 2)

Set up your circuit board in a third hand tool and heat up the connections with your soldering iron before applying some more solder to engulf the pad and wire. After all the connections cool, trim away excess wire with your flush snips.

Hot glue your Gemma in place with the USB port facing the edge of the circle.

Apply the front/top cover and manipulate the edge to seat the pieces together cleanly. You may have to trim your bottom circle just a bit to make it fit, and likewise pull the edge open to accommodate its mate. Glue the pushbutton in place wherever you like.

Step 8: Use It!

Plug in a USB cable, press the pushbutton, and enjoy! You can switch up the colors and animations by changing the code. Use a USB power adapter if you want to mount it on a wall. At this point you can make another small foamcore edge piece to close up the remaining gap, if you wish. Some suggested uses: hang it on your wall, keep it at your desk, give it to a friend!

You can easily run this project with an internal battery instead of connecting a USB cable. The orientation at which you glue the Gemma will determine the access to the battery port, so you may want to re-glue it at a different angle. 19 RGBW pixels times 80ma max current draw (plus ~10ma for the Gemma) equals 1530ma, which means we technically need a battery with at least that many mAh. However the code for the mirror doesn't come close to using all four pixels' LEDs at full brightness together, so in reality the maximum current draw is far less. A healthy battery compromise is a 1200mAh rechargeable lipoly battery.

Thanks for following along with this Arduino project! To learn more basics, check out my introductory Arduino Class. I can't wait to see your versions in the comments and welcome your thoughts and feedback.