From 899ffbdd51a88dcf1e4a6aabcc7feae3fd14e5a9 Mon Sep 17 00:00:00 2001 From: ThePendulum Date: Fri, 1 May 2026 04:37:58 +0200 Subject: [PATCH] Added rainbow animation. Moved indicator to edge, improved animation. --- QuadLightsV3.ino | 183 ++++++++++++++++++++++++++++++++++++----------- 1 file changed, 141 insertions(+), 42 deletions(-) diff --git a/QuadLightsV3.ino b/QuadLightsV3.ino index c0f2530..f7b20b4 100644 --- a/QuadLightsV3.ino +++ b/QuadLightsV3.ino @@ -12,13 +12,17 @@ constexpr int LEDS_TAIL_SIDE_LENGTH = 3; constexpr int LEDS_LEFT_PIN = PA5; constexpr int LEDS_RIGHT_PIN = PA7; -constexpr int LEDS_TOP_LENGTH = 11; -constexpr int LEDS_SIDE_LENGTH = 2; -constexpr int LEDS_BOTTOM_LENGTH = 10; +/* default configuration right lamp (left from the front) +RING EDGE +29 30 31 32 33 34 35 37 0 1 2 3 4 5 6 7 8 9 10 +28 27 26 25 24 23 22 21 20 19 18 17 16 15 14 13 12 11 +*/ +constexpr int LEDS_TOP_LENGTH = 12; +constexpr int LEDS_BOTTOM_LENGTH = 11; constexpr int LEDS_RING_LENGTH = 16; -constexpr int LEDS_EDGE_LENGTH = LEDS_TOP_LENGTH + LEDS_SIDE_LENGTH + LEDS_BOTTOM_LENGTH; -constexpr int LEDS_HEAD_LENGTH = LEDS_TOP_LENGTH + LEDS_SIDE_LENGTH + LEDS_BOTTOM_LENGTH + LEDS_RING_LENGTH; -constexpr int LEDS_HEAD_BRIGHTNESS = 100; +constexpr int LEDS_EDGE_LENGTH = LEDS_TOP_LENGTH + LEDS_BOTTOM_LENGTH; +constexpr int LEDS_HEAD_LENGTH = LEDS_TOP_LENGTH + LEDS_BOTTOM_LENGTH + LEDS_RING_LENGTH; +constexpr int LEDS_HEAD_BRIGHTNESS = 200; constexpr int LEDS_DASH_PIN = PB0; constexpr int LEDS_DASH_LENGTH = 10; @@ -79,7 +83,8 @@ enum Mode { MODE_COUNT, }; -Mode activeMode = MODE_HEAD; +// Mode activeMode = MODE_HEAD; +Mode activeMode = MODE_HAZARD; unsigned long animationFrame = 0; unsigned long lastFrame = 0; @@ -148,11 +153,23 @@ void animateTailLight() { ledsTail.fill(COLOR_RED); } +void animateDash() { + ledsDash.clear(); +} + void animateReverse() { ledsTail.fill(COLOR_WHITE); } -constexpr int INDICATOR_DURATION = 3000; // total ms +uint32_t colorHSV(float hue, float sat = 1.0, float val = 1.0) { + return Adafruit_NeoPixel::ColorHSV( + (uint16_t)(hue / 360.0 * 65535), + (uint8_t)(sat * 255), + (uint8_t)(val * 255) + ); +} + +constexpr int INDICATOR_DURATION = 3200; // total ms constexpr int INDICATOR_HOLD_FRAMES = 15; constexpr int INDICATOR_OFF_FRAMES = 20; constexpr float INDICATOR_SPEED = 2; @@ -162,44 +179,39 @@ void animateIndicator(Direction direction) { ? ledsHeadLeft : ledsHeadRight; - const int ringStart = LEDS_EDGE_LENGTH; - const int ringEnd = LEDS_EDGE_LENGTH + LEDS_RING_LENGTH; + int dashTargetIndex = direction == LEFT + ? 1 + : 0; - const long indicatorFrame = - lround(animationFrame * INDICATOR_SPEED) - % (LEDS_RING_LENGTH + INDICATOR_HOLD_FRAMES + INDICATOR_OFF_FRAMES); + const long indicatorFrame = lround(animationFrame * INDICATOR_SPEED) % (LEDS_EDGE_LENGTH + INDICATOR_HOLD_FRAMES + INDICATOR_OFF_FRAMES); - const long sweepEnd = LEDS_RING_LENGTH; - const long holdEnd = LEDS_RING_LENGTH + INDICATOR_HOLD_FRAMES; + const long sweepEnd = LEDS_TOP_LENGTH; + const long holdEnd = LEDS_TOP_LENGTH + INDICATOR_HOLD_FRAMES; - const long sweepIndex = direction == LEFT - ? ringStart + (indicatorFrame % LEDS_RING_LENGTH) - : ringEnd - (indicatorFrame % LEDS_RING_LENGTH); - - for (int ledIndex = ringStart; ledIndex < ringEnd; ledIndex++) { + for (int ledIndex = LEDS_TOP_LENGTH - 1; ledIndex >= 0; ledIndex--) { if (indicatorFrame < sweepEnd) { // SWEEP - const bool isLit = direction == LEFT - ? ledIndex < sweepIndex - : ledIndex >= sweepIndex; + bool isLit = ledIndex >= (LEDS_TOP_LENGTH - 1 - indicatorFrame); + ledsTarget.setPixelColor(ledIndex, isLit ? COLOR_AMBER : COLOR_OFF); - ledsDash.fill(COLOR_AMBER); + if (ledIndex > 0) { + ledsTarget.setPixelColor(LEDS_EDGE_LENGTH - ledIndex, isLit ? COLOR_AMBER : COLOR_OFF); + } } else if (indicatorFrame < holdEnd) { // HOLD ledsTarget.setPixelColor(ledIndex, COLOR_AMBER); + + if (ledIndex > 0) { + ledsTarget.setPixelColor(LEDS_EDGE_LENGTH - ledIndex, COLOR_AMBER); + } } else { // OFF - ledsTarget.setPixelColor(ledIndex, COLOR_OFF); + ledsTarget.fill(COLOR_OFF, 0, LEDS_EDGE_LENGTH); } } - - if (indicatorFrame < holdEnd) { - ledsDash.fill(COLOR_AMBER); - } else { - ledsDash.fill(COLOR_OFF); - } } + constexpr float POLICE_SPEED = 0.75; enum PoliceStates { POLICE_LEFT, POLICE_RIGHT, POLICE_OFF }; @@ -225,12 +237,21 @@ void animatePolice() { if (policeBeaconFrames[policeFrame] == POLICE_LEFT) { ledsHeadLeft.fill(COLOR_POLICE_BLUE, 0, LEDS_EDGE_LENGTH); ledsHeadRight.fill(COLOR_OFF, 0, LEDS_EDGE_LENGTH); + + ledsDash.setPixelColor(1, COLOR_POLICE_BLUE); + ledsDash.setPixelColor(0, COLOR_OFF); } else if (policeBeaconFrames[policeFrame] == POLICE_RIGHT) { ledsHeadLeft.fill(COLOR_OFF, 0, LEDS_EDGE_LENGTH); ledsHeadRight.fill(COLOR_POLICE_BLUE, 0, LEDS_EDGE_LENGTH); + + ledsDash.setPixelColor(1, COLOR_OFF); + ledsDash.setPixelColor(0, COLOR_POLICE_BLUE); } else { ledsHeadLeft.fill(COLOR_OFF, 0, LEDS_EDGE_LENGTH); ledsHeadRight.fill(COLOR_OFF, 0, LEDS_EDGE_LENGTH); + + ledsDash.setPixelColor(1, COLOR_OFF); + ledsDash.setPixelColor(0, COLOR_OFF); } if (policeFrame < 6) { @@ -242,10 +263,40 @@ void animatePolice() { } } +constexpr float RAINBOW_SPEED = 2.0; +constexpr float RAINBOW_DENSITY = 5.0; + +void animateRainbow() { + for (int ledIndex = 0; ledIndex < LEDS_HEAD_LENGTH; ledIndex++) { + const uint32_t pixelColor = colorHSV((animationFrame * RAINBOW_SPEED) + (ledIndex * RAINBOW_DENSITY)); + + ledsHeadLeft.setPixelColor(ledIndex, pixelColor); + ledsHeadRight.setPixelColor(ledIndex, pixelColor); + } + + /* + for (int ledIndex = 0; ledIndex < LEDS_TAIL_LENGTH; ledIndex++) { + const uint32_t pixelColor = ledsHeadLeft.ColorHSV((animationFrame * 1000 * RAINBOW_SPEED) + (ledIndex * 1000 * RAINBOW_DENSITY * 5), 255, 255); + + if (ledIndex <= 5) { + ledsTail.setPixelColor(ledIndex, pixelColor); + } else { + ledsTail.setPixelColor(LEDS_TAIL_LENGTH - ledIndex, pixelColor); + } + } + */ + + ledsTail.fill(ledsTail.ColorHSV(animationFrame * 1000 * RAINBOW_SPEED)); + + ledsDash.setPixelColor(1, colorHSV(animationFrame * RAINBOW_SPEED)); + ledsDash.setPixelColor(0, colorHSV(animationFrame * RAINBOW_SPEED + 90)); +} + void animate(long now) { // always animate headlights, let other modes override animateHeadlights(); animateTailLight(); + animateDash(); if (isReversing) { animateReverse(); @@ -255,6 +306,15 @@ void animate(long now) { animatePolice(); } + if (activeMode == MODE_HAZARD) { + animateIndicator(LEFT); + animateIndicator(RIGHT); + } + + if (activeMode == MODE_RAINBOW) { + animateRainbow(); + } + // we need the boolean to prevent the indicators from turning on right after boot if (indicatorLeftActive && now - indicatorLeftStart < INDICATOR_DURATION) { animateIndicator(LEFT); @@ -272,11 +332,43 @@ void render() { ledsDash.show(); } +bool isOff = false; + +void shutdown() { + // digitalWrite(PIN_BUCK, BUCK_OFF); + Serial.println("SHUTTING DOWN"); + + Serial.println("BUCK OFF"); + digitalWrite(PIN_BUCK, LOW); + + delay(100); + + Serial.println("SELF OFF, GOODBYE"); + digitalWrite(PIN_ALIVE, LOW); + + isOff = true; +} + +// only used when STLink or USB prevents full power cut +void restart() { + lastFrame = millis(); + animationFrame = 0; + isOff = false; + + digitalWrite(PIN_BUCK, BUCK_ON); +} + +unsigned long modePressStart = 0; +bool modePressed = false; + // the loop function runs over and over again forever void loop() { const unsigned long now = millis(); if (buttonMode.pressed()) { + modePressed = true; + modePressStart = now; + activeMode = (Mode)((activeMode + 1) % MODE_COUNT); animationFrame = 0; // ensures animations start cleanly instead of halfway @@ -284,6 +376,14 @@ void loop() { Serial.println(activeMode); } + if (buttonMode.released()) { + modePressed = false; + } + + if (modePressed && now - modePressStart > 5000) { + shutdown(); + } + if (buttonLeft.pressed()) { animationFrame = 0; @@ -312,6 +412,10 @@ void loop() { lastPedalPress = now; isReversing = true; + if (isOff) { + restart(); + } + Serial.println("PEDAL REVERSE"); } else { isReversing = false; @@ -320,10 +424,14 @@ void loop() { if (pedalForward.read() == Button::PRESSED) { lastPedalPress = now; + if (isOff) { + restart(); + } + Serial.println("PEDAL FORWARD"); } - if (now - lastFrame >= ANIMATION_INTERVAL) { + if (!isOff && now - lastFrame >= ANIMATION_INTERVAL) { lastFrame += ANIMATION_INTERVAL; animationFrame += 1; @@ -332,15 +440,6 @@ void loop() { } if (KEEPALIVE_ENABLE && now - lastPedalPress > KEEPALIVE_TIMEOUT) { - // digitalWrite(PIN_BUCK, BUCK_OFF); - Serial.println("SHUTTING DOWN"); - - Serial.println("BUCK OFF"); - digitalWrite(PIN_BUCK, LOW); - - delay(100); - - Serial.println("SELF OFF, GOODBYE"); - digitalWrite(PIN_ALIVE, LOW); + shutdown(); } } \ No newline at end of file