aboutsummaryrefslogtreecommitdiff
path: root/spaghetti-monster
diff options
context:
space:
mode:
Diffstat (limited to 'spaghetti-monster')
-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
8 files changed, 547 insertions, 423 deletions
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
If you find this content useful please consider a small donation via bitcoin: bitcoin:1mxKyQsHHugqRxPKgwaA7wUwJEGCNthn2?amount=0.001&message=Donation Scraping git contents via cgit is very resource intensive. The continued hosting of these git repositories is entirely financed by your bitcoin contributions. For donations of a least 1 mBTC you include your IP address in the message of your bitcoin donation. This will remove this message in the future for requests from your IP, which will significantly reduce token usage. In any case, any contributions to bitcoin address 1mxKyQsHHugqRxPKgwaA7wUwJEGCNthn2 are very welcome. Thanks in advance for you contribution to a self-sustaining ecosystem.