aboutsummaryrefslogtreecommitdiff
path: root/hw/sofirn/blf-lt1
diff options
context:
space:
mode:
Diffstat (limited to 'hw/sofirn/blf-lt1')
-rw-r--r--hw/sofirn/blf-lt1/cfg.h105
-rw-r--r--hw/sofirn/blf-lt1/hwdef.c204
-rw-r--r--hw/sofirn/blf-lt1/hwdef.h114
3 files changed, 423 insertions, 0 deletions
diff --git a/hw/sofirn/blf-lt1/cfg.h b/hw/sofirn/blf-lt1/cfg.h
new file mode 100644
index 0000000..53c3203
--- /dev/null
+++ b/hw/sofirn/blf-lt1/cfg.h
@@ -0,0 +1,105 @@
+// BLF Lantern config options for Anduril
+// Copyright (C) 2018-2023 Selene ToyKeeper
+// SPDX-License-Identifier: GPL-3.0-or-later
+#pragma once
+
+#define MODEL_NUMBER "0621"
+#include "hwdef-blf-lt1.h"
+// ATTINY: 85
+
+// the button lights up
+#define USE_INDICATOR_LED
+// the button is visible while main LEDs are on
+#define USE_INDICATOR_LED_WHILE_RAMPING
+// off mode: low (1)
+// lockout: blinking (3)
+#define INDICATOR_LED_DEFAULT_MODE ((3<<2) + 1)
+
+// channel modes...
+// CM_CH1, CM_CH2, CM_BOTH, CM_BLEND, CM_AUTO
+#define DEFAULT_CHANNEL_MODE CM_AUTO
+//#define DEFAULT_BLINK_CHANNEL CM_BOTH // takes too much space
+
+// how much to increase total brightness at middle tint
+// (0 = 100% brightness, 64 = 200% brightness)
+//#define TINT_RAMPING_CORRECTION 26 // prototype, 140%
+//#define TINT_RAMPING_CORRECTION 10 // production model, 115%
+#define TINT_RAMPING_CORRECTION 0 // none
+
+#define RAMP_SIZE 150
+// delta-sigma modulated PWM (0b0HHHHHHHHLLLLLLL = 0, 8xHigh, 7xLow bits)
+// (max is (255 << 7), because it's 8-bit PWM plus 7 bits of DSM)
+// level_calc.py 3.333 1 150 7135 32 0.2 600 --pwm 32640
+#define PWM1_LEVELS 32,35,38,41,45,50,55,61,67,74,82,91,100,110,121,133,146,160,175,192,209,227,247,268,291,314,340,366,395,424,456,489,524,560,599,639,681,726,772,820,871,924,979,1036,1096,1158,1222,1289,1359,1431,1506,1584,1664,1747,1834,1923,2015,2111,2209,2311,2416,2524,2636,2751,2870,2992,3118,3247,3380,3518,3659,3803,3952,4105,4262,4423,4589,4759,4933,5111,5294,5482,5674,5871,6073,6279,6491,6707,6928,7155,7386,7623,7865,8113,8365,8624,8888,9157,9432,9713,10000,10292,10591,10895,11206,11523,11846,12175,12511,12853,13202,13557,13919,14287,14663,15045,15434,15830,16233,16644,17061,17486,17919,18358,18805,19260,19723,20193,20671,21156,21650,22152,22662,23180,23706,24241,24784,25335,25895,26464,27041,27627,28222,28826,29439,30060,30691,31332,31981,32640
+
+#define DEFAULT_LEVEL 75
+#define MAX_1x7135 75
+#define HALFSPEED_LEVEL 44
+#define QUARTERSPEED_LEVEL 34
+#undef USE_DYNAMIC_UNDERCLOCKING // makes huge bumps in the ramp
+
+#define USE_SMOOTH_STEPS
+//#define USE_SET_LEVEL_GRADUALLY
+
+// the default of 26 looks a bit flat, so increase it
+#define CANDLE_AMPLITUDE 40
+
+// override default ramp style
+#undef RAMP_STYLE
+#define RAMP_STYLE 1 // 0 = smooth, 1 = stepped
+// set floor and ceiling as far apart as possible
+// because this lantern isn't overpowered
+#define RAMP_SMOOTH_FLOOR 1
+#define RAMP_SMOOTH_CEIL 150
+#define RAMP_DISCRETE_FLOOR 1
+#define RAMP_DISCRETE_CEIL 150
+#define RAMP_DISCRETE_STEPS 7
+
+// LT1 can handle heat well, so don't limit simple mode
+#define SIMPLE_UI_FLOOR 10
+#define SIMPLE_UI_CEIL 150
+#define SIMPLE_UI_STEPS 5
+
+// Allow 3C in Simple UI for switching between smooth and stepped ramping
+#define USE_SIMPLE_UI_RAMPING_TOGGLE
+#define USE_EXTENDED_SIMPLE_UI
+
+// also at Sofirn's request, enable 2 click turbo (Anduril 1 style)
+#define DEFAULT_2C_STYLE 1
+
+
+// the sensor (attiny85) is nowhere near the emitters
+// so thermal regulation can't work
+#ifdef USE_THERMAL_REGULATION
+#undef USE_THERMAL_REGULATION
+#endif
+
+// don't blink while ramping
+#ifdef BLINK_AT_RAMP_FLOOR
+#undef BLINK_AT_RAMP_FLOOR
+#endif
+#ifdef BLINK_AT_RAMP_MIDDLE
+#undef BLINK_AT_RAMP_MIDDLE
+#endif
+#ifdef BLINK_AT_RAMP_CEIL
+#undef BLINK_AT_RAMP_CEIL
+#endif
+
+// too big, turn off extra features
+//#undef USE_STEPPED_TINT_RAMPING
+#undef USE_MOMENTARY_MODE
+#undef USE_TACTICAL_MODE
+#undef USE_SOS_MODE
+//#undef USE_SIMPLE_UI
+//#undef USE_BEACON_MODE
+//#undef USE_RAMP_SPEED_CONFIG
+#undef USE_RAMP_AFTER_MOON_CONFIG
+#undef USE_2C_STYLE_CONFIG
+#undef USE_VOLTAGE_CORRECTION
+//#undef USE_CHANNEL_PER_STROBE
+// party strobe, tac strobe, lightning, candle, bike
+#define DEFAULT_STROBE_CHANNELS CM_BOTH,CM_BOTH,CM_AUTO,CM_AUTO,CM_AUTO
+
+// for consistency with other models
+#define USE_SOFT_FACTORY_RESET
+
diff --git a/hw/sofirn/blf-lt1/hwdef.c b/hw/sofirn/blf-lt1/hwdef.c
new file mode 100644
index 0000000..8a4c0e1
--- /dev/null
+++ b/hw/sofirn/blf-lt1/hwdef.c
@@ -0,0 +1,204 @@
+// BLF LT1 PWM functions
+// Copyright (C) 2023 Selene ToyKeeper
+// SPDX-License-Identifier: GPL-3.0-or-later
+#pragma once
+
+
+void set_level_zero();
+
+void set_level_ch1(uint8_t level);
+void set_level_ch2(uint8_t level);
+void set_level_both(uint8_t level);
+void set_level_blend(uint8_t level);
+//void set_level_auto(uint8_t level); // redundant
+
+#if 0 // gradual adjustments are disabled to save space
+bool gradual_tick_ch1(uint8_t gt);
+bool gradual_tick_ch2(uint8_t gt);
+bool gradual_tick_both(uint8_t gt);
+bool gradual_tick_blend(uint8_t gt);
+//bool gradual_tick_auto(uint8_t gt); // redundant
+#endif
+
+
+Channel channels[] = {
+ { // channel 1 only
+ .set_level = set_level_ch1,
+ //.gradual_tick = gradual_tick_ch1,
+ .has_args = 0
+ },
+ { // channel 2 only
+ .set_level = set_level_ch2,
+ //.gradual_tick = gradual_tick_ch2,
+ .has_args = 0
+ },
+ { // both channels, tied together (max "200%" power)
+ .set_level = set_level_both,
+ //.gradual_tick = gradual_tick_both,
+ .has_args = 0
+ },
+ { // both channels, manual blend (max "100%" power)
+ .set_level = set_level_blend,
+ //.gradual_tick = gradual_tick_blend,
+ .has_args = 1
+ },
+ { // both channels, auto blend
+ .set_level = set_level_blend,
+ //.gradual_tick = gradual_tick_blend,
+ .has_args = 1
+ },
+};
+
+void set_level_zero() {
+ // disable timer 0 overflow interrupt
+ // (helps improve button press handling from Off state)
+ DSM_INTCTRL &= ~DSM_OVF_bm;
+
+ // turn off all LEDs
+ ch1_dsm_lvl = 0;
+ ch2_dsm_lvl = 0;
+ CH1_PWM = 0;
+ CH2_PWM = 0;
+}
+
+// wrap setting the dsm vars, to get a faster response
+// (just setting *_dsm_lvl doesn't work well for strobes)
+void set_hw_levels(PWM_DATATYPE ch1, PWM_DATATYPE ch2) {
+ // set delta-sigma soft levels
+ ch1_dsm_lvl = ch1;
+ ch2_dsm_lvl = ch2;
+
+ // set hardware PWM levels and init dsm loop
+ CH1_PWM = ch1_pwm = ch1 >> 7;
+ CH2_PWM = ch2_pwm = ch2 >> 7;
+
+ // enable timer overflow interrupt so DSM can work
+ DSM_INTCTRL |= DSM_OVF_bm;
+}
+
+// delta-sigma modulation of PWM outputs
+// happens on each Timer overflow (every 512 cpu clock cycles)
+// uses 8-bit pwm w/ 7-bit dsm (0b 0PPP PPPP PDDD DDDD)
+ISR(DSM_vect) {
+ // set new hardware values first,
+ // for best timing (reduce effect of interrupt jitter)
+ CH1_PWM = ch1_pwm;
+ CH2_PWM = ch2_pwm;
+
+ // calculate next values, now that timing matters less
+
+ // accumulate error
+ ch1_dsm += (ch1_dsm_lvl & 0x007f);
+ // next PWM = base PWM value + carry bit
+ ch1_pwm = (ch1_dsm_lvl >> 7) + (ch1_dsm > 0x7f);
+ // clear carry bit
+ ch1_dsm &= 0x7f;
+
+ // repeat for other channels
+
+ ch2_dsm += (ch2_dsm_lvl & 0x007f);
+ ch2_pwm = (ch2_dsm_lvl >> 7) + (ch2_dsm > 0x7f);
+ ch2_dsm &= 0x7f;
+}
+
+
+void set_level_ch1(uint8_t level) {
+ set_hw_levels(PWM_GET(pwm1_levels, level), 0);
+}
+
+void set_level_ch2(uint8_t level) {
+ set_hw_levels(0, PWM_GET(pwm1_levels, level));
+}
+
+void set_level_both(uint8_t level) {
+ PWM_DATATYPE pwm = PWM_GET(pwm1_levels, level);
+ set_hw_levels(pwm, pwm);
+}
+
+void blend_helper(PWM_DATATYPE *warm, PWM_DATATYPE *cool, uint8_t level) {
+ PWM_DATATYPE brightness = PWM_GET(pwm1_levels, level);
+ uint8_t blend;
+ if (channel_mode == CM_AUTO) {
+ blend = 255 * (uint16_t)level / RAMP_SIZE;
+ if (cfg.channel_mode_args[channel_mode] & 0b01000000)
+ blend = 255 - blend;
+ } else {
+ blend = cfg.channel_mode_args[channel_mode];
+ }
+
+ calc_2ch_blend(warm, cool, brightness, DSM_TOP, blend);
+}
+
+void set_level_blend(uint8_t level) {
+ PWM_DATATYPE warm, cool;
+ blend_helper(&warm, &cool, level);
+ set_hw_levels(warm, cool);
+}
+
+/*
+void set_level_auto(uint8_t level) {
+ PWM_DATATYPE warm, cool;
+ blend_helper(&warm, &cool, level);
+ set_hw_levels(warm, cool);
+}
+*/
+
+///// "gradual tick" functions for smooth thermal regulation /////
+// (and other smooth adjustments)
+
+#if 0 // disabled to save space
+///// bump each channel toward a target value /////
+bool gradual_adjust(PWM_DATATYPE ch1, PWM_DATATYPE ch2) {
+ // adjust multiple times based on current brightness
+ // (so it adjusts faster/coarser when bright, slower/finer when dim)
+
+ // higher shift = slower/finer adjustments
+ const uint8_t shift = 9; // ((255 << 7) >> 9) = 63 max
+ uint8_t steps;
+
+ steps = ch1_dsm_lvl >> shift;
+ for (uint8_t i=0; i<=steps; i++)
+ GRADUAL_ADJUST_SIMPLE(ch1, ch1_dsm_lvl);
+
+ steps = ch2_dsm_lvl >> shift;
+ for (uint8_t i=0; i<=steps; i++)
+ GRADUAL_ADJUST_SIMPLE(ch2, ch2_dsm_lvl);
+
+ if ((ch1 == ch1_dsm_lvl)
+ && (ch2 == ch2_dsm_lvl )) {
+ return true; // done
+ }
+ return false; // not done yet
+}
+
+bool gradual_tick_ch1(uint8_t gt) {
+ PWM_DATATYPE pwm = PWM_GET(pwm1_levels, gt);
+ return gradual_adjust(pwm, 0);
+}
+
+bool gradual_tick_ch2(uint8_t gt) {
+ PWM_DATATYPE pwm = PWM_GET(pwm1_levels, gt);
+ return gradual_adjust(0, pwm);
+}
+
+bool gradual_tick_both(uint8_t gt) {
+ PWM_DATATYPE pwm = PWM_GET(pwm1_levels, gt);
+ return gradual_adjust(pwm, pwm);
+}
+
+bool gradual_tick_blend(uint8_t gt) {
+ PWM_DATATYPE warm, cool;
+ blend_helper(&warm, &cool, gt);
+ return gradual_adjust(warm, cool);
+}
+
+/*
+bool gradual_tick_auto(uint8_t gt) {
+ PWM_DATATYPE warm, cool;
+ blend_helper(&warm, &cool, gt);
+ return gradual_adjust(warm, cool);
+}
+*/
+
+#endif // if 0
+
diff --git a/hw/sofirn/blf-lt1/hwdef.h b/hw/sofirn/blf-lt1/hwdef.h
new file mode 100644
index 0000000..b113fd4
--- /dev/null
+++ b/hw/sofirn/blf-lt1/hwdef.h
@@ -0,0 +1,114 @@
+// BLF LT1 driver layout
+// Copyright (C) 2018-2023 Selene ToyKeeper
+// SPDX-License-Identifier: GPL-3.0-or-later
+#pragma once
+
+/*
+ * ----
+ * Reset -|1 8|- VCC
+ * eswitch -|2 7|- (unused)
+ * aux LED -|3 6|- PWM (5000K)
+ * GND -|4 5|- PWM (3000K)
+ * ----
+ */
+
+#define ATTINY 85
+#include <avr/io.h>
+
+#define HWDEF_C_FILE hwdef-blf-lt1.c
+
+// channel modes:
+// * 0. channel 1 only
+// * 1. channel 2 only
+// * 2. both channels, tied together, max "200%" power
+// * 3. both channels, manual blend, max "100%" power
+// * 4. both channels, auto blend, reversible
+#define NUM_CHANNEL_MODES 5
+enum channel_modes_e {
+ CM_CH1 = 0,
+ CM_CH2,
+ CM_BOTH,
+ CM_BLEND,
+ CM_AUTO,
+};
+
+
+// right-most bit first, modes are in fedcba9876543210 order
+#define CHANNEL_MODES_ENABLED 0b00011000
+#define USE_CHANNEL_MODE_ARGS
+// _, _, _, 128=middle CCT, 0=warm-to-cool
+#define CHANNEL_MODE_ARGS 0,0,0,128,0
+
+// can use some of the common handlers
+#define USE_CALC_2CH_BLEND
+
+
+#define PWM_CHANNELS 1 // old, remove this
+
+#define PWM_BITS 16 // 8-bit hardware PWM + 16-bit DSM
+
+#define PWM_GET PWM_GET16
+#define PWM_DATATYPE uint16_t
+#define PWM_DATATYPE2 uint32_t // only needs 32-bit if ramp values go over 255
+#define PWM1_DATATYPE uint16_t // 15-bit PWM+DSM ramp
+
+// PWM parameters of both channels are tied together because they share a counter
+#define PWM_TOP_INIT 255
+#define DSM_TOP (255<<7) // 15-bit resolution leaves 1 bit for carry
+
+// timer interrupt for DSM
+#define DSM_vect TIMER0_OVF_vect
+#define DSM_INTCTRL TIMSK
+#define DSM_OVF_bm (1<<TOIE0)
+
+#define DELAY_FACTOR 90 // less time in delay() because more time spent in interrupts
+
+// warm LEDs
+uint16_t ch1_dsm_lvl;
+uint8_t ch1_pwm, ch1_dsm;
+#define CH1_PIN PB1 // pin 6, warm tint PWM
+#define CH1_PWM OCR0B // OCR0B is the output compare register for PB1
+
+// cold LEDs
+uint16_t ch2_dsm_lvl;
+uint8_t ch2_pwm, ch2_dsm;
+#define CH2_PIN PB0 // pin 5, cold tint PWM
+#define CH2_PWM OCR0A // OCR0A is the output compare register for PB0
+
+// lighted button
+#define AUXLED_PIN PB4 // pin 3
+
+// e-switch
+#define SWITCH_PIN PB3 // pin 2
+#define SWITCH_PCINT PCINT3 // pin 2 pin change interrupt
+
+#define ADC_PRSCL 0x07 // clk/128
+
+// average drop across diode on this hardware
+#ifndef VOLTAGE_FUDGE_FACTOR
+#define VOLTAGE_FUDGE_FACTOR 7 // add 0.35V
+#endif
+
+#define FAST 0xA3 // fast PWM both channels
+#define PHASE 0xA1 // phase-correct PWM both channels
+
+
+inline void hwdef_setup() {
+ // configure PWM channels
+ DDRB = (1 << CH1_PIN)
+ | (1 << CH2_PIN);
+
+ TCCR0B = 0x01; // pre-scaler for timer (1 => 1, 2 => 8, 3 => 64...)
+ TCCR0A = PHASE;
+
+ // enable timer overflow interrupt for DSM purposes
+ //DSM_INTCTRL |= DSM_OVF_bm; // moved to hwdef.c functions instead
+
+ // configure e-switch
+ PORTB = (1 << SWITCH_PIN); // e-switch is the only input
+ PCMSK = (1 << SWITCH_PIN); // pin change interrupt uses this pin
+}
+
+
+#define LAYOUT_DEFINED
+