commit e338a5fd0f3f5447be5a47f8635dec8af9aa32dd Author: Christoph Hagen Date: Mon Aug 29 11:17:03 2022 +0200 First version diff --git a/include/config.h b/include/config.h new file mode 100644 index 0000000..624e689 --- /dev/null +++ b/include/config.h @@ -0,0 +1,66 @@ +#pragma once +// MARK: Conversions + +#define MS_PER_MIN 60000 + +// MARK: Hardware + +#define NUM_LEDS 12 +#define LED_SPIN_MAX_COUNT (NUM_LEDS * 2) +#define LED_BATCH_COUNT 2 + +// MARK: Demo + +// Enable the demo mode +//#define ENABLE_DEMO + +// MARK: Logging + +// Enable logging to serial connection +// #define SERIAL_LOGGING + + +// Hardware pins + +#define LED_STRIP_PIN 5 +#define BUTTON_PIN 2 + +// Storage + +#define DEMO_MODE_BYTE 0 + +// Colors + +#define MAX_GREEN 90 +#define MAX_RED 5 + +// Brightness + +#define MAX_BRIGHTNESS 250 +#define PULSING_BRIGHTNESS 50 + +// MARK: Durations + +#define STAY_GREEN_DURATION_DEMO 20000 +#define FADE_TO_RED_DURATION_DEMO 30000 +#define PULSING_UNTIL_OFF_DURATION_DEMO 60000 + +#define STAY_GREEN_DURATION 30*MS_PER_MIN +#define FADE_TO_RED_DURATION 30*MS_PER_MIN +#define PULSING_UNTIL_OFF_DURATION 20*MS_PER_MIN + +#define SHORT_FADE_DURATION 1500 + +// Pulsing config + +#define PULSING_START_INTERVAL 3500 +#define PULSING_END_INTERVAL 500 +#define PULSE_VARIANCE (PULSING_START_INTERVAL-PULSING_END_INTERVAL) + +// Spin config + +#define SPIN_SATURATION 255 +#define SPIN_DEMO_ENABLED_HUE 0 +#define SPIN_DEMO_DISABLED_HUE 90 + +#define MAXIMUM_DELAY 250 diff --git a/include/definitions.h b/include/definitions.h new file mode 100644 index 0000000..b16c487 --- /dev/null +++ b/include/definitions.h @@ -0,0 +1,86 @@ +#pragma once +// MARK: State definition +#include +#include "config.h" + +enum class ProgramState { + fadeToGreen = 1, + stayGreen = 2, + fadeToRed = 3, + fadePulseLow = 4, + fadePulseHigh = 5, + fadeToSleeping = 6, + sleeping = 7, + toggleDemoMode = 8, +}; + +struct FadeComponent { + + // Timestamps for next fade step + uint32_t nextFade; + + // Intervals in ms for each fade step + uint32_t interval; + + // Fade direction for each component + uint8_t direction; + + // Indicator to alternate between LED batches + uint8_t skip; + + // Current color + uint8_t color; + + // Target color + uint8_t endColor; + + uint8_t needsStep(uint32_t time) { + if (isDone()) { + return 0; + } + return time >= nextFade; + } + + uint8_t isDone() { + return color == endColor; + } + + uint8_t nextColor() { + return color + direction; + } + + uint8_t advanceSkipAndColorIfNeeded() { + skip = (skip + 1) % LED_BATCH_COUNT; + nextFade += interval; + if (skip) { + // Only update color when all batches are done + // i.e. when skip is zero + return interval; + } + if (isDone()) { + return 255; + } + color += direction; + return interval; + } + + void setNewColor(uint8_t newColor, uint32_t time, uint32_t duration) { + endColor = newColor; + nextFade = time; + skip = 0; + if (isDone()) { + interval = 255; + direction = 0; + return; + } + uint8_t steps; + if (endColor > color) { + steps = endColor - color; + direction = 1; + } else { + steps = color - endColor; + direction = -1; + } + interval = (duration / steps) / LED_BATCH_COUNT; + } +}; diff --git a/include/log.h b/include/log.h new file mode 100644 index 0000000..f86d21f --- /dev/null +++ b/include/log.h @@ -0,0 +1,15 @@ +#ifdef SERIAL_LOGGING +#define INIT_LOGGING Serial.begin(115200) + +#define LOG_DEBUG(X) \ +Serial.print(X); + +#define LOG_DEBUG_LN(X) \ +Serial.println(X); + +#else + +#define INIT_LOGGING +#define LOG_DEBUG(X); +#define LOG_DEBUG_LN(X); +#endif diff --git a/platformio.ini b/platformio.ini new file mode 100644 index 0000000..e574508 --- /dev/null +++ b/platformio.ini @@ -0,0 +1,19 @@ +; PlatformIO Project Configuration File +; +; Build options: build flags, source filter +; Upload options: custom upload port, speed and extra flags +; Library options: dependencies, extra library storages +; Advanced options: extra scripting +; +; Please visit documentation for the other options and examples +; https://docs.platformio.org/page/projectconf.html + +[env:nanoatmega328] +platform = atmelavr +board = nanoatmega328 +framework = arduino +lib_deps = fastled/FastLED@^3.5.0 +platform_packages = + toolchain-atmelavr@>=1.70300.0 +monitor_speed = 115200 +upload_port = /dev/tty.usbserial-1470 diff --git a/src/main.cpp b/src/main.cpp new file mode 100644 index 0000000..146fdb6 --- /dev/null +++ b/src/main.cpp @@ -0,0 +1,360 @@ +#include +//#include +#include "FastLED.h" + +#include "definitions.h" +#include "config.h" + +#include "log.h" + +// MARK: State memory + +uint8_t spinLed; +uint8_t isInDemoMode; +uint32_t fadeToRedDuration; +uint32_t stayGreenDuration; +uint32_t pulsingUntilOffDuration; + +uint8_t numberOfPresses; +uint8_t buttonIsPressed; + +ProgramState state = ProgramState::fadeToGreen; // Current phase + +CRGB leds[NUM_LEDS]; + +uint32_t pulsingEnd = 0; // The time after which the pulsing should end +uint8_t pulsingLow = 0; // Alternate between pulse phases + +uint32_t stayGreenEnd = 0; // Timestamp to start fading to red + +// Fading state + +// Fade info for color components +FadeComponent rgb[3]; + +void startFadeToOff(); +void startSwitchDemoMode(); + +// MARK: Color fading + +void startFade(CRGB target, uint32_t duration) { + uint32_t time = millis(); + for (uint8_t i = 0; i < 3; i++) { + uint8_t end = target[i]; + rgb[i].setNewColor(end, time, duration); + } +} + +void showFadeEndColor() { + CRGB c = CRGB(rgb[0].endColor, rgb[1].endColor, rgb[2].endColor); + //rgb[0].endColor = c[0]; + //rgb[1].endColor = c[1]; + //rgb[2].endColor = c[2]; + fill_solid(leds, NUM_LEDS, c); + LOG_DEBUG_LN("Fade end"); + FastLED.show(); +} + +uint8_t continueFade() { + uint32_t t = millis(); + uint8_t next = MAXIMUM_DELAY; + + for (uint8_t i = 0; i < 3; i++) { + // Skip if color component is already at the right position, + // or next fade time is not reached + if (!rgb[i].needsStep(t)) { + continue; + } + + // Advance the current led batch in the correct direction + for (uint8_t k = rgb[i].skip; k < NUM_LEDS; k += LED_BATCH_COUNT) { + leds[k][i] = rgb[i].nextColor(); + } + + // Wait until next change + uint8_t componentDelay = rgb[i].advanceSkipAndColorIfNeeded(); + next = min(componentDelay, next); + } + + // Signal end of fade + if (rgb[0].isDone() && rgb[1].isDone() && rgb[2].isDone()) { + showFadeEndColor(); + return 0; + } + + FastLED.show(); + return next; +} + +// MARK: User input + +uint8_t puckIsLifted() { + return digitalRead(BUTTON_PIN); +} + +// MARK: Demo mode + +void updateFadeDurations() { + if (isInDemoMode) { + fadeToRedDuration = FADE_TO_RED_DURATION_DEMO; + stayGreenDuration = STAY_GREEN_DURATION_DEMO; + pulsingUntilOffDuration = PULSING_UNTIL_OFF_DURATION_DEMO; + } else { + fadeToRedDuration = FADE_TO_RED_DURATION; + stayGreenDuration = STAY_GREEN_DURATION; + pulsingUntilOffDuration = PULSING_UNTIL_OFF_DURATION; + } +} + +void loadFadeDurations() { + #ifdef ENABLE_DEMO + isInDemoMode = 1; + //EEPROM.read(DEMO_MODE_BYTE); + #else + isInDemoMode = 0; + #endif + updateFadeDurations(); +} + +// MARK: State transitions + +void startFadeToGreen() { + state = ProgramState::fadeToGreen; + startFade(CHSV(MAX_GREEN, 255, MAX_BRIGHTNESS), SHORT_FADE_DURATION); + LOG_DEBUG_LN("-> green"); +} + +void startStayingGreen() { + state = ProgramState::stayGreen; + + stayGreenEnd = millis() + stayGreenDuration; + LOG_DEBUG_LN("-> stay"); +} + +void startFadeToRed() { + state = ProgramState::fadeToRed; + startFade(CHSV(MAX_RED, 255, MAX_BRIGHTNESS), fadeToRedDuration); + LOG_DEBUG_LN("-> red"); +} + +void startPulseToLow() { + state = ProgramState::fadePulseLow; + + uint32_t t = millis(); + if (t > pulsingEnd) { + startFadeToOff(); + return; + } + + uint32_t d = (pulsingEnd - t) / (pulsingUntilOffDuration/PULSE_VARIANCE) + PULSING_END_INTERVAL; + + CHSV c = CHSV(MAX_RED, 255, PULSING_BRIGHTNESS); + startFade(c, d); + + LOG_DEBUG_LN("-> low"); +} + +void startPulseToHigh() { + state = ProgramState::fadePulseHigh; + + uint32_t t = millis(); + if (t > pulsingEnd) { + startFadeToOff(); + return; + } + + uint32_t d = (pulsingEnd - t) / (pulsingUntilOffDuration/PULSE_VARIANCE) + PULSING_END_INTERVAL; + + CHSV c = CHSV(MAX_RED, 255, MAX_BRIGHTNESS); + startFade(c, d); + + LOG_DEBUG_LN("-> high"); +} + +void startFadeToOff() { + state = ProgramState::fadeToSleeping; + startFade(CRGB(0,0,0), SHORT_FADE_DURATION); + LOG_DEBUG_LN("-> sleep") +} + +void startSleeping() { + state = ProgramState::sleeping; + LOG_DEBUG_LN("-> off"); +} + +void startSwitchDemoMode() { + state = ProgramState::toggleDemoMode; + + fill_solid(leds, NUM_LEDS, CRGB(0,0,0)); + + // if (isInDemoMode) { + // EEPROM.write(DEMO_MODE_BYTE, 0); + // isInDemoMode = 0; + // } else { + // EEPROM.write(DEMO_MODE_BYTE, 1); + // isInDemoMode = 1; + // } + updateFadeDurations(); + LOG_DEBUG_LN("-> demo"); +} + +// MARK: State actions + +uint8_t isFadingToGreenAfterPuckIsLifted() { + if (puckIsLifted()) { + startFadeToGreen(); + return 1; + } + return 0; +} + +void fadeToGreen() { + uint8_t wait = continueFade(); + if (wait) { + delay(wait); + } else { + // Fade to green completed, stay green + startStayingGreen(); + } +} + +void stayGreen() { + if (puckIsLifted()) { + startFadeToOff(); + return; + } + + if (millis() > stayGreenEnd) { + // Staying green completed, slowly fade to red + startFadeToRed(); + } else { + delay(MAXIMUM_DELAY); + } +} + +void fadeToRed() { + if (isFadingToGreenAfterPuckIsLifted()) { + return; + } + + uint8_t wait = continueFade(); + if (wait) { + delay(wait); + } else { + // Fade to red completed + pulsingEnd = millis() + pulsingUntilOffDuration; + startPulseToLow(); + } +} + +void fadePulseLow() { + if (isFadingToGreenAfterPuckIsLifted()) { + return; + } + + uint8_t wait = continueFade(); + if (wait) { + delay(wait); + } else { + // Pulse completed, start next + startPulseToHigh(); + } +} + +void fadePulseHigh() { + if (isFadingToGreenAfterPuckIsLifted()) { + return; + } + + uint8_t wait = continueFade(); + if (wait) { + delay(wait); + } else { + // Pulse completed, start next + startPulseToLow(); + } +} + +void fadeToOff() { + uint8_t wait = continueFade(); + if (wait) { + delay(wait); + } else { + startSleeping(); + } +} + +void sleeping() { + if (puckIsLifted() == 0) { + startFadeToGreen(); + return; + } + delay(MAXIMUM_DELAY); +} + +void toggleDemoMode() { + uint8_t hue = isInDemoMode ? SPIN_DEMO_ENABLED_HUE : SPIN_DEMO_DISABLED_HUE; + leds[(spinLed + 0) % NUM_LEDS] = CHSV(hue, SPIN_SATURATION, 50); + leds[(spinLed + 1) % NUM_LEDS] = CHSV(hue, SPIN_SATURATION, 125); + leds[(spinLed + 2) % NUM_LEDS] = CHSV(hue, SPIN_SATURATION, 250); + leds[(spinLed + 3) % NUM_LEDS] = CHSV(hue, SPIN_SATURATION, 250); + leds[(spinLed + 4) % NUM_LEDS] = CHSV(hue, SPIN_SATURATION, 125); + leds[(spinLed + 5) % NUM_LEDS] = CHSV(hue, SPIN_SATURATION, 50); + for (uint8_t i = 6; i < NUM_LEDS; i += 1) { + leds[(spinLed + i) % NUM_LEDS] = CHSV(hue, SPIN_SATURATION, 0); + } + FastLED.show(); + delay(50); + spinLed += 1; + if (spinLed == LED_SPIN_MAX_COUNT) { + spinLed = 0; + startFadeToGreen(); + } +} + +// MARK: Run loop + +void setup() { + FastLED.addLeds(leds, NUM_LEDS); + + // User buttons + pinMode(BUTTON_PIN, INPUT_PULLUP); + + INIT_LOGGING; + + LOG_DEBUG_LN("Started"); + + loadFadeDurations(); + + startSwitchDemoMode(); + //startFadeToGreen(); +} + +void loop() { + switch (state) { + case ProgramState::fadeToGreen: + fadeToGreen(); + break; + case ProgramState::stayGreen: + stayGreen(); + break; + case ProgramState::fadeToRed: + fadeToRed(); + break; + case ProgramState::fadePulseLow: + fadePulseLow(); + break; + case ProgramState::fadePulseHigh: + fadePulseHigh(); + break; + case ProgramState::fadeToSleeping: + fadeToOff(); + break; + case ProgramState::sleeping: + sleeping(); + break; + case ProgramState::toggleDemoMode: + toggleDemoMode(); + break; + } +}