Introduction: Remote Controlled LED Eyes & Costume Hood

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…

Twin Jawas! Double Orko! Two ghost wizards from Bubble-Bobble! This costume hood can be any LED-eyed creature you choose just by changing the colors. I first made this project in 2015 with a very simple circuit and code, but this year I wanted to create an upgraded version with simultaneous animation control across two costumes. This circuit uses one simple, close-range RF remote to control two receivers on the same frequency, and Arduino code employing interrupts to achieve responsive animation changes, based on Bill Earl's tutorial code.

For this project, you will need:

To keep up with what I'm working on, follow me on YouTube, Instagram, Twitter, Pinterest, and subscribe to my newsletter. As an Amazon Associate I earn from qualifying purchases you make using my affiliate links.

Before you begin, you may want to read up on the following prerequisites:

Step 1: Circuit Diagram and Code

The circuit connections are as follows:

  • Gemma D2 to wireless receiver D0
  • Gemma D0 to wireless receiver D1
  • Gemma 3V to wireless receiver +5V
  • Gemma GND to wireless receiver GND and NeoPixel jewels GND
  • Gemma D1 to NeoPixel jewel data IN
  • Gemma Vout to NeoPixel jewels PWR
  • NeoPixel jewel data OUT to other NeoPixel Jewel data IN

See next step for assembly notes.

Code is based on Multi-tasking the Arduino sketch by Bill Earl, and modified to control two NeoPixel jewels with two digital inputs. So you don't have to use the wireless receiver-- you could use buttons on the circuit itself instead. Download this Arduino code file from this step's attachments, or copy and paste from here into an empty Arduino sketch:

#include "Adafruit_NeoPixel.h"

// Pattern types supported:
enum  pattern { NONE, RAINBOW_CYCLE, THEATER_CHASE, COLOR_WIPE, SCANNER, FADE };
// Patern directions supported:
enum  direction { FORWARD, REVERSE };

// NeoPattern Class - derived from the Adafruit_NeoPixel class
class NeoPatterns : public Adafruit_NeoPixel
{
    public:

    // Member Variables:  
    pattern  ActivePattern;  // which pattern is running
    direction Direction;     // direction to run the pattern
    
    unsigned long Interval;   // milliseconds between updates
    unsigned long lastUpdate; // last update of position
    
    uint32_t Color1, Color2;  // What colors are in use
    uint16_t TotalSteps;  // total number of steps in the pattern
    uint16_t Index;  // current step within the pattern
    
    void (*OnComplete)();  // Callback on completion of pattern
    
    // Constructor - calls base-class constructor to initialize strip
    NeoPatterns(uint16_t pixels, uint8_t pin, uint8_t type, void (*callback)())
    :Adafruit_NeoPixel(pixels, pin, type)
    {
        OnComplete = callback;
    }
    
    // Update the pattern
    void Update()
    {
        if((millis() - lastUpdate) > Interval) // time to update
        {
            lastUpdate = millis();
            switch(ActivePattern)
            {
                case RAINBOW_CYCLE:
                    RainbowCycleUpdate();
                    break;
                case THEATER_CHASE:
                    TheaterChaseUpdate();
                    break;
                case COLOR_WIPE:
                    ColorWipeUpdate();
                    break;
                case SCANNER:
                    ScannerUpdate();
                    break;
                case FADE:
                    FadeUpdate();
                    break;
                default:
                    break;
            }
        }
    }
  
    // Increment the Index and reset at the end
    void Increment()
    {
        if (Direction == FORWARD)
        {
           Index++;
           if (Index >= TotalSteps)
            {
                Index = 0;
                if (OnComplete != NULL)
                {
                    OnComplete(); // call the comlpetion callback
                }
            }
        }
        else // Direction == REVERSE
        {
            --Index;
            if (Index <= 0)
            {
                Index = TotalSteps-1;
                if (OnComplete != NULL)
                {
                    OnComplete(); // call the comlpetion callback
                }
            }
        }
    }
    
    // Reverse pattern direction
    void Reverse()
    {
        if (Direction == FORWARD)
        {
            Direction = REVERSE;
            Index = TotalSteps-1;
        }
        else
        {
            Direction = FORWARD;
            Index = 0;
        }
    }
    
    // Initialize for a RainbowCycle
    void RainbowCycle(uint8_t interval, direction dir = FORWARD)
    {
        ActivePattern = RAINBOW_CYCLE;
        Interval = interval;
        TotalSteps = 255;
        Index = 0;
        Direction = dir;
    }
    
    // Update the Rainbow Cycle Pattern
    void RainbowCycleUpdate()
    {
        for(int i=0; i< numPixels(); i++)
        {
            setPixelColor(i, Wheel(((i * 256 / numPixels()) + Index) & 255));
        }
        show();
        Increment();
    }

