First version

This commit is contained in:
Christoph Hagen 2022-08-29 11:17:03 +02:00
commit e338a5fd0f
5 changed files with 546 additions and 0 deletions

66
include/config.h Normal file
View File

@ -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

86
include/definitions.h Normal file
View File

@ -0,0 +1,86 @@
#pragma once
// MARK: State definition
#include <Arduino.h>
#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;
}
};

15
include/log.h Normal file
View File

@ -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

19
platformio.ini Normal file
View File

@ -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

360
src/main.cpp Normal file
View File

@ -0,0 +1,360 @@
#include <Arduino.h>
//#include <EEPROM.h>
#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<WS2812B, LED_STRIP_PIN, GRB>(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;
}
}