Desktop Infinity Mirror (Lasercut!)

Updated 27 August 2018

A smaller version of our amazing Infinity-Mirror Table - the Desktop Infinity Mirror compresses all the colourful goodness into a picture frame-sized display. The entire design is made from laser-cut acrylic that we cut in-house. Check out the video to see it in action!

A few weeks ago a brand new Trotec laser cutter arrived at our doorstep. After the usual cutting/engraving marathon that follows receiving such a toy, we decided it would be neat to try our hand at bonafide laser-cut electronics project. I recently completed the Infinity-Mirror Table project, and this seemed like a great platform to be miniaturised into a desktop version.


Since the electronics and code are pretty similar to the original project, this time around all the design effort went into designing 11 separate layers of acrylic to fit together so that all their features line up just so. In total, there are three different types of acrylic used in this project: Clear, black and a specialty mirror-acrylic. 

Here's a shot of the rear control box - Excuse the grubby power lead.


The Photon fits nice and snug into a precisely cut pocket. There's even a slot cut out to serve as a strain-releif for the power cable and help prevent the USB connector on the Photon being damaged. We decided to have the power lead exit the top to keep it as invisible from the front as possible. It of course can't exit the bottom edge, which must remain flat so the mirror can stand on a desk.

The whole unit comes in under 40mm thick (not counting the potentiometer stick-out.



The electronics are very simple. A single potentiometer serves as a re-configurable user-interface, and a single GPIO drives the LED strip. The LED strip is powered from the Vin pin, which is downstream of a polarity protection diode fed by the 5V from USB. This brings the strip supply voltage down to about 4.8V (as tested). Logic-High to the LED strip should be no less than 0.7*Vdd = 0.7*5 = 3.36V so the 3.3V signal from the Photon is technically out of spec... but what's sixty-odd millivolts between friends, eh?



This code is under active development, but for now here's the functionality as shown in this project's video. Drop us a comment with any feature ideas you would think would be interesting!

For now, the code is a quick-and-dirty hack of that from the Infinity-Mirror Table. Because this project has two fewer user-input pots, there's a few magic numbers sprinkled throughout, to hard-code some parameters.

The Photon is running in SEMI_AUTOMATIC mode. It powers-up without connecting to WiFi, and runs the code immediately. If you wish, you can connect with the stored WiFi credentials by pressing the SETUP button. To change what network the Photon connects to, you will have to reset the Photon's WiFi settings.

* This program drives the Core Electronics Infinity Kit
* 2017
* 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
* 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 .

#include "Particle.h"
#include "math.h"
#include "neopixel.h"

 * AUTOMATIC: Photon must have a valid WiFi network to begin running this code.
 * SEMI_AUTOMATIC: Photon will run this code immediately, and attempt to connect to a WiFi network only if SETUP button is pressed.

* Hardware Definitions
* You won't need to change these
const int strip_pin = 0; // The digital pin that drives the LED strip
const int num_leds = 44; // Number of LEDs in the strip. Shouldn't need changing unless you hack the hardware
const int pot = 0; // Potentiometer pin selects the mode
const int ADC_precision = 4095; // Particle use 12bit ADCs. If you wish to port to a different platform you might need to redefine the ADC precision eg: 1023 for Arduino UNO

* Global Variables
// States for the state-machine
enum statevar {

static uint8_t state; // The state that the user demands
static uint8_t state_current; // The state currently being executed

Adafruit_NeoPixel strip(num_leds, strip_pin, WS2812B);
static uint32_t ledBuffer[num_leds]; // Buffer for storing (and then scaling if necessary) LED R,G,B values.
static float userBright = 1.0; // User-set brightness [0 - 1] // TODO roll into LED-strip object


void setup()
  update(); // Initialize all pixels to off

void loop()
  static uint32_t userColour = Wheel(0); // User-set colour TODO roll into LED-strip object


  // Read potentiometer values for user-input
  state = getState(pot);    // Select the operation mode

  // State Machine.
    case state_off:
    clearStrip();   // "Off" state.

    case state_rainbow:
    rainbow(); // Adafruit's rainbow demo, modified for seamless wraparound. We are passing the Pot # instead of the option because delay value needs to be updated WITHIN the rainbow function. Not just at the start of each main loop.

    case state_brightness:

    case state_comet:
    comet(userColour); // An under-construction comet demo.

    case state_solid:

    case state_scroll:
    userColour = scroll();

    default: // If getState() returns some unexpected value, this section will execute


* Functions
*  Connect to stored WiFi credentials. Only useful if you have claimed your
*  particle photon to your particle account:
void connectWIFIonButtonPress() {
  if (System.buttonPushed() > 1) {
    if( !Particle.connected() ) Particle.connect();

// Break potentiometer rotation into sectors for setting mode
// This is the function that determines the order that settings are available from the user-input pot.
uint8_t getState(int pot){
  // TODO: find better, more flexible method of defining pot sectors? Remove magic number?
  float val = float(analogRead(pot)) / float(ADC_precision);

  if (val < 0.05) {
    return state_off;
  } else if (val < 0.25) {
    return state_rainbow;
  }else if (val < 0.5) {
    return state_scroll;
  } else if (val < 0.75) {
    return state_comet;
  } else if (val < 0.95) {
    return state_solid;
  } else {
    return state_brightness;

/* Run the comet demo
* This feature is largely experimental and quite incomplete.
* The idea is to involve multiple comets that can interact by colour-addition
void comet(uint32_t colour){
  state_current = state_comet;
  uint16_t i, j, k;
  uint16_t ofs = 15;

  for (j=0; j down and update a brightness parameter for other modes.
void brightness(uint32_t col) {
  state_current = state_brightness;

  const float maxBright = 1.0; // Leave this as 1. Taking it above 1 won't make things brighter.
  const float minBright = 0.01; // must be strictly greater than zero

  // glow on to max
  for ( userBright; userBright < maxBright; userBright += 0.05*userBright ){
    if(getState(pot) != state_current) break; // Check if mode knob is still on this mode
  userBright = min(userBright, maxBright); // Prevent overshooting 1.0

  // hold at max for a moment
  for (int i = 0; i < 20; i++) {
    if(getState(pot) != state_current) break; // Check if mode knob is still on this mode

  // glow down to min
  for ( userBright; userBright > minBright; userBright -= 0.05*userBright ){
    if(getState(pot) != state_current) break; // Check if mode knob is still on this mode
  userBright = max(minBright, userBright); // Prevent undershoot
  userBright = max(userBright, 0); // Prevent dead-locking at zero, just in case user ignored minBright requirements

  // hold at min for a moment
  for (int i = 0; i < 20; i++) {
    if(getState(pot) != state_current) break; // Check if mode knob is still on this mode



8888888b.                                                   8888888888P
888  "Y88b                                                        d88P
888    888                                                       d88P
888    888  8888b.  88888b.   .d88b.   .d88b.  888d888          d88P    .d88b.  88888b.   .d88b.
888    888     "88b 888 "88b d88P"88b d8P  Y8b 888P"           d88P    d88""88b 888 "88b d8P  Y8b
888    888 .d888888 888  888 888  888 88888888 888            d88P     888  888 888  888 88888888
888  .d88P 888  888 888  888 Y88b 888 Y8b.     888           d88P      Y88..88P 888  888 Y8b.
8888888P"  "Y888888 888  888  "Y88888  "Y8888  888          d8888888888 "Y88P"  888  888  "Y8888
                            Y8b d88P

Changing the code below this line could REALLY DAMAGE your Infinity Mirror.

* Current-Limiting code
* As it stands, if the user manually drives the LED strip, there exists the ability to drive the strip to ~1.5 A.
* The LED strip is powered from the Vin pin, which can supply only 1.0 A.
* The following code serves as wrappers around Adafruit's NeoPixel function calls that scales the user-demanded
* LED values if they would result in LEDs drawing too much current

// Wrapper for LED buffer
void setPixel(int ledIndex, uint32_t colour){
  ledBuffer[ledIndex] = colour;

// Wrapper for safe pixel updating - prevent user from requesting too much current
// TODO refactor, retain brightness adjusted calculations through the function to avoid re-computing and improve readability
void update(){
  uint8_t R, G, B;

  const float iLim = 0.87; // [A] Current limit (0.9A) for external power supply or 1A capable computer USB port.
  // const float iLim = 0.35; // [A] Current limit for 500mA computer USB port
  // const float iLim = 10; // DANGER effectively DISABLE current limiting.
  const float FSDcurrentCorrection = 0.8824; // "Full-scale deflection" correction. The LED response is nonlinear i.e. Amp/LeastSignificantBit is not a constant. This is an attempt to allow the user to specify maximum current as a real value.
  float lsbToAmp = 5.06e-5; // [LSB/Ampere] the relationship between an LED setting and current
  float sum = 0; // Initial sum of currents

  // Sum the LED currents
  for(uint8_t i=0; i iLim ) { // Too much current requested
    for(uint8_t i=0; i> 8) & 0xFF;
  *R = (col >> 16) & 0xFF;

// Scale the demanded colour by the user set brightness 0-100%
void applyBrightness(uint8_t *R, uint8_t *G, uint8_t *B) {
  *B = userBright * *B;
  *G = userBright * *G;
  *R = userBright * *R;

// Clear all leds and update. Clears LED buffer too.
void clearStrip(void){
  for(uint8_t i=0; i

Where to from here?

The Particle Photon is a WiFi enabled device which connects to the Particle Cloud - an ecosystem for passing data between internet connected devices. It'll be interesting to experiment with using this small infinity mirror as some kind of web-connected, desktop display. This means that it not only does it look cool, but by being web-connected it could warn you if you need to take a rainjacket to work today, let you know if the home-server is down, or even if your plants need watering - with the right hardware!

The code for this project is still being actively developed, so drop us a comment with any feature ideas you would like to see implemented in a desktop infinity mirror!

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

Please enter minimum 20 characters

Your comment will be posted (automatically) on our Support Forum which is publicly accessible. Don't enter private information, such as your phone number.

Expect a quick reply during business hours, many of us check-in over the weekend as well.



Please continue if you would like to leave feedback for any of these topics:

  • Website features/issues
  • Content errors/improvements
  • Missing products/categories
  • Product assignments to categories
  • Search results relevance

For all other inquiries (orders status, stock levels, etc), please contact our support team for quick assistance.

Note: click continue and a draft email will be opened to edit. If you don't have an email client on your device, then send a message via the chat icon on the bottom left of our website.

Makers love reviews as much as you do, please follow this link to review the products you have purchased.