    // Initialize for a Theater Chase
    void TheaterChase(uint32_t color1, uint32_t color2, uint8_t interval, direction dir = FORWARD)
    {
        ActivePattern = THEATER_CHASE;
        Interval = interval;
        TotalSteps = numPixels();
        Color1 = color1;
        Color2 = color2;
        Index = 0;
        Direction = dir;
   }
    
    // Update the Theater Chase Pattern
    void TheaterChaseUpdate()
    {
        for(int i=0; i< numPixels(); i++)
        {
            if ((i + Index) % 3 == 0)
            {
                setPixelColor(i, Color1);
            }
            else
            {
                setPixelColor(i, Color2);
            }
        }
        show();
        Increment();
    }

    // Initialize for a ColorWipe
    void ColorWipe(uint32_t color, uint8_t interval, direction dir = FORWARD)
    {
        ActivePattern = COLOR_WIPE;
        Interval = interval;
        TotalSteps = numPixels();
        Color1 = color;
        Index = 0;
        Direction = dir;
    }
    
    // Update the Color Wipe Pattern
    void ColorWipeUpdate()
    {
        setPixelColor(Index, Color1);
        show();
        Increment();
    }
    
    // Initialize for a SCANNNER
    void Scanner(uint32_t color1, uint8_t interval)
    {
        ActivePattern = SCANNER;
        Interval = interval;
        TotalSteps = (numPixels() - 1) * 2;
        Color1 = color1;
        Index = 0;
    }

    // Update the Scanner Pattern
    void ScannerUpdate()
    { 
        for (int i = 0; i < numPixels(); i++)
        {
            if (i == Index)  // Scan Pixel to the right
            {
                 setPixelColor(i, Color1);
            }
            else if (i == TotalSteps - Index) // Scan Pixel to the left
            {
                 setPixelColor(i, Color1);
            }
            else // Fading tail
            {
                 setPixelColor(i, DimColor(getPixelColor(i)));
            }
        }
        show();
        Increment();
    }
    
    // Initialize for a Fade
    void Fade(uint32_t color1, uint32_t color2, uint16_t steps, uint8_t interval, direction dir = FORWARD)
    {
        ActivePattern = FADE;
        Interval = interval;
        TotalSteps = steps;
        Color1 = color1;
        Color2 = color2;
        Index = 0;
        Direction = dir;
    }
    
    // Update the Fade Pattern
    void FadeUpdate()
    {
        // Calculate linear interpolation between Color1 and Color2
        // Optimise order of operations to minimize truncation error
        uint8_t red = ((Red(Color1) * (TotalSteps - Index)) + (Red(Color2) * Index)) / TotalSteps;
        uint8_t green = ((Green(Color1) * (TotalSteps - Index)) + (Green(Color2) * Index)) / TotalSteps;
        uint8_t blue = ((Blue(Color1) * (TotalSteps - Index)) + (Blue(Color2) * Index)) / TotalSteps;
        ColorSet(Color(red, green, blue));
        show();
        Increment();
    }
   
    // Calculate 50% dimmed version of a color (used by ScannerUpdate)
    uint32_t DimColor(uint32_t color)
    {
        // Shift R, G and B components one bit to the right
        uint32_t dimColor = Color(Red(color) >> 1, Green(color) >> 1, Blue(color) >> 1);
        return dimColor;
    }

    // Set all pixels to a color (synchronously)
    void ColorSet(uint32_t color)
    {
        for (int i = 0; i < numPixels(); i++)
        {
            setPixelColor(i, color);
        }
        show();
    }

    // Returns the Red component of a 32-bit color
    uint8_t Red(uint32_t color)
    {
        return (color >> 16) & 0xFF;
    }

    // Returns the Green component of a 32-bit color
    uint8_t Green(uint32_t color)
    {
        return (color >> 8) & 0xFF;
    }

    // Returns the Blue component of a 32-bit color
    uint8_t Blue(uint32_t color)
    {
        return color & 0xFF;
    }
    
    // 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 Color(255 - WheelPos * 3, 0, WheelPos * 3);
        }
        else if(WheelPos < 170)
        {
            WheelPos -= 85;
            return Color(0, WheelPos * 3, 255 - WheelPos * 3);
        }
        else
        {
            WheelPos -= 170;
            return Color(WheelPos * 3, 255 - WheelPos * 3, 0);
        }
    }
};

void JewelsComplete();


// Define some NeoPatterns for the two rings and the stick
//  as well as some completion routines
NeoPatterns Jewels(14, 1, NEO_GRBW + NEO_KHZ800, &JewelsComplete);


  const int BRIGHTNESS =  50;



// Initialize everything and prepare to start
void setup()
{
  Serial.begin(115200);

   pinMode(2, INPUT);
   pinMode(0, INPUT);
    
    // Initialize all the pixels
    Jewels.setBrightness(BRIGHTNESS);
    Jewels.begin();
    
    // Kick off a pattern
    Jewels.TheaterChase(Jewels.Color(255,50,0), Jewels.Color(0,0,0,50), 100);
}

