diff options
| author | Selene ToyKeeper | 2023-04-14 18:01:03 -0600 |
|---|---|---|
| committer | Selene ToyKeeper | 2023-04-14 18:01:03 -0600 |
| commit | f8e1150ba52fb3128d452e68ae2d8dda97a53ff1 (patch) | |
| tree | 9321c93ae344918c99327bd852dbc90968d8780e | |
| parent | refactor progress checkpoint ... got Sofirn LT1S Pro and Emisar D4v2 working (diff) | |
| download | anduril-f8e1150ba52fb3128d452e68ae2d8dda97a53ff1.tar.gz anduril-f8e1150ba52fb3128d452e68ae2d8dda97a53ff1.tar.bz2 anduril-f8e1150ba52fb3128d452e68ae2d8dda97a53ff1.zip | |
LT1S Pro: added dynamic PWM (much better low modes!)
| -rw-r--r-- | hwdef-Sofirn_LT1S-Pro.c | 114 | ||||
| -rw-r--r-- | hwdef-Sofirn_LT1S-Pro.h | 75 | ||||
| -rw-r--r-- | spaghetti-monster/anduril/cfg-sofirn-lt1s-pro.h | 39 | ||||
| -rw-r--r-- | spaghetti-monster/anduril/channel-modes.h | 1 | ||||
| -rw-r--r-- | spaghetti-monster/fsm-ramping.h | 2 |
5 files changed, 168 insertions, 63 deletions
diff --git a/hwdef-Sofirn_LT1S-Pro.c b/hwdef-Sofirn_LT1S-Pro.c index 3c31c96..61d2157 100644 --- a/hwdef-Sofirn_LT1S-Pro.c +++ b/hwdef-Sofirn_LT1S-Pro.c @@ -5,22 +5,105 @@ #pragma once -// "auto tint" channel mode -void set_level_auto_3ch_blend(uint8_t level) { - BLEND_PWM_DATATYPE vpwm; - +// single set of LEDs with 1 power channel and dynamic PWM +void set_level_1ch_dyn(uint8_t level) { if (level == 0) { - vpwm = 0; + RED_PWM_LVL = 0; + PWM_CNT = 0; // reset phase } else { level --; // PWM array index = level - 1 - vpwm = PWM_GET(blend_pwm_levels, level); + 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/ 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]; + + 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; + } + + WARM_PWM_LVL = warm_PWM; + COOL_PWM_LVL = cool_PWM; + PWM_TOP = top; + if (! actual_level) PWM_CNT = 0; // reset phase +} + + +// "auto tint" channel mode with dynamic PWM +void set_level_auto_3ch_dyn_blend(uint8_t level) { + if (level == 0) { + WARM_PWM_LVL = 0; + COOL_PWM_LVL = 0; + RED_PWM_LVL = 0; + PWM_CNT = 0; // reset phase + return; } + 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; - BLEND_PWM_DATATYPE a, b, c; + PWM_DATATYPE a, b, c; // red is high at 0, low at 255 a = (((PWM_DATATYPE2)(255 - mytint) @@ -35,6 +118,8 @@ void set_level_auto_3ch_blend(uint8_t level) { RED_PWM_LVL = a; WARM_PWM_LVL = b; COOL_PWM_LVL = c; + PWM_TOP = top; + if (! actual_level) PWM_CNT = 0; } @@ -42,19 +127,19 @@ void set_level_auto_3ch_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_blend(level); + set_level_2ch_dyn_blend(level); channel_mode = CM_WHITE_RED; - BLEND_PWM_DATATYPE vpwm; - // set the red LED as a ratio of the white output level if (level == 0) { - vpwm = 0; - } else { - level --; // PWM array index = level - 1 - vpwm = PWM_GET(blend_pwm_levels, level); + RED_PWM_LVL = 0; + PWM_CNT = 0; // reset phase + return; } + level --; // PWM array index = level - 1 + PWM_DATATYPE vpwm = PWM_GET(pwm1_levels, level); + // 0 = no red // 255 = red at 100% of white channel PWM uint8_t ratio = channel_mode_args[channel_mode]; @@ -63,5 +148,6 @@ void set_level_red_white_blend(uint8_t level) { red_pwm = (((PWM_DATATYPE2)ratio * (PWM_DATATYPE2)vpwm) + 127) / 255; RED_PWM_LVL = red_pwm; + if (! actual_level) PWM_CNT = 0; // reset phase } diff --git a/hwdef-Sofirn_LT1S-Pro.h b/hwdef-Sofirn_LT1S-Pro.h index c52364f..c994d09 100644 --- a/hwdef-Sofirn_LT1S-Pro.h +++ b/hwdef-Sofirn_LT1S-Pro.h @@ -15,9 +15,6 @@ Driver pinout: #define HWDEF_C_FILE hwdef-Sofirn_LT1S-Pro.c -#ifdef ATTINY -#undef ATTINY -#endif #define ATTINY 1616 #include <avr/io.h> @@ -39,20 +36,21 @@ Driver pinout: // TODO: blend mode should enable this automatically? #define USE_CHANNEL_MODE_ARGS // 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_blend, \ - set_level_auto_3ch_blend, \ - set_level_1ch, \ +#define SET_LEVEL_MODES set_level_2ch_dyn_blend, \ + set_level_auto_3ch_dyn_blend, \ + set_level_1ch_dyn, \ set_level_red_white_blend // TODO: gradual ticking for thermal regulation #define GRADUAL_TICK_MODES gradual_tick_2ch_blend, \ gradual_tick_auto_3ch_blend, \ gradual_tick_1ch, \ gradual_tick_red_white_blend -// can use some of the common handlers -#define USE_SET_LEVEL_2CH_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_1CH //#define USE_SET_LEVEL_RED_WHITE_BLEND // TODO: //#define USE_GRADUAL_TICK_2CH_BLEND @@ -78,27 +76,30 @@ Driver pinout: #define SWITCH_INTFLG VPORTA.INTFLAGS +// dynamic PWM +// PWM parameters of all channels are tied together because they share a counter +#define PWM_TOP_INIT 511 // highest value used in the top half of the ramp +#define PWM_TOP TCA0.SINGLE.PERBUF // holds the TOP value for for variable-resolution PWM +#define PWM_CNT TCA0.SINGLE.CNT // for resetting phase after each TOP adjustment + // warm tint channel -#define WARM_PWM_PIN PB0 -#define WARM_PWM_LVL TCA0.SINGLE.CMP0 // CMP1 is the output compare register for PB0 +//#define WARM_PWM_PIN PB0 +#define WARM_PWM_LVL TCA0.SINGLE.CMP0BUF // CMP1 is the output compare register for PB0 // cold tint channel -#define COOL_PWM_PIN PB1 -#define COOL_PWM_LVL TCA0.SINGLE.CMP1 // CMP0 is the output compare register for PB1 +//#define COOL_PWM_PIN PB1 +#define COOL_PWM_LVL TCA0.SINGLE.CMP1BUF // CMP0 is the output compare register for PB1 // red channel -#define RED_PWM_PIN PB0 // -#define RED_PWM_LVL TCA0.SINGLE.CMP2 // CMP2 is the output compare register for PB2 - -// translate cfg names to FSM names -#define LOW_PWM_LEVELS RED_PWM_LEVELS -#define LOW_PWM_LVL RED_PWM_LVL -#define LOW_PWM_PIN RED_PWM_PIN +//#define RED_PWM_PIN PB2 +#define RED_PWM_LVL TCA0.SINGLE.CMP2BUF // CMP2 is the output compare register for PB2 -// only using 8-bit on this light -#define PWM_GET PWM_GET8 -#define PWM_DATATYPE uint8_t -#define BLEND_PWM_DATATYPE uint8_t +// only using 16-bit PWM on this light +#define PWM_BITS 16 +#define PWM_GET PWM_GET16 +#define PWM_DATATYPE uint16_t +#define PWM1_DATATYPE uint16_t +#define PWM_DATATYPE2 uint32_t // average drop across diode on this hardware @@ -118,7 +119,9 @@ Driver pinout: // custom channel modes -void set_level_auto_3ch_blend(uint8_t level); +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_white_blend(uint8_t level); @@ -128,7 +131,11 @@ inline void hwdef_setup() { _PROTECTED_WRITE( CLKCTRL.MCLKCTRLB, CLKCTRL_PDIV_2X_gc | CLKCTRL_PEN_bm ); //VPORTA.DIR = ...; - VPORTB.DIR = PIN0_bm | PIN1_bm | PIN2_bm | PIN5_bm; // Outputs: Aux LED and PWMs + // Outputs: + VPORTB.DIR = PIN0_bm // warm white + | PIN1_bm // cool white + | PIN2_bm // red + | PIN5_bm; // aux LED //VPORTC.DIR = ...; // enable pullups on the unused pins to reduce power @@ -154,10 +161,20 @@ inline void hwdef_setup() { PORTC.PIN3CTRL = PORT_PULLUPEN_bm; // set up the PWM + // https://ww1.microchip.com/downloads/en/DeviceDoc/ATtiny1614-16-17-DataSheet-DS40002204A.pdf + // PB0 is TCA0:WO0, use TCA_SINGLE_CMP0EN_bm + // PB1 is TCA0:WO1, use TCA_SINGLE_CMP1EN_bm + // PB2 is TCA0:WO2, use TCA_SINGLE_CMP2EN_bm + // For Fast (Single Slope) PWM use TCA_SINGLE_WGMODE_SINGLESLOPE_gc + // For Phase Correct (Dual Slope) PWM use TCA_SINGLE_WGMODE_DSBOTTOM_gc // TODO: add references to MCU documentation - TCA0.SINGLE.CTRLB = TCA_SINGLE_CMP0EN_bm | TCA_SINGLE_CMP1EN_bm | TCA_SINGLE_CMP2EN_bm | TCA_SINGLE_WGMODE_DSBOTTOM_gc; - TCA0.SINGLE.PER = 255; - TCA0.SINGLE.CTRLA = TCA_SINGLE_CLKSEL_DIV1_gc | TCA_SINGLE_ENABLE_bm; + TCA0.SINGLE.CTRLB = TCA_SINGLE_CMP0EN_bm + | TCA_SINGLE_CMP1EN_bm + | TCA_SINGLE_CMP2EN_bm + | TCA_SINGLE_WGMODE_DSBOTTOM_gc; + PWM_TOP = PWM_TOP_INIT; + TCA0.SINGLE.CTRLA = TCA_SINGLE_CLKSEL_DIV1_gc + | TCA_SINGLE_ENABLE_bm; } diff --git a/spaghetti-monster/anduril/cfg-sofirn-lt1s-pro.h b/spaghetti-monster/anduril/cfg-sofirn-lt1s-pro.h index e009e02..c9e786d 100644 --- a/spaghetti-monster/anduril/cfg-sofirn-lt1s-pro.h +++ b/spaghetti-monster/anduril/cfg-sofirn-lt1s-pro.h @@ -4,31 +4,32 @@ #include "hwdef-Sofirn_LT1S-Pro.h" // ATTINY: 1616 -// off mode: high (1) +// off mode: high (2) // lockout: blinking (3) -#define INDICATOR_LED_DEFAULT_MODE ((3<<2) + 1) +#define INDICATOR_LED_DEFAULT_MODE ((3<<2) + 2) -// the lantern has two PWM channels, but they drive different sets of emitters -// (one channel for warm emitters, one channel for cold) -// so enable a special ramping mode which changes tint instead of brightness -//#define USE_TINT_RAMPING // how much to increase total brightness at middle tint // (0 = 100% brightness, 64 = 200% brightness) -#define TINT_RAMPING_CORRECTION 10 // 115% +// seems unnecessary on this light +#define TINT_RAMPING_CORRECTION 0 -// level_calc.py 1 150 7135 1 30 800 #define RAMP_SIZE 150 -// TODO: use dynamic PWM instead of plain 8-bit +// TODO? 200% power at top of ramp on white blend mode +// use dynamic PWM instead of plain 8-bit // (so we can get lower lows and a smoother ramp) -// TODO: 200% power at top of ramp on white blend mode -#define PWM_LEVELS 1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,8,9,9,10,11,11,12,13,13,14,15,15,16,17,18,18,19,20,21,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,43,44,45,46,48,49,50,51,53,54,56,57,58,60,61,63,64,66,67,69,70,72,74,75,77,79,80,82,84,85,87,89,91,93,95,97,98,100,102,104,106,108,111,113,115,117,119,121,124,126,128,130,133,135,137,140,142,145,147,150,152,155,157,160,163,165,168,171,173,176,179,182,185,188,190,193,196,199,202,205,209,212,215,218,221,224,228,231,234,238,241,245,248,251,255 -#define BLEND_PWM_LEVELS PWM_LEVELS -#define RED_PWM_LEVELS PWM_LEVELS -#define MAX_1x7135 65 +// level_calc.py 5.99 1 150 7135 1 0.2 600 --pwm dyn:74:16383:511 +// (with a few manual tweaks at the mid-point) +#define PWM_LEVELS 1,1,2,3,3,4,5,6,7,8,9,10,11,13,14,16,17,19,20,22,24,26,28,30,32,34,36,38,41,43,46,48,51,54,56,59,62,65,67,70,73,76,79,82,84,87,90,92,95,97,99,101,103,105,106,107,108,108,109,108,108,107,105,103,101,97,93,89,83,77,70,62,53,43,36,37,38,39,40,42,44,46,48,50,52,54,56,58,60,63,65,68,71,74,77,80,83,87,90,94,98,101,105,109,114,118,123,127,132,137,142,147,153,158,164,170,176,183,189,196,203,210,217,224,232,240,248,257,265,274,283,293,302,312,322,333,343,354,366,377,389,401,414,427,440,453,467,481,496,511 +#define PWM_TOPS 16383,11015,13411,15497,11274,12834,13512,13749,13735,13565,13292,12948,12555,13284,12747,13087,12495,12621,12010,12007,11928,11790,11609,11395,11155,10895,10622,10338,10307,9996,9905,9581,9452,9302,8973,8806,8627,8440,8124,7935,7743,7549,7354,7159,6883,6696,6511,6260,6084,5851,5628,5414,5210,5014,4782,4562,4355,4120,3937,3694,3501,3288,3060,2848,2651,2418,2202,2003,1775,1566,1353,1140,926,713,511,511,511,511,511,511,511,511,511,511,511,511,511,511,511,511,511,511,511,511,511,511,511,511,511,511,511,511,511,511,511,511,511,511,511,511,511,511,511,511,511,511,511,511,511,511,511,511,511,511,511,511,511,511,511,511,511,511,511,511,511,511,511,511,511,511,511,511,511,511,511,511,511,511,511,511 +// shared table for white and red +#define PWM1_LEVELS PWM_LEVELS +#define MAX_1x7135 75 // 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) -#define HALFSPEED_LEVEL 256 // red LEDs use a QX7138 chip which has max PWM speed of 10 kHz, so never run faster than halfspeed +// red LEDs use a QX7138 chip which has max PWM speed of 10 kHz, +// so PWM is 512 clock cycles long to avoid running faster than that +#define HALFSPEED_LEVEL 12 #define QUARTERSPEED_LEVEL 5 // the default of 26 looks a bit flat, so increase it @@ -41,16 +42,18 @@ // because this lantern isn't overpowered #define RAMP_SMOOTH_FLOOR 1 #define RAMP_SMOOTH_CEIL 150 -#define RAMP_DISCRETE_FLOOR 10 +//#define RAMP_DISCRETE_FLOOR 17 // 17 50 83 116 150 +#define RAMP_DISCRETE_FLOOR 1 // 1 25 50 75 100 125 150 #define RAMP_DISCRETE_CEIL RAMP_SMOOTH_CEIL -#define RAMP_DISCRETE_STEPS 5 +#define RAMP_DISCRETE_STEPS 7 // LT1S can handle heat well, so don't limit simple mode +//#define SIMPLE_UI_FLOOR 10 // 10 45 80 115 150 #define SIMPLE_UI_FLOOR RAMP_DISCRETE_FLOOR #define SIMPLE_UI_CEIL RAMP_DISCRETE_CEIL #define SIMPLE_UI_STEPS RAMP_DISCRETE_STEPS -// Allow 3C in Simple UI for switching between smooth and stepped ramping +// Allow 3C (or 6C) in Simple UI (toggle smooth or stepped ramping) #define USE_SIMPLE_UI_RAMPING_TOGGLE // allow Aux Config and Strobe Modes in Simple UI diff --git a/spaghetti-monster/anduril/channel-modes.h b/spaghetti-monster/anduril/channel-modes.h index f536d58..167c293 100644 --- a/spaghetti-monster/anduril/channel-modes.h +++ b/spaghetti-monster/anduril/channel-modes.h @@ -7,7 +7,6 @@ #pragma once #if defined(USE_MANUAL_MEMORY) && defined(USE_CHANNEL_MODE_ARGS) -// TODO: save to eeprom // remember and reset 1 extra parameter per channel mode (like tint) uint8_t manual_memory_channel_args[NUM_CHANNEL_MODES] = { CHANNEL_MODE_ARGS }; #endif diff --git a/spaghetti-monster/fsm-ramping.h b/spaghetti-monster/fsm-ramping.h index 3021ff2..028157f 100644 --- a/spaghetti-monster/fsm-ramping.h +++ b/spaghetti-monster/fsm-ramping.h @@ -168,7 +168,7 @@ PROGMEM const PWM_DATATYPE blend_pwm_levels[] = { BLEND_PWM_LEVELS }; // pulse frequency modulation, a.k.a. dynamic PWM // (different ceiling / frequency at each ramp level) // FIXME: dynamic PWM should be a per-channel option, not global -#ifdef USE_DYN_PWM +#ifdef PWM_TOPS PROGMEM const PWM_DATATYPE pwm_tops[] = { PWM_TOPS }; #endif |
