aboutsummaryrefslogtreecommitdiff
path: root/hw
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--hwdef-Sofirn_LT1S-Pro.c114
-rw-r--r--hwdef-Sofirn_LT1S-Pro.h75
2 files changed, 146 insertions, 43 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;
}
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.