Introduction: Remote Controlled LED Eyes & Costume Hood
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:Two NeoPixel Jewels
- GEMMA M0 microcontroller
- 315MHz wireless receiver, latching type
- 315MHz wireless RF remote in four, two, or single button configuration
- Silicone coated stranded wire (30awg recommended)
- Soldering iron and solder
- Wire strippers
- Flush cutters
- Tweezers
- Helping third hand tool (optional)
- Sewing pins
- Tailor's chalk (optional)
- 19awg galvanized steel wire
- Thick fabric for hood/cape (for this version I used two layers of white tobacco cloth (1yd) and one layer of white cheesecloth (1yd), then lined the inside of the hood with solid black to block the light)
- Translucent black fabric for face panel (1yd)
- Sewing machine
- Scissors
- Sewing needle and thread
- 3D printer with flexible filament (optional)
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:
- 13 Ideas for Diffusing LEDs
- Diffused LED Strip Sign With Arduino/Bluetooth
- YouTube Subscriber Counter with ESP8266
- Easy Infinity Mirror
- 3 Beginner Arduino Mistakes
To keep up with what I'm working on, follow me on YouTube, Instagram, Twitter, and Pinterest.