Skip to content

Instantly share code, notes, and snippets.

@jake-walker
Created August 23, 2025 20:49
Show Gist options
  • Select an option

  • Save jake-walker/03f4f63ad9d297d752321dc1afda23d1 to your computer and use it in GitHub Desktop.

Select an option

Save jake-walker/03f4f63ad9d297d752321dc1afda23d1 to your computer and use it in GitHub Desktop.
WS2812b Bucket Hat Project
#include <Arduino.h>
#include "FastLED.h"
// Pin definitions
#define NUM_LEDS 40
#define LED_PIN 1
#define BUTTON_PIN 23
// Globals
CRGB leds[NUM_LEDS];
// Effect management
enum Effect
{
EFFECT_OFF = 0,
EFFECT_SOLID,
EFFECT_RAINBOW,
EFFECT_CYLON,
EFFECT_CHASE,
EFFECT_PULSE,
EFFECT_COLOR_WAVES,
EFFECT_PLASMA,
EFFECT_SINELON,
EFFECT_AUTO_CYCLE,
NUM_EFFECTS
};
Effect currentEffect = EFFECT_OFF;
Effect autoCycleEffect = EFFECT_OFF;
// Button logic
int lastButtonState = HIGH;
long lastButtonPressTime = 0;
long longPressDuration = 1500;
bool isLongPress = false;
// Brightness control
uint8_t brightnessLevels[] = {1, 10, 100, 255};
uint8_t currentBrightnessIndex = 3;
// Effect timing
long lastAutoCycleChange = 0;
const long autoCycleInterval = 180000; // 3 minutes
// Solid color palette
CRGB solidColors[] = {
CRGB::Red,
CRGB::Green,
CRGB::Blue,
CRGB::Yellow,
CRGB::Cyan,
CRGB::Magenta,
CRGB::White};
uint8_t currentSolidColorIndex = 0;
// Function declarations
void run_effect_off();
void run_effect_solid();
void run_effect_rainbow();
void run_effect_cylon();
void run_effect_chase();
void run_effect_pulse();
void run_effect_color_waves();
void run_effect_plasma();
void run_effect_sinelon();
void run_effect_auto_cycle();
void set_random_effect();
void setup()
{
Serial.begin(115200);
FastLED.addLeds<WS2812B, LED_PIN, GRB>(leds, NUM_LEDS);
FastLED.setBrightness(brightnessLevels[currentBrightnessIndex]);
pinMode(BUTTON_PIN, INPUT_PULLUP);
Serial.println("Ready!");
FastLED.show();
}
void loop()
{
int buttonState = digitalRead(BUTTON_PIN);
// check for button state transitions
if (buttonState == LOW && lastButtonState == HIGH)
{
lastButtonPressTime = millis();
isLongPress = false;
}
// check for long presses
if (buttonState == LOW && !isLongPress && (millis() - lastButtonPressTime > longPressDuration))
{
if (currentEffect == EFFECT_SOLID)
{
currentSolidColorIndex = (currentSolidColorIndex + 1) % (sizeof(solidColors) / sizeof(solidColors[0]));
FastLED.clear();
Serial.print("Solid color changed to: ");
Serial.println(brightnessLevels[currentBrightnessIndex]);
}
else
{
currentBrightnessIndex = (currentBrightnessIndex + 1) % (sizeof(brightnessLevels) / sizeof(brightnessLevels[0]));
FastLED.setBrightness(brightnessLevels[currentBrightnessIndex]);
Serial.print("Brightness changed to: ");
Serial.println(brightnessLevels[currentBrightnessIndex]);
}
isLongPress = true;
}
if (buttonState == HIGH && lastButtonState == LOW && !isLongPress)
{
currentEffect = static_cast<Effect>((currentEffect + 1) % NUM_EFFECTS);
Serial.print("Switching to effect: ");
Serial.println(currentEffect);
if (currentEffect == EFFECT_AUTO_CYCLE)
{
lastAutoCycleChange = millis();
set_random_effect();
}
FastLED.clear();
FastLED.show();
}
lastButtonState = buttonState;
switch (currentEffect)
{
case EFFECT_OFF:
run_effect_off();
break;
case EFFECT_SOLID:
run_effect_solid();
break;
case EFFECT_RAINBOW:
run_effect_rainbow();
break;
case EFFECT_CYLON:
run_effect_cylon();
break;
case EFFECT_CHASE:
run_effect_chase();
break;
case EFFECT_PULSE:
run_effect_pulse();
break;
case EFFECT_COLOR_WAVES:
run_effect_color_waves();
break;
case EFFECT_PLASMA:
run_effect_plasma();
break;
case EFFECT_SINELON:
run_effect_sinelon();
break;
case EFFECT_AUTO_CYCLE:
run_effect_auto_cycle();
break;
default:
FastLED.clear();
break;
}
Serial.print(".");
}
void run_effect_off()
{
FastLED.clear();
FastLED.show();
}
void run_effect_solid()
{
fill_solid(leds, NUM_LEDS, solidColors[currentSolidColorIndex]);
FastLED.show();
}
void run_effect_rainbow()
{
static uint8_t hue = 0;
static long lastUpdate = 0;
const long updateDelay = 20;
if (millis() - lastUpdate > updateDelay)
{
hue++;
if (hue > 255)
{
hue = 0;
}
fill_rainbow(leds, NUM_LEDS, hue, 255 / NUM_LEDS);
FastLED.show();
lastUpdate = millis();
}
}
void run_effect_cylon()
{
static int current_led = 0;
static int direction = 1;
static long lastUpdate = 0;
const long updateDelay = 50;
if (millis() - lastUpdate > updateDelay)
{
for (int i = 0; i < NUM_LEDS; i++)
{
leds[i].fadeToBlackBy(100);
}
leds[current_led] = solidColors[currentSolidColorIndex];
current_led += direction;
if (current_led >= NUM_LEDS - 1)
{
direction = -1;
}
else if (current_led <= 0)
{
direction = 1;
}
FastLED.show();
lastUpdate = millis();
}
}
void run_effect_chase()
{
static int head = 0;
static long lastUpdate = 0;
const long updateDelay = 100;
if (millis() - lastUpdate > updateDelay)
{
// Fade all LEDs
for (int i = 0; i < NUM_LEDS; i++)
{
leds[i].fadeToBlackBy(20);
}
// Create the "head" and a small trail
leds[head] = CHSV(millis() / 20, 255, 255);
leds[(head + NUM_LEDS - 1) % NUM_LEDS] = CHSV(millis() / 20, 255, 100);
leds[(head + NUM_LEDS - 2) % NUM_LEDS] = CHSV(millis() / 20, 255, 50);
head = (head + 1) % NUM_LEDS;
FastLED.show();
lastUpdate = millis();
}
}
void run_effect_pulse()
{
static uint8_t hue = 0;
static long lastUpdate = 0;
const long updateDelay = 20;
if (millis() - lastUpdate > updateDelay)
{
hue++;
if (hue > 255)
{
hue = 0;
}
// Use a sine wave to create a pulsing brightness
uint8_t pulse_brightness = sin8(millis() / 4);
for (int i = 0; i < NUM_LEDS; i++)
{
leds[i] = CHSV(hue + i * 2, 255, pulse_brightness);
}
FastLED.show();
lastUpdate = millis();
}
}
void run_effect_color_waves()
{
static long lastUpdate = 0;
const long updateDelay = 50;
static uint8_t startIndex = 0;
if (millis() - lastUpdate > updateDelay)
{
startIndex++;
// Use fill_palette to paint a color wave across the strip
fill_palette(leds, NUM_LEDS, startIndex, 2, PartyColors_p, 255, LINEARBLEND);
FastLED.show();
lastUpdate = millis();
}
}
void run_effect_plasma()
{
static uint16_t time_ms = 0;
static long lastUpdate = 0;
const long updateDelay = 20;
if (millis() - lastUpdate > updateDelay)
{
time_ms += 1;
for (int i = 0; i < NUM_LEDS; i++)
{
int x = i * 255 / NUM_LEDS;
int y = 127;
int r = sin8(x + time_ms);
int g = sin8(y + time_ms);
int b = sin8(x + y + time_ms);
leds[i] = CRGB(r, g, b);
}
FastLED.show();
lastUpdate = millis();
}
}
void run_effect_sinelon()
{
static long lastUpdate = 0;
const long updateDelay = 20;
if (millis() - lastUpdate > updateDelay)
{
// Fade all LEDs slightly to create a trail
for (int i = 0; i < NUM_LEDS; i++)
{
leds[i].fadeToBlackBy(10);
}
// Calculate a position and color based on time
int pos1 = beatsin16(13, 0, NUM_LEDS - 1);
int pos2 = beatsin16(16, 0, NUM_LEDS - 1);
int pos3 = beatsin16(19, 0, NUM_LEDS - 1);
leds[pos1] = CHSV(millis() / 20, 255, 255);
leds[pos2] = CHSV(millis() / 20 + 85, 255, 255);
leds[pos3] = CHSV(millis() / 20 + 170, 255, 255);
FastLED.show();
lastUpdate = millis();
}
}
void run_effect_auto_cycle()
{
if (millis() - lastAutoCycleChange > autoCycleInterval)
{
set_random_effect();
lastAutoCycleChange = millis();
}
switch (autoCycleEffect)
{
case EFFECT_SOLID:
currentSolidColorIndex = random(sizeof(solidColors) / sizeof(solidColors[0]));
run_effect_solid();
break;
case EFFECT_RAINBOW:
run_effect_rainbow();
break;
case EFFECT_CYLON:
currentSolidColorIndex = random(sizeof(solidColors) / sizeof(solidColors[0]));
run_effect_cylon();
break;
case EFFECT_CHASE:
run_effect_chase();
break;
case EFFECT_PULSE:
run_effect_pulse();
break;
case EFFECT_COLOR_WAVES:
run_effect_color_waves();
break;
case EFFECT_PLASMA:
run_effect_plasma();
break;
case EFFECT_SINELON:
run_effect_sinelon();
break;
default:
run_effect_rainbow();
break;
}
}
void set_random_effect()
{
// Pick a random effect from the "fun" ones, excluding AUTO_CYCLE, OFF, and SOLID
autoCycleEffect = static_cast<Effect>(random(EFFECT_RAINBOW, EFFECT_AUTO_CYCLE));
Serial.print("Auto-cycling to a new effect: ");
Serial.println(autoCycleEffect);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment