aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--hwdef-Sofirn_LT1S-Pro.c234
-rw-r--r--hwdef-Sofirn_LT1S-Pro.h44
-rw-r--r--spaghetti-monster/anduril/cfg-sofirn-lt1s-pro.h16
-rw-r--r--spaghetti-monster/fsm-ramping.c33
-rw-r--r--spaghetti-monster/fsm-ramping.h41
5 files changed, 243 insertions, 125 deletions
diff --git a/hwdef-Sofirn_LT1S-Pro.c b/hwdef-Sofirn_LT1S-Pro.c
index 61d2157..a6d2b8f 100644
--- a/hwdef-Sofirn_LT1S-Pro.c
+++ b/hwdef-Sofirn_LT1S-Pro.c
@@ -5,68 +5,51 @@
#pragma once
-// single set of LEDs with 1 power channel and dynamic PWM
-void set_level_1ch_dyn(uint8_t level) {
- if (level == 0) {
- RED_PWM_LVL = 0;
- PWM_CNT = 0; // reset phase
- } else {
- level --; // PWM array index = level - 1
- RED_PWM_LVL = PWM_GET(pwm1_levels, level);
- // pulse frequency modulation, a.k.a. dynamic PWM
- PWM_TOP = PWM_GET(pwm_tops, level);
- // force reset phase when turning on from zero
- // (because otherwise the initial response is inconsistent)
- if (! actual_level) PWM_CNT = 0;
- }
-}
+// 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) {
-
-// warm + cool blend w/ middle sag correction and dynamic PWM
-void set_level_2ch_dyn_blend(uint8_t level) {
#ifndef TINT_RAMPING_CORRECTION
#define TINT_RAMPING_CORRECTION 26 // 140% brightness at middle tint
#endif
- if (level == 0) {
- WARM_PWM_LVL = 0;
- COOL_PWM_LVL = 0;
- PWM_CNT = 0; // reset phase
- return;
- }
-
- level --; // PWM array index = level - 1
- PWM_DATATYPE brightness = PWM_GET(pwm1_levels, level);
- uint16_t top = PWM_GET(pwm_tops, level);
-
// calculate actual PWM levels based on a single-channel ramp
- // and a global tint value
- uint16_t warm_PWM, cool_PWM;
- uint8_t mytint = channel_mode_args[channel_mode];
-
+ // 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(mytint) / 255);
+ * 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(mytint) / 255
+ * triangle_wave(blend) / 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;
+ 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) {
@@ -77,6 +60,76 @@ void set_level_2ch_dyn_blend(uint8_t level) {
warm_PWM = top;
}
+ *warm = warm_PWM;
+ *cool = cool_PWM;
+}
+
+
+// calculate a 3-channel "auto tint" blend
+// (like red -> warm white -> cool white)
+// results are placed in *a, *b, and *c vars
+// level : ramp level to convert into 3 channel levels
+// (assumes ramp table is "pwm1_levels")
+void calc_auto_3ch_blend(
+ PWM_DATATYPE *a,
+ PWM_DATATYPE *b,
+ PWM_DATATYPE *c,
+ uint8_t level) {
+
+ PWM_DATATYPE vpwm = PWM_GET(pwm1_levels, level);
+
+ // tint goes from 0 (red) to 127 (warm white) to 255 (cool white)
+ uint8_t mytint;
+ mytint = 255 * (uint16_t)level / RAMP_SIZE;
+
+ // red is high at 0, low at 255 (linear)
+ *a = (((PWM_DATATYPE2)(255 - mytint)
+ * (PWM_DATATYPE2)vpwm) + 127) / 255;
+ // warm white is low at 0 and 255, high at 127 (linear triangle)
+ *b = (((PWM_DATATYPE2)triangle_wave(mytint)
+ * (PWM_DATATYPE2)vpwm) + 127) / 255;
+ // cool white is low at 0, high at 255 (linear)
+ *c = (((PWM_DATATYPE2)mytint
+ * (PWM_DATATYPE2)vpwm) + 127) / 255;
+
+}
+
+
+// single set of LEDs with 1 power channel and dynamic PWM
+void set_level_red(uint8_t level) {
+ if (level == 0) {
+ RED_PWM_LVL = 0;
+ PWM_CNT = 0; // reset phase
+ } else {
+ level --; // PWM array index = level - 1
+ RED_PWM_LVL = PWM_GET(pwm1_levels, level);
+ // pulse frequency modulation, a.k.a. dynamic PWM
+ PWM_TOP = PWM_GET(pwm_tops, level);
+ // force reset phase when turning on from zero
+ // (because otherwise the initial response is inconsistent)
+ if (! actual_level) PWM_CNT = 0;
+ }
+}
+
+
+// warm + cool blend w/ dynamic PWM
+void set_level_white_blend(uint8_t level) {
+ if (level == 0) {
+ WARM_PWM_LVL = 0;
+ COOL_PWM_LVL = 0;
+ PWM_CNT = 0; // reset phase
+ return;
+ }
+
+ level --; // PWM array index = level - 1
+
+ PWM_DATATYPE warm_PWM, cool_PWM;
+ PWM_DATATYPE brightness = PWM_GET(pwm1_levels, level);
+ PWM_DATATYPE top = PWM_GET(pwm_tops, level);
+ uint8_t blend = channel_mode_args[channel_mode];
+
+ calc_2ch_blend(&warm_PWM, &cool_PWM, brightness, top, blend);
+
WARM_PWM_LVL = warm_PWM;
COOL_PWM_LVL = cool_PWM;
PWM_TOP = top;
@@ -85,7 +138,7 @@ void set_level_2ch_dyn_blend(uint8_t level) {
// "auto tint" channel mode with dynamic PWM
-void set_level_auto_3ch_dyn_blend(uint8_t level) {
+void set_level_auto_3ch_blend(uint8_t level) {
if (level == 0) {
WARM_PWM_LVL = 0;
COOL_PWM_LVL = 0;
@@ -95,25 +148,12 @@ void set_level_auto_3ch_dyn_blend(uint8_t level) {
}
level --; // PWM array index = level - 1
- PWM_DATATYPE vpwm = PWM_GET(pwm1_levels, level);
- // pulse frequency modulation, a.k.a. dynamic PWM
- uint16_t top = PWM_GET(pwm_tops, level);
-
- // tint goes from 0 (red) to 127 (warm white) to 255 (cool white)
- uint8_t mytint;
- mytint = 255 * (uint16_t)level / RAMP_SIZE;
PWM_DATATYPE a, b, c;
+ calc_auto_3ch_blend(&a, &b, &c, level);
- // red is high at 0, low at 255
- a = (((PWM_DATATYPE2)(255 - mytint)
- * (PWM_DATATYPE2)vpwm) + 127) / 255;
- // warm white is low at 0 and 255, high at 127
- b = (((PWM_DATATYPE2)triangle_wave(mytint)
- * (PWM_DATATYPE2)vpwm) + 127) / 255;
- // cool white is low at 0, high at 255
- c = (((PWM_DATATYPE2)mytint
- * (PWM_DATATYPE2)vpwm) + 127) / 255;
+ // pulse frequency modulation, a.k.a. dynamic PWM
+ uint16_t top = PWM_GET(pwm_tops, level);
RED_PWM_LVL = a;
WARM_PWM_LVL = b;
@@ -127,10 +167,9 @@ void set_level_auto_3ch_dyn_blend(uint8_t level) {
void set_level_red_white_blend(uint8_t level) {
// set the warm+cool white LEDs first
channel_mode = CM_WHITE;
- set_level_2ch_dyn_blend(level);
+ set_level_white_blend(level);
channel_mode = CM_WHITE_RED;
- // set the red LED as a ratio of the white output level
if (level == 0) {
RED_PWM_LVL = 0;
PWM_CNT = 0; // reset phase
@@ -140,14 +179,91 @@ void set_level_red_white_blend(uint8_t level) {
level --; // PWM array index = level - 1
PWM_DATATYPE vpwm = PWM_GET(pwm1_levels, level);
+ // set the red LED as a ratio of the white output level
// 0 = no red
// 255 = red at 100% of white channel PWM
uint8_t ratio = channel_mode_args[channel_mode];
- PWM_DATATYPE red_pwm;
- red_pwm = (((PWM_DATATYPE2)ratio * (PWM_DATATYPE2)vpwm) + 127) / 255;
-
- RED_PWM_LVL = red_pwm;
+ RED_PWM_LVL = (((PWM_DATATYPE2)ratio * (PWM_DATATYPE2)vpwm) + 127) / 255;
if (! actual_level) PWM_CNT = 0; // reset phase
}
+
+///// "gradual tick" functions for smooth thermal regulation /////
+
+void gradual_tick_red() {
+ GRADUAL_TICK_SETUP();
+
+ GRADUAL_ADJUST_1CH(pwm1_levels, RED_PWM_LVL);
+
+ if ((RED_PWM_LVL == PWM_GET(pwm1_levels, gt)))
+ {
+ GRADUAL_IS_ACTUAL();
+ }
+}
+
+
+void gradual_tick_white_blend() {
+ uint8_t gt = gradual_target;
+ if (gt < actual_level) gt = actual_level - 1;
+ else if (gt > actual_level) gt = actual_level + 1;
+ gt --;
+
+ // figure out what exact PWM levels we're aiming for
+ PWM_DATATYPE warm_PWM, cool_PWM;
+ PWM_DATATYPE brightness = PWM_GET(pwm1_levels, gt);
+ PWM_DATATYPE top = PWM_GET(pwm_tops, gt);
+ uint8_t blend = channel_mode_args[channel_mode];
+
+ calc_2ch_blend(&warm_PWM, &cool_PWM, brightness, top, blend);
+
+ // move up/down if necessary
+ GRADUAL_ADJUST_SIMPLE(warm_PWM, WARM_PWM_LVL);
+ GRADUAL_ADJUST_SIMPLE(cool_PWM, COOL_PWM_LVL);
+
+ // check for completion
+ if ( (WARM_PWM_LVL == warm_PWM)
+ && (COOL_PWM_LVL == cool_PWM)
+ )
+ {
+ GRADUAL_IS_ACTUAL();
+ }
+}
+
+
+void gradual_tick_auto_3ch_blend() {
+ uint8_t gt = gradual_target;
+ if (gt < actual_level) gt = actual_level - 1;
+ else if (gt > actual_level) gt = actual_level + 1;
+ gt --;
+
+ // figure out what exact PWM levels we're aiming for
+ PWM_DATATYPE red, warm, cool;
+ calc_auto_3ch_blend(&red, &warm, &cool, gt);
+
+ // move up/down if necessary
+ GRADUAL_ADJUST_SIMPLE(red, RED_PWM_LVL);
+ GRADUAL_ADJUST_SIMPLE(warm, WARM_PWM_LVL);
+ GRADUAL_ADJUST_SIMPLE(cool, COOL_PWM_LVL);
+
+ // check for completion
+ if ( (RED_PWM_LVL == red)
+ && (WARM_PWM_LVL == warm)
+ && (COOL_PWM_LVL == cool)
+ )
+ {
+ GRADUAL_IS_ACTUAL();
+ }
+}
+
+
+void gradual_tick_red_white_blend() {
+ // do the white blend thing...
+ channel_mode = CM_WHITE;
+ gradual_tick_white_blend();
+ channel_mode = CM_WHITE_RED;
+ // ... and then update red to the closest ramp level
+ // (coarse red adjustments aren't visible here anyway)
+ set_level_red(actual_level);
+}
+
diff --git a/hwdef-Sofirn_LT1S-Pro.h b/hwdef-Sofirn_LT1S-Pro.h
index c994d09..84623fd 100644
--- a/hwdef-Sofirn_LT1S-Pro.h
+++ b/hwdef-Sofirn_LT1S-Pro.h
@@ -38,33 +38,18 @@ Driver pinout:
// TODO: or maybe if args are defined, the USE_ should be auto-set?
// 128=middle CCT, N/A, N/A, 255=100% red
#define CHANNEL_MODE_ARGS 128,0,0,255
-#define SET_LEVEL_MODES set_level_2ch_dyn_blend, \
- set_level_auto_3ch_dyn_blend, \
- set_level_1ch_dyn, \
+#define SET_LEVEL_MODES set_level_white_blend, \
+ set_level_auto_3ch_blend, \
+ set_level_red, \
set_level_red_white_blend
-// TODO: gradual ticking for thermal regulation
-#define GRADUAL_TICK_MODES gradual_tick_2ch_blend, \
+// gradual ticking for thermal regulation
+#define GRADUAL_TICK_MODES gradual_tick_white_blend, \
gradual_tick_auto_3ch_blend, \
- gradual_tick_1ch, \
+ gradual_tick_red, \
gradual_tick_red_white_blend
-// can use some of the common handlers?
-//#define USE_SET_LEVEL_2CH_BLEND
-//#define USE_SET_LEVEL_AUTO_3CH_BLEND
-//#define USE_SET_LEVEL_1CH
-//#define USE_SET_LEVEL_RED_WHITE_BLEND
-// TODO:
-//#define USE_GRADUAL_TICK_2CH_BLEND
-//#define USE_GRADUAL_TICK_AUTO_3CH_BLEND
-//#define USE_GRADUAL_TICK_1CH
-//#define USE_GRADUAL_TICK_RED_WHITE_BLEND
-
-#define DEFAULT_CHANNEL_MODE CM_AUTO
-
-#define FACTORY_RESET_WARN_CHANNEL CM_RED
-#define FACTORY_RESET_SUCCESS_CHANNEL CM_WHITE
-
-#define POLICE_COLOR_STROBE_CH1 CM_RED
-#define POLICE_COLOR_STROBE_CH2 CM_WHITE
+// can use some of the common handlers
+//#define USE_CALC_2CH_BLEND
+//#define USE_CALC_AUTO_3CH_BLEND
// TODO: remove this as soon as it's not needed
#define PWM_CHANNELS 1
@@ -119,11 +104,16 @@ Driver pinout:
// custom channel modes
-void set_level_1ch_dyn(uint8_t level);
-void set_level_2ch_dyn_blend(uint8_t level);
-void set_level_auto_3ch_dyn_blend(uint8_t level);
+void set_level_red(uint8_t level);
+void set_level_white_blend(uint8_t level);
+void set_level_auto_3ch_blend(uint8_t level);
void set_level_red_white_blend(uint8_t level);
+void gradual_tick_red();
+void gradual_tick_white_blend();
+void gradual_tick_auto_3ch_blend();
+void gradual_tick_red_white_blend();
+
inline void hwdef_setup() {
diff --git a/spaghetti-monster/anduril/cfg-sofirn-lt1s-pro.h b/spaghetti-monster/anduril/cfg-sofirn-lt1s-pro.h
index fb412a6..fbcbf59 100644
--- a/spaghetti-monster/anduril/cfg-sofirn-lt1s-pro.h
+++ b/spaghetti-monster/anduril/cfg-sofirn-lt1s-pro.h
@@ -16,6 +16,16 @@
// (it seriously would be more practical to just use moon instead)
#define INDICATOR_LED_DEFAULT_MODE ((3<<2) + 1)
+// channel modes...
+// CM_WHITE, CM_AUTO, CM_RED, CM_WHITE_RED
+#define DEFAULT_CHANNEL_MODE CM_AUTO
+
+#define FACTORY_RESET_WARN_CHANNEL CM_RED
+#define FACTORY_RESET_SUCCESS_CHANNEL CM_WHITE
+
+#define POLICE_COLOR_STROBE_CH1 CM_RED
+#define POLICE_COLOR_STROBE_CH2 CM_WHITE
+
// how much to increase total brightness at middle tint
// (0 = 100% brightness, 64 = 200% brightness)
// seems unnecessary on this light
@@ -32,6 +42,7 @@
// shared table for white and red
#define PWM1_LEVELS PWM_LEVELS
#define MAX_1x7135 75
+#define MIN_THERM_STEPDOWN 75 // should be above highest dyn_pwm level
// FIXME: clock at 5 MHz w/ full+half+quarter speeds,
// instead of 10 MHz with w/ only half+quarter
// (10 MHz is just wasting power)
@@ -77,11 +88,6 @@
#undef TACTICAL_LEVELS
#define TACTICAL_LEVELS 120,30,(RAMP_SIZE+3) // high, low, police strobe
-// FIXME: thermal regulation should actually work fine on this light
-#ifdef USE_THERMAL_REGULATION
-#undef USE_THERMAL_REGULATION
-#endif
-
// don't blink while ramping
#ifdef BLINK_AT_RAMP_MIDDLE
#undef BLINK_AT_RAMP_MIDDLE
diff --git a/spaghetti-monster/fsm-ramping.c b/spaghetti-monster/fsm-ramping.c
index 5096dfd..a55c74b 100644
--- a/spaghetti-monster/fsm-ramping.c
+++ b/spaghetti-monster/fsm-ramping.c
@@ -344,39 +344,6 @@ void gradual_tick() {
}
-// reduce repetition with macros
-// common code at the beginning of every gradual tick handler
-#define GRADUAL_TICK_SETUP() \
- uint8_t gt = gradual_target; \
- if (gt < actual_level) gt = actual_level - 1; \
- else if (gt > actual_level) gt = actual_level + 1; \
- gt --; \
- PWM_DATATYPE target;
-
-// tick the top layer of the stack
-#define GRADUAL_ADJUST_1CH(TABLE,PWM) \
- target = PWM_GET(TABLE, gt); \
- if (PWM < target) PWM ++; \
- else if (PWM > target) PWM --;
-
-// tick a base level of the stack
-// (with support for special DD FET behavior
-// like "low=0, high=255" --> "low=255, high=254")
-#define GRADUAL_ADJUST(TABLE,PWM,TOP) \
- target = PWM_GET(TABLE, gt); \
- if ((gt < actual_level) \
- && (PWM == 0) \
- && (target == TOP)) PWM = TOP; \
- else \
- if (PWM < target) PWM ++; \
- else if (PWM > target) PWM --;
-
-// do this when output exactly matches a ramp level
-#define GRADUAL_IS_ACTUAL() \
- uint8_t orig = gradual_target; \
- set_level(gt + 1); \
- gradual_target = orig;
-
#ifdef USE_GRADUAL_TICK_1CH
void gradual_tick_1ch() {
GRADUAL_TICK_SETUP();
diff --git a/spaghetti-monster/fsm-ramping.h b/spaghetti-monster/fsm-ramping.h
index 028157f..8a12cc8 100644
--- a/spaghetti-monster/fsm-ramping.h
+++ b/spaghetti-monster/fsm-ramping.h
@@ -97,7 +97,46 @@ void set_level(uint8_t level);
uint8_t gradual_target;
inline void set_level_gradually(uint8_t lvl);
void gradual_tick();
-#endif
+
+// reduce repetition with macros
+// common code at the beginning of every gradual tick handler
+#define GRADUAL_TICK_SETUP() \
+ uint8_t gt = gradual_target; \
+ if (gt < actual_level) gt = actual_level - 1; \
+ else if (gt > actual_level) gt = actual_level + 1; \
+ gt --; \
+ PWM_DATATYPE target;
+
+// tick to a specific value
+#define GRADUAL_ADJUST_SIMPLE(TARGET,PWM) \
+ if (PWM < TARGET) PWM ++; \
+ else if (PWM > TARGET) PWM --;
+
+// tick the top layer of the stack
+#define GRADUAL_ADJUST_1CH(TABLE,PWM) \
+ target = PWM_GET(TABLE, gt); \
+ if (PWM < target) PWM ++; \
+ else if (PWM > target) PWM --;
+
+// tick a base level of the stack
+// (with support for special DD FET behavior
+// like "low=0, high=255" --> "low=255, high=254")
+#define GRADUAL_ADJUST(TABLE,PWM,TOP) \
+ target = PWM_GET(TABLE, gt); \
+ if ((gt < actual_level) \
+ && (PWM == 0) \
+ && (target == TOP)) PWM = TOP; \
+ else \
+ if (PWM < target) PWM ++; \
+ else if (PWM > target) PWM --;
+
+// do this when output exactly matches a ramp level
+#define GRADUAL_IS_ACTUAL() \
+ uint8_t orig = gradual_target; \
+ set_level(gt + 1); \
+ gradual_target = orig;
+
+#endif // ifdef USE_SET_LEVEL_GRADUALLY
// auto-detect the data type for PWM tables
// FIXME: PWM bits and data type should be per PWM table
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.