aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSelene ToyKeeper2023-04-14 18:01:03 -0600
committerSelene ToyKeeper2023-04-14 18:01:03 -0600
commitf8e1150ba52fb3128d452e68ae2d8dda97a53ff1 (patch)
tree9321c93ae344918c99327bd852dbc90968d8780e
parentrefactor progress checkpoint ... got Sofirn LT1S Pro and Emisar D4v2 working (diff)
downloadanduril-f8e1150ba52fb3128d452e68ae2d8dda97a53ff1.tar.gz
anduril-f8e1150ba52fb3128d452e68ae2d8dda97a53ff1.tar.bz2
anduril-f8e1150ba52fb3128d452e68ae2d8dda97a53ff1.zip
LT1S Pro: added dynamic PWM (much better low modes!)
Diffstat (limited to '')
-rw-r--r--hwdef-Sofirn_LT1S-Pro.c114
-rw-r--r--hwdef-Sofirn_LT1S-Pro.h75
-rw-r--r--spaghetti-monster/anduril/cfg-sofirn-lt1s-pro.h39
-rw-r--r--spaghetti-monster/anduril/channel-modes.h1
-rw-r--r--spaghetti-monster/fsm-ramping.h2
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