NeoPixel Lightsaber

There are 3 components to the NeoPixel Lightsaber: the hilt, the electronics, and the blade. The saber is powered by a portable charger with a micro USB. The Arduino Nano controls the patterns of the NeoPixel lights where the button switches between solid blue and party mode. It took me two milestones to complete the project.

Engineer

Andrew C

Area of Interest

Finance

School

Acalanes High School

Grade

Incoming Junior

First Milestone

Explanation

For my first milestone, I used a breadboard to wire power to the neopixel strips and LED button. My laptop provides power to the arduino nano, which contains the code for the circuit and controls the Neopixel light strips. The light strips have three ports: ground, which is white; data, which is green; and power, which is red. Because the two strips of lights are doing the same thing, I put the three ports in the same three rows in the breadboard. The button is a bit different. Because the button has an LED ring on the top to look more like it would on a lightsaber, it requires power to light up. It also must be wired to ground and the 5V of power. The portable charger provides these 5V.

I faced a whole lot of challenges doing this. The code is brutal and took a long time to figure out how to download the libraries for FastLED so the lights would even turn on. I also had a lot of problems with the electrical wiring and getting the LED button to turn the lights on and off and switching between modes.

I’ve made some great progress and the lights look really good. Now I need to physically solder everything rather than having it all on the breadboard. That will probably be very challenging as I’m not going to be using a perfboard for the connections, I’m going straight onto the Arduino nano. This will be a lot different from the breadboard, but it’s good because it should all be able to fit into the hilt.

Final Milestone

Explanation

For the final milestone, I assembled the two pieces for the hilt and secured it with a bolt and hex nut. I was able to fit the electronics into the hilt because I soldered the wires directly to the arduino nano rather than having to have a breadboard in between. The NeoPixels are wrapped in packing foam to give it a cloudy, lightsaber effect. The polycarbonate tube was sanded to give the same effect as the foam. The LED button is superglued in to assure that it does not move.

#include “FastLED.h”

#if defined(FASTLED_VERSION) && (FASTLED_VERSION < 3001000)

#warning “Requires FastLED 3.1 or later; check github for latest code.”

#endif

#define LED_PIN 4

#define LED_TYPE    WS2812

#define COLOR_ORDER GRB

#define NUM_LEDS    144

#define switchPin 2

CRGB leds[NUM_LEDS];

#define BRIGHTNESS          250

#define FRAMES_PER_SECOND  120

int saber_height = 0;

int prev_pos = 0;

int temp_pos = 0;

int pulse_center = 30;

int oldMode = 0;  // assume switch closed because of pull-down resistor

const unsigned long debounceTime = 1000;  // milliseconds

/*Not really used yet. Thought to be able to switch between sound reactive

mode, and general gradient pulsing/static color*/

int mode = 0;

//For Fire

#define SPARKING 300

#define COOLING  45

bool gReverseDirection = false;

#define FRAMES_PER_SECOND 180

void saber_on();

//config for balls

#define GRAVITY           -4.81 // Downward (negative) acceleration of gravity in m/s^2

#define h0                1 // Starting height, in meters, of the ball (strip length)

#define NUM_BALLS         10 // Number of bouncing balls you want (recommend < 7, butx 20 is fun in its own way)

#define NUM_BALLS_2         10 // Number of bouncing balls you want (recommend < 7, butx 20 is fun in its own way)

float h[NUM_BALLS] ;                         // An array of heights

float h_2[NUM_BALLS_2] ;                         // An array of heights

float vImpact0 = sqrt( -2 * GRAVITY * h0 );  // Impact velocity of the ball when it hits the ground if “dropped” from the top of the strip

float vImpact[NUM_BALLS] ;                   // As time goes on the impact velocity will change, so make an array to store those values

float tCycle[NUM_BALLS] ;                    // The time since the last time the ball struck the ground

int   pos[NUM_BALLS] ;                       // The integer position of the dot on the strip (LED index)

int   pos_2[NUM_BALLS] ;                       // The integer position of the dot on the strip (LED index)

long  tLast[NUM_BALLS] ;                     // The clock time of the last ground strike

float COR[NUM_BALLS] ;                       // Coefficient of Restitution (bounce damping)

void setup() {

    pinMode (switchPin, INPUT);

    pinMode(LED_PIN, OUTPUT);

  delay(1000); // 3 second delay for recovery

  

  // tell FastLED about the LED strip configuration

  FastLED.addLeds<LED_TYPE,LED_PIN,COLOR_ORDER>(leds, NUM_LEDS).setCorrection(TypicalLEDStrip);

  

  // set master brightness control

  FastLED.setBrightness(BRIGHTNESS);

  FastLED.setMaxPowerInVoltsAndMilliamps(5,2000); 

  delay(1000);

   for (int i = 0 ; i < NUM_BALLS ; i++) {    // Initialize variables

    tLast[i] = millis();

    h[i] = h0;

    pos[i] = 0;                              // Balls start on the ground

    vImpact[i] = vImpact0;                   // And “pop” up at vImpact0

    tCycle[i] = 0;

    COR[i] = 0.90 – float(i)/pow(NUM_BALLS,2);  

  

  

  Serial.begin(9600);

}

// List of patterns to cycle through.  Each is defined as a separate function below.

typedef void (*SimplePatternList[])();

SimplePatternList gPatterns = {Balls, blur, confetti, sinelon, juggle, bpm };

uint8_t gCurrentPatternNumber = 0; // Index number of which pattern is current

uint8_t gHue = 150; // rotating “base color” used by many of the patterns

  

void loop()

{

  

// see if switch is open or closed

  int switchState = !digitalRead (switchPin);

  Serial.print(“SWITCH STATE “);

  Serial.println(switchState);

 

  // has it changed since last time?

  if (switchState == 0)

    

      delay(300);

    if (oldMode == 3)

       

        oldMode = 0;

        gHue = 150;

 

       }  // end if switchState is LOW

      else {

        oldMode++;

   

      

    }  // end of state change

  switch(oldMode) {

      

    case 0:

      saber_on();

  

      break;

     case 1:

      saber_off();

      break;    

    case 2:

       pattern_rotate();

      break;

    case 3:

      saber_off();

      break;

      

    default:

      break;

  

}

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

void nextPattern()

{

  // add one to the current pattern number, and wrap around at the end

  gCurrentPatternNumber = (gCurrentPatternNumber + 1) % ARRAY_SIZE( gPatterns);

}

void saber_on() {

  

  if (saber_height == 0) {

    for (int i = 0; i < NUM_LEDS-10; i = i+2) {

      leds[i] = CHSV( gHue, 250, 220);

      leds[i+1] = CHSV( gHue, 250, 180);

      leds[i+2] = CHSV( gHue, 250, 120);

      leds[i+3] = CHSV( gHue, 250, 80);

      leds[i+4] = CHSV( gHue, 250, 50);

      leds[i+5] = CHSV( gHue, 250, 20);

      FastLED.show();

      delay(3);

    

    saber_height = 1;

//    delay(50);

//    for (int j =0; j < NUM_LEDS_2; j++) {

//      leds_2[j] = CRGB(gHue, 250, 220);

//      FastLED.show();

//    }

  

  

  else { 

    int pos = beatsin16( 8, 230, 250);

    int pos_2 = beatsin16(15, 180, 220);

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

      leds[i] = CHSV( 150, pos, pos_2);

    

    

  

  FastLED.show();

}

void saber_off() {

  if (saber_height == 1) {

    

    for (int i = NUM_LEDS-1; i > 6; i = i-2) {

      leds[i-5] = CHSV( gHue, 250, 150);

      leds[i-4] = CHSV( gHue, 250, 120);

      leds[i-3] = CHSV( gHue, 250, 100);

      leds[i-2] = CHSV( gHue, 250, 60);

      leds[i-1] = CHSV( gHue, 250, 30);

      leds[i] = CHSV( 0, 0, 0);

      

 

      FastLED.show();

     

    

    saber_height = 0;

  

  

  else { 

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

      leds[i] = CHSV( 0, 0, 0);

    

    

  

  FastLED.show();

}

void pattern_rotate() {

  // Call the current pattern function once, updating the ‘leds’ array

  gPatterns[gCurrentPatternNumber]();

  // send the ‘leds’ array out to the actual LED strip

  FastLED.show();  

  // insert a delay to keep the framerate modest

  FastLED.delay(1000/FRAMES_PER_SECOND); 

  // do some periodic updates

  EVERY_N_MILLISECONDS( 5 ) { gHue++; } // slowly cycle the “base color” through the rainbow

  EVERY_N_SECONDS( 10 ) { nextPattern(); } // change patterns periodically

}

void rainbow() 

{

  // FastLED’s built-in rainbow generator

  fill_rainbow( leds, NUM_LEDS-1, gHue, 7);

}

void rainbowWithGlitter() 

{

  // built-in FastLED rainbow, plus some random sparkly glitter

  rainbow();

  addGlitter(80);

}

void addGlitter( fract8 chanceOfGlitter) 

{

  if( random8() < chanceOfGlitter) {

    leds[ random16(NUM_LEDS-1) ] += CRGB::White;

  

}

void confetti() 

{

  // random colored speckles that blink in and fade smoothly

  fadeToBlackBy( leds, NUM_LEDS, 10);

  int pos = random16(NUM_LEDS-1);

  leds[pos] += CHSV( gHue + random8(64), 200, 240);

}

void sinelon()

{

  // a colored dot sweeping back and forth, with fading trails

  fadeToBlackBy( leds, NUM_LEDS, 8);

  int pos = beatsin16( 40, 0, NUM_LEDS-1 );

  leds[pos] += CHSV( gHue, 255, 192);

}

void bpm()

{

  // colored stripes pulsing at a defined Beats-Per-Minute (BPM)

  uint8_t BeatsPerMinute = 120;

  CRGBPalette16 palette = PartyColors_p;

  uint8_t beat = beatsin8( BeatsPerMinute, 64, 230);

  for( int i = 0; i < NUM_LEDS; i++) { //9948

    leds[i] = ColorFromPalette(palette, gHue+(i*2), beat-gHue+(i*10));

  

 

  

}

void juggle() {

  // eight colored dots, weaving in and out of sync with each other

  fadeToBlackBy( leds, NUM_LEDS, 20);

  byte dothue = 0;

  for( int i = 0; i < 8; i++) {

    leds[beatsin16( i+7, 0, NUM_LEDS-1 )] |= CHSV(dothue, 200, 230);

    dothue += 32;

  

 

}

void blur() {

  uint8_t blurAmount = dim8_raw( beatsin8(3,64, 192) );       // A sinewave at 3 Hz with values ranging from 64 to 192.

  blur1d( leds, NUM_LEDS, blurAmount);                        // Apply some blurring to whatever’s already on the strip, which will eventually go black.

  uint8_t  i = beatsin8(  9, 0, NUM_LEDS-1);

  uint8_t  j = beatsin8( 7, 0, NUM_LEDS-1);

  uint8_t  k = beatsin8(  5, 0, NUM_LEDS-1);

  

  // The color of each point shifts over time, each at a different speed.

  uint16_t ms = millis();  

  leds[(i+j)/2] = CHSV( ms / 29, 200, 220);

  leds[(j+k)/2] = CHSV( ms / 41, 200, 220);

  leds[(k+i)/2] = CHSV( ms / 73, 200, 220);

  leds[(k+i+j)/3] = CHSV( ms / 53, 200, 220);

  

  FastLED.show();

  

} // loop()

void Balls() {

  for (int i = 0 ; i < NUM_BALLS ; i++) {

    tCycle[i] =  millis() – tLast[i] ;     // Calculate the time since the last time the ball was on the ground

    // A little kinematics equation calculates positon as a function of time, acceleration (gravity) and intial velocity

    h[i] = 0.5 * GRAVITY * pow( tCycle[i]/1000 , 2.0 ) + vImpact[i] * tCycle[i]/1000;

    if ( h[i] < 0 ) {                      

      h[i] = 0;                            // If the ball crossed the threshold of the “ground,” put it back on the ground

      vImpact[i] = COR[i] * vImpact[i] ;   // and recalculate its new upward velocity as it’s old velocity * COR

      tLast[i] = millis();

      if ( vImpact[i] < 0.01 ) vImpact[i] = vImpact0;  // If the ball is barely moving, “pop” it back up at vImpact0

    

    pos[i] = round( h[i] * (NUM_LEDS – 1) / h0);       // Map “h” to a “pos” integer index position on the LED strip

  

  //Choose color of LEDs, then the “pos” LED on

  for (int i = 0 ; i < NUM_BALLS ; i++) {

    leds[pos[i]] = CHSV( uint8_t (i * 40) , 255, 220);

  

  FastLED.show();

  //Then off for the next loop around

  for (int i = 0 ; i < NUM_BALLS ; i++) {

    leds[pos[i]] = CRGB::Black;

  

}

Conclusion

I faced so many challenges with this final milestone. For one, when I soldered the button in even after it was secured with superglue, it proceeded to short and display weird colors that were not right. After securing the blade into the hilt one time, I picked up the lightsaber and the blade fell out. This was devastating.

I love how this project tuned out. After so many attempts and seeing it finally work, it was super rewarding. I would definitely recommend this project to anyone that is considering doing it.

Starter Project – MintyBoost

Explanation

I made the MintyBoost for my starter project. It uses the 3V output from two AA batteries and converts it to 5V throughout the circuit to power your phone with a USB. It took a lot of work and perseverance, but the final output is very worth it because it is a cheap alternative to expensive portable chargers like you would normally see.

Demonstration

How it works

The MintyBoost has 3 main components: the battery pack, the tin, and the circuit board. It is powered by two AA batteries, which output 3V of power into the circuit. An inductor, or boost, ramps up the voltage of the current as it goes through a coil. The IC chip serves an important purpose- opening and closing the circuit. When closed, the current goes through a loop as the voltage gradually rises with the inductor. When the voltage has built up high enough, the IC opens the circuit and forces the current through a diode, a piece that limits the current to one direction. Then, the current moves into a ceramic capacitor, which stores the voltage. Finally, the current moves out to the USB and into your phone!

Click Here to See Instructions

MintyBoost Schematic

mintyboost-schematic
Image Source: https://github.com/adafruit/Adafruit-MiniPOV4-Kit/tree/master/Hardware

Start typing and press Enter to search