Hackster is hosting Hackster Holidays, Ep. 5: Livestream & Giveaway Drawing. Watch previous episodes or stream live on Friday!Stream Hackster Holidays, Ep. 5 on Friday!
MHolcombe
Created October 23, 2016

The Lich

The Lich is a skull mask and hooded robe, arduino controlled, lit by 62 LEDs that cycle 3 plasma patterns simultaneously.

152
The Lich

Things used in this project

Hardware components

Arduino Nano R3
Arduino Nano R3
any atmega328 based controller should work, provided it has 4 in/outputs
×1
Pushbutton switch 12mm
SparkFun Pushbutton switch 12mm
I used three so i could cycle backwards and forwards through the color palettes. you could use two and simply cycle in one direction if you wanted.
×3
Capacitor 1000 µF
Capacitor 1000 µF
needed for the power source to reduce flicker in the pixels.
×1
Resistor 330 ohm
Resistor 330 ohm
Helps reduce flicker on the data line.
×1
Resistor 100 ohm
Resistor 100 ohm
To help debounce the buttons.
×3
NeoPixel strip
NeoPixel strip
60-bit strip is what I used, the more pixels you use, the more elaborate the plasma scrolls.
×1
Flora RGB Neopixel LEDs- Pack of 4
Adafruit Flora RGB Neopixel LEDs- Pack of 4
you only need two for the eyes.
×1
Paintball Saftey Mask, full face
any mask will work, the mesh eye sockets are useful for holding the eyes in place.
×1
AA Batteries
AA Batteries
Use lion or lipo rechargeables if you use 4, 3 if you are using alkaline.
×4
AA to Barrel jack connector
You can get a 4 battery sled with a barrel jack connector 2-pack for around 2 dollars on amazon.
×1
Barrel Jack
Center positive, make sure it's the same size as your sled's connector
×1
Snappable PC BreadBoard
×4

Software apps and online services

Arduino IDE
Arduino IDE

Hand tools and fabrication machines

Soldering iron (generic)
Soldering iron (generic)
Hot glue gun (generic)
Hot glue gun (generic)

Story

Read more

Schematics

Mask Lighting Boards

These are the four breadboards for stabilizing the wiring for the mask.

Code

Mask Lighting

Arduino
This is the code for the lighting. Copy it into the IDE and it's ready to run :) Be sure to download/install the FastLED library, it is so much smoother than the Adafruit library at higher pixel counts.
#include "FastLED.h"

FASTLED_USING_NAMESPACE

#if defined(FASTLED_VERSION) && (FASTLED_VERSION < 3001000)
#warning "Requires FastLED 3.1 or later; check github for latest version"
#endif

#define DATA_PIN    6         // set the data pin to 6
#define LED_TYPE    WS2812    // look in the library for your specific LED type if not using NeoPixel/ws2812 type
#define COLOR_ORDER GRB
#define NUM_LEDS    50        // the number of pixels outside the mask
#define NUM_INT_LEDS 10       // the number of pixels inside the mouth and nose area
#define NUM_EYE_LEDS 2        // the number of pixels for the eyes. you could use more, but it limits visibility

CRGB leds[NUM_LEDS + NUM_INT_LEDS + NUM_EYE_LEDS];

// palettes

DEFINE_GRADIENT_PALETTE( Sunset_Real_gp ) {
    0, 120,  0,  0,
   22, 179, 22,  0,
   51, 255,104,  0,
   85, 167, 22, 18,
  135, 100,  0,103,
  198,  16,  0,130,
  255,   0,  0,160};
  
DEFINE_GRADIENT_PALETTE( es_rivendell_15_gp ) {
    0,   1, 14,  5,
  101,  16, 36, 14,
  165,  56, 68, 30,
  242, 150,156, 99,
  255, 150,156, 99};

DEFINE_GRADIENT_PALETTE( retro2_16_gp ) {
    0, 188,135,  1,
  255,  46,  7,  1};

DEFINE_GRADIENT_PALETTE( es_ocean_breeze_036_gp ) {
    0,   1,  6,  7,
   89,   1, 99,111,
  153, 144,209,255,
  255,   0, 73, 82};

DEFINE_GRADIENT_PALETTE( es_vintage_01_gp ) {
    0,   4,  1,  1,
   51,  16,  0,  1,
   76,  97,104,  3,
  101, 255,131, 19,
  127,  67,  9,  4,
  153,  16,  0,  1,
  229,   4,  1,  1,
  255,   4,  1,  1};

DEFINE_GRADIENT_PALETTE( rainbowsherbet_gp ) {
    0, 255, 33,  4,
   43, 255, 68, 25,
   86, 255,  7, 25,
  127, 255, 82,103,
  170, 255,255,242,
  209,  42,255, 22,
  255,  87,255, 65};

// define the palette array to cycle through

