#include <FastLED.h>
//================================================================================
// Hardware Configuration
//================================================================================
#define NUM_LEDS 60
#define DATA_PIN 2 // Change to your data pin
#define LED_TYPE WS2812B
#define COLOR_ORDER GRB
#define BRIGHTNESS 250
CRGB leds[NUM_LEDS];
//================================================================================
// Effect Parameters (ported from your C# class)
//================================================================================
const float MaxSpeed = 30.0f; // Pixels per second. Adjusted for smaller scale.
const float NewParticleProbability = 0.02f; // Odds of a new particle burst each frame.
const float ParticlePreignitonTime = 0.0f; // How long to "wink" into existence.
const float ParticleIgnition = 0.1f; // How long to "flash" white at birth (in seconds).
const float ParticleHoldTime = 0.25f; // Main lifecycle time (in seconds).
const float ParticleFadeTime = 1.5f; // Fade out time (in seconds).
// We use a fixed-size array for particles to be memory-safe on Arduino
#define MAX_PARTICLES 80
//================================================================================
// Particle Class (ported from your C# class)
//================================================================================
class Particle {
public:
bool active;
CRGB color;
unsigned long birthTime;
unsigned long lastUpdate;
float velocity;
float position;
Particle() {
active = false; // Start as inactive
}
// Initialize a new particle. This replaces the C# constructor.
void init(CRGB starColor, float pos, float maxSpeed) {
position = pos;
// random(-100, 100) / 100.0f gives a float between -1.0 and 1.0
velocity = (random(-100, 100) / 100.0f) * maxSpeed;
color = starColor;
birthTime = millis();
lastUpdate = birthTime;
active = true;
}
float getAge() {
// Age in seconds
return (millis() - birthTime) / 1000.0f;
}
void update() {
if (!active) return;
float deltaTime = (millis() - lastUpdate) / 1000.0f; // time since last update in seconds
lastUpdate = millis();
position += velocity * deltaTime;
// Apply some drag to slow the particle down
velocity -= (2.0f * velocity * deltaTime);
// Randomly fade the color to simulate "glitter"
if (random(0, 10) == 0) {
color.nscale8(240); // a little bit of fade
}
}
};
//================================================================================
// Global Variables for the Effect
//================================================================================
Particle particles[MAX_PARTICLES];
DEFINE_GRADIENT_PALETTE( rainbow_gp ) {
0, 255, 0, 0, // Red
42, 255,128, 0, // Orange
85, 255,255, 0, // Yellow
128, 0,255, 0, // Green
170, 0, 0,255, // Blue
212, 128, 0,255, // Purple
255, 255, 0, 0 // Red
};
CRGBPalette16 gPalette = rainbow_gp;
//================================================================================
// Main Program Logic
//================================================================================
void setup() {
delay(2000); // Power-on delay
// *** THIS IS THE CORRECTED LINE ***
FastLED.addLeds<LED_TYPE, DATA_PIN, COLOR_ORDER>(leds, NUM_LEDS);
FastLED.setBrightness(BRIGHTNESS);
// Seed the random number generator
randomSeed(analogRead(0));
}
// This function replaces your Render() method
void runFireworks() {
// 1. Randomly create new particle bursts
// The C# code looped based on DotCount. We'll do a simple probability check each frame.
if (random(1000) / 1000.0f < NewParticleProbability) {
// Pick a random color from the palette
CRGB color = ColorFromPalette(gPalette, random8(), 255, LINEARBLEND);
uint16_t startPos = random16(NUM_LEDS);
// The number of particles in one explosion
int particleCount = random(10, 30);
float burstSpeed = random(100, 300) / 100.0f; // a multiplier for this burst's speed
for (int i = 0; i < particleCount; i++) {
// Find an inactive particle to use
for (int p_idx = 0; p_idx < MAX_PARTICLES; p_idx++) {
if (!particles[p_idx].active) {
particles[p_idx].init(color, startPos, MaxSpeed * burstSpeed);
break; // Move to the next particle in the burst
}
}
}
}
// 2. Start with a black canvas
// For a trail effect, you can use fadeToBlackBy. For crisp fireworks, fill_solid is better.
fill_solid(leds, NUM_LEDS, CRGB::Black);
// fadeToBlackBy(leds, NUM_LEDS, 40);
// 3. Update and draw all active particles
for (int i = 0; i < MAX_PARTICLES; i++) {
if (particles[i].active) {
particles[i].update();
float age = particles[i].getAge();
float totalLifetime = ParticleIgnition + ParticleHoldTime + ParticleFadeTime;
// Deactivate particle if its life is over
if (age > totalLifetime) {
particles[i].active = false;
continue;
}
CRGB finalColor = particles[i].color;
// Stage 1: Ignition - Flash white
if (age < ParticleIgnition) {
finalColor = CRGB::White;
}
// Stage 2: Fading
else if (age > (ParticleIgnition + ParticleHoldTime)) {
float fadeAge = age - (ParticleIgnition + ParticleHoldTime);
float fadeProgress = fadeAge / ParticleFadeTime;
// nscale8_video is a fast way to fade
finalColor.nscale8(255 * (1.0f - fadeProgress));
}
// Draw the particle
int pos = (int)particles[i].position;
if (pos >= 0 && pos < NUM_LEDS) {
// Additive blending looks great for explosions
leds[pos] += finalColor;
}
}
}
}
void loop() {
runFireworks();
FastLED.show();
FastLED.delay(16); // Aim for ~60 FPS (1000 / 60 = 16.6)
}