aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSelene ToyKeeper2023-10-28 07:02:38 -0600
committerSelene ToyKeeper2023-10-28 07:02:38 -0600
commitaaa760e5243cf87a43297945b20e41a448c906e4 (patch)
treea29f30ceeeedf5573be58e43d486f8ec393f3657
parentenabled smooth steps on blf-q8 and sofirn-sp36, instead of tactical mode (diff)
downloadanduril-aaa760e5243cf87a43297945b20e41a448c906e4.tar.gz
anduril-aaa760e5243cf87a43297945b20e41a448c906e4.tar.bz2
anduril-aaa760e5243cf87a43297945b20e41a448c906e4.zip
switched blf-lt1-t1616 from plain PWM to PWM+DSM
(and made DSM interrupt definitions a bit cleaner)
-rw-r--r--hwdef-blf-lt1-t1616.c143
-rw-r--r--hwdef-blf-lt1-t1616.h64
-rw-r--r--hwdef-noctigon-m44.c14
-rw-r--r--hwdef-noctigon-m44.h13
-rw-r--r--spaghetti-monster/anduril/cfg-blf-lantern-t1616.h41
5 files changed, 186 insertions, 89 deletions
diff --git a/hwdef-blf-lt1-t1616.c b/hwdef-blf-lt1-t1616.c
index ea7e6b4..48a401d 100644
--- a/hwdef-blf-lt1-t1616.c
+++ b/hwdef-blf-lt1-t1616.c
@@ -1,6 +1,6 @@
// Sofirn LT1-t1616 PWM helpers
// Copyright (C) 2023 SiteRelEnby, Selene ToyKeeper
-// (adapted from emisar-2ch 15/10/2023)
+// SPDX-License-Identifier: GPL-3.0-or-later
#pragma once
#include "chan-aux.c"
@@ -52,59 +52,125 @@ Channel channels[] = {
void set_level_zero() {
- CH1_PWM = 0;
- CH2_PWM = 0;
+ // disable timer 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;
+ PWM_CNT = 0;
}
-void set_pwms(PWM_DATATYPE ch1_pwm, PWM_DATATYPE ch2_pwm) {
+void set_hw_levels(PWM_DATATYPE ch1, PWM_DATATYPE ch2) {
+
+ bool was_on = (CH1_PWM>0) || (CH2_PWM>0);
+
+ // 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;
+
+ // reset phase when turning on
+ if (! was_on) PWM_CNT = 0;
+
+}
+
+// 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_pwms(PWM_GET(pwm1_levels, level), 0);
+ set_hw_levels(PWM_GET(pwm1_levels, level), 0);
}
void set_level_ch2(uint8_t level) {
- set_pwms(0, PWM_GET(pwm1_levels, 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_pwms(pwm, pwm);
+ set_hw_levels(pwm, pwm);
}
-void set_level_blend(uint8_t level) {
- PWM_DATATYPE ch1_pwm, ch2_pwm;
+void blend_helper(PWM_DATATYPE *warm, PWM_DATATYPE *cool, uint8_t level) {
PWM_DATATYPE brightness = PWM_GET(pwm1_levels, level);
- uint8_t blend = cfg.channel_mode_args[channel_mode];
+ 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(&ch1_pwm, &ch2_pwm, brightness, PWM_TOP_INIT, blend);
+ calc_2ch_blend(warm, cool, brightness, DSM_TOP, blend);
+}
- set_pwms(ch1_pwm, ch2_pwm);
+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 ch1_pwm, ch2_pwm;
- PWM_DATATYPE brightness = PWM_GET(pwm1_levels, level);
- uint8_t blend = 255 * (uint16_t)level / RAMP_SIZE;
- if (cfg.channel_mode_args[channel_mode] & 0b01000000)
- blend = 255 - blend;
-
- calc_2ch_blend(&ch1_pwm, &ch2_pwm, brightness, PWM_TOP_INIT, blend);
-
- set_pwms(ch1_pwm, ch2_pwm);
+ 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)
///// bump each channel toward a target value /////
-bool gradual_adjust(uint16_t ch1_pwm, uint16_t ch2_pwm) {
- GRADUAL_ADJUST_SIMPLE(ch1_pwm, CH1_PWM);
- GRADUAL_ADJUST_SIMPLE(ch2_pwm, CH2_PWM);
+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;
- // check for completion
- if ((ch1_pwm == CH1_PWM)
- && (ch2_pwm == CH2_PWM)) {
+ 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
@@ -126,24 +192,15 @@ bool gradual_tick_both(uint8_t gt) {
}
bool gradual_tick_blend(uint8_t gt) {
- PWM_DATATYPE ch1_pwm, ch2_pwm;
- PWM_DATATYPE brightness = PWM_GET(pwm1_levels, gt);
- uint8_t blend = cfg.channel_mode_args[channel_mode];
-
- calc_2ch_blend(&ch1_pwm, &ch2_pwm, brightness, PWM_TOP_INIT, blend);
-
- return gradual_adjust(ch1_pwm, ch2_pwm);
+ PWM_DATATYPE warm, cool;
+ blend_helper(&warm, &cool, gt);
+ return gradual_adjust(warm, cool);
}
bool gradual_tick_auto(uint8_t gt) {
- PWM_DATATYPE ch1_pwm, ch2_pwm;
- PWM_DATATYPE brightness = PWM_GET(pwm1_levels, gt);
- uint8_t blend = 255 * (uint16_t)gt / RAMP_SIZE;
- if (cfg.channel_mode_args[channel_mode] & 0b01000000)
- blend = 255 - blend;
-
- calc_2ch_blend(&ch1_pwm, &ch2_pwm, brightness, PWM_TOP_INIT, blend);
-
- return gradual_adjust(ch1_pwm, ch2_pwm);
+ PWM_DATATYPE warm, cool;
+ blend_helper(&warm, &cool, gt);
+ return gradual_adjust(warm, cool);
}
+
diff --git a/hwdef-blf-lt1-t1616.h b/hwdef-blf-lt1-t1616.h
index 4a411d4..cc6d065 100644
--- a/hwdef-blf-lt1-t1616.h
+++ b/hwdef-blf-lt1-t1616.h
@@ -46,40 +46,49 @@ enum channel_modes_e {
#define USE_CALC_2CH_BLEND
-#define PWM_CHANNELS 1 // old, remove this
+#define PWM_CHANNELS 1 // old, remove this
-#define PWM_BITS 8 //
-
-#define PWM_GET PWM_GET8
-#define PWM_DATATYPE uint8_t
-#define PWM_DATATYPE2 uint16_t // only needs 32-bit if ramp values go over 255
-#define PWM1_DATATYPE uint8_t //
+#define PWM_BITS 16 // 0 to 32640 (0 to 255 PWM + 0 to 127 DSM) at constant kHz
+#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 // 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
-// TODO: implement DSM
+// dynamic PWM
+#define PWM_TOP TCA0.SINGLE.PERBUF // holds the TOP value for for variable-resolution PWM
+#define PWM_TOP_INIT 255
+#define PWM_CNT TCA0.SINGLE.CNT // for resetting phase after each TOP adjustment
+// (max is (255 << 7), because it's 8-bit PWM plus 7 bits of DSM)
#define DSM_TOP (255<<7) // 15-bit resolution leaves 1 bit for carry
+// timer interrupt for DSM
+#define DSM_vect TCA0_OVF_vect
+#define DSM_INTCTRL TCA0.SINGLE.INTCTRL
+#define DSM_OVF_bm TCA_SINGLE_OVF_bm
+
// warm LEDs
+uint16_t ch1_dsm_lvl;
+uint8_t ch1_pwm, ch1_dsm;
#define CH1_PIN PB1
-#define CH1_PWM TCA0.SINGLE.CMP1 // CMP1 is the output compare register for PB1
+#define CH1_PWM TCA0.SINGLE.CMP1 // CMP1 is the output compare register for PB1
// cold LEDs
-#define CH2_PIN PB0
-#define CH2_PWM TCA0.SINGLE.CMP0 // CMP0 is the output compare register for PB0
+uint16_t ch2_dsm_lvl;
+uint8_t ch2_pwm, ch2_dsm;
+#define CH2_PIN PB0
+#define CH2_PWM TCA0.SINGLE.CMP0 // CMP0 is the output compare register for PB0
// lighted button
#define AUXLED_PIN PIN5_bp
#define AUXLED_PORT PORTB
// e-switch
-#define SWITCH_PIN PIN5_bp
-#define SWITCH_PORT VPORTA.IN
-#define SWITCH_ISC_REG PORTA.PIN2CTRL
-#define SWITCH_VECT PORTA_PORT_vect
-#define SWITCH_INTFLG VPORTA.INTFLAGS
+#define SWITCH_PIN PIN5_bp
+#define SWITCH_PORT VPORTA.IN
+#define SWITCH_ISC_REG PORTA.PIN2CTRL
+#define SWITCH_VECT PORTA_PORT_vect
+#define SWITCH_INTFLG VPORTA.INTFLAGS
// average drop across diode on this hardware
#ifndef VOLTAGE_FUDGE_FACTOR
@@ -89,11 +98,12 @@ enum channel_modes_e {
inline void hwdef_setup() {
- // set up the system clock to run at 5 MHz instead of the default 3.33 MHz
- _PROTECTED_WRITE( CLKCTRL.MCLKCTRLB, CLKCTRL_PDIV_4X_gc | CLKCTRL_PEN_bm );
+ // set up the system clock to run at 10 MHz instead of the default 3.33 MHz
+ _PROTECTED_WRITE( CLKCTRL.MCLKCTRLB,
+ CLKCTRL_PDIV_2X_gc | CLKCTRL_PEN_bm );
//VPORTA.DIR = ...;
- // Outputs:
+ // Outputs
VPORTB.DIR = PIN0_bm // cool white
| PIN1_bm // warm white
| PIN5_bm; // aux LED
@@ -130,10 +140,16 @@ inline void hwdef_setup() {
// TODO: add references to MCU documentation
TCA0.SINGLE.CTRLB = TCA_SINGLE_CMP0EN_bm
| TCA_SINGLE_CMP1EN_bm
- | TCA_SINGLE_WGMODE_SINGLESLOPE_gc;
- PWM_TOP = PWM_TOP_INIT;
+ | TCA_SINGLE_WGMODE_DSBOTTOM_gc;
TCA0.SINGLE.CTRLA = TCA_SINGLE_CLKSEL_DIV1_gc
| TCA_SINGLE_ENABLE_bm;
+
+ PWM_TOP = PWM_TOP_INIT;
+
+ // set up interrupt for delta-sigma modulation
+ // (moved to hwdef.c functions so it can be enabled/disabled based on ramp level)
+ //DSM_INTCTRL |= DSM_OVF_bm; // interrupt once for each timer cycle
+
}
diff --git a/hwdef-noctigon-m44.c b/hwdef-noctigon-m44.c
index 0b7f027..395a7a2 100644
--- a/hwdef-noctigon-m44.c
+++ b/hwdef-noctigon-m44.c
@@ -52,9 +52,9 @@ Channel channels[] = {
void set_level_zero() {
- // disable timer 1 overflow interrupt
+ // disable timer overflow interrupt
// (helps improve button press handling from Off state)
- TIMSK &= ~(1 << TOIE1);
+ DSM_INTCTRL &= ~DSM_OVF_bm;
// turn off all LEDs
ch1_dsm_lvl = 0;
@@ -120,19 +120,19 @@ void set_hw_levels(PWM_DATATYPE ch1, PWM_DATATYPE ch2,
PWM_TOP = top;
#endif
- // enable timer 1 overflow interrupt so DSM can work
- TIMSK |= (1 << TOIE1);
+ // enable timer overflow interrupt so DSM can work
+ DSM_INTCTRL |= DSM_OVF_bm;
- // reset phase when turning on or off
+ // reset phase when turning on
//if ((! was_on) | (! now_on)) PWM_CNT = 0;
if (! was_on) PWM_CNT = 0;
}
// delta-sigma modulation of PWM outputs
-// happens on each Timer0 overflow (every 512 cpu clock cycles)
+// happens on each Timer overflow (every 512 cpu clock cycles)
// uses 8-bit pwm w/ 7-bit dsm (0b 0PPP PPPP PDDD DDDD)
-ISR(TIMER1_OVF_vect) {
+ISR(DSM_vect) {
// set new hardware values first,
// for best timing (reduce effect of interrupt jitter)
CH1_PWM = ch1_pwm;
diff --git a/hwdef-noctigon-m44.h b/hwdef-noctigon-m44.h
index 2a9198a..a41ea79 100644
--- a/hwdef-noctigon-m44.h
+++ b/hwdef-noctigon-m44.h
@@ -39,8 +39,8 @@
// channel modes:
// * 0. channel 1 only
// * 1. channel 2 only
-// * 2. both channels, tied together
-// * 3. both channels, manual blend
+// * 2. both channels, tied together, max "200%" power
+// * 3. both channels, manual blend, max "100%" power
// * 4? both channels, manual blend, max 200% power
// * 4. both channels, auto blend, reversible
#define NUM_CHANNEL_MODES (5 + NUM_RGB_AUX_CHANNEL_MODES)
@@ -65,7 +65,7 @@ enum channel_modes_e {
#define PWM_CHANNELS 1 // old, remove this
-#define PWM_BITS 16 // 0 to 16383 at variable Hz, not 0 to 255 at 16 kHz
+#define PWM_BITS 16 // 0 to 32640 (0 to 255 PWM + 0 to 127 DSM) at constant kHz
#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
@@ -80,6 +80,11 @@ enum channel_modes_e {
// (max is (255 << 7), because it's 8-bit PWM plus 7 bits of DSM)
#define DSM_TOP (255<<7) // 15-bit resolution leaves 1 bit for carry
+// timer interrupt for DSM
+#define DSM_vect TIMER1_OVF_vect
+#define DSM_INTCTRL TIMSK
+#define DSM_OVF_bm (1<<TOIE1)
+
// 1st channel (8 LEDs)
uint16_t ch1_dsm_lvl;
uint8_t ch1_pwm, ch1_dsm;
@@ -187,7 +192,7 @@ inline void hwdef_setup() {
// set up interrupt for delta-sigma modulation
// (moved to hwdef.c functions so it can be enabled/disabled based on ramp level)
- //TIMSK |= (1<<TOIE1); // interrupt once for each timer 1 cycle
+ //DSM_INTCTRL |= DSM_OVF_bm; // interrupt once for each timer cycle
// set up e-switch
SWITCH_PUE = (1 << SWITCH_PIN); // pull-up for e-switch
diff --git a/spaghetti-monster/anduril/cfg-blf-lantern-t1616.h b/spaghetti-monster/anduril/cfg-blf-lantern-t1616.h
index 85784bb..24a7c7c 100644
--- a/spaghetti-monster/anduril/cfg-blf-lantern-t1616.h
+++ b/spaghetti-monster/anduril/cfg-blf-lantern-t1616.h
@@ -19,6 +19,7 @@
// CM_CH1, CM_CH2, CM_BOTH, CM_BLEND, CM_AUTO
#define DEFAULT_CHANNEL_MODE CM_AUTO
#define DEFAULT_BLINK_CHANNEL CM_BOTH
+#define USE_CHANNEL_MODE_ARGS
// how much to increase total brightness at middle tint
// (0 = 100% brightness, 64 = 200% brightness)
@@ -26,14 +27,19 @@
#define TINT_RAMPING_CORRECTION 10 // production model, 115%
#define RAMP_SIZE 150
-// level_calc.py 1 150 7135 1 30 800
-#define PWM1_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 MAX_1x7135 65
-#define HALFSPEED_LEVEL 14
-#define QUARTERSPEED_LEVEL 5
+// 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 1 // lowest level for tint ramping correction
+#define QUARTERSPEED_LEVEL 1
+#undef USE_DYNAMIC_UNDERCLOCKING // makes huge bumps in the ramp
+
+#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
@@ -42,7 +48,7 @@
// 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 1
#define RAMP_DISCRETE_CEIL 150
#define RAMP_DISCRETE_STEPS 7
@@ -58,6 +64,22 @@
#define USE_SOS_MODE
#define USE_SOS_MODE_IN_BLINKY_GROUP
+// the default of 26 looks a bit flat, so increase it
+#define CANDLE_AMPLITUDE 40
+
+#define USE_POLICE_COLOR_STROBE_MODE
+#define POLICE_COLOR_STROBE_CH1 CM_CH1
+#define POLICE_COLOR_STROBE_CH2 CM_CH2
+// aux red + aux blue are the correct colors, but are dim
+//#define POLICE_COLOR_STROBE_CH1 CM_AUXRED
+//#define POLICE_COLOR_STROBE_CH2 CM_AUXBLU
+
+#undef TACTICAL_LEVELS
+#define TACTICAL_LEVELS 120,30,(RAMP_SIZE+3) // high, low, police strobe
+
+// party strobe, tac strobe, police, lightning, candle, bike
+#define DEFAULT_STROBE_CHANNELS CM_BOTH,CM_BOTH,CM_BOTH,CM_AUTO,CM_AUTO,CM_AUTO
+
// the sensor (attiny1616) is nowhere near the emitters
// so thermal regulation can't work
#ifdef USE_THERMAL_REGULATION
@@ -76,9 +98,6 @@
#define BLINK_AT_RAMP_CEIL
#endif
-#define USE_CHANNEL_MODE_ARGS
-#define USE_SET_LEVEL_GRADUALLY
-
// for consistency with other models
#define USE_SOFT_FACTORY_RESET