CRGBPalette16 aPal[] = {
  Sunset_Real_gp,
  es_rivendell_15_gp,
  es_vintage_01_gp,
  es_ocean_breeze_036_gp,
  rainbowsherbet_gp,
  retro2_16_gp
  };

// clickPal changes as you press the palette buttons

int clickPal;

// button pins
const int BUTPIN1  =  3;
const int BUTPIN2  =  7;
const int BUTPIN3  =  8;

// standard input set up
int button1State;
int lastButton1State = LOW;

int button2State;
int lastButton2State = LOW;

int button3State;
int lastButton3State = LOW;

long lastDebounceTime = 0;
long debounceDelay = 50;

// BRIGHTNESS and FRAMES_PER_SECOND: you can slow the plasma by reducing the fps, but I recommend 90 or higher, brightness is user preference, 160+ looks good.

#define BRIGHTNESS          255
#define FRAMES_PER_SECOND   120

// off: when true, the plasma stops

bool off;

// x and y coordinates for the plasma. if you want the inner and outer to sync, you'll need to add " + NUM_INT_LEDS" and not call the interior mask functions

uint8_t x[NUM_LEDS];
uint8_t y[NUM_LEDS];

CRGBPalette16 gPal;

void setup() {

  pinMode(BUTPIN1, INPUT);
  pinMode(BUTPIN2, INPUT);
  pinMode(BUTPIN3, INPUT);

  // define the initial angle for the plasma

  for (uint16_t i = 0; i < NUM_LEDS; i++) {
    uint8_t angle = (i * 255) / NUM_LEDS;
    x[i] = cos8( angle );
    y[i] = sin8( angle );
  }

  // 3 second delay for recovery
  delay(3000);
  
  // tell FastLED the LED strip configuration
  FastLED.addLeds<LED_TYPE, DATA_PIN, COLOR_ORDER>(leds, NUM_LEDS + NUM_INT_LEDS + NUM_EYE_LEDS).setCorrection(TypicalLEDStrip);

  // set master brightness control
  FastLED.setBrightness(BRIGHTNESS);
}



// List of patterns to cycle through. Patterns are defined as functions below.
typedef void (*SimplePatternList[])();
// you can write your own pattern functions and include them here, no arguments allowed.
SimplePatternList gPatterns = { turnOn, slow_xy, slow_yx, fast_z, turnOff };

uint8_t gCurrentPatternNumber = 0; // start the plasma with the "turnOn" function
uint8_t gHue = 0; // rotating base color used by some of the patterns



void loop() {
  
  button1State = digitalRead(BUTPIN1);
  button2State = digitalRead(BUTPIN2);
  button3State = digitalRead(BUTPIN3);
  
  unsigned long curMillis = millis();

  if ((unsigned long)(curMillis - lastDebounceTime) >= debounceDelay)
  {
    lastButton1State = checkButtonState(BUTPIN1, button1State, lastButton1State);
    lastButton2State = checkButtonState(BUTPIN2, button2State, lastButton2State);
    lastButton3State = checkButtonState(BUTPIN3, button3State, lastButton3State);
    
    lastDebounceTime = millis();
  }

  // Call the current pattern function once updating the leds array
  gPatterns[gCurrentPatternNumber]();

  gPal = aPal[clickPal];

  // if the plasma is running, also run the mouth and eyes functions

  if(off == false){
    mouth();
    eyes();
  }

  // send the leds array to the strip
  FastLED.show();
  // insert a delay to keep the framerate reasonable
  FastLED.delay(1000 / FRAMES_PER_SECOND);

  // perform periodic updates
  EVERY_N_MILLISECONDS( 20 ) {
    gHue++;  // change the base color every 20 ms
  }

}


// button and input control functions

int checkButtonState(int button, int bState, int lBState)
{
  if ( lBState != bState )
  {
    // call change function
    if(bState == HIGH)
    {
      buttonHigh(button);
    }
  } else {
    // do other thing
    
  }
  return bState;
}

void buttonHigh(int button)
{
  if(button == BUTPIN1)
  {
    bPatClick();  
  }
  if(button == BUTPIN2)
  {
    bPalDClick();
  }
  if(button == BUTPIN3)
  {
    bPalUClick();
  }
}


#define ARRAY_SIZE(A) (sizeof(A) / sizeof((A) [0]))

void bPalDClick()
{
  clickPal--;
  if(clickPal < 0)
  {
    clickPal = ARRAY_SIZE(aPal) - 1;
  }
}

void bPalUClick()
{
  clickPal++;
  if(clickPal > ARRAY_SIZE(aPal) - 1)
  {
    clickPal = 0;
  }
}

void bPatClick()
{
  nextPattern();
}

void nextPattern()
{
  // add one to the current pattern number and wrap around at the end
  gCurrentPatternNumber = (gCurrentPatternNumber + 1) % ARRAY_SIZE( gPatterns );
}

