Created
August 23, 2025 20:49
-
-
Save jake-walker/03f4f63ad9d297d752321dc1afda23d1 to your computer and use it in GitHub Desktop.
WS2812b Bucket Hat Project
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #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