Main Project

Neopixel Lightsaber

For my main project, the NeoPixel Lightsaber, I completed two milestones on my path to a finished project. These milestones are documented below with videos and notes. There are 3 components to the NeoPixel Lightsaber: the hilt, the electronics, and the neopixel blade. The neopixel controlled by an arduino nano that dictates the LED patterns in the blade. The hilt encases the Arduino and other components.

Engineer

Walter W.

School

Acalanes High School

Grade

Rising Junior

Second Milestone

For my second milestone, I put together the entire lightsaber blade and inserted the neopixels along with the Arduino and other components. This process involved a lot of soldering, sawing, taping and twisting. There was much more of a focus on construction rather than the coding and electrical work which was very relevant to reach the first milestone. I encountered many challenges while reaching this milestone, including electrical shorts; minimal instructions; and lack of correct equipment, space and time. By the second milestone, I learned how to overcome problems without looking at a guide to get me through them.

First Milestone

My First Milestone for the Neopixel Lightsaber was to have working electrical components. This means that I moved all of my wires and components from a breadboard to a free-floating circuit. I had to do this in order to fit everything into the hilt of the lightsaber. This process was difficult because I had to look at both the original schematic for the project and my breadboard to make the circuit because I made multiple modifications to make use of the materials we had at the workshop. Although this took some time and effort, it made the build cheaper and unique.

#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 8

#define LED_TYPE WS2812
#define COLOR_ORDER GRB
#define NUM_LEDS 129

#define switchPin 5

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 -9.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 6 // Number of bouncing balls you want (recommend < 7, butx 20 is fun in its own way)
#define NUM_BALLS_2 3 // 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);

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

// Serial.begin(9600);

// 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);

}
}

// 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);

// 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 = 62;
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;
}
}

Starter Project

My Starter Project was the MintyBoost portable charger. This was a battery-powered charger which fit inside an Altoids tin. It was very simple to make and introduced me to soldering and the basics of electrical work. There were many components such as resistors and capacitors which were completely new to me and fun to work with.

MintyBoost Images

Stem summer camp NYC

How it works

The MintyBoost is powered by an AA battery back which has an output of 3 volts. The power from the battery pack runs into the circuit controlled by an IC chip. This IC chip controls the circuit via a switch which will force the current down two different paths. If the switch is closed, the current will go through an inductor which increase energy via magnetic field. The goal of the inductor is to increase the voltage to the 5 volts required to power a USB port which the MintyBoost uses to charge electronics. When the current reaches 5 volts, the switch is opened, and the current goes through to 2 ceramic capacitors to smooth out the circuit and prevent large power fluctuations. This line also forces the current through to the USB port where a USB type charger can be plugged in and supplied power.

MintyBoost Schematic

adafruit_products_mintyboostv3sch
Image Source: https://learn.adafruit.com/minty-boost/parts-list

Start typing and press Enter to search