aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--hwdef-emisar-2ch.c46
-rw-r--r--hwdef-emisar-2ch.h53
-rw-r--r--spaghetti-monster/anduril/cfg-emisar-2ch.h3
-rw-r--r--spaghetti-monster/anduril/load-save-config-fsm.h2
-rw-r--r--spaghetti-monster/chan-rgbaux.h50
-rw-r--r--spaghetti-monster/fsm-channels.c347
-rw-r--r--spaghetti-monster/fsm-channels.h136
-rw-r--r--spaghetti-monster/fsm-ramping.c345
-rw-r--r--spaghetti-monster/fsm-ramping.h85
-rw-r--r--spaghetti-monster/spaghetti-monster.h2
10 files changed, 610 insertions, 459 deletions
diff --git a/hwdef-emisar-2ch.c b/hwdef-emisar-2ch.c
index 53117e0..427509f 100644
--- a/hwdef-emisar-2ch.c
+++ b/hwdef-emisar-2ch.c
@@ -3,6 +3,52 @@
// SPDX-License-Identifier: GPL-3.0-or-later
#pragma once
+#include "chan-rgbaux.c"
+
+
+void set_level_ch1(uint8_t level);
+void set_level_ch2(uint8_t level);
+void set_level_both(uint8_t level);
+void set_level_blend(uint8_t level);
+void set_level_auto(uint8_t level);
+
+bool gradual_tick_ch1(uint8_t gt);
+bool gradual_tick_ch2(uint8_t gt);
+bool gradual_tick_both(uint8_t gt);
+bool gradual_tick_blend(uint8_t gt);
+bool gradual_tick_auto(uint8_t gt);
+
+
+Channel channels[] = {
+ { // channel 1 only
+ .set_level = set_level_ch1,
+ .gradual_tick = gradual_tick_ch1,
+ .has_args = 0
+ },
+ { // channel 2 only
+ .set_level = set_level_ch2,
+ .gradual_tick = gradual_tick_ch2,
+ .has_args = 0
+ },
+ { // both channels, tied together (max "200%" power)
+ .set_level = set_level_both,
+ .gradual_tick = gradual_tick_both,
+ .has_args = 0
+ },
+ { // both channels, manual blend (max "100%" power)
+ .set_level = set_level_blend,
+ .gradual_tick = gradual_tick_blend,
+ .has_args = 1
+ },
+ { // both channels, auto blend
+ .set_level = set_level_auto,
+ .gradual_tick = gradual_tick_auto,
+ .has_args = 1
+ },
+ RGB_AUX_CHANNELS
+};
+
+
// set new values for both channels,
// handling any possible combination
// and any before/after state
diff --git a/hwdef-emisar-2ch.h b/hwdef-emisar-2ch.h
index 9d1b185..32cbc3b 100644
--- a/hwdef-emisar-2ch.h
+++ b/hwdef-emisar-2ch.h
@@ -33,37 +33,31 @@
#define HWDEF_C_FILE hwdef-emisar-2ch.c
-#define USE_CHANNEL_MODES
+// allow using aux LEDs as extra channel modes
+#include "chan-rgbaux.h"
+
// channel modes:
// * 0. channel 1 only
// * 1. channel 2 only
// * 2. both channels, tied together
// * 3. both channels, manual blend, max 200% power?
// * 4. both channels, auto blend, reversible
-#define NUM_CHANNEL_MODES 5
-#define CM_CH1 0
-#define CM_CH2 1
-#define CM_BOTH 2
-#define CM_BLEND 3
-#define CM_AUTO 4
-
-#define CHANNEL_MODES_ENABLED 0b00011111
-#define CHANNEL_HAS_ARGS 0b00011000
+#define NUM_CHANNEL_MODES (5 + NUM_RGB_AUX_CHANNEL_MODES)
+enum channel_modes_e {
+ CM_CH1 = 0,
+ CM_CH2,
+ CM_BOTH,
+ CM_BLEND,
+ CM_AUTO,
+ RGB_AUX_ENUMS
+};
+
+// right-most bit first, modes are in fedcba9876543210 order
+#define CHANNEL_MODES_ENABLED 0b0000000000011111
#define USE_CHANNEL_MODE_ARGS
// _, _, _, 128=middle CCT, 0=warm-to-cool
-#define CHANNEL_MODE_ARGS 0,0,0,128,0
-
-#define SET_LEVEL_MODES set_level_ch1, \
- set_level_ch2, \
- set_level_both, \
- set_level_blend, \
- set_level_auto
-// gradual ticking for thermal regulation
-#define GRADUAL_TICK_MODES gradual_tick_ch1, \
- gradual_tick_ch2, \
- gradual_tick_both, \
- gradual_tick_blend, \
- gradual_tick_auto
+#define CHANNEL_MODE_ARGS 0,0,0,128,0,RGB_AUX_CM_ARGS
+
// can use some of the common handlers
#define USE_CALC_2CH_BLEND
@@ -151,19 +145,6 @@
#define BUTTON_LED_PUE PUEA // for all "PA" pins
-void set_level_ch1(uint8_t level);
-void set_level_ch2(uint8_t level);
-void set_level_both(uint8_t level);
-void set_level_blend(uint8_t level);
-void set_level_auto(uint8_t level);
-
-bool gradual_tick_ch1(uint8_t gt);
-bool gradual_tick_ch2(uint8_t gt);
-bool gradual_tick_both(uint8_t gt);
-bool gradual_tick_blend(uint8_t gt);
-bool gradual_tick_auto(uint8_t gt);
-
-
inline void hwdef_setup() {
// enable output ports
//DDRC = (1 << CH3_PIN);
diff --git a/spaghetti-monster/anduril/cfg-emisar-2ch.h b/spaghetti-monster/anduril/cfg-emisar-2ch.h
index 97e46fc..d158b88 100644
--- a/spaghetti-monster/anduril/cfg-emisar-2ch.h
+++ b/spaghetti-monster/anduril/cfg-emisar-2ch.h
@@ -31,6 +31,9 @@
//#define CONFIG_WAITING_CHANNEL CM_CH2
//#define CONFIG_BLINK_CHANNEL CM_BOTH
+// blink numbers on the main LEDs by default (but allow user to change it)
+#define DEFAULT_BLINK_CHANNEL CM_BLEND
+
#define POLICE_COLOR_STROBE_CH1 CM_CH1
#define POLICE_COLOR_STROBE_CH2 CM_CH2
diff --git a/spaghetti-monster/anduril/load-save-config-fsm.h b/spaghetti-monster/anduril/load-save-config-fsm.h
index 64ff6fd..462b41c 100644
--- a/spaghetti-monster/anduril/load-save-config-fsm.h
+++ b/spaghetti-monster/anduril/load-save-config-fsm.h
@@ -48,7 +48,7 @@ typedef struct Config {
///// channel modes / color modes
#if NUM_CHANNEL_MODES > 1
uint8_t channel_mode;
- uint8_t channel_modes_enabled;
+ uint16_t channel_modes_enabled;
#ifdef USE_MANUAL_MEMORY
uint8_t manual_memory_channel_mode;
#endif
diff --git a/spaghetti-monster/chan-rgbaux.h b/spaghetti-monster/chan-rgbaux.h
index d19f6ad..ebb1bb9 100644
--- a/spaghetti-monster/chan-rgbaux.h
+++ b/spaghetti-monster/chan-rgbaux.h
@@ -3,6 +3,56 @@
// SPDX-License-Identifier: GPL-3.0-or-later
#pragma once
+#define RGB_AUX_ENUMS \
+ CM_AUXRED, \
+ CM_AUXYEL, \
+ CM_AUXGRN, \
+ CM_AUXCYN, \
+ CM_AUXBLU, \
+ CM_AUXPRP, \
+ CM_AUXWHT
+
+#define RGB_AUX_CM_ARGS 0,0,0,0,0,0,0
+
+#define NUM_RGB_AUX_CHANNEL_MODES 7
+
+#define RGB_AUX_CHANNELS \
+ { \
+ .set_level = set_level_auxred, \
+ .gradual_tick = gradual_tick_null, \
+ .has_args = 0 \
+ }, \
+ { \
+ .set_level = set_level_auxyel, \
+ .gradual_tick = gradual_tick_null, \
+ .has_args = 0 \
+ }, \
+ { \
+ .set_level = set_level_auxgrn, \
+ .gradual_tick = gradual_tick_null, \
+ .has_args = 0 \
+ }, \
+ { \
+ .set_level = set_level_auxcyn, \
+ .gradual_tick = gradual_tick_null, \
+ .has_args = 0 \
+ }, \
+ { \
+ .set_level = set_level_auxblu, \
+ .gradual_tick = gradual_tick_null, \
+ .has_args = 0 \
+ }, \
+ { \
+ .set_level = set_level_auxprp, \
+ .gradual_tick = gradual_tick_null, \
+ .has_args = 0 \
+ }, \
+ { \
+ .set_level = set_level_auxwht, \
+ .gradual_tick = gradual_tick_null, \
+ .has_args = 0 \
+ }
+
void set_level_auxred(uint8_t level);
void set_level_auxyel(uint8_t level);
void set_level_auxgrn(uint8_t level);
diff --git a/spaghetti-monster/fsm-channels.c b/spaghetti-monster/fsm-channels.c
new file mode 100644
index 0000000..3b30b58
--- /dev/null
+++ b/spaghetti-monster/fsm-channels.c
@@ -0,0 +1,347 @@
+// fsm-channels.c: Channel mode functions for SpaghettiMonster.
+// Copyright (C) 2023 Selene ToyKeeper
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#pragma once
+
+#include "fsm-ramping.h"
+
+
+void set_channel_mode(uint8_t mode) {
+ uint8_t cur_level = actual_level;
+ // turn off old LEDs before changing channel
+ set_level(0);
+
+ // change the channel
+ CH_MODE = mode;
+
+ // update the LEDs
+ set_level(cur_level);
+}
+
+
+#ifdef USE_CALC_2CH_BLEND
+// calculate a "tint ramp" blend between 2 channels
+// results are placed in *warm and *cool vars
+// brightness : total amount of light units to distribute
+// top : maximum allowed brightness per channel
+// blend : ratio between warm and cool (0 = warm, 128 = 50%, 255 = cool)
+void calc_2ch_blend(
+ PWM_DATATYPE *warm,
+ PWM_DATATYPE *cool,
+ PWM_DATATYPE brightness,
+ PWM_DATATYPE top,
+ uint8_t blend) {
+
+ #ifndef TINT_RAMPING_CORRECTION
+ #define TINT_RAMPING_CORRECTION 26 // 140% brightness at middle tint
+ #endif
+
+ // calculate actual PWM levels based on a single-channel ramp
+ // and a blend value
+ PWM_DATATYPE warm_PWM, cool_PWM;
+ PWM_DATATYPE2 base_PWM = brightness;
+
+ #if defined(TINT_RAMPING_CORRECTION) && (TINT_RAMPING_CORRECTION > 0)
+ uint8_t level = actual_level - 1;
+
+ // middle tints sag, so correct for that effect
+ // by adding extra power which peaks at the middle tint
+ // (correction is only necessary when PWM is fast)
+ if (level > HALFSPEED_LEVEL) {
+ base_PWM = brightness
+ + ((((PWM_DATATYPE2)brightness) * TINT_RAMPING_CORRECTION / 64)
+ * triangle_wave(blend) / 255);
+ }
+ // fade the triangle wave out when above 100% power,
+ // so it won't go over 200%
+ if (brightness > top) {
+ base_PWM -= 2 * (
+ ((brightness - top) * TINT_RAMPING_CORRECTION / 64)
+ * triangle_wave(blend) / 255
+ );
+ }
+ // guarantee no more than 200% power
+ if (base_PWM > (top << 1)) { base_PWM = top << 1; }
+ #endif
+
+ cool_PWM = (((PWM_DATATYPE2)blend * (PWM_DATATYPE2)base_PWM) + 127) / 255;
+ warm_PWM = base_PWM - cool_PWM;
+ // when running at > 100% power, spill extra over to other channel
+ if (cool_PWM > top) {
+ warm_PWM += (cool_PWM - top);
+ cool_PWM = top;
+ } else if (warm_PWM > top) {
+ cool_PWM += (warm_PWM - top);
+ warm_PWM = top;
+ }
+
+ *warm = warm_PWM;
+ *cool = cool_PWM;
+}
+#endif // ifdef USE_CALC_2CH_BLEND
+
+
+#ifdef USE_HSV2RGB
+RGB_t hsv2rgb(uint8_t h, uint8_t s, uint8_t v) {
+ RGB_t color;
+
+ uint16_t region, fpart, high, low, rising, falling;
+
+ if (s == 0) { // grey
+ color.r = color.g = color.b = v;
+ return color;
+ }
+
+ // make hue 0-5
+ region = ((uint16_t)h * 6) >> 8;
+ // find remainder part, make it from 0-255
+ fpart = ((uint16_t)h * 6) - (region << 8);
+
+ // calculate graph segments, doing integer multiplication
+ high = v;
+ low = (v * (255 - s)) >> 8;
+ falling = (v * (255 - ((s * fpart) >> 8))) >> 8;
+ rising = (v * (255 - ((s * (255 - fpart)) >> 8))) >> 8;
+
+ // default floor
+ color.r = low;
+ color.g = low;
+ color.b = low;
+
+ // assign graph shapes based on color cone region
+ switch (region) {
+ case 0:
+ color.r = high;
+ color.g = rising;
+ //color.b = low;
+ break;
+ case 1:
+ color.r = falling;
+ color.g = high;
+ //color.b = low;
+ break;
+ case 2:
+ //color.r = low;
+ color.g = high;
+ color.b = rising;
+ break;
+ case 3:
+ //color.r = low;
+ color.g = falling;
+ color.b = high;
+ break;
+ case 4:
+ color.r = rising;
+ //color.g = low;
+ color.b = high;
+ break;
+ default:
+ color.r = high;
+ //color.g = low;
+ color.b = falling;
+ break;
+ }
+
+ return color;
+}
+#endif // ifdef USE_HSV2RGB
+
+
+///// Common set_level_*() functions shared by multiple lights /////
+// (unique lights should use their own,
+// but these common versions cover most of the common hardware designs)
+
+// TODO: upgrade some older lights to dynamic PWM
+// TODO: 1ch w/ dynamic PWM
+// TODO: 1ch w/ dynamic PWM and opamp enable pins?
+// TODO: 2ch stacked w/ dynamic PWM
+// TODO: 2ch stacked w/ dynamic PWM and opamp enable pins?
+
+
+#ifdef USE_SET_LEVEL_1CH
+// single set of LEDs with 1 power channel
+void set_level_1ch(uint8_t level) {
+ if (level == 0) {
+ LOW_PWM_LVL = 0;
+ } else {
+ level --; // PWM array index = level - 1
+ LOW_PWM_LVL = PWM_GET(low_pwm_levels, level);
+ }
+}
+#endif
+
+
+#ifdef USE_SET_LEVEL_2CH_STACKED
+// single set of LEDs with 2 stacked power channels, DDFET+1 or DDFET+linear
+void set_level_2ch_stacked(uint8_t level) {
+ if (level == 0) {
+ LOW_PWM_LVL = 0;
+ HIGH_PWM_LVL = 0;
+ } else {
+ level --; // PWM array index = level - 1
+ LOW_PWM_LVL = PWM_GET(low_pwm_levels, level);
+ HIGH_PWM_LVL = PWM_GET(high_pwm_levels, level);
+ }
+}
+#endif
+
+
+#ifdef USE_SET_LEVEL_3CH_STACKED
+// single set of LEDs with 3 stacked power channels, like DDFET+N+1
+void set_level_3ch_stacked(uint8_t level) {
+ if (level == 0) {
+ LOW_PWM_LVL = 0;
+ MED_PWM_LVL = 0;
+ HIGH_PWM_LVL = 0;
+ } else {
+ level --; // PWM array index = level - 1
+ LOW_PWM_LVL = PWM_GET(low_pwm_levels, level);
+ MED_PWM_LVL = PWM_GET(med_pwm_levels, level);
+ HIGH_PWM_LVL = PWM_GET(high_pwm_levels, level);
+ }
+}
+#endif
+
+
+#if defined(USE_TINT_RAMPING) && (!defined(TINT_RAMP_TOGGLE_ONLY))
+void set_level_2ch_blend() {
+ #ifndef TINT_RAMPING_CORRECTION
+ #define TINT_RAMPING_CORRECTION 26 // 140% brightness at middle tint
+ #endif
+
+ // calculate actual PWM levels based on a single-channel ramp
+ // and a global tint value
+ //PWM_DATATYPE brightness = PWM_GET(pwm1_levels, level);
+ uint16_t brightness = PWM1_LVL;
+ uint16_t warm_PWM, cool_PWM;
+ #ifdef USE_STACKED_DYN_PWM
+ uint16_t top = PWM1_TOP;
+ //uint16_t top = PWM_GET(pwm_tops, actual_level-1);
+ #else
+ const uint16_t top = PWM_TOP;
+ #endif
+
+ // auto-tint modes
+ uint8_t mytint;
+ uint8_t level = actual_level - 1;
+ #if 1
+ // perceptual by ramp level
+ if (tint == 0) { mytint = 255 * (uint16_t)level / RAMP_SIZE; }
+ else if (tint == 255) { mytint = 255 - (255 * (uint16_t)level / RAMP_SIZE); }
+ #else
+ // linear with power level
+ //if (tint == 0) { mytint = brightness; }
+ //else if (tint == 255) { mytint = 255 - brightness; }
+ #endif
+ // stretch 1-254 to fit 0-255 range (hits every value except 98 and 198)
+ else { mytint = (tint * 100 / 99) - 1; }
+
+ PWM_DATATYPE2 base_PWM = brightness;
+ #if defined(TINT_RAMPING_CORRECTION) && (TINT_RAMPING_CORRECTION > 0)
+ // middle tints sag, so correct for that effect
+ // by adding extra power which peaks at the middle tint
+ // (correction is only necessary when PWM is fast)
+ if (level > HALFSPEED_LEVEL) {
+ base_PWM = brightness
+ + ((((PWM_DATATYPE2)brightness) * TINT_RAMPING_CORRECTION / 64) * triangle_wave(mytint) / 255);
+ }
+ // fade the triangle wave out when above 100% power,
+ // so it won't go over 200%
+ if (brightness > top) {
+ base_PWM -= 2 * (
+ ((brightness - top) * TINT_RAMPING_CORRECTION / 64)
+ * triangle_wave(mytint) / 255
+ );
+ }
+ // guarantee no more than 200% power
+ if (base_PWM > (top << 1)) { base_PWM = top << 1; }
+ #endif
+
+ cool_PWM = (((PWM_DATATYPE2)mytint * (PWM_DATATYPE2)base_PWM) + 127) / 255;
+ warm_PWM = base_PWM - cool_PWM;
+ // when running at > 100% power, spill extra over to other channel
+ if (cool_PWM > top) {
+ warm_PWM += (cool_PWM - top);
+ cool_PWM = top;
+ } else if (warm_PWM > top) {
+ cool_PWM += (warm_PWM - top);
+ warm_PWM = top;
+ }
+
+ TINT1_LVL = warm_PWM;
+ TINT2_LVL = cool_PWM;
+
+ // disable the power channel, if relevant
+ #ifdef LED_ENABLE_PIN
+ if (warm_PWM)
+ LED_ENABLE_PORT |= (1 << LED_ENABLE_PIN);
+ else
+ LED_ENABLE_PORT &= ~(1 << LED_ENABLE_PIN);
+ #endif
+ #ifdef LED2_ENABLE_PIN
+ if (cool_PWM)
+ LED2_ENABLE_PORT |= (1 << LED2_ENABLE_PIN);
+ else
+ LED2_ENABLE_PORT &= ~(1 << LED2_ENABLE_PIN);
+ #endif
+}
+#endif // ifdef USE_TINT_RAMPING
+
+
+#ifdef USE_GRADUAL_TICK_1CH
+void gradual_tick_1ch() {
+ GRADUAL_TICK_SETUP();
+
+ GRADUAL_ADJUST_1CH(low_pwm_levels, LOW_PWM_LVL);
+
+ // did we go far enough to hit the next defined ramp level?
+ // if so, update the main ramp level tracking var
+ if ((LOW_PWM_LVL == PWM_GET(low_pwm_levels, gt)))
+ {
+ GRADUAL_IS_ACTUAL();
+ }
+}
+#endif
+
+
+#ifdef USE_GRADUAL_TICK_2CH_STACKED
+void gradual_tick_2ch_stacked() {
+ GRADUAL_TICK_SETUP();
+
+ GRADUAL_ADJUST(low_pwm_levels, LOW_PWM_LVL, PWM_TOP);
+ GRADUAL_ADJUST_1CH(high_pwm_levels, HIGH_PWM_LVL);
+
+ // did we go far enough to hit the next defined ramp level?
+ // if so, update the main ramp level tracking var
+ if ( (LOW_PWM_LVL == PWM_GET(low_pwm_levels, gt))
+ && (HIGH_PWM_LVL == PWM_GET(high_pwm_levels, gt))
+ )
+ {
+ GRADUAL_IS_ACTUAL();
+ }
+}
+#endif
+
+
+#ifdef USE_GRADUAL_TICK_3CH_STACKED
+void gradual_tick_3ch_stacked() {
+ GRADUAL_TICK_SETUP();
+
+ GRADUAL_ADJUST(low_pwm_levels, LOW_PWM_LVL, PWM_TOP);
+ GRADUAL_ADJUST(med_pwm_levels, MED_PWM_LVL, PWM_TOP);
+ GRADUAL_ADJUST_1CH(high_pwm_levels, HIGH_PWM_LVL);
+
+ // did we go far enough to hit the next defined ramp level?
+ // if so, update the main ramp level tracking var
+ if ( (LOW_PWM_LVL == PWM_GET(low_pwm_levels, gt))
+ && (MED_PWM_LVL == PWM_GET(med_pwm_levels, gt))
+ && (HIGH_PWM_LVL == PWM_GET(high_pwm_levels, gt))
+ )
+ {
+ GRADUAL_IS_ACTUAL();
+ }
+}
+#endif
+
+
diff --git a/spaghetti-monster/fsm-channels.h b/spaghetti-monster/fsm-channels.h
new file mode 100644
index 0000000..b86e9ba
--- /dev/null
+++ b/spaghetti-monster/fsm-channels.h
@@ -0,0 +1,136 @@
+// fsm-channels.h: Channel mode functions for SpaghettiMonster.
+// Copyright (C) 2023 Selene ToyKeeper
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#pragma once
+
+// always enable channel modes, even if there is only one
+#define USE_CHANNEL_MODES
+
+// typedefs
+typedef void SetLevelFunc(uint8_t level);
+typedef SetLevelFunc * SetLevelFuncPtr;
+
+typedef bool GradualTickFunc(uint8_t gt);
+typedef GradualTickFunc * GradualTickFuncPtr;
+
+typedef struct Channel {
+ SetLevelFuncPtr set_level;
+ #ifdef USE_SET_LEVEL_GRADUALLY
+ GradualTickFuncPtr gradual_tick;
+ #endif
+ #ifdef USE_CHANNEL_MODE_ARGS
+ bool has_args;
+ //uint8_t arg; // is in the config struct, not here
+ #endif
+} Channel;
+
+Channel channels[]; // values are defined in the hwdef-*.c
+
+// TODO: size-optimize the case with only 1 channel mode?
+// (the arrays and stuff shouldn't be needed)
+
+#ifdef USE_CFG
+ #define CH_MODE cfg.channel_mode
+#else
+ // current multi-channel mode
+ uint8_t channel_mode = DEFAULT_CHANNEL_MODE;
+ #define CH_MODE channel_mode
+#endif
+
+// FIXME: remove this?
+#if NUM_CHANNEL_MODES > 1
+#define USE_CHANNEL_MODES
+#endif
+
+#ifdef USE_CUSTOM_CHANNEL_3H_MODES
+// different 3H behavior per channel?
+// TODO: move to progmem
+// TODO: move to Anduril, not FSM
+StatePtr channel_3H_modes[NUM_CHANNEL_MODES];
+#endif
+
+//#ifdef USE_CHANNEL_MODE_TOGGLES
+#if NUM_CHANNEL_MODES > 1
+// user can take unwanted modes out of the rotation
+// bitmask
+#ifdef USE_CFG
+ #define channel_mode_enabled(n) ((cfg.channel_modes_enabled >> n) & 1)
+ #define channel_mode_enable(n) cfg.channel_modes_enabled |= (1 << n)
+ #define channel_mode_disable(n) cfg.channel_modes_enabled &= ((1 << n) ^ 0xff)
+#else
+ uint16_t channel_modes_enabled = CHANNEL_MODES_ENABLED;
+ #define channel_mode_enabled(n) ((channel_modes_enabled >> n) & 1)
+ #define channel_mode_enable(n) channel_modes_enabled |= (1 << n)
+ #define channel_mode_disable(n) channel_modes_enabled &= ((1 << n) ^ 0xff)
+ #endif
+#endif
+
+#ifdef USE_CHANNEL_MODE_ARGS
+ #ifndef USE_CFG
+ // one byte of extra data per channel mode, like for tint value
+ uint8_t channel_mode_args[NUM_CHANNEL_MODES] = { CHANNEL_MODE_ARGS };
+ #endif
+ // which modes respond to their "arg", and which don't?
+ //const uint8_t channel_has_args = CHANNEL_HAS_ARGS;
+ //#define channel_has_args(n) ((CHANNEL_HAS_ARGS >> n) & 1)
+ // struct member
+ #define channel_has_args(n) (channels[n].has_args)
+#endif
+
+void set_channel_mode(uint8_t mode);
+
+#ifdef USE_CALC_2CH_BLEND
+void calc_2ch_blend(
+ PWM_DATATYPE *warm,
+ PWM_DATATYPE *cool,
+ PWM_DATATYPE brightness,
+ PWM_DATATYPE top,
+ uint8_t blend);
+#endif
+
+#ifdef USE_HSV2RGB
+typedef struct RGB_t {
+ uint8_t r;
+ uint8_t g;
+ uint8_t b;
+} RGB_t;
+RGB_t hsv2rgb(uint8_t h, uint8_t s, uint8_t v);
+#endif // ifdef USE_HSV2RGB
+
+
+#ifdef USE_SET_LEVEL_1CH
+// TODO: remove this
+void set_level_1ch(uint8_t level);
+#endif
+
+#ifdef USE_SET_LEVEL_2CH_STACKED
+// TODO: remove this
+void set_level_2ch_stacked(uint8_t level);
+#endif
+
+#ifdef USE_SET_LEVEL_3CH_STACKED
+// TODO: remove this
+void set_level_3ch_stacked(uint8_t level);
+#endif
+
+#if defined(USE_TINT_RAMPING) && (!defined(TINT_RAMP_TOGGLE_ONLY))
+// TODO: remove this
+void set_level_2ch_blend();
+#endif
+
+#ifdef USE_GRADUAL_TICK_1CH
+// TODO: remove this
+void gradual_tick_1ch();
+#endif
+
+#ifdef USE_GRADUAL_TICK_2CH_STACKED
+// TODO: remove this
+void gradual_tick_2ch_stacked();
+#endif
+
+#ifdef USE_GRADUAL_TICK_3CH_STACKED
+// TODO: remove this
+void gradual_tick_3ch_stacked();
+#endif
+
diff --git a/spaghetti-monster/fsm-ramping.c b/spaghetti-monster/fsm-ramping.c
index d4e2068..6419bfd 100644
--- a/spaghetti-monster/fsm-ramping.c
+++ b/spaghetti-monster/fsm-ramping.c
@@ -6,18 +6,6 @@
#ifdef USE_RAMPING
-void set_channel_mode(uint8_t mode) {
- uint8_t cur_level = actual_level;
- // turn off old LEDs before changing channel
- set_level(0);
-
- // change the channel
- CH_MODE = mode;
-
- // update the LEDs
- set_level(cur_level);
-}
-
#ifdef HAS_AUX_LEDS
inline void set_level_aux_leds(uint8_t level) {
#ifdef USE_INDICATOR_LED_WHILE_RAMPING
@@ -55,6 +43,9 @@ void set_level(uint8_t level) {
// (pulse the output high for a moment to wake up the power regulator)
// (only do this when starting from off and going to a low level)
// TODO: allow different jump start behavior per channel mode
+ // FIXME: don't jump-start during factory reset
+ // (it seems to cause some eeprom issues on KR4
+ // when doing a click with a loose tailcap)
if ((! actual_level)
&& level
&& (level < JUMP_START_LEVEL)) {
@@ -68,7 +59,7 @@ void set_level(uint8_t level) {
#endif
// call the relevant hardware-specific set_level_*()
- SetLevelFuncPtr set_level_func = channel_modes[CH_MODE];
+ SetLevelFuncPtr set_level_func = channels[CH_MODE].set_level;
set_level_func(level);
if (actual_level != level) prev_level = actual_level;
@@ -83,185 +74,6 @@ void set_level(uint8_t level) {
#endif
}
-///// Common set_level_*() functions shared by multiple lights /////
-// (unique lights should use their own,
-// but these common versions cover most of the common hardware designs)
-
-#ifdef USE_SET_LEVEL_1CH
-// single set of LEDs with 1 power channel
-void set_level_1ch(uint8_t level) {
- if (level == 0) {
- LOW_PWM_LVL = 0;
- } else {
- level --; // PWM array index = level - 1
- LOW_PWM_LVL = PWM_GET(low_pwm_levels, level);
- }
-}
-#endif
-
-#ifdef USE_SET_LEVEL_2CH_STACKED
-// single set of LEDs with 2 stacked power channels, DDFET+1 or DDFET+linear
-void set_level_2ch_stacked(uint8_t level) {
- if (level == 0) {
- LOW_PWM_LVL = 0;
- HIGH_PWM_LVL = 0;
- } else {
- level --; // PWM array index = level - 1
- LOW_PWM_LVL = PWM_GET(low_pwm_levels, level);
- HIGH_PWM_LVL = PWM_GET(high_pwm_levels, level);
- }
-}
-#endif
-
-#ifdef USE_SET_LEVEL_3CH_STACKED
-// single set of LEDs with 3 stacked power channels, like DDFET+N+1
-void set_level_3ch_stacked(uint8_t level) {
- if (level == 0) {
- LOW_PWM_LVL = 0;
- MED_PWM_LVL = 0;
- HIGH_PWM_LVL = 0;
- } else {
- level --; // PWM array index = level - 1
- LOW_PWM_LVL = PWM_GET(low_pwm_levels, level);
- MED_PWM_LVL = PWM_GET(med_pwm_levels, level);
- HIGH_PWM_LVL = PWM_GET(high_pwm_levels, level);
- }
-}
-#endif
-
-// TODO: upgrade some older lights to dynamic PWM
-// TODO: 1ch w/ dynamic PWM
-// TODO: 1ch w/ dynamic PWM and opamp enable pins?
-// TODO: 2ch stacked w/ dynamic PWM
-// TODO: 2ch stacked w/ dynamic PWM and opamp enable pins?
-
-#ifdef USE_CALC_2CH_BLEND
-// calculate a "tint ramp" blend between 2 channels
-// results are placed in *warm and *cool vars
-// brightness : total amount of light units to distribute
-// top : maximum allowed brightness per channel
-// blend : ratio between warm and cool (0 = warm, 128 = 50%, 255 = cool)
-void calc_2ch_blend(
- PWM_DATATYPE *warm,
- PWM_DATATYPE *cool,
- PWM_DATATYPE brightness,
- PWM_DATATYPE top,
- uint8_t blend) {
-
- #ifndef TINT_RAMPING_CORRECTION
- #define TINT_RAMPING_CORRECTION 26 // 140% brightness at middle tint
- #endif
-
- // calculate actual PWM levels based on a single-channel ramp
- // and a blend value
- PWM_DATATYPE warm_PWM, cool_PWM;
- PWM_DATATYPE2 base_PWM = brightness;
-
- #if defined(TINT_RAMPING_CORRECTION) && (TINT_RAMPING_CORRECTION > 0)
- uint8_t level = actual_level - 1;
-
- // middle tints sag, so correct for that effect
- // by adding extra power which peaks at the middle tint
- // (correction is only necessary when PWM is fast)
- if (level > HALFSPEED_LEVEL) {
- base_PWM = brightness
- + ((((PWM_DATATYPE2)brightness) * TINT_RAMPING_CORRECTION / 64)
- * triangle_wave(blend) / 255);
- }
- // fade the triangle wave out when above 100% power,
- // so it won't go over 200%
- if (brightness > top) {
- base_PWM -= 2 * (
- ((brightness - top) * TINT_RAMPING_CORRECTION / 64)
- * triangle_wave(blend) / 255
- );
- }
- // guarantee no more than 200% power
- if (base_PWM > (top << 1)) { base_PWM = top << 1; }
- #endif
-
- cool_PWM = (((PWM_DATATYPE2)blend * (PWM_DATATYPE2)base_PWM) + 127) / 255;
- warm_PWM = base_PWM - cool_PWM;
- // when running at > 100% power, spill extra over to other channel
- if (cool_PWM > top) {
- warm_PWM += (cool_PWM - top);
- cool_PWM = top;
- } else if (warm_PWM > top) {
- cool_PWM += (warm_PWM - top);
- warm_PWM = top;
- }
-
- *warm = warm_PWM;
- *cool = cool_PWM;
-}
-#endif // ifdef USE_CALC_2CH_BLEND
-
-#ifdef USE_HSV2RGB
-RGB_t hsv2rgb(uint8_t h, uint8_t s, uint8_t v) {
- RGB_t color;
-
- uint16_t region, fpart, high, low, rising, falling;
-
- if (s == 0) { // grey
- color.r = color.g = color.b = v;
- return color;
- }
-
- // make hue 0-5
- region = ((uint16_t)h * 6) >> 8;
- // find remainder part, make it from 0-255
- fpart = ((uint16_t)h * 6) - (region << 8);
-
- // calculate graph segments, doing integer multiplication
- high = v;
- low = (v * (255 - s)) >> 8;
- falling = (v * (255 - ((s * fpart) >> 8))) >> 8;
- rising = (v * (255 - ((s * (255 - fpart)) >> 8))) >> 8;
-
- // default floor
- color.r = low;
- color.g = low;
- color.b = low;
-
- // assign graph shapes based on color cone region
- switch (region) {
- case 0:
- color.r = high;
- color.g = rising;
- //color.b = low;
- break;
- case 1:
- color.r = falling;
- color.g = high;
- //color.b = low;
- break;
- case 2:
- //color.r = low;
- color.g = high;
- color.b = rising;
- break;
- case 3:
- //color.r = low;
- color.g = falling;
- color.b = high;
- break;
- case 4:
- color.r = rising;
- //color.g = low;
- color.b = high;
- break;
- default:
- color.r = high;
- //color.g = low;
- color.b = falling;
- break;
- }
-
- return color;
-}
-#endif // ifdef USE_HSV2RGB
-
-
#ifdef USE_LEGACY_SET_LEVEL
// (this is mostly just here for reference, temporarily)
// single set of LEDs with 1 to 3 stacked power channels,
@@ -405,7 +217,7 @@ void gradual_tick() {
gt --;
// call the relevant hardware-specific function
- GradualTickFuncPtr gradual_tick_func = gradual_tick_modes[CH_MODE];
+ GradualTickFuncPtr gradual_tick_func = channels[CH_MODE].gradual_tick;
bool done = gradual_tick_func(gt);
if (done) {
@@ -414,155 +226,8 @@ void gradual_tick() {
gradual_target = orig;
}
}
-
-
-#ifdef USE_GRADUAL_TICK_1CH
-void gradual_tick_1ch() {
- GRADUAL_TICK_SETUP();
-
- GRADUAL_ADJUST_1CH(low_pwm_levels, LOW_PWM_LVL);
-
- // did we go far enough to hit the next defined ramp level?
- // if so, update the main ramp level tracking var
- if ((LOW_PWM_LVL == PWM_GET(low_pwm_levels, gt)))
- {
- GRADUAL_IS_ACTUAL();
- }
-}
-#endif
-
-#ifdef USE_GRADUAL_TICK_2CH_STACKED
-void gradual_tick_2ch_stacked() {
- GRADUAL_TICK_SETUP();
-
- GRADUAL_ADJUST(low_pwm_levels, LOW_PWM_LVL, PWM_TOP);
- GRADUAL_ADJUST_1CH(high_pwm_levels, HIGH_PWM_LVL);
-
- // did we go far enough to hit the next defined ramp level?
- // if so, update the main ramp level tracking var
- if ( (LOW_PWM_LVL == PWM_GET(low_pwm_levels, gt))
- && (HIGH_PWM_LVL == PWM_GET(high_pwm_levels, gt))
- )
- {
- GRADUAL_IS_ACTUAL();
- }
-}
-#endif
-
-#ifdef USE_GRADUAL_TICK_3CH_STACKED
-void gradual_tick_3ch_stacked() {
- GRADUAL_TICK_SETUP();
-
- GRADUAL_ADJUST(low_pwm_levels, LOW_PWM_LVL, PWM_TOP);
- GRADUAL_ADJUST(med_pwm_levels, MED_PWM_LVL, PWM_TOP);
- GRADUAL_ADJUST_1CH(high_pwm_levels, HIGH_PWM_LVL);
-
- // did we go far enough to hit the next defined ramp level?
- // if so, update the main ramp level tracking var
- if ( (LOW_PWM_LVL == PWM_GET(low_pwm_levels, gt))
- && (MED_PWM_LVL == PWM_GET(med_pwm_levels, gt))
- && (HIGH_PWM_LVL == PWM_GET(high_pwm_levels, gt))
- )
- {
- GRADUAL_IS_ACTUAL();
- }
-}
-#endif
#endif // ifdef USE_SET_LEVEL_GRADUALLY
-#if defined(USE_TINT_RAMPING) && (!defined(TINT_RAMP_TOGGLE_ONLY))
-void set_level_2ch_blend() {
- #ifndef TINT_RAMPING_CORRECTION
- #define TINT_RAMPING_CORRECTION 26 // 140% brightness at middle tint
- #endif
-
- // calculate actual PWM levels based on a single-channel ramp
- // and a global tint value
- //PWM_DATATYPE brightness = PWM_GET(pwm1_levels, level);
- uint16_t brightness = PWM1_LVL;
- uint16_t warm_PWM, cool_PWM;
- #ifdef USE_STACKED_DYN_PWM
- uint16_t top = PWM1_TOP;
- //uint16_t top = PWM_GET(pwm_tops, actual_level-1);
- #else
- const uint16_t top = PWM_TOP;
- #endif
-
- // auto-tint modes
- uint8_t mytint;
- uint8_t level = actual_level - 1;
- #if 1
- // perceptual by ramp level
- if (tint == 0) { mytint = 255 * (uint16_t)level / RAMP_SIZE; }
- else if (tint == 255) { mytint = 255 - (255 * (uint16_t)level / RAMP_SIZE); }
- #else
- // linear with power level
- //if (tint == 0) { mytint = brightness; }
- //else if (tint == 255) { mytint = 255 - brightness; }
- #endif
- // stretch 1-254 to fit 0-255 range (hits every value except 98 and 198)
- else { mytint = (tint * 100 / 99) - 1; }
-
- PWM_DATATYPE2 base_PWM = brightness;
- #if defined(TINT_RAMPING_CORRECTION) && (TINT_RAMPING_CORRECTION > 0)
- // middle tints sag, so correct for that effect
- // by adding extra power which peaks at the middle tint
- // (correction is only necessary when PWM is fast)
- if (level > HALFSPEED_LEVEL) {
- base_PWM = brightness
- + ((((PWM_DATATYPE2)brightness) * TINT_RAMPING_CORRECTION / 64) * triangle_wave(mytint) / 255);
- }
- // fade the triangle wave out when above 100% power,
- // so it won't go over 200%
- if (brightness > top) {
- base_PWM -= 2 * (
- ((brightness - top) * TINT_RAMPING_CORRECTION / 64)
- * triangle_wave(mytint) / 255
- );
- }
- // guarantee no more than 200% power
- if (base_PWM > (top << 1)) { base_PWM = top << 1; }
- #endif
-
- cool_PWM = (((PWM_DATATYPE2)mytint * (PWM_DATATYPE2)base_PWM) + 127) / 255;
- warm_PWM = base_PWM - cool_PWM;
- // when running at > 100% power, spill extra over to other channel
- if (cool_PWM > top) {
- warm_PWM += (cool_PWM - top);
- cool_PWM = top;
- } else if (warm_PWM > top) {
- cool_PWM += (warm_PWM - top);
- warm_PWM = top;
- }
-
- TINT1_LVL = warm_PWM;
- TINT2_LVL = cool_PWM;
-
- // disable the power channel, if relevant
- #ifdef LED_ENABLE_PIN
- if (warm_PWM)
- LED_ENABLE_PORT |= (1 << LED_ENABLE_PIN);
- else
- LED_ENABLE_PORT &= ~(1 << LED_ENABLE_PIN);
- #endif
- #ifdef LED2_ENABLE_PIN
- if (cool_PWM)
- LED2_ENABLE_PORT |= (1 << LED2_ENABLE_PIN);
- else
- LED2_ENABLE_PORT &= ~(1 << LED2_ENABLE_PIN);
- #endif
-}
-#endif // ifdef USE_TINT_RAMPING
-
-
-// define the channel mode lists
-// TODO: move to progmem
-SetLevelFuncPtr channel_modes[NUM_CHANNEL_MODES] = { SET_LEVEL_MODES };
-#ifdef USE_SET_LEVEL_GRADUALLY
-GradualTickFuncPtr gradual_tick_modes[NUM_CHANNEL_MODES] = { GRADUAL_TICK_MODES };
-#endif
-
-
#endif // ifdef USE_RAMPING
diff --git a/spaghetti-monster/fsm-ramping.h b/spaghetti-monster/fsm-ramping.h
index 36cf89c..0c299c4 100644
--- a/spaghetti-monster/fsm-ramping.h
+++ b/spaghetti-monster/fsm-ramping.h
@@ -11,91 +11,9 @@ uint8_t actual_level = 0;
// the level used before actual
uint8_t prev_level = 0;
-// TODO: size-optimize the case with only 1 channel mode
-// (the arrays and stuff shouldn't be needed)
-
-#ifdef USE_CFG
- #define CH_MODE cfg.channel_mode
-#else
- // current multi-channel mode
- uint8_t channel_mode = DEFAULT_CHANNEL_MODE;
- #define CH_MODE channel_mode
-#endif
-
-#if NUM_CHANNEL_MODES > 1
-#define USE_CHANNEL_MODES
-#endif
-
-// one function per channel mode
-typedef void SetLevelFunc(uint8_t level);
-typedef SetLevelFunc * SetLevelFuncPtr;
-// TODO: move to progmem
-SetLevelFuncPtr channel_modes[NUM_CHANNEL_MODES];
-
-#ifdef USE_SET_LEVEL_GRADUALLY
-// the gradual tick mechanism may be different per channel
-typedef bool GradualTickFunc(uint8_t gt);
-typedef GradualTickFunc * GradualTickFuncPtr;
-// TODO: move to progmem
-GradualTickFuncPtr gradual_tick_modes[NUM_CHANNEL_MODES];
-#endif
-
-#ifdef USE_CUSTOM_CHANNEL_3H_MODES
-// different 3H behavior per channel?
-// TODO: move to progmem
-// TODO: move to Anduril, not FSM
-StatePtr channel_3H_modes[NUM_CHANNEL_MODES];
-#endif
-
-//#ifdef USE_CHANNEL_MODE_TOGGLES
-#if NUM_CHANNEL_MODES > 1
-// user can take unwanted modes out of the rotation
-// bitmask
-#ifdef USE_CFG
- #define channel_mode_enabled(n) ((cfg.channel_modes_enabled >> n) & 1)
- #define channel_mode_enable(n) cfg.channel_modes_enabled |= (1 << n)
- #define channel_mode_disable(n) cfg.channel_modes_enabled &= ((1 << n) ^ 0xff)
-#else
- uint8_t channel_modes_enabled = CHANNEL_MODES_ENABLED;
- #define channel_mode_enabled(n) ((channel_modes_enabled >> n) & 1)
- #define channel_mode_enable(n) channel_modes_enabled |= (1 << n)
- #define channel_mode_disable(n) channel_modes_enabled &= ((1 << n) ^ 0xff)
- #endif
-#endif
-
-#ifdef USE_CHANNEL_MODE_ARGS
- #ifndef USE_CFG
- // one byte of extra data per channel mode, like for tint value
- uint8_t channel_mode_args[NUM_CHANNEL_MODES] = { CHANNEL_MODE_ARGS };
- #endif
- // bitmask: which modes respond to their "arg", and which don't?
- //const uint8_t channel_has_args = CHANNEL_HAS_ARGS;
- #define channel_has_args(n) ((CHANNEL_HAS_ARGS >> n) & 1)
-#endif
-
-void set_channel_mode(uint8_t mode);
-
void set_level(uint8_t level);
//void set_level_smooth(uint8_t level);
-#ifdef USE_CALC_2CH_BLEND
-void calc_2ch_blend(
- PWM_DATATYPE *warm,
- PWM_DATATYPE *cool,
- PWM_DATATYPE brightness,
- PWM_DATATYPE top,
- uint8_t blend);
-#endif
-
-#ifdef USE_HSV2RGB
-typedef struct RGB_t {
- uint8_t r;
- uint8_t g;
- uint8_t b;
-} RGB_t;
-RGB_t hsv2rgb(uint8_t h, uint8_t s, uint8_t v);
-#endif // ifdef USE_HSV2RGB
-
#ifdef USE_SET_LEVEL_GRADUALLY
// adjust brightness very smoothly
uint8_t gradual_target;
@@ -140,6 +58,7 @@ void gradual_tick();
// auto-detect the data type for PWM tables
// FIXME: PWM bits and data type should be per PWM table
+// FIXME: this whole thing is a mess and should be removed
#ifndef PWM1_BITS
#define PWM1_BITS 8
#define PWM1_TOP 255
@@ -188,6 +107,7 @@ PROGMEM const PWM3_DATATYPE pwm3_levels[] = { PWM3_LEVELS };
#endif
// convenience defs for 1 LED with stacked channels
+// FIXME: remove this, use pwm1/2/3 instead
#ifdef LOW_PWM_LEVELS
PROGMEM const PWM_DATATYPE low_pwm_levels[] = { LOW_PWM_LEVELS };
#endif
@@ -200,6 +120,7 @@ PROGMEM const PWM_DATATYPE high_pwm_levels[] = { HIGH_PWM_LEVELS };
// 2 channel CCT blending ramp
#ifdef BLEND_PWM_LEVELS
+// FIXME: remove this, use pwm1/2/3 instead
PROGMEM const PWM_DATATYPE blend_pwm_levels[] = { BLEND_PWM_LEVELS };
#endif
diff --git a/spaghetti-monster/spaghetti-monster.h b/spaghetti-monster/spaghetti-monster.h
index a6916e3..77431f8 100644
--- a/spaghetti-monster/spaghetti-monster.h
+++ b/spaghetti-monster/spaghetti-monster.h
@@ -25,6 +25,7 @@
#include "fsm-wdt.h"
#include "fsm-pcint.h"
#include "fsm-standby.h"
+#include "fsm-channels.h"
#include "fsm-ramping.h"
#include "fsm-random.h"
#ifdef USE_EEPROM
@@ -63,6 +64,7 @@ void loop();
#include "fsm-wdt.c"
#include "fsm-pcint.c"
#include "fsm-standby.c"
+#include "fsm-channels.c"
#include "fsm-ramping.c"
#include "fsm-random.c"
#ifdef USE_EEPROM