// Main loop
void loop()
{
    // Update the jewels.
    Jewels.Update();
    
    // Switch patterns on a button press:
    if (digitalRead(2) == HIGH) // Button #1 pressed
    {
          Jewels.Color1 = Jewels.Color(255, 50, 0);
          Jewels.ActivePattern = FADE;
          Jewels.TotalSteps = 100;
          Jewels.Interval = 1;
    }
    else if (digitalRead(0) == HIGH) // Button #2 pressed
    {
          Jewels.Color1 = Jewels.Color(255, 0, 0);
          Jewels.ActivePattern = SCANNER;
          Jewels.TotalSteps = Jewels.numPixels();
          Jewels.Interval = 100;
    }
    else // Back to normal operation
    {
        // Restore all pattern parameters to normal values
        Jewels.Color1 = Jewels.Color(255, 50, 0);
        Jewels.ActivePattern = THEATER_CHASE;
        Jewels.TotalSteps = Jewels.numPixels();
        Jewels.Interval = 100;
    }    
}

//------------------------------------------------------------
//Completion Routines - get called on completion of a pattern
//------------------------------------------------------------


// Jewels Completion Callback
void JewelsComplete()
{
    // Random color change for next scan
    //Jewels.Color1 = Jewels.Wheel(random(255));
    Jewels.Reverse();
}


Step 2: Assemble Circuit

A set of helping third hands grippers can make the process of soldering wires to components very straightforward and fun. But don't worry if you don't have a set; you can always use some tape or poster putty to keep your board steady while you solder.

Use thin pieces of stranded wire (about 6in/15cmin length) for the connections between the two NeoPixel jewels (diagram in previous step). If you use wires that are too short, you won't be able to place your LED eyes far enough apart, and if you use too much wire, the slack will get in your face while you're wearing the costume.

The main circuit will live in the lapel area (where your chest meets your shoulder), so for the connections between the first NeoPixel jewel in the chain and the Gemma, the wires will be much longer. You can hold the wire up to your eye area and draw it out to measure the distance the wire should travel, then add a bit more for slack and insurance.

To connect between the Gemma and wireless receiver, I chose to use prototyping wires with female headers, since the wireless receiver already has header pins attached.

Step 3: Battery Power

To power the circuit, I used a 500mAh lipoly battery. If using a lipoly battery, it's wise to protect it from scratches, puncture, abrasions, bending, and other abuse. You could wrap it in some sturdy fabric tape, or make a 3D printed holder for it.

You could easily use a 3xAAA holder instead (carry it in your pocket instead of inside the lapel).

Step 4: Sewing Pattern & Cutting Fabric

I used the same pattern I created for the first version of this costume, which is a multi-page PDF that tiles together to create the pattern pieces.

Fold your fabric, aligning selvedge edges to align fabric grain, and place/pin pattern pieces along fold as marked. Trace a seam allowance outside the pattern pieces (except the fold) of about 5/8in/3cm using a marking chalk or pencil. Since my fabric was thin, I wanted to double it up, and since I made two hoods, I ended up cutting four of each pattern piece in the main fabric, then another layer in gauzy cheesecloth to add texture to the outside, and ultimately a layer of black fabric as a liner to block the light coming in. I think if I had planned ahead for that, I could have dropped one of the initial white layers and the hoods would have consisted of only three layers each instead of four.

Step 5: Assemble Fabric Pieces

Pin and sew darts/shoulder seams on each pattern piece, then align hood and cape pieces along neck seam with right sides together. Stitch the seam, as well as a seam straight across the top of the hood.

Try on the hood. Fold over and pin the raw front edge of the hood and stitch it down to create a neat edge as well as a channel for a wire to go through.

Next, cut a roundish piece of sheer black fabric to cover the front of the hood. This is what will support the circuit and hide your face. Pin it in place while wearing the hood for best fit, then hand or machine sew it to the hood opening.

Step 6: Install Circuit in Hood

I put the hood on, turned the circuit on, and used a mirror to suss out the best location for the LEDs. Then I used pins to mark the locations and carefully stitched using black thread, attaching the mounting holes on the NeoPixel jewels to the sheer black front panel. Mine sit just below my real eyes, which makes it easy to see past them.

Rinse and repeat if you're making a second hood.

Step 7: Wear It!

These are so much fun to wear. It's easy to see out, and not easy for others to see your face. The whole thing is pretty comfortable too, thanks to the oversize hood and wire frame, which keeps the front fabric from draping on your face.

My boyfriend and I wore these to DJ my hackerspace's Halloween party this year, and while I could see the interface for the laser projector software, he couldn't quite make out the tiny text in abelton, so we had to adapt his to have a better view. I removed the black fabric panel from the top bit of the hood, and folded over the excess. In a dark room, you couldn't really tell the difference between the two, though you can see it in the photo of us together above.

Thanks for reading! If you like this project, you may be interested in some of my others:

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

Make it Glow Contest 2018

Participated in the
Make it Glow Contest 2018