diff options
| -rw-r--r-- | hwdef-Sofirn_LT1S-Pro.c | 234 | ||||
| -rw-r--r-- | hwdef-Sofirn_LT1S-Pro.h | 44 | ||||
| -rw-r--r-- | spaghetti-monster/anduril/cfg-sofirn-lt1s-pro.h | 16 | ||||
| -rw-r--r-- | spaghetti-monster/fsm-ramping.c | 33 | ||||
| -rw-r--r-- | spaghetti-monster/fsm-ramping.h | 41 |
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 |
