aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-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
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.