/******************************************************************************
* Addressable LED Strip Light Saber V2
* By Joe Larson, the 3D Printing Professor
* http://3dpprofessor.com
******************************************************************************/
#include "FastLED.h"
//#include <Wire.h>
//#include <MPU6065.h>
/***************************************
* Hardware definitions *
***************************************/
#define NUM_LEDS 50
#define LED_DATA_PIN 3
#define POT_PIN A3
/***************************************/
#define Brightness 255
CRGB leds[NUM_LEDS];
int curmode = 1;
CRGBPalette16 gPal;
void setup()
{
// put your setup code here, to run once:
Serial.begin(9600);
Serial.println("LED Strip Light Saber V1.2");
Serial.println("By Joe Larson the 3D Printing Professor");
Serial.println("http://3dpprofessor.com");
pinMode(POT_PIN, INPUT);
randomSeed(analogRead(A0));
FastLED.addLeds<WS2812B, LED_DATA_PIN, GRB>(leds, NUM_LEDS); // Added "GRB" for my manufacturer who decided to mix up the colors
FastLED.setBrightness(Brightness);
}
const int numModes = 4;
void loop()
{
curmode = getMode();
switch (curmode)
{
case 1:
Serial.println("Solid Color Mode");
colorPulse();
break;
case 2:
Serial.println("Scroll Mode");
paletteScroll();
break;
case 3:
Serial.println("Fire Mode");
fireBlade();
break;
case 4:
Serial.println("Lava Lamp/Fireworks");
LavaLampBlade();
break;
default:
Serial.println("Blade is starting in dead zone. Move the Pot a bit.");
delay(200);
}
}
void retractBlade()
{
for (int i = NUM_LEDS-1; i >= 0; i--)
{
leds[i] = CRGB (0, 0, 0);
FastLED.show();
delay(10);
}
}
/***************************************/
void colorPulse()
{
CRGB color = CHSV(getFineMode(255), 255, 255);
Serial.println("Transition");
retractBlade();
for (int i = 0; i <= NUM_LEDS; i++)
{
color = getBrightColor();
fillWithWhiteGradientTo(i, color);
FastLED.show();
delay(20);
}
while (!isTimeToChange())
{
color = getBrightColor();
// Active delay
int delayamount = random(8)*500;
while (delayamount > 0 && !isTimeToChange())
{
fillWithWhiteGradientTo(NUM_LEDS, getBrightColor());
FastLED.show();
delay (20);
delayamount -= 20;
}
for (int pulse = 0; pulse < 12 && !isTimeToChange(); pulse++)
{
color = getBrightColor();
int pulsefactor = 4 * pulse;
fillWithWhiteGradientTo(NUM_LEDS, color+CRGB(pulsefactor, pulsefactor, pulsefactor));
FastLED.show();
delay(10);
}
for (int pulse = 12; pulse >= 0 && !isTimeToChange(); pulse--)
{
color = getBrightColor();
int pulsefactor = 4 * pulse;
fillWithWhiteGradientTo(NUM_LEDS, color+CRGB(pulsefactor, pulsefactor, pulsefactor));
FastLED.show();
delay(30);
}
}
retractBlade();
}
CRGB getBrightColor()
{
return CHSV(getFineMode(255), 255, 255);
}
void fillWithWhiteGradientTo(int num, CRGB color)
{
// There's probably a FastLED library to do this, add a little white gradient
// to the base of the blade, but I don't know what it is.
int fade = 100;
for (int i = 0; i < num; i++)
{
leds[i] = color + CRGB (fade, fade, fade);
fade -= 6;
if (fade < 0) fade = 0;
}
}
/***************************************/
TBlendType currentBlending = LINEARBLEND;
void paletteScroll()
{
// Borrowed from: https://wokwi.com/arduino/libraries/FastLED/ColorPalette
uint8_t startIndex = 0;
int chancetopulse = 0;
int pulsefactor = 0;
int bladelength = 1;
word curFineMode = -1;
while (!isTimeToChange())
{
// Choose the palate
if (curFineMode != getFineMode(4))
{
curFineMode = getFineMode(4);
retractBlade();
bladelength = 1;
}
switch (curFineMode)
{
case 3:
//Serial.println("Candy Cane Scroll");
gPal = CRGBPalette16(
CRGB::Red, CRGB::White, CRGB::Red, CRGB::White,
CRGB::Red, CRGB::White, CRGB::Red, CRGB::White,
CRGB::Red, CRGB::White, CRGB::Red, CRGB::White,
CRGB::Red, CRGB::White, CRGB::Red, CRGB::White);
currentBlending = NOBLEND;
chancetopulse = 0;
break;
case 2:
//Serial.println("Halloween Scroll");
gPal = CRGBPalette16(
CRGB::Orange, CRGB::Black, CRGB::OrangeRed, CRGB::Black,
CRGB::Orange, CRGB::Black, CRGB::OrangeRed, CRGB::Black,
CRGB::Orange, CRGB::Black, CRGB::OrangeRed, CRGB::Black,
CRGB::Orange, CRGB::Black, CRGB::OrangeRed, CRGB::Black);
currentBlending = NOBLEND;
chancetopulse=10;
break;
case 1:
//Serial.println("Camo Scroll");
gPal = CRGBPalette16
(
CRGB::ForestGreen, CRGB::Black, CRGB::DarkGreen, CRGB::Tan,
CRGB::Black, CRGB::GreenYellow, CRGB::Black, CRGB::ForestGreen,
CRGB::Gray, CRGB::Black, CRGB::DarkGreen,CRGB::LawnGreen,
CRGB::Black, CRGB::Tan, CRGB::Black, CRGB::ForestGreen
);
currentBlending = LINEARBLEND;
chancetopulse=0;
break;
default:
// Rainbow Blade inspired by the short animation Star Wars Visions : The Twins.
curFineMode = 0;
gPal = CRGBPalette16
(
0xFF0000, 0xFF5540, 0xD52A00, 0xFFAA40,
0xABAB00, 0x95FF40, 0x00FF00, 0x40FF6A,
0x00AB55, 0x4095EA, 0x0000FF, 0x5540FF,
0x2A00D5, 0xBF40C0, 0xD5002B, 0xFF4055
);
currentBlending = LINEARBLEND;
chancetopulse = 50;
break;
}
startIndex = startIndex + 1; /* motion speed */
if( chancetopulse && random8() < chancetopulse ) {
pulsefactor += 32;
}
FillLEDsFromPaletteColors(bladelength, startIndex, pulsefactor);
FastLED.show();
pulsefactor /= 1.2;
if (bladelength < NUM_LEDS)
{
for (int i=0; (i < 4) && (bladelength < NUM_LEDS); i++)
{
delay(20);
bladelength += 1;
FillLEDsFromPaletteColors(bladelength, startIndex, pulsefactor);
FastLED.show();
}
} else {
bladelength = NUM_LEDS;
FastLED.delay(40);
}
}
retractBlade();
}
void FillLEDsFromPaletteColors(int num, uint8_t shift, uint8_t pulsefactor)
{
uint8_t brightness = 255;
for( int i = 0; i < num; i++) {
// leds[i] = ColorFromPalette(gPal, 4*i-colorIndex, 255, currentBlending) + CRGB(pulsefactor, pulsefactor, pulsefactor);
leds[i] = ColorFromPalette(gPal, ((float)i/15)*i-shift, 255, currentBlending) + CRGB(pulsefactor, pulsefactor, pulsefactor);
}
}
/***************************************/
// Borrowed from Fire2012 by Mark Kriegsman, July 2012
// as part of "Five Elements" shown here: http://youtu.be/knWiGsmgycY
// https://wokwi.com/arduino/libraries/FastLED/Fire2012
#define COOLING 55
#define SPARKING 100
// Array of temperature readings at each simulation cell
static byte heat[NUM_LEDS];
void fireBlade()
{
while (!isTimeToChange())
{
checkFirePalatte();
fireHeatAndDrift();
fireSpark();
fireShow();
delay(20);
}
// Burnout
for (int i = 0; i < NUM_LEDS; i++)
{
fireHeatAndDrift();
fireShow();
delay(20);
}
}
void checkFirePalatte()
{
switch (getFineMode(5))
{
case 1:
gPal = LavaColors_p;
break;
case 2:
gPal = CRGBPalette16(
CRGB::Black,CRGB::DarkGoldenrod,CRGB::Black,CRGB::CRGB::DarkOrange,
CRGB::Orange, CRGB::Gold, CRGB::Yellow, CRGB::FairyLightNCC,
CRGB::OrangeRed, CRGB::Yellow, CRGB::Orange, CRGB::OrangeRed,
CRGB::LightGoldenrodYellow,CRGB::OrangeRed,CRGB::Red,CRGB::FairyLightNCC
);
break;
case 3:
gPal = CRGBPalette16(
CRGB::Black,CRGB::DarkGreen,CRGB::Black,CRGB::DarkOliveGreen,
CRGB::Green, CRGB::ForestGreen, CRGB::OliveDrab, CRGB::Green,
CRGB::SeaGreen, CRGB::MediumAquamarine, CRGB::LimeGreen, CRGB::CRGB::LawnGreen,
CRGB::LightGreen,CRGB::LawnGreen,CRGB::MediumAquamarine,CRGB::ForestGreen
);
break;
case 4:
gPal = CRGBPalette16(
CRGB::Black, CRGB::MidnightBlue, CRGB::Black, CRGB::Navy,
CRGB::DarkBlue, CRGB::MediumBlue, CRGB::SeaGreen, CRGB::Teal,
CRGB::CadetBlue, CRGB::Blue, CRGB::DarkCyan, CRGB::CornflowerBlue,
CRGB::Aquamarine,CRGB::SeaGreen,CRGB::Aqua,CRGB::LightSkyBlue
);
break;
default:
gPal = HeatColors_p;
break;
}
}
void fireHeatAndDrift()
{
// Step 1. Cool down every cell a little
for( int i = 0; i < NUM_LEDS; i++)
{
heat[i] = qsub8( heat[i], random8(0, ((COOLING * 10) / NUM_LEDS) + 2));
}
// Step 2. Heat from each cell drifts 'up' and diffuses a little
for( int k= NUM_LEDS - 1; k >= 2; k--)
{
heat[k] = (heat[k - 1] + heat[k - 2] + heat[k - 2] ) / 3;
}
}
void fireSpark()
{
// Step 3. Randomly ignite new 'sparks' of heat near the bottom
if( random8() < SPARKING ) {
int y = random8(7);
heat[y] = qadd8( heat[y], random8(160,255) );
}
}
void fireShow()
{
// Step 4. Map from heat cells to LED colors
for( int j = 0; j < NUM_LEDS; j++) {
// Scale the heat value from 0-255 down to 0-240
// for best results with color palettes.
byte colorindex = scale8( heat[j], 240);
leds[j] = ColorFromPalette( gPal, colorindex);
}
blur1d(leds, NUM_LEDS, 128);
FastLED.show();
}
/***************************************/
int numParticles = 5;
void LavaLampBlade()
{
int pPos[numParticles];
int heat[NUM_LEDS];
while (!isTimeToChange())
{
if (getFineMode(2) == 1)
{
fireworks();
}
else
{
CRGB DarkColor = CHSV((getFineMode(511)+128)%255, 100, 50);
CRGB LightColor = CHSV(getFineMode(511), 255, 255);
gPal = CRGBPalette16(
DarkColor,DarkColor,DarkColor,DarkColor,
LightColor,LightColor,LightColor,LightColor,
LightColor,LightColor,LightColor,LightColor,
LightColor,LightColor,LightColor,LightColor
);
// Move the "blobs"
for (int p = 0; p < numParticles; p++)
{
pPos[p] = beatsin8(p+1, 0, NUM_LEDS-1);
}
for (int i = 0; i < NUM_LEDS; i++)
{
heat[i] = 0;
}
for (int p = 0; p < numParticles; p++)
{
heat[pPos[p]] = 200;
for (int spread = 1; spread < 2*(numParticles-p)+1; spread++)
{
if ((pPos[p] + spread) <= NUM_LEDS)
heat[pPos[p] + spread] += 200/spread;
if ((pPos[p] - spread) >= 0)
heat[pPos[p] - spread] += 200/spread;
}
}
for (int i = 0; i < NUM_LEDS; i++)
{
if (heat[i] > 240)
{
heat[i] = 240;
}
leds[i] = ColorFromPalette(gPal, heat[i]);
}
FastLED.show();
delay(20);
}
}
}
/******************************************************************************/
const int numsparks = NUM_LEDS/3;
uint8_t fworkType[numsparks];
float fworkHeat[numsparks];
CRGB fworkColor[numsparks];
float fworkLocation[numsparks];
float fworkVelocity[numsparks];
void fireworks()
{
while (!isTimeToChange())
{
if (getFineMode(2) == 0)
return;
fadeToBlackBy(leds, NUM_LEDS, 96);
//fill_solid(leds, NUM_LEDS, CRGB::Black);
if (sparkCount() == 0 || random(100) <= 2) // randomly fire off a new one
{
newSpark(1, CRGB(29, 48, 48), 0, 1.5 + (float)random(13)/4.0);
}
updateFireworks();
showFireworks();
FastLED.show();
delay(50);
}
}
void updateFireworks()
{
for (int i = 0; i < numsparks; i++)
{
switch (fworkType[i])
{
case 0:
break;
case 1: // rising
fworkLocation[i] += constrain(fworkVelocity[i], -1.0, 1.0);
fworkVelocity[i] -= .08; // gravity
if (fworkVelocity[i] < 1.0) { // Explode
fworkType[i] = 0;
int numNew = 6 + random16(6);
float newDiamter = ((float)random16(4) / 2.0) + 2.0;
CRGB newColor = CHSV(random(255), 192, 255);
for (int j = 0; j < numNew; j++)
{
float newVel = (newDiamter / 2.0) - ((newDiamter/(float)numNew) * (float)(j));
newSpark(2, newColor, fworkLocation[i]+newVel, newVel + (float)random16(10)/20.0);
}
}
break;
case 2: // exploding
fworkLocation[i] += fworkVelocity[i];
fworkHeat[i] -= 0.07;
if (fworkHeat[i] <= 0)
{
fworkType[i] = 0;
}
else if (fworkHeat[i] < 0.5)
{
fworkColor[i] -= CRGB(128,128,128);
fworkVelocity[i] /= 3;
if (random8(100) < 50)
fworkVelocity[i] += 0.05;
else
fworkVelocity[i] -= 0.05;
if (random8(100) < 50)
fworkColor[i] += CRGB(25,25,25);
else
fworkColor[i] -= CRGB(25,25,25);
}
break;
}
}
}
void newSpark(uint8_t type, CRGB newCol, int newLoc, float newVel)
{
int newfirework = nextdead();
if (newfirework < 0)
{
//Serial.println("Out of new sparks for now");
return;
}
fworkType[newfirework] = type;
fworkHeat[newfirework] = 1.0;
fworkColor[newfirework] = newCol;
fworkLocation[newfirework] = newLoc;
fworkVelocity[newfirework] = newVel;
}
void showFireworks()
{
for (int i = 0; i < numsparks; i++)
{
//Serial.print(fworkVelocity[i]); Serial.print(",");
if (fworkType[i] > 0)
{
if ((fworkLocation[i] < 0)|| (fworkLocation[i] >= NUM_LEDS))
{
fworkType[i] = 0; // just kill it if it goes out of bounds.
}
else
{
if (getFineMode(4) == 3)
leds[NUM_LEDS - (int)fworkLocation[i]] += fworkColor[i];
else
leds[(int)fworkLocation[i]] += fworkColor[i];
}
}
}
//Serial.println();
}
int sparkCount()
{
int retval = 0;
for (int i = 0; i < numsparks; i++)
if (fworkType[i] > 0)
retval++;
return retval;
}
int nextdead()
{
for (int i = 0; i < numsparks; i++)
{
if (fworkType[i] == 0)
return i;
}
return -1;
}
/******************************************************************************/
bool isTimeToChange()
{
return getMode() != curmode;
}
const float range = 1024.0 / numModes;
int getMode()
{
/*****************************************************************************
* Getmode changes the pot's 0-1024 input into one of numModes zone.
* But it's tricky (because of course it is). Zone 1 is the first 255, so that
* the pot data can be used to change through all the colors. Then the rest of
* the zones are divided evenly among the remaining range with a "dead zone"
* of 2 spaces between each, to overcome the "shake" that pots sometimes
* return when straddling two inputs. If it's in that dead zone, it just echos
* curmode value.
*****************************************************************************/
int val = analogRead(POT_PIN);
int zone = (val/range)+1;
if (val%(int)range < 2) // If it's close to the division edge, return "dead"
{
Serial.println("Returning curmode");
return curmode;
}
return zone;
}
int getFineMode(int numZones)
{
/*****************************************************************************
* Returns between 0 and numZones-1 based on the pot's position within the
* coarser "zone" returned in GetMode above.
*****************************************************************************/
int val = analogRead(POT_PIN);
if (val%(int)range < 2) val -= 2; // Compensate for the dead zone
int zone = val/range;
int fineVal = val - (range * zone);
return fineVal*((float)numZones/range);
}