// plasma functions

void addGlitter( fract8 chanceOfGlitter, CRGB::HTMLColorCode ColorGlit )
{
  fadeToBlackBy(leds, NUM_LEDS, 20);
  if ( random8() < chanceOfGlitter ) {
    leds[ random16(NUM_LEDS) ] += ColorGlit;
  }
}

void slow_xy()
{
  uint8_t scale = 255; // noise zoom factor

  for( uint16_t i = 0; i < NUM_LEDS; i++ )
  {
    uint16_t shift_x = beatsin8(17);            // x pos of noise field swings @ 17 bpm
    uint16_t shift_y = millis() / 100;          // y pos slowly incremented

    uint32_t real_x = (x[i] + shift_x) * scale; // calculate coords within noise field
    uint32_t real_y = (y[i] + shift_y) * scale; // based on precalc-ed positions

    uint8_t noise = inoise16(real_x, real_y, 4223) >> 8;      // get noise data and scale down

    uint8_t index = noise * 3;                  // map led color based on noise data
    uint8_t bri   = noise;

    CRGB color = ColorFromPalette( gPal, index, bri);
    leds[i] = color;
  }
}

void slow_yx()
{

  uint8_t scale = 255;                               // the "zoom factor" for the noise

  for (uint16_t i = 0; i < NUM_LEDS; i++) {

    uint16_t shift_x = millis() / 10;                 // x as a function of time
    uint16_t shift_y = 0;

    uint32_t real_x = (x[i] + shift_x) * scale;       // calculate the coordinates within the noise field
    uint32_t real_y = (y[i] + shift_y) * scale;       // based on the precalculated positions

    uint8_t noise = inoise16(real_x, real_y, 4223) >> 8;           // get the noise data and scale it down

    uint8_t index = noise * 3;                        // map led color based on noise data
    uint8_t bri   = noise;

    CRGB color = ColorFromPalette( gPal, index, bri);
    leds[i] = color;
  }
}

void fast_z()
{
  
  uint8_t scale = 255;                               // the "zoom factor" for the noise

  for (uint16_t i = 0; i < NUM_LEDS; i++) {

    uint16_t shift_x = 0;                             // no movement along x and y
    uint16_t shift_y = 0;


    uint32_t real_x = (x[i] + shift_x) * scale;       // calculate the coordinates within the noise field
    uint32_t real_y = (y[i] + shift_y) * scale;       // based on the precalculated positions
    
    uint32_t real_z = millis() * 20;                  // increment z linear

    uint8_t noise = inoise16(real_x, real_y, real_z) >> 8;           // get the noise data and scale it down

    uint8_t index = noise * 3;                        // map led color based on noise data
    uint8_t bri   = noise;

    CRGB color = ColorFromPalette( gPal, index, bri);
    leds[i] = color;
  }
}

// non-plasma palette cycles
void mouth()
{
 jPuggle(NUM_LEDS, NUM_INT_LEDS);
}

void eyes()
{
  rBuggle(NUM_LEDS + NUM_INT_LEDS, NUM_EYE_LEDS);
}


void cFuggle(int startPixel, int pixelCount)
{
  fadeToBlackBy( leds + startPixel, pixelCount, 5 );
  int pos = random8(pixelCount);
  leds[pos] += ColorFromPalette(gPal, gHue, gHue + (30));
}

void sLuggle(int startPixel, int pixelCount)
{
  fadeToBlackBy( leds + startPixel, pixelCount, 20 );
  int pos = beatsin16(12, 0, pixelCount);
  leds[pos + startPixel] += ColorFromPalette(gPal, gHue, gHue + 13);
}

void jPuggle(int startPixel, int pixelCount)
{
  fadeToBlackBy( leds + startPixel, pixelCount, 20 );
  CRGBPalette16 palette = aPal[0];
  for (int i = 0; i < 8; i++) {
    leds[(startPixel) + beatsin16( i + 7, 0, pixelCount)] = ColorFromPalette(palette, gHue + (i * 2), gHue + (i * 10));
  }
}

void turnOff()
{
  off = true;
  fadeToBlackBy( leds, NUM_LEDS + NUM_INT_LEDS + NUM_EYE_LEDS, 20 );
  byte dothue = 0;
  for ( int i = 0; i < 8; i++ ) {
    leds[beatsin16(i + 7, 0, NUM_LEDS + NUM_INT_LEDS + NUM_EYE_LEDS)] |= CHSV(dothue, 0, 0);
  }
}

void turnOn()
{
  off = false;
  addGlitter(3, CRGB::DarkBlue);
}

void rBuggle(int startPixel, int endPixel)
{
  // built-in rainbow
  fill_rainbow( leds + startPixel, endPixel, gHue, 7 );
}

Credits

MHolcombe

MHolcombe

1 project • 0 followers
Words are long and hard.

Comments