aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--hwdef-Emisar_D4v2.h97
-rw-r--r--hwdef-Sofirn_LT1S-Pro.c67
-rw-r--r--hwdef-Sofirn_LT1S-Pro.h109
-rw-r--r--spaghetti-monster/anduril/anduril.c39
-rwxr-xr-xspaghetti-monster/anduril/build-all.sh4
-rw-r--r--spaghetti-monster/anduril/candle-mode.c2
-rw-r--r--spaghetti-monster/anduril/cfg-emisar-d4v2-219.h13
-rw-r--r--spaghetti-monster/anduril/cfg-emisar-d4v2-nofet.h23
-rw-r--r--spaghetti-monster/anduril/cfg-emisar-d4v2.h21
-rw-r--r--spaghetti-monster/anduril/cfg-sofirn-lt1s-pro.h41
-rw-r--r--spaghetti-monster/anduril/channel-modes.c146
-rw-r--r--spaghetti-monster/anduril/channel-modes.h20
-rw-r--r--spaghetti-monster/anduril/factory-reset.c50
-rw-r--r--spaghetti-monster/anduril/factory-reset.h26
-rw-r--r--spaghetti-monster/anduril/hank-cfg.h7
-rw-r--r--spaghetti-monster/anduril/load-save-config-fsm.h30
-rw-r--r--spaghetti-monster/anduril/load-save-config.c44
-rw-r--r--spaghetti-monster/anduril/off-mode.c10
-rw-r--r--spaghetti-monster/anduril/ramp-mode.c55
-rw-r--r--spaghetti-monster/anduril/ramp-mode.h15
-rw-r--r--spaghetti-monster/anduril/strobe-modes.c30
-rw-r--r--spaghetti-monster/anduril/strobe-modes.h7
-rw-r--r--spaghetti-monster/anduril/tint-ramping.c23
-rw-r--r--spaghetti-monster/anduril/tint-ramping.h22
-rw-r--r--spaghetti-monster/fsm-main.c1
-rw-r--r--spaghetti-monster/fsm-misc.h5
-rw-r--r--spaghetti-monster/fsm-ramping.c422
-rw-r--r--spaghetti-monster/fsm-ramping.h196
-rw-r--r--spaghetti-monster/fsm-states.h2
29 files changed, 1019 insertions, 508 deletions
diff --git a/hwdef-Emisar_D4v2.h b/hwdef-Emisar_D4v2.h
index e118ed5..51e30f8 100644
--- a/hwdef-Emisar_D4v2.h
+++ b/hwdef-Emisar_D4v2.h
@@ -1,7 +1,8 @@
-#ifndef HWDEF_EMISAR_D4V2_H
-#define HWDEF_EMISAR_D4V2_H
+#pragma once
-/* Emisar D4v2 driver layout (attiny1634)
+/* hwdef for Emisar D4v2 (attiny1634)
+ * Copyright (C) 2019 Selene ToyKeeper
+ * SPDX-License-Identifier: GPL-3.0-or-later
*
* Pin / Name / Function
* 1 PA6 FET PWM (PWM1B)
@@ -33,6 +34,16 @@
#define ATTINY 1634
#include <avr/io.h>
+// TODO: add aux red and aux blue as disabled channel modes,
+// so they can be used for the police strobe?
+#define NUM_CHANNEL_MODES 1
+#define DEFAULT_CHANNEL_MODE 0
+#define SET_LEVEL_MODES set_level_2ch_stacked
+#define GRADUAL_TICK_MODES gradual_tick_2ch_stacked
+// no special handlers needed, can use common handlers
+#define USE_SET_LEVEL_2CH_STACKED
+#define USE_GRADUAL_TICK_2CH_STACKED
+
#define PWM_CHANNELS 2
#define SWITCH_PIN PA2 // pin 5
@@ -41,12 +52,17 @@
#define SWITCH_PCMSK PCMSK0 // PCMSK0 is for PCINT[7:0]
#define SWITCH_PORT PINA // PINA or PINB or PINC
-#define PWM1_PIN PB3 // pin 16, 1x7135 PWM
-#define PWM1_LVL OCR1A // OCR1A is the output compare register for PB3
+#define LOW_PWM_PIN PB3 // pin 16, 1x7135 PWM
+#define LOW_PWM_LVL OCR1A // OCR1A is the output compare register for PB3
+#define PWM1_BITS 8
-#define PWM2_PIN PA6 // pin 1, FET PWM
-#define PWM2_LVL OCR1B // OCR1B is the output compare register for PB1
+#define HIGH_PWM_PIN PA6 // pin 1, FET PWM
+#define HIGH_PWM_LVL OCR1B // OCR1B is the output compare register for PB1
+#define PWM2_BITS 8
+// only using 8-bit on this light
+#define PWM_GET PWM_GET8
+#define PWM_DATATYPE uint8_t
#define ADC_PRSCL 0x07 // clk/128
@@ -68,37 +84,46 @@
#define BUTTON_LED_DDR DDRA // for all "PA" pins
#define BUTTON_LED_PUE PUEA // for all "PA" pins
-// with so many pins, doing this all with #ifdefs gets awkward...
-// ... so just hardcode it in each hwdef file instead
+// this light has three aux LED channels: R, G, B
+#define USE_AUX_RGB_LEDS
+// it also has an independent LED in the button
+#define USE_BUTTON_LED
+// the aux LEDs are front-facing, so turn them off while main LEDs are on
+// TODO: the whole "indicator LED" thing needs to be refactored into
+// "aux LED(s)" and "button LED(s)" since they work a bit differently
+#ifdef USE_INDICATOR_LED_WHILE_RAMPING
+#undef USE_INDICATOR_LED_WHILE_RAMPING
+#endif
+
inline void hwdef_setup() {
- // enable output ports
- // 7135
- DDRB = (1 << PWM1_PIN);
- // FET, aux R/G/B, button LED
- DDRA = (1 << PWM2_PIN)
- | (1 << AUXLED_R_PIN)
- | (1 << AUXLED_G_PIN)
- | (1 << AUXLED_B_PIN)
- | (1 << BUTTON_LED_PIN)
- ;
-
- // configure PWM
- // Setup PWM. F_pwm = F_clkio / 2 / N / TOP, where N = prescale factor, TOP = top of counter
- // pre-scale for timer: N = 1
- TCCR1A = (0<<WGM11) | (1<<WGM10) // 8-bit (TOP=0xFF) (DS table 12-5)
- | (1<<COM1A1) | (0<<COM1A0) // PWM 1A in normal direction (DS table 12-4)
- | (1<<COM1B1) | (0<<COM1B0) // PWM 1B in normal direction (DS table 12-4)
- ;
- TCCR1B = (0<<CS12) | (0<<CS11) | (1<<CS10) // clk/1 (no prescaling) (DS table 12-6)
- | (0<<WGM13) | (0<<WGM12) // phase-correct PWM (DS table 12-5)
- ;
-
- // set up e-switch
- //PORTA = (1 << SWITCH_PIN); // TODO: configure PORTA / PORTB / PORTC?
- PUEA = (1 << SWITCH_PIN); // pull-up for e-switch
- SWITCH_PCMSK = (1 << SWITCH_PCINT); // enable pin change interrupt
+ // enable output ports
+ // 7135
+ DDRB = (1 << LOW_PWM_PIN);
+ // FET, aux R/G/B, button LED
+ DDRA = (1 << HIGH_PWM_PIN)
+ | (1 << AUXLED_R_PIN)
+ | (1 << AUXLED_G_PIN)
+ | (1 << AUXLED_B_PIN)
+ | (1 << BUTTON_LED_PIN)
+ ;
+
+ // configure PWM
+ // Setup PWM. F_pwm = F_clkio / 2 / N / TOP, where N = prescale factor, TOP = top of counter
+ // pre-scale for timer: N = 1
+ TCCR1A = (0<<WGM11) | (1<<WGM10) // 8-bit (TOP=0xFF) (DS table 12-5)
+ | (1<<COM1A1) | (0<<COM1A0) // PWM 1A in normal direction (DS table 12-4)
+ | (1<<COM1B1) | (0<<COM1B0) // PWM 1B in normal direction (DS table 12-4)
+ ;
+ TCCR1B = (0<<CS12) | (0<<CS11) | (1<<CS10) // clk/1 (no prescaling) (DS table 12-6)
+ | (0<<WGM13) | (0<<WGM12) // phase-correct PWM (DS table 12-5)
+ ;
+
+ // set up e-switch
+ //PORTA = (1 << SWITCH_PIN); // TODO: configure PORTA / PORTB / PORTC?
+ PUEA = (1 << SWITCH_PIN); // pull-up for e-switch
+ SWITCH_PCMSK = (1 << SWITCH_PCINT); // enable pin change interrupt
}
+
#define LAYOUT_DEFINED
-#endif
diff --git a/hwdef-Sofirn_LT1S-Pro.c b/hwdef-Sofirn_LT1S-Pro.c
new file mode 100644
index 0000000..3c31c96
--- /dev/null
+++ b/hwdef-Sofirn_LT1S-Pro.c
@@ -0,0 +1,67 @@
+// BLF LT1S Pro hwdef functions
+// Copyright (C) 2023 Selene ToyKeeper
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#pragma once
+
+
+// "auto tint" channel mode
+void set_level_auto_3ch_blend(uint8_t level) {
+ BLEND_PWM_DATATYPE vpwm;
+
+ if (level == 0) {
+ vpwm = 0;
+ } else {
+ level --; // PWM array index = level - 1
+ vpwm = PWM_GET(blend_pwm_levels, 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;
+
+ // red is high at 0, low at 255
+ a = (((PWM_DATATYPE2)(255 - mytint)
+ * (PWM_DATATYPE2)vpwm) + 127) / 255;
+ // warm white is low at 0 and 255, high at 127
+ b = (((PWM_DATATYPE2)triangle_wave(mytint)
+ * (PWM_DATATYPE2)vpwm) + 127) / 255;
+ // cool white is low at 0, high at 255
+ c = (((PWM_DATATYPE2)mytint
+ * (PWM_DATATYPE2)vpwm) + 127) / 255;
+
+ RED_PWM_LVL = a;
+ WARM_PWM_LVL = b;
+ COOL_PWM_LVL = c;
+}
+
+
+// "white + red" channel mode
+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);
+ 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);
+ }
+
+ // 0 = no red
+ // 255 = red at 100% of white channel PWM
+ uint8_t ratio = channel_mode_args[channel_mode];
+
+ PWM_DATATYPE red_pwm;
+ red_pwm = (((PWM_DATATYPE2)ratio * (PWM_DATATYPE2)vpwm) + 127) / 255;
+
+ RED_PWM_LVL = red_pwm;
+}
+
diff --git a/hwdef-Sofirn_LT1S-Pro.h b/hwdef-Sofirn_LT1S-Pro.h
index ce6ee3a..c52364f 100644
--- a/hwdef-Sofirn_LT1S-Pro.h
+++ b/hwdef-Sofirn_LT1S-Pro.h
@@ -1,6 +1,3 @@
-#ifndef HWDEF_SOFIRN_LT1S_PRO_H
-#define HWDEF_SOFIRN_LT1S_PRO_H
-
/* BLF LT1S Pro driver layout using the Attiny1616
Driver pinout:
@@ -13,8 +10,10 @@ Driver pinout:
*/
+#pragma once
-#define LAYOUT_DEFINED
+
+#define HWDEF_C_FILE hwdef-Sofirn_LT1S-Pro.c
#ifdef ATTINY
#undef ATTINY
@@ -22,40 +21,85 @@ Driver pinout:
#define ATTINY 1616
#include <avr/io.h>
+// channel modes:
+// * 0. warm/cool white blend
+// * 1. auto 3ch blend (red -> warm -> cool by ramp level)
+// * 2. red only
+// * 3. red + white blend
+#define NUM_CHANNEL_MODES 4
+//#define CHANNEL_MODES_ENABLED 1,1,1,1
+#define CHANNEL_MODES_ENABLED 0b00001111
+#define CM_WHITE 0
+#define CM_AUTO 1
+#define CM_RED 2
+#define CM_WHITE_RED 3
+
+// TODO: blend mode should enable this automatically?
+#define USE_CHANNEL_MODES
+// 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?
+#define CHANNEL_MODE_ARGS 128,0,0,255
+#define SET_LEVEL_MODES set_level_2ch_blend, \
+ set_level_auto_3ch_blend, \
+ set_level_1ch, \
+ 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
+//#define USE_SET_LEVEL_AUTO_3CH_BLEND
+#define USE_SET_LEVEL_1CH
+//#define USE_SET_LEVEL_RED_WHITE_BLEND
+// TODO:
+//#define USE_GRADUAL_TICK_2CH_BLEND
+//#define USE_GRADUAL_TICK_AUTO_3CH_BLEND
+//#define USE_GRADUAL_TICK_1CH
+//#define USE_GRADUAL_TICK_RED_WHITE_BLEND
+
+#define DEFAULT_CHANNEL_MODE CM_AUTO
+
+#define FACTORY_RESET_WARN_CHANNEL CM_RED
+#define FACTORY_RESET_SUCCESS_CHANNEL CM_WHITE
+
+#define POLICE_COLOR_STROBE_CH1 CM_RED
+#define POLICE_COLOR_STROBE_CH2 CM_WHITE
+
+// TODO: remove this as soon as it's not needed
#define PWM_CHANNELS 1
-#ifndef SWITCH_PIN
#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
-#endif
-
-// usually PWM1_LVL would be a hardware register, but we need to abstract
-// it out to a soft brightness value, in order to handle tint ramping
-// (this allows smooth thermal regulation to work, and makes things
-// otherwise simpler and easier)
-uint8_t PWM1_LVL;
// warm tint channel
-#ifndef PWM1_PIN
-#define PWM1_PIN PB0 //
-#define TINT1_LVL TCA0.SINGLE.CMP0 // CMP1 is the output compare register for PB0
-#endif
+#define WARM_PWM_PIN PB0
+#define WARM_PWM_LVL TCA0.SINGLE.CMP0 // CMP1 is the output compare register for PB0
// cold tint channel
-#ifndef PWM2_PIN
-#define PWM2_PIN PB1 //
-#define TINT2_LVL TCA0.SINGLE.CMP1 // CMP0 is the output compare register for PB1
-#endif
+#define COOL_PWM_PIN PB1
+#define COOL_PWM_LVL TCA0.SINGLE.CMP1 // CMP0 is the output compare register for PB1
// red channel
-#ifndef PWM3_PIN
-#define PWM3_PIN PB0 //
-#define PWM3_LVL TCA0.SINGLE.CMP2 // CMP2 is the output compare register for PB2
-#endif
+#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
+
+// only using 8-bit on this light
+#define PWM_GET PWM_GET8
+#define PWM_DATATYPE uint8_t
+#define BLEND_PWM_DATATYPE uint8_t
+
// average drop across diode on this hardware
#ifndef VOLTAGE_FUDGE_FACTOR
@@ -64,14 +108,20 @@ uint8_t PWM1_LVL;
// lighted button
-#ifndef AUXLED_PIN
#define AUXLED_PIN PIN5_bp
#define AUXLED_PORT PORTB
-#endif
+
+// the button lights up
+#define USE_INDICATOR_LED
+// the button is visible while main LEDs are on
+#define USE_INDICATOR_LED_WHILE_RAMPING
+
+
+// custom channel modes
+void set_level_auto_3ch_blend(uint8_t level);
+void set_level_red_white_blend(uint8_t level);
-// with so many pins, doing this all with #ifdefs gets awkward...
-// ... so just hardcode it in each hwdef file instead
inline void hwdef_setup() {
// set up the system clock to run at 10 MHz instead of the default 3.33 MHz
@@ -111,4 +161,5 @@ inline void hwdef_setup() {
}
-#endif
+#define LAYOUT_DEFINED
+
diff --git a/spaghetti-monster/anduril/anduril.c b/spaghetti-monster/anduril/anduril.c
index 47cd00f..07560bc 100644
--- a/spaghetti-monster/anduril/anduril.c
+++ b/spaghetti-monster/anduril/anduril.c
@@ -54,10 +54,10 @@
/********* specific settings for known driver types *********/
// Anduril config file name (set it here or define it at the gcc command line)
-//#define CONFIGFILE cfg-blf-q8.h
+//#define CFG_H cfg-blf-q8.h
#include "tk.h"
-#include incfile(CONFIGFILE)
+#include incfile(CFG_H)
/********* Include headers which need to be before FSM *********/
@@ -92,8 +92,11 @@
#include "spaghetti-monster.h"
/********* does this build target have special code to include? *********/
-#ifdef OVERRIDES_FILE
-#include incfile(OVERRIDES_FILE)
+#ifdef HWDEF_C_FILE
+#include incfile(HWDEF_C_FILE)
+#endif
+#ifdef CFG_C_FILE
+#include incfile(CFG_C_FILE)
#endif
@@ -138,8 +141,10 @@
#include "tactical-mode.h"
#endif
-#ifdef USE_TINT_RAMPING
-#include "tint-ramping.h"
+// allow the channel mode handler even when only 1 mode
+// (so a tint ramp light could still use 3H even if there's no other mode)
+#if defined(USE_CHANNEL_MODES)
+#include "channel-modes.h"
#endif
#ifdef USE_FACTORY_RESET
@@ -197,8 +202,8 @@
#include "tactical-mode.c"
#endif
-#ifdef USE_TINT_RAMPING
-#include "tint-ramping.c"
+#if defined(USE_CHANNEL_MODES)
+#include "channel-modes.c"
#endif
#ifdef USE_FACTORY_RESET
@@ -234,13 +239,13 @@ void setup() {
#if defined(USE_MANUAL_MEMORY) && defined(USE_MANUAL_MEMORY_TIMER)
// without this, initial boot-up brightness is wrong
// when manual mem is enabled with a non-zero timer
- if (manual_memory) memorized_level = manual_memory;
+ if (manual_memory) manual_memory_restore();
#endif
- #ifdef USE_TINT_RAMPING
- // add tint ramping underneath every other state
- push_state(tint_ramping_state, 0);
- #endif // ifdef USE_TINT_RAMPING
+ #if defined(USE_CHANNEL_MODES)
+ // add channel mode functions underneath every other state
+ push_state(channel_mode_state, 0);
+ #endif
push_state(off_state, 1);
@@ -250,10 +255,10 @@ void setup() {
// power clicky acts as a momentary mode
load_config();
- #ifdef USE_TINT_RAMPING
- // add tint ramping underneath every other state
- push_state(tint_ramping_state, 0);
- #endif // ifdef USE_TINT_RAMPING
+ #if defined(USE_CHANNEL_MODES)
+ // add channel mode functions underneath every other state
+ push_state(channel_mode_state, 0);
+ #endif
if (button_is_pressed())
// hold button to go to moon
diff --git a/spaghetti-monster/anduril/build-all.sh b/spaghetti-monster/anduril/build-all.sh
index b9f6d15..768559c 100755
--- a/spaghetti-monster/anduril/build-all.sh
+++ b/spaghetti-monster/anduril/build-all.sh
@@ -33,8 +33,8 @@ for TARGET in cfg-*.h ; do
if [ -z "$ATTINY" ]; then ATTINY=85 ; fi
# try to compile
- echo ../../../bin/build.sh $ATTINY "$UI" "-DCONFIGFILE=${TARGET}"
- ../../../bin/build.sh $ATTINY "$UI" "-DCONFIGFILE=${TARGET}"
+ echo ../../../bin/build.sh $ATTINY "$UI" "-DCFG_H=${TARGET}"
+ ../../../bin/build.sh $ATTINY "$UI" "-DCFG_H=${TARGET}"
# track result, and rename compiled files
if [ 0 = $? ] ; then
diff --git a/spaghetti-monster/anduril/candle-mode.c b/spaghetti-monster/anduril/candle-mode.c
index d15195e..12ffa84 100644
--- a/spaghetti-monster/anduril/candle-mode.c
+++ b/spaghetti-monster/anduril/candle-mode.c
@@ -28,7 +28,7 @@
uint8_t candle_mode_state(Event event, uint16_t arg) {
static int8_t ramp_direction = 1;
- #define MAX_CANDLE_LEVEL (RAMP_LENGTH-CANDLE_AMPLITUDE-15)
+ #define MAX_CANDLE_LEVEL (MAX_LEVEL-CANDLE_AMPLITUDE-15)
static uint8_t candle_wave1 = 0;
static uint8_t candle_wave2 = 0;
static uint8_t candle_wave3 = 0;
diff --git a/spaghetti-monster/anduril/cfg-emisar-d4v2-219.h b/spaghetti-monster/anduril/cfg-emisar-d4v2-219.h
index 414971a..dad84f0 100644
--- a/spaghetti-monster/anduril/cfg-emisar-d4v2-219.h
+++ b/spaghetti-monster/anduril/cfg-emisar-d4v2-219.h
@@ -4,9 +4,10 @@
#define MODEL_NUMBER "0114"
// ATTINY: 1634
-// don't turn off first channel at turbo level
-#undef PWM1_LEVELS
-#define PWM1_LEVELS 1,1,2,2,3,3,4,4,5,6,7,8,9,10,12,13,14,15,17,19,20,22,24,26,29,31,34,36,39,42,45,48,51,55,59,62,66,70,75,79,84,89,93,99,104,110,115,121,127,134,140,147,154,161,168,176,184,192,200,209,217,226,236,245,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255
-// 65% FET power
-#undef PWM2_LEVELS
-#define PWM2_LEVELS 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,2,3,4,5,6,7,8,9,10,11,12,13,13,15,16,17,18,19,21,22,23,25,26,27,28,30,32,33,34,36,38,39,41,42,44,46,47,49,51,53,55,57,59,61,63,65,67,69,71,73,75,78,80,82,84,87,90,92,94,97,99,102,104,108,110,113,116,119,121,125,127,130,134,136,140,143,146,149,153,156,159,163,166
+// don't turn off the low channel at turbo level
+#undef LOW_PWM_LEVELS
+#define LOW_PWM_LEVELS 1,1,2,2,3,3,4,4,5,6,7,8,9,10,12,13,14,15,17,19,20,22,24,26,29,31,34,36,39,42,45,48,51,55,59,62,66,70,75,79,84,89,93,99,104,110,115,121,127,134,140,147,154,161,168,176,184,192,200,209,217,226,236,245,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255
+// 65% DDFET power
+#undef HIGH_PWM_LEVELS
+#define HIGH_PWM_LEVELS 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,2,3,4,5,6,7,8,9,10,11,12,13,13,15,16,17,18,19,21,22,23,25,26,27,28,30,32,33,34,36,38,39,41,42,44,46,47,49,51,53,55,57,59,61,63,65,67,69,71,73,75,78,80,82,84,87,90,92,94,97,99,102,104,108,110,113,116,119,121,125,127,130,134,136,140,143,146,149,153,156,159,163,166
+
diff --git a/spaghetti-monster/anduril/cfg-emisar-d4v2-nofet.h b/spaghetti-monster/anduril/cfg-emisar-d4v2-nofet.h
index 717afcf..5e33a05 100644
--- a/spaghetti-monster/anduril/cfg-emisar-d4v2-nofet.h
+++ b/spaghetti-monster/anduril/cfg-emisar-d4v2-nofet.h
@@ -4,11 +4,23 @@
#define MODEL_NUMBER "0115"
// ATTINY: 1634
+// switch to 1-channel support functions
+#undef USE_SET_LEVEL_2CH_STACKED
+#undef USE_GRADUAL_TICK_2CH_STACKED
+#define USE_SET_LEVEL_1CH
+#define USE_GRADUAL_TICK_1CH
+#undef SET_LEVEL_MODES
+#undef GRADUAL_TICK_MODES
+#define SET_LEVEL_MODES set_level_1ch
+#define GRADUAL_TICK_MODES gradual_tick_1ch
+
#undef PWM_CHANNELS
#define PWM_CHANNELS 1
-#undef PWM1_LEVELS
-#undef PWM2_LEVELS
-#define PWM1_LEVELS 1,1,1,2,2,2,2,3,3,3,3,4,4,5,5,6,6,6,7,8,8,9,9,10,10,11,12,13,13,14,15,16,16,17,18,19,20,21,22,23,23,24,26,27,28,29,30,31,32,33,34,36,37,38,39,41,42,43,45,46,47,49,50,52,53,55,56,58,59,61,62,64,66,67,69,71,72,74,76,78,80,81,83,85,87,89,91,93,95,97,99,101,103,105,107,109,111,113,116,118,120,122,125,127,129,132,134,136,139,141,144,146,148,151,154,156,159,161,164,166,169,172,174,177,180,183,185,188,191,194,197,200,203,205,208,211,214,217,220,223,226,230,233,236,239,242,245,249,252,255
+
+#undef LOW_PWM_LEVELS
+#undef HIGH_PWM_LEVELS
+#define LOW_PWM_LEVELS 1,1,1,2,2,2,2,3,3,3,3,4,4,5,5,6,6,6,7,8,8,9,9,10,10,11,12,13,13,14,15,16,16,17,18,19,20,21,22,23,23,24,26,27,28,29,30,31,32,33,34,36,37,38,39,41,42,43,45,46,47,49,50,52,53,55,56,58,59,61,62,64,66,67,69,71,72,74,76,78,80,81,83,85,87,89,91,93,95,97,99,101,103,105,107,109,111,113,116,118,120,122,125,127,129,132,134,136,139,141,144,146,148,151,154,156,159,161,164,166,169,172,174,177,180,183,185,188,191,194,197,200,203,205,208,211,214,217,220,223,226,230,233,236,239,242,245,249,252,255
+
#undef MAX_1x7135
#define MAX_1x7135 150
#undef QUARTERSPEED_LEVEL
@@ -39,4 +51,7 @@
#undef THERM_FASTER_LEVEL
#define THERM_FASTER_LEVEL 150
-#undef USE_THERMAL_REGULATION
+// maybe keep this, in case someone uses a higher power channel?
+//#undef USE_THERMAL_REGULATION
+//#undef USE_SET_LEVEL_GRADUALLY
+
diff --git a/spaghetti-monster/anduril/cfg-emisar-d4v2.h b/spaghetti-monster/anduril/cfg-emisar-d4v2.h
index 4121465..54de297 100644
--- a/spaghetti-monster/anduril/cfg-emisar-d4v2.h
+++ b/spaghetti-monster/anduril/cfg-emisar-d4v2.h
@@ -1,28 +1,19 @@
// Emisar D4 config options for Anduril
+// Copyright (C) 2019 Selene ToyKeeper
+// SPDX-License-Identifier: GPL-3.0-or-later
+
#define MODEL_NUMBER "0113"
#include "hwdef-Emisar_D4v2.h"
#include "hank-cfg.h"
// ATTINY: 1634
-// this light has three aux LED channels: R, G, B
-#define USE_AUX_RGB_LEDS
-// it also has an independent LED in the button
-#define USE_BUTTON_LED
-// the aux LEDs are front-facing, so turn them off while main LEDs are on
-// TODO: the whole "indicator LED" thing needs to be refactored into
-// "aux LED(s)" and "button LED(s)" since they work a bit differently
-#ifdef USE_INDICATOR_LED_WHILE_RAMPING
-#undef USE_INDICATOR_LED_WHILE_RAMPING
-#endif
-
-
// copied from original D4, since it's also a FET+1 and has the same host
// ../../bin/level_calc.py 1 65 7135 1 0.8 150
// ... mixed with this:
// ../../bin/level_calc.py 2 150 7135 4 0.33 150 FET 1 10 1500
-#define RAMP_LENGTH 150
-#define PWM1_LEVELS 1,1,2,2,3,3,4,4,5,6,7,8,9,10,12,13,14,15,17,19,20,22,24,26,29,31,34,36,39,42,45,48,51,55,59,62,66,70,75,79,84,89,93,99,104,110,115,121,127,134,140,147,154,161,168,176,184,192,200,209,217,226,236,245,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,0
-#define PWM2_LEVELS 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,3,4,5,7,8,9,11,12,14,15,17,19,20,22,24,25,27,29,31,33,35,37,39,41,43,45,48,50,52,55,57,59,62,64,67,70,72,75,78,81,84,87,90,93,96,99,102,105,109,112,115,119,122,126,129,133,137,141,144,148,152,156,160,165,169,173,177,182,186,191,195,200,205,209,214,219,224,229,234,239,244,250,255
+#define RAMP_SIZE 150
+#define LOW_PWM_LEVELS 1,1,2,2,3,3,4,4,5,6,7,8,9,10,12,13,14,15,17,19,20,22,24,26,29,31,34,36,39,42,45,48,51,55,59,62,66,70,75,79,84,89,93,99,104,110,115,121,127,134,140,147,154,161,168,176,184,192,200,209,217,226,236,245,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,0
+#define HIGH_PWM_LEVELS 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,3,4,5,7,8,9,11,12,14,15,17,19,20,22,24,25,27,29,31,33,35,37,39,41,43,45,48,50,52,55,57,59,62,64,67,70,72,75,78,81,84,87,90,93,96,99,102,105,109,112,115,119,122,126,129,133,137,141,144,148,152,156,160,165,169,173,177,182,186,191,195,200,205,209,214,219,224,229,234,239,244,250,255
#define MAX_1x7135 65
#define HALFSPEED_LEVEL 14
#define QUARTERSPEED_LEVEL 6
diff --git a/spaghetti-monster/anduril/cfg-sofirn-lt1s-pro.h b/spaghetti-monster/anduril/cfg-sofirn-lt1s-pro.h
index 4e5993b..e009e02 100644
--- a/spaghetti-monster/anduril/cfg-sofirn-lt1s-pro.h
+++ b/spaghetti-monster/anduril/cfg-sofirn-lt1s-pro.h
@@ -1,19 +1,9 @@
// Sofirn LT1S Pro
+
#define MODEL_NUMBER "0623"
#include "hwdef-Sofirn_LT1S-Pro.h"
// ATTINY: 1616
-// this model requires some special code
-#define OVERRIDES_FILE cfg-sofirn-lt1s-pro.c
-#define OVERRIDE_SET_LEVEL
-inline void set_level_override(uint8_t level);
-
-// uses 4C action while On to switch between white and red channels (overrides lockout action)
-#define USE_OUTPUT_MUX
-// 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: high (1)
// lockout: blinking (3)
#define INDICATOR_LED_DEFAULT_MODE ((3<<2) + 1)
@@ -21,19 +11,23 @@ inline void set_level_override(uint8_t level);
// 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
+//#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%
-#ifdef RAMP_LENGTH
-#undef RAMP_LENGTH
-#endif
-
// level_calc.py 1 150 7135 1 30 800
-#define RAMP_LENGTH 150
-#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 RAMP_SIZE 150
+// TODO: 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
+// 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
#define QUARTERSPEED_LEVEL 5
@@ -68,8 +62,11 @@ inline void set_level_override(uint8_t level);
#define USE_SOS_MODE
#define USE_SOS_MODE_IN_BLINKY_GROUP
-// the sensor (attiny) is nowhere near the emitters
-// so thermal regulation can't work
+#define USE_POLICE_COLOR_STROBE_MODE
+#undef TACTICAL_LEVELS
+#define TACTICAL_LEVELS 120,30,(RAMP_SIZE+3) // high, low, police strobe
+
+// FIXME: thermal regulation should actually work fine on this light
#ifdef USE_THERMAL_REGULATION
#undef USE_THERMAL_REGULATION
#endif
@@ -84,7 +81,7 @@ inline void set_level_override(uint8_t level);
#ifdef BLINK_AT_RAMP_CEIL
#undef BLINK_AT_RAMP_CEIL
#endif
+// without this, it's really hard to tell when ramping up stops
+#define BLINK_AT_RAMP_CEIL
-#ifndef USE_SOFT_FACTORY_RESET
#define USE_SOFT_FACTORY_RESET
-#endif \ No newline at end of file
diff --git a/spaghetti-monster/anduril/channel-modes.c b/spaghetti-monster/anduril/channel-modes.c
new file mode 100644
index 0000000..958b375
--- /dev/null
+++ b/spaghetti-monster/anduril/channel-modes.c
@@ -0,0 +1,146 @@
+/*
+ * channel-modes.c: Multi-channel functions for Anduril.
+ * Copyright (C) 2017-2023 Selene ToyKeeper
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#pragma once
+
+#include "channel-modes.h"
+
+
+uint8_t channel_mode_state(Event event, uint16_t arg) {
+ #ifdef USE_CHANNEL_MODE_ARGS
+ static int8_t tint_ramp_direction = 1;
+ static uint8_t prev_tint = 0;
+ // don't activate auto-tint modes unless the user hits the edge
+ // and keeps pressing for a while
+ static uint8_t past_edge_counter = 0;
+ // bugfix: click-click-hold from off to strobes would invoke tint ramping
+ // in addition to changing state... so ignore any tint-ramp events which
+ // don't look like they were meant to be here
+ static uint8_t active = 0;
+ uint8_t tint = channel_mode_args[channel_mode];
+ #endif
+
+ // it's possible that a light may need 3H but not 3C,
+ // so try to detect if 3C is needed
+ #if NUM_CHANNEL_MODES > 1
+ // 3 clicks: next channel mode
+ if (event == EV_3clicks) {
+ uint8_t next = channel_mode;
+ // go to next channel mode until we find one which is enabled
+ // (and don't do any infinite loops if the user disabled them all)
+ uint8_t count = 0;
+ do {
+ count ++;
+ next = (next + 1) % NUM_CHANNEL_MODES;
+ } while ((! channel_mode_enabled(next)) && count < NUM_CHANNEL_MODES);
+ //} while ((! channel_modes_enabled[next]) && count < NUM_CHANNEL_MODES);
+
+ // undo change if infinite loop detected (redundant?)
+ //if (NUM_CHANNEL_MODES == count) next = channel_mode;
+
+ // if mode hasn't changed, abort
+ if (channel_mode == next)
+ return EVENT_NOT_HANDLED;
+
+ set_channel_mode(next);
+
+ // remember after battery changes
+ save_config();
+ return EVENT_HANDLED;
+ } else
+ #endif // if NUM_CHANNEL_MODES > 1
+
+ #ifdef USE_CUSTOM_CHANNEL_3H_MODES
+ // defer to mode-specific function if defined
+ if (tint_ramp_modes[channel_mode]) {
+ StatePtr tint_func = channel_3H_modes[channel_mode];
+ return tint_func(channel_mode);
+ } else
+ #endif
+ #ifdef USE_CHANNEL_MODE_ARGS
+ #ifndef DONT_USE_DEFAULT_CHANNEL_ARG_MODE
+ // click, click, hold: change the current channel's arg (like tint)
+ if (event == EV_click3_hold) {
+ ///// adjust value from 0 to 255
+ // reset at beginning of movement
+ if (! arg) {
+ active = 1; // first frame means this is for us
+ past_edge_counter = 0; // doesn't start until user hits the edge
+ }
+ // ignore event if we weren't the ones who handled the first frame
+ if (! active) return EVENT_NOT_HANDLED;
+
+ // change normal tints
+ if ((tint_ramp_direction > 0) && (tint < 255)) {
+ tint += 1;
+ }
+ else if ((tint_ramp_direction < 0) && (tint > 0)) {
+ tint -= 1;
+ }
+ // if tint change stalled, let user know we hit the edge
+ else if (prev_tint == tint) {
+ if (past_edge_counter == 0) blip();
+ past_edge_counter = 1;
+ }
+ prev_tint = tint;
+ channel_mode_args[channel_mode] = tint;
+ set_level(actual_level);
+ return EVENT_HANDLED;
+ }
+
+ // click, click, hold, release: reverse direction for next ramp
+ else if (event == EV_click3_hold_release) {
+ active = 0; // ignore next hold if it wasn't meant for us
+ // reverse
+ tint_ramp_direction = -tint_ramp_direction;
+ if (0 == tint) tint_ramp_direction = 1;
+ else if (255 == tint) tint_ramp_direction = -1;
+ // remember tint after battery change
+ channel_mode_args[channel_mode] = tint;
+ save_config();
+ // bug?: for some reason, brightness can seemingly change
+ // from 1/150 to 2/150 without this next line... not sure why
+ set_level(actual_level);
+ return EVENT_HANDLED;
+ }
+ #endif // ifndef DONT_USE_DEFAULT_CHANNEL_ARG_MODE
+ #endif // ifdef USE_CHANNEL_MODE_ARGS
+
+ #if defined(USE_SIMPLE_UI)
+ // remaining mappings aren't "simple", so stop here
+ if (simple_ui_active) {
+ return EVENT_NOT_HANDLED;
+ }
+ #endif
+
+ #if NUM_CHANNEL_MODES > 1
+ // channel toggle menu on ... 9H?
+ else if (event == EV_click9_hold) {
+ push_state(channel_mode_config_state, 0);
+ return MISCHIEF_MANAGED;
+ }
+ #endif
+
+ return EVENT_NOT_HANDLED;
+}
+
+#if NUM_CHANNEL_MODES > 1
+void channel_mode_config_save(uint8_t step, uint8_t value) {
+ // 1 menu item per channel mode, to enable or disable that mode
+ step --; // step is 1-based, channel modes are 0-based
+ if (value) channel_mode_enable(step);
+ else channel_mode_disable(step);
+}
+
+uint8_t channel_mode_config_state(Event event, uint16_t arg) {
+ // 1 menu item per channel mode, to enable or disable that mode
+ return config_state_base(
+ event, arg,
+ NUM_CHANNEL_MODES,
+ channel_mode_config_save
+ );
+}
+#endif
diff --git a/spaghetti-monster/anduril/channel-modes.h b/spaghetti-monster/anduril/channel-modes.h
new file mode 100644
index 0000000..f536d58
--- /dev/null
+++ b/spaghetti-monster/anduril/channel-modes.h
@@ -0,0 +1,20 @@
+/*
+ * channel-modes.h: Multi-channel functions for Anduril.
+ * Copyright (C) 2017-2023 Selene ToyKeeper
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#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
+
+// not actually a mode, more of a fallback under other modes
+uint8_t channel_mode_state(Event event, uint16_t arg);
+
+#if NUM_CHANNEL_MODES > 1
+uint8_t channel_mode_config_state(Event event, uint16_t arg);
+#endif
diff --git a/spaghetti-monster/anduril/factory-reset.c b/spaghetti-monster/anduril/factory-reset.c
index ecb4cc2..a14b5b9 100644
--- a/spaghetti-monster/anduril/factory-reset.c
+++ b/spaghetti-monster/anduril/factory-reset.c
@@ -1,27 +1,15 @@
-/*
- * factory-reset.c: Factory reset functions for Anduril.
- *
- * Copyright (C) 2017 Selene ToyKeeper
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- */
+// factory-reset.c: Factory reset functions for Anduril.
+// Copyright (C) 2017-2023 Selene ToyKeeper
+// SPDX-License-Identifier: GPL-3.0-or-later
-#ifndef FACTORY_RESET_C
-#define FACTORY_RESET_C
+#pragma once
#include "factory-reset.h"
+// allows setting channel mode per animation stage,
+// so it can ramp up in red then explode in white (as one example)
+// TODO: maybe also do the same in menus?
+
void factory_reset() {
// display a warning for a few seconds before doing the actual reset,
// so the user has time to abort if they want
@@ -31,6 +19,9 @@ void factory_reset() {
uint8_t bright;
uint8_t reset = 1;
// wind up to an explosion
+ #ifdef FACTORY_RESET_WARN_CHANNEL
+ set_channel_mode(FACTORY_RESET_WARN_CHANNEL);
+ #endif
for (bright=0; bright<SPLODEY_STEPS; bright++) {
set_level(bright);
nice_delay_ms(SPLODEY_TIME_PER_STEP/2);
@@ -43,17 +34,29 @@ void factory_reset() {
}
// explode, if button pressed long enough
if (reset) {
+ // auto-calibrate temperature
// AVR 1-Series has factory calibrated thermal sensor, always remove the offset on reset
#if defined(USE_THERMAL_REGULATION) && defined(AVRXMEGA3)
- thermal_config_save(1,temperature - therm_cal_offset); // this will cancel out the offset
+ // this will cancel out the offset
+ thermal_config_save(1, temperature - therm_cal_offset);
#elif defined(USE_THERMAL_REGULATION) && defined(USE_THERM_AUTOCALIBRATE)
- // auto-calibrate temperature... assume current temperature is 21 C
+ // assume current temperature is 21 C
thermal_config_save(1, 21);
#endif
+
+ #if defined(FACTORY_RESET_WARN_CHANNEL) && defined(DEFAULT_CHANNEL_MODE) && (FACTORY_RESET_WARN_CHANNEL != DEFAULT_CHANNEL_MODE)
+ // return to default channel before saving
+ set_channel_mode(DEFAULT_CHANNEL_MODE);
+ #endif
+
// save all settings to eeprom
// (assuming they're all at default because we haven't loaded them yet)
save_config();
+ // explosion animation
+ #ifdef FACTORY_RESET_SUCCESS_CHANNEL
+ set_channel_mode(FACTORY_RESET_SUCCESS_CHANNEL);
+ #endif
bright = MAX_LEVEL;
for (; bright > 0; bright--) {
set_level(bright);
@@ -69,6 +72,3 @@ void factory_reset() {
}
}
-
-#endif
-
diff --git a/spaghetti-monster/anduril/factory-reset.h b/spaghetti-monster/anduril/factory-reset.h
index 9f0af38..63c25cd 100644
--- a/spaghetti-monster/anduril/factory-reset.h
+++ b/spaghetti-monster/anduril/factory-reset.h
@@ -1,26 +1,8 @@
-/*
- * factory-reset.h: Factory reset functions for Anduril.
- *
- * Copyright (C) 2017 Selene ToyKeeper
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- */
+// factory-reset.h: Factory reset functions for Anduril.
+// Copyright (C) 2017-2023 Selene ToyKeeper
+// SPDX-License-Identifier: GPL-3.0-or-later
-#ifndef FACTORY_RESET_H
-#define FACTORY_RESET_H
+#pragma once
void factory_reset();
-
-#endif
diff --git a/spaghetti-monster/anduril/hank-cfg.h b/spaghetti-monster/anduril/hank-cfg.h
index 11eb0a1..f24ea67 100644
--- a/spaghetti-monster/anduril/hank-cfg.h
+++ b/spaghetti-monster/anduril/hank-cfg.h
@@ -1,5 +1,7 @@
-#ifndef HANK_CFG
-#define HANK_CFG
+// Intl-Outdoor (Hank)'s config options for Anduril
+// Copyright (C) 2021 Selene ToyKeeper
+// SPDX-License-Identifier: GPL-3.0-or-later
+#pragma once
// config preferences for Hank Wang of Intl-Outdoor (Emisar, Noctigon)
@@ -18,4 +20,3 @@
// double click while on goes to full-power turbo, not ramp ceiling
#define DEFAULT_2C_STYLE 1
-#endif // ifndef HANK_CFG
diff --git a/spaghetti-monster/anduril/load-save-config-fsm.h b/spaghetti-monster/anduril/load-save-config-fsm.h
index f0de161..9004f1f 100644
--- a/spaghetti-monster/anduril/load-save-config-fsm.h
+++ b/spaghetti-monster/anduril/load-save-config-fsm.h
@@ -54,13 +54,6 @@ typedef enum {
#ifdef USE_MANUAL_MEMORY_TIMER
manual_memory_timer_e,
#endif
- #ifdef USE_TINT_RAMPING
- manual_memory_tint_e,
- #endif
- #endif
- #ifdef USE_TINT_RAMPING
- tint_e,
- tint_style_e,
#endif
#ifdef USE_JUMP_START
jump_start_level_e,
@@ -100,7 +93,28 @@ typedef enum {
tactical_lvl_2_e,
tactical_lvl_3_e,
#endif
- eeprom_indexes_e_END
+ #if NUM_CHANNEL_MODES > 1
+ channel_mode_e,
+ channel_modes_enabled_e,
+ #if defined(USE_MANUAL_MEMORY)
+ manual_memory_channel_mode_e,
+ #endif
+ #endif
+ #ifdef USE_CHANNEL_MODE_ARGS
+ // this is an array, needs a few bytes
+ channel_mode_args_e,
+ #if defined(USE_MANUAL_MEMORY)
+ // this is an array, needs a few bytes
+ manual_memory_channel_args_e = channel_mode_args_e + NUM_CHANNEL_MODES,
+ // and this is an ugly ugly kludge
+ // FIXME: use a struct for eeprom, not an array
+ eeprom_indexes_e_END = manual_memory_channel_args_e + NUM_CHANNEL_MODES
+ #else
+ eeprom_indexes_e_END = channel_mode_args_e + NUM_CHANNEL_MODES
+ #endif
+ #else
+ eeprom_indexes_e_END
+ #endif
} eeprom_indexes_e;
#define EEPROM_BYTES eeprom_indexes_e_END
diff --git a/spaghetti-monster/anduril/load-save-config.c b/spaghetti-monster/anduril/load-save-config.c
index 4101e7a..ad84450 100644
--- a/spaghetti-monster/anduril/load-save-config.c
+++ b/spaghetti-monster/anduril/load-save-config.c
@@ -56,13 +56,6 @@ void load_config() {
#ifdef USE_MANUAL_MEMORY_TIMER
manual_memory_timer = eeprom[manual_memory_timer_e];
#endif
- #ifdef USE_TINT_RAMPING
- manual_memory_tint = eeprom[manual_memory_tint_e];
- #endif
- #endif
- #ifdef USE_TINT_RAMPING
- tint = eeprom[tint_e];
- tint_style = eeprom[tint_style_e];
#endif
#ifdef USE_JUMP_START
jump_start_level = eeprom[jump_start_level_e],
@@ -100,6 +93,21 @@ void load_config() {
tactical_levels[1] = eeprom[tactical_lvl_2_e];
tactical_levels[2] = eeprom[tactical_lvl_3_e];
#endif
+ #if NUM_CHANNEL_MODES > 1
+ channel_mode = eeprom[channel_mode_e];
+ channel_modes_enabled = eeprom[channel_modes_enabled_e];
+ #if defined(USE_MANUAL_MEMORY)
+ manual_memory_channel_mode = eeprom[manual_memory_channel_mode_e];
+ #endif
+ #endif
+ #ifdef USE_CHANNEL_MODE_ARGS
+ for (uint8_t i=0; i<NUM_CHANNEL_MODES; i++)
+ channel_mode_args[i] = eeprom[i + channel_mode_args_e];
+ #if defined(USE_MANUAL_MEMORY)
+ for (uint8_t i=0; i<NUM_CHANNEL_MODES; i++)
+ manual_memory_channel_args[i] = eeprom[i + manual_memory_channel_args_e];
+ #endif
+ #endif
}
#ifdef START_AT_MEMORIZED_LEVEL
if (load_eeprom_wl()) {
@@ -140,13 +148,6 @@ void save_config() {
#ifdef USE_MANUAL_MEMORY_TIMER
eeprom[manual_memory_timer_e] = manual_memory_timer;
#endif
- #ifdef USE_TINT_RAMPING
- eeprom[manual_memory_tint_e] = manual_memory_tint;
- #endif
- #endif
- #ifdef USE_TINT_RAMPING
- eeprom[tint_e] = tint;
- eeprom[tint_style_e] = tint_style;
#endif
#ifdef USE_JUMP_START
eeprom[jump_start_level_e] = jump_start_level,
@@ -184,6 +185,21 @@ void save_config() {
eeprom[tactical_lvl_2_e] = tactical_levels[1];
eeprom[tactical_lvl_3_e] = tactical_levels[2];
#endif
+ #if NUM_CHANNEL_MODES > 1
+ eeprom[channel_mode_e] = channel_mode;
+ eeprom[channel_modes_enabled_e] = channel_modes_enabled;
+ #if defined(USE_MANUAL_MEMORY)
+ eeprom[manual_memory_channel_mode_e] = manual_memory_channel_mode;
+ #endif
+ #endif
+ #ifdef USE_CHANNEL_MODE_ARGS
+ for (uint8_t i=0; i<NUM_CHANNEL_MODES; i++)
+ eeprom[i + channel_mode_args_e] = channel_mode_args[i];
+ #if defined(USE_MANUAL_MEMORY)
+ for (uint8_t i=0; i<NUM_CHANNEL_MODES; i++)
+ eeprom[i + manual_memory_channel_args_e] = manual_memory_channel_args[i];
+ #endif
+ #endif
save_eeprom();
}
diff --git a/spaghetti-monster/anduril/off-mode.c b/spaghetti-monster/anduril/off-mode.c
index 6781ade..68ab234 100644
--- a/spaghetti-monster/anduril/off-mode.c
+++ b/spaghetti-monster/anduril/off-mode.c
@@ -65,10 +65,7 @@ uint8_t off_state(Event event, uint16_t arg) {
// reset to manual memory level when timer expires
if (manual_memory &&
(arg >= (manual_memory_timer * SLEEP_TICKS_PER_MINUTE))) {
- memorized_level = manual_memory;
- #ifdef USE_TINT_RAMPING
- tint = manual_memory_tint;
- #endif
+ manual_memory_restore();
}
#endif
#ifdef USE_INDICATOR_LED
@@ -131,10 +128,7 @@ uint8_t off_state(Event event, uint16_t arg) {
// this clause probably isn't used by any configs any more
// but is included just in case someone configures it this way
if (manual_memory) {
- memorized_level = manual_memory;
- #ifdef USE_TINT_RAMPING
- tint = manual_memory_tint;
- #endif
+ manual_memory_restore();
}
#endif
set_level(nearest_level(memorized_level));
diff --git a/spaghetti-monster/anduril/ramp-mode.c b/spaghetti-monster/anduril/ramp-mode.c
index 6e8ba88..e57b68f 100644
--- a/spaghetti-monster/anduril/ramp-mode.c
+++ b/spaghetti-monster/anduril/ramp-mode.c
@@ -26,6 +26,7 @@
#include "sunset-timer.h"
#endif
+
uint8_t steady_state(Event event, uint16_t arg) {
static int8_t ramp_direction = 1;
#if (B_TIMING_OFF == B_RELEASE_T)
@@ -378,7 +379,26 @@ uint8_t steady_state(Event event, uint16_t arg) {
#endif
// 3 clicks: toggle smooth vs discrete ramping
- else if (event == EV_3clicks) {
+ // (and/or 6 clicks when there are multiple channel modes)
+ // (handle 3C here anyway, when all but 1 mode is disabled)
+ else if ((event == EV_3clicks)
+ #if NUM_CHANNEL_MODES > 1
+ || (event == EV_6clicks)
+ ) {
+ // detect if > 1 channel mode is enabled,
+ // and if so, fall through so channel mode code can handle it
+ // otherwise, change the ramp style
+ if (event == EV_3clicks) {
+ uint8_t enabled = 0;
+ for (uint8_t m=0; m<NUM_CHANNEL_MODES; m++)
+ enabled += channel_mode_enabled(m);
+ if (enabled > 1)
+ return EVENT_NOT_HANDLED;
+ }
+ #else
+ ) {
+ #endif
+
ramp_style = !ramp_style;
save_config();
#ifdef START_AT_MEMORIZED_LEVEL
@@ -400,9 +420,9 @@ uint8_t steady_state(Event event, uint16_t arg) {
}
#endif
- #ifndef USE_TINT_RAMPING
// 3H: momentary turbo (on lights with no tint ramping)
- else if (event == EV_click3_hold) {
+ // (or 4H on lights with tint ramping)
+ else if (event == EV_MOMENTARY_TURBO) {
if (! arg) { // first frame only, to allow thermal regulation to work
#ifdef USE_2C_STYLE_CONFIG
uint8_t tl = style_2c ? MAX_LEVEL : turbo_level;
@@ -413,11 +433,10 @@ uint8_t steady_state(Event event, uint16_t arg) {
}
return MISCHIEF_MANAGED;
}
- else if (event == EV_click3_hold_release) {
+ else if (event == EV_MOMENTARY_TURBO_RELEASE) {
set_level_and_therm_target(memorized_level);
return MISCHIEF_MANAGED;
}
- #endif // ifndef USE_TINT_RAMPING
#ifdef USE_MOMENTARY_MODE
// 5 clicks: shortcut to momentary mode
@@ -439,10 +458,7 @@ uint8_t steady_state(Event event, uint16_t arg) {
#ifdef USE_MANUAL_MEMORY
else if (event == EV_10clicks) {
// turn on manual memory and save current brightness
- manual_memory = actual_level;
- #ifdef USE_TINT_RAMPING
- manual_memory_tint = tint; // remember tint too
- #endif
+ manual_memory_save();
save_config();
blink_once();
return MISCHIEF_MANAGED;
@@ -643,6 +659,27 @@ void set_level_and_therm_target(uint8_t level) {
#define set_level_and_therm_target(level) set_level(level)
#endif
+void manual_memory_restore() {
+ memorized_level = manual_memory;
+ #if NUM_CHANNEL_MODES > 1
+ channel_mode = manual_memory_channel_mode;
+ #endif
+ #ifdef USE_CHANNEL_MODE_ARGS
+ for (uint8_t i=0; i<NUM_CHANNEL_MODES; i++)
+ channel_mode_args[i] = manual_memory_channel_args[i];
+ #endif
+}
+
+void manual_memory_save() {
+ manual_memory = actual_level;
+ #if NUM_CHANNEL_MODES > 1
+ manual_memory_channel_mode = channel_mode;
+ #endif
+ #ifdef USE_CHANNEL_MODE_ARGS
+ for (uint8_t i=0; i<NUM_CHANNEL_MODES; i++)
+ manual_memory_channel_args[i] = channel_mode_args[i];
+ #endif
+}
#endif
diff --git a/spaghetti-monster/anduril/ramp-mode.h b/spaghetti-monster/anduril/ramp-mode.h
index ed76ed5..969cb6b 100644
--- a/spaghetti-monster/anduril/ramp-mode.h
+++ b/spaghetti-monster/anduril/ramp-mode.h
@@ -72,6 +72,17 @@
#define B_TIMING_OFF B_TIMEOUT_T
#endif
+
+// move a couple actions depending on whether there are channel modes
+#ifdef USE_CHANNEL_MODE_ARGS
+ #define EV_MOMENTARY_TURBO EV_click4_hold
+ #define EV_MOMENTARY_TURBO_RELEASE EV_click4_hold_release
+#else
+ #define EV_MOMENTARY_TURBO EV_click3_hold
+ #define EV_MOMENTARY_TURBO_RELEASE EV_click3_hold_release
+#endif
+
+
// default ramp options if not overridden earlier per-driver
#ifndef RAMP_STYLE
#define RAMP_STYLE 0 // smooth default
@@ -102,6 +113,7 @@
// mile marker(s) partway up the ramp
// default: blink only at border between regulated and FET
#ifdef BLINK_AT_RAMP_MIDDLE
+ // FIXME: remove PWM_CHANNELS, use some other abstraction
#if PWM_CHANNELS >= 3
#ifndef BLINK_AT_RAMP_MIDDLE_1
#define BLINK_AT_RAMP_MIDDLE_1 MAX_Nx7135
@@ -166,6 +178,9 @@ uint8_t manual_memory = DEFAULT_MANUAL_MEMORY;
uint8_t manual_memory_timer = DEFAULT_MANUAL_MEMORY_TIMER;
#endif
#endif
+void manual_memory_restore();
+void manual_memory_save();
+
#ifdef USE_SIMPLE_UI
// whether to enable the simplified interface or not
uint8_t simple_ui_active = SIMPLE_UI_ACTIVE;
diff --git a/spaghetti-monster/anduril/strobe-modes.c b/spaghetti-monster/anduril/strobe-modes.c
index 78fe240..5ee2386 100644
--- a/spaghetti-monster/anduril/strobe-modes.c
+++ b/spaghetti-monster/anduril/strobe-modes.c
@@ -184,6 +184,12 @@ inline void strobe_state_iter() {
break;
#endif
+ #ifdef USE_POLICE_COLOR_STROBE_MODE
+ case police_color_strobe_e:
+ police_color_strobe_iter();
+ break;
+ #endif
+
#ifdef USE_LIGHTNING_MODE
case lightning_storm_e:
lightning_storm_iter();
@@ -205,7 +211,7 @@ inline void party_tactical_strobe_mode_iter(uint8_t st) {
uint8_t del = strobe_delays[st];
// TODO: make tac strobe brightness configurable?
set_level(STROBE_BRIGHTNESS);
- if (0) {} // placeholde0
+ if (0) {} // placeholder
#ifdef USE_PARTY_STROBE_MODE
else if (st == party_strobe_e) { // party strobe
#ifdef PARTY_STROBE_ONTIME
@@ -226,6 +232,28 @@ inline void party_tactical_strobe_mode_iter(uint8_t st) {
}
#endif
+#ifdef USE_POLICE_COLOR_STROBE_MODE
+inline void police_color_strobe_iter() {
+ // one iteration of main loop()
+ uint8_t del = 66;
+ // TODO: make police strobe brightness configurable
+ uint8_t bright = memorized_level;
+ uint8_t channel = channel_mode;
+
+ for (uint8_t i=0; i<10; i++) {
+ if (0 == i) set_channel_mode(POLICE_COLOR_STROBE_CH1);
+ else if (5 == i) set_channel_mode(POLICE_COLOR_STROBE_CH2);
+ set_level(bright);
+ nice_delay_ms(del >> 1);
+ set_level(STROBE_OFF_LEVEL);
+ nice_delay_ms(del);
+ }
+
+ // restore this when done
+ set_channel_mode(channel);
+}
+#endif
+
#ifdef USE_LIGHTNING_MODE
inline void lightning_storm_iter() {
// one iteration of main loop()
diff --git a/spaghetti-monster/anduril/strobe-modes.h b/spaghetti-monster/anduril/strobe-modes.h
index c6cfb53..685c249 100644
--- a/spaghetti-monster/anduril/strobe-modes.h
+++ b/spaghetti-monster/anduril/strobe-modes.h
@@ -29,6 +29,9 @@ typedef enum {
#ifdef USE_TACTICAL_STROBE_MODE
tactical_strobe_e,
#endif
+ #ifdef USE_POLICE_COLOR_STROBE_MODE
+ police_color_strobe_e,
+ #endif
#ifdef USE_LIGHTNING_MODE
lightning_storm_e,
#endif
@@ -81,6 +84,10 @@ uint8_t strobe_delays[] = { 41, 67 }; // party strobe 24 Hz, tactical strobe 10
inline void party_tactical_strobe_mode_iter(uint8_t st);
#endif
+#ifdef USE_POLICE_COLOR_STROBE_MODE
+inline void police_color_strobe_iter();
+#endif
+
#ifdef USE_LIGHTNING_MODE
inline void lightning_storm_iter();
#endif
diff --git a/spaghetti-monster/anduril/tint-ramping.c b/spaghetti-monster/anduril/tint-ramping.c
index d270d9d..13f5d29 100644
--- a/spaghetti-monster/anduril/tint-ramping.c
+++ b/spaghetti-monster/anduril/tint-ramping.c
@@ -1,24 +1,10 @@
/*
* tint-ramping.c: Tint ramping functions for Anduril.
- *
- * Copyright (C) 2017 Selene ToyKeeper
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ * Copyright (C) 2017-2023 Selene ToyKeeper
+ * SPDX-License-Identifier: GPL-3.0-or-later
*/
-#ifndef TINT_RAMPING_C
-#define TINT_RAMPING_C
+#pragma once
#include "tint-ramping.h"
@@ -100,6 +86,3 @@ uint8_t tint_ramping_state(Event event, uint16_t arg) {
return EVENT_NOT_HANDLED;
}
-
-#endif
-
diff --git a/spaghetti-monster/anduril/tint-ramping.h b/spaghetti-monster/anduril/tint-ramping.h
index 1c5e22a..9b7f9a8 100644
--- a/spaghetti-monster/anduril/tint-ramping.h
+++ b/spaghetti-monster/anduril/tint-ramping.h
@@ -1,24 +1,10 @@
/*
* tint-ramping.h: Tint ramping functions for Anduril.
- *
- * Copyright (C) 2017 Selene ToyKeeper
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ * Copyright (C) 2017-2023 Selene ToyKeeper
+ * SPDX-License-Identifier: GPL-3.0-or-later
*/
-#ifndef TINT_RAMPING_H
-#define TINT_RAMPING_H
+#pragma once
// 0: smooth tint ramp
// 1: toggle tint only between two extremes
@@ -35,5 +21,3 @@ uint8_t manual_memory_tint;
// not actually a mode, more of a fallback under other modes
uint8_t tint_ramping_state(Event event, uint16_t arg);
-
-#endif
diff --git a/spaghetti-monster/fsm-main.c b/spaghetti-monster/fsm-main.c
index 30b8a67..fca1e83 100644
--- a/spaghetti-monster/fsm-main.c
+++ b/spaghetti-monster/fsm-main.c
@@ -39,6 +39,7 @@ ISR(TIMER1_COMPA_vect) {
}
#endif
+// FIXME: hw_setup() shouldn't be here ... move it entirely to hwdef files
#if (ATTINY == 25) || (ATTINY == 45) || (ATTINY == 85)
static inline void hw_setup() {
// configure PWM channels
diff --git a/spaghetti-monster/fsm-misc.h b/spaghetti-monster/fsm-misc.h
index 66d31ba..17ed66f 100644
--- a/spaghetti-monster/fsm-misc.h
+++ b/spaghetti-monster/fsm-misc.h
@@ -53,6 +53,11 @@ void indicator_led(uint8_t lvl);
void button_led_set(uint8_t lvl);
#endif
+// if any type of aux LEDs exist, define a shorthand flag for it
+#if defined(USE_INDICATOR_LED) || defined(USE_AUX_RGB_LEDS) || defined(USE_BUTTON_LED)
+#define HAS_AUX_LEDS
+#endif
+
#ifdef USE_AUX_RGB_LEDS
// value: 0b00BBGGRR
// each pair of bits: 0=off, 1=low, 2=high
diff --git a/spaghetti-monster/fsm-ramping.c b/spaghetti-monster/fsm-ramping.c
index 63692c8..5096dfd 100644
--- a/spaghetti-monster/fsm-ramping.c
+++ b/spaghetti-monster/fsm-ramping.c
@@ -2,46 +2,28 @@
* fsm-ramping.c: Ramping functions for SpaghettiMonster.
* Handles 1- to 4-channel smooth ramping on a single LED.
*
- * Copyright (C) 2017 Selene ToyKeeper
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ * Copyright (C) 2017-2023 Selene ToyKeeper
+ * SPDX-License-Identifier: GPL-3.0-or-later
*/
-#ifndef FSM_RAMPING_C
-#define FSM_RAMPING_C
+#pragma once
#ifdef USE_RAMPING
-void set_level(uint8_t level) {
- #ifdef USE_JUMP_START
- // maybe "jump start" the engine, if it's prone to slow starts
- // (pulse the output high for a moment to wake up the power regulator)
- // (only do this when starting from off and going to a low level)
- if ((! actual_level)
- && level
- && (level < jump_start_level)) {
- set_level(jump_start_level);
- delay_4ms(JUMP_START_TIME/4);
- }
- #endif // ifdef USE_JUMP_START
+void set_channel_mode(uint8_t mode) {
+ uint8_t cur_level = actual_level;
+ // turn off old LEDs before changing channel
+ set_level(0);
- actual_level = level;
+ // change the channel
+ channel_mode = mode;
- #ifdef USE_SET_LEVEL_GRADUALLY
- gradual_target = level;
- #endif
+ // update the LEDs
+ set_level(cur_level);
+}
+#ifdef HAS_AUX_LEDS
+inline void set_level_aux_leds(uint8_t level) {
#ifdef USE_INDICATOR_LED_WHILE_RAMPING
// use side-facing aux LEDs while main LEDs are on
if (! go_to_standby) {
@@ -52,9 +34,6 @@ void set_level(uint8_t level) {
button_led_set((level > 0) + (level > DEFAULT_LEVEL));
#endif
}
- //if (level > MAX_1x7135) indicator_led(2);
- //else if (level > 0) indicator_led(1);
- //else if (! go_to_standby) indicator_led(0);
#else // turn off front-facing aux LEDs while main LEDs are on
#if defined(USE_INDICATOR_LED) || defined(USE_AUX_RGB_LEDS)
if (! go_to_standby) {
@@ -70,33 +49,173 @@ void set_level(uint8_t level) {
}
#endif
#endif
+}
+#endif // ifdef HAS_AUX_LEDS
- #ifdef OVERRIDE_SET_LEVEL
- set_level_override(level);
- #else
- #if defined(PWM1_CNT) && defined(PWM1_PHASE_RESET_ON) || defined(PWM1_PHASE_SYNC)
- static uint8_t prev_level = 0;
- uint8_t api_level = level;
+void set_level(uint8_t level) {
+ #ifdef USE_JUMP_START
+ // maybe "jump start" the engine, if it's prone to slow starts
+ // (pulse the output high for a moment to wake up the power regulator)
+ // (only do this when starting from off and going to a low level)
+ // TODO: allow different jump start behavior per channel mode
+ if ((! actual_level)
+ && level
+ && (level < jump_start_level)) {
+ set_level(jump_start_level);
+ delay_4ms(JUMP_START_TIME/4);
+ }
+ #endif
+
+ #ifdef HAS_AUX_LEDS
+ set_level_aux_leds(level);
+ #endif
+
+ // call the relevant hardware-specific set_level_*()
+ SetLevelFuncPtr set_level_func = channel_modes[channel_mode];
+ set_level_func(level);
+
+ actual_level = level;
+
+ #ifdef USE_SET_LEVEL_GRADUALLY
+ gradual_target = level;
+ #endif
+
+ #ifdef USE_DYNAMIC_UNDERCLOCKING
+ auto_clock_speed();
+ #endif
+}
+
+///// Common set_level_*() functions shared by multiple lights /////
+// (unique lights should use their own,
+// but these common versions cover most of the common hardware designs)
+
+#ifdef USE_SET_LEVEL_1CH
+// single set of LEDs with 1 power channel
+void set_level_1ch(uint8_t level) {
+ if (level == 0) {
+ LOW_PWM_LVL = 0;
+ } else {
+ level --; // PWM array index = level - 1
+ LOW_PWM_LVL = PWM_GET(low_pwm_levels, level);
+ }
+}
+#endif
+
+#ifdef USE_SET_LEVEL_2CH_STACKED
+// single set of LEDs with 2 stacked power channels, DDFET+1 or DDFET+linear
+void set_level_2ch_stacked(uint8_t level) {
+ if (level == 0) {
+ LOW_PWM_LVL = 0;
+ HIGH_PWM_LVL = 0;
+ } else {
+ level --; // PWM array index = level - 1
+ LOW_PWM_LVL = PWM_GET(low_pwm_levels, level);
+ HIGH_PWM_LVL = PWM_GET(high_pwm_levels, level);
+ }
+}
+#endif
+
+#ifdef USE_SET_LEVEL_3CH_STACKED
+// single set of LEDs with 3 stacked power channels, like DDFET+N+1
+void set_level_3ch_stacked(uint8_t level) {
+ if (level == 0) {
+ LOW_PWM_LVL = 0;
+ MED_PWM_LVL = 0;
+ HIGH_PWM_LVL = 0;
+ } else {
+ level --; // PWM array index = level - 1
+ LOW_PWM_LVL = PWM_GET(low_pwm_levels, level);
+ MED_PWM_LVL = PWM_GET(med_pwm_levels, level);
+ HIGH_PWM_LVL = PWM_GET(high_pwm_levels, level);
+ }
+}
+#endif
+
+// TODO: upgrade some older lights to dynamic PWM
+// TODO: 1ch w/ dynamic PWM
+// TODO: 1ch w/ dynamic PWM and opamp enable pins?
+// TODO: 2ch stacked w/ dynamic PWM
+// TODO: 2ch stacked w/ dynamic PWM and opamp enable pins?
+
+#ifdef USE_SET_LEVEL_2CH_BLEND
+// warm + cool blend w/ middle sag correction
+void set_level_2ch_blend(uint8_t level) {
+ #ifndef TINT_RAMPING_CORRECTION
+ #define TINT_RAMPING_CORRECTION 26 // 140% brightness at middle tint
+ #endif
+
+ BLEND_PWM_DATATYPE vpwm;
+
+ if (level == 0) {
+ vpwm = 0;
+ } else {
+ level --; // PWM array index = level - 1
+ vpwm = PWM_GET(blend_pwm_levels, level);
+ }
+
+ // calculate actual PWM levels based on a single-channel ramp
+ // and a global tint value
+ uint16_t brightness = vpwm;
+ uint16_t warm_PWM, cool_PWM;
+ const uint16_t top = PWM_TOP;
+
+ // auto-tint modes
+ 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
- //TCCR0A = PHASE;
+ 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;
+}
+#endif // ifdef USE_TINT_RAMPING
+
+#ifdef USE_LEGACY_SET_LEVEL
+// (this is mostly just here for reference, temporarily)
+// single set of LEDs with 1 to 3 stacked power channels,
+// like linear, FET+1, and FET+N+1
+// (default set_level_*() function for most lights)
+void set_level_legacy(uint8_t level) {
if (level == 0) {
#if PWM_CHANNELS >= 1
- PWM1_LVL = 0;
+ PWM1_LVL = 0;
#endif
#if PWM_CHANNELS >= 2
- PWM2_LVL = 0;
+ PWM2_LVL = 0;
#endif
#if PWM_CHANNELS >= 3
- PWM3_LVL = 0;
- #endif
- #if PWM_CHANNELS >= 4
- PWM4_LVL = 0;
- #endif
- #ifdef USE_TINT_RAMPING
- TINT1_LVL = 0;
- TINT2_LVL = 0;
+ PWM3_LVL = 0;
#endif
#if defined(PWM1_CNT) && defined(PWM1_PHASE_RESET_OFF)
PWM1_CNT = 0;
@@ -121,7 +240,6 @@ void set_level(uint8_t level) {
#endif
} else {
// enable the power channel, if relevant
- #ifndef USE_TINT_RAMPING // update_tint handles this better
#ifdef LED_ENABLE_PIN
#ifdef LED_ON_DELAY
uint8_t led_enable_port_save = LED_ENABLE_PORT;
@@ -161,7 +279,6 @@ void set_level(uint8_t level) {
delay_4ms(LED2_ON_DELAY/4);
#endif
#endif
- #endif // ifndef USE_TINT_RAMPING
// PWM array index = level - 1
level --;
@@ -175,9 +292,6 @@ void set_level(uint8_t level) {
#if PWM_CHANNELS >= 3
PWM3_LVL = PWM_GET(pwm3_levels, level);
#endif
- #if PWM_CHANNELS >= 4
- PWM4_LVL = PWM_GET(pwm4_levels, level);
- #endif
#ifdef USE_DYN_PWM
uint16_t top = PWM_GET(pwm_tops, level);
@@ -190,29 +304,15 @@ void set_level(uint8_t level) {
// (but don't wait when turning on from zero, because
// it'll reset the phase below anyway)
// to be safe, allow at least 32 cycles to update TOP
- while(prev_level && (PWM1_CNT > (top - 32))) {}
+ while(actual_level && (PWM1_CNT > (top - 32))) {}
#endif
// pulse frequency modulation, a.k.a. dynamic PWM
PWM1_TOP = top;
-
- // repeat for other channels if necessary
- #ifdef PMW2_TOP
- #if defined(PWM2_CNT) && defined(PWM2_PHASE_SYNC)
- while(prev_level && (PWM2_CNT > (top - 32))) {}
- #endif
- PWM2_TOP = top;
- #endif
- #ifdef PMW3_TOP
- #if defined(PWM3_CNT) && defined(PWM3_PHASE_SYNC)
- while(prev_level && (PWM3_CNT > (top - 32))) {}
- #endif
- PWM3_TOP = top;
- #endif
#endif // ifdef USE_DYN_PWM
#if defined(PWM1_CNT) && defined(PWM1_PHASE_RESET_ON)
// force reset phase when turning on from zero
// (because otherwise the initial response is inconsistent)
- if (! prev_level) {
+ if (! actual_level) {
PWM1_CNT = 0;
#if defined(PWM2_CNT) && defined(PWM2_PHASE_RESET_ON)
PWM2_CNT = 0;
@@ -223,114 +323,117 @@ void set_level(uint8_t level) {
}
#endif
}
- #ifdef USE_TINT_RAMPING
- update_tint();
- #endif
-
- #if defined(PWM1_CNT) && defined(PWM1_PHASE_RESET_ON) || defined(PWM1_PHASE_SYNC)
- prev_level = api_level;
- #endif
- #endif // ifdef OVERRIDE_SET_LEVEL
#ifdef USE_DYNAMIC_UNDERCLOCKING
auto_clock_speed();
#endif
}
+#endif
+
#ifdef USE_SET_LEVEL_GRADUALLY
inline void set_level_gradually(uint8_t lvl) {
gradual_target = lvl;
}
-#ifndef OVERRIDE_GRADUAL_TICK
+
// call this every frame or every few frames to change brightness very smoothly
void gradual_tick() {
- // go by only one ramp level at a time instead of directly to the target
- uint8_t gt = gradual_target;
- if (gt < actual_level) gt = actual_level - 1;
- else if (gt > actual_level) gt = actual_level + 1;
-
- /*
- #ifdef LED_ENABLE_PIN_LEVEL_MIN
- // only enable during part of the ramp
- if ((gt >= LED_ENABLE_PIN_LEVEL_MIN)
- && (gt <= LED_ENABLE_PIN_LEVEL_MAX))
- LED_ENABLE_PORT |= (1 << LED_ENABLE_PIN);
- else // disable during other parts of the ramp
- LED_ENABLE_PORT &= ~(1 << LED_ENABLE_PIN);
- #endif
- */
+ // call the relevant hardware-specific function
+ GradualTickFuncPtr gradual_tick_func = gradual_tick_modes[channel_mode];
+ gradual_tick_func();
+}
- gt --; // convert 1-based number to 0-based
+// reduce repetition with macros
+// common code at the beginning of every gradual tick handler
+#define GRADUAL_TICK_SETUP() \
+ uint8_t gt = gradual_target; \
+ if (gt < actual_level) gt = actual_level - 1; \
+ else if (gt > actual_level) gt = actual_level + 1; \
+ gt --; \
PWM_DATATYPE target;
- #if PWM_CHANNELS >= 1
- target = PWM_GET(pwm1_levels, gt);
- #if PWM_CHANNELS > 1
- if ((gt < actual_level) // special case for FET-only turbo
- && (PWM1_LVL == 0) // (bypass adjustment period for first step)
- && (target == PWM_TOP)) PWM1_LVL = PWM_TOP;
- else
- #endif
- if (PWM1_LVL < target) PWM1_LVL ++;
- else if (PWM1_LVL > target) PWM1_LVL --;
- #endif
- #if PWM_CHANNELS >= 2
- target = PWM_GET(pwm2_levels, gt);
- #if PWM_CHANNELS > 2
- if ((gt < actual_level) // special case for FET-only turbo
- && (PWM2_LVL == 0) // (bypass adjustment period for first step)
- && (target == PWM_TOP)) PWM2_LVL = PWM_TOP;
- else
- #endif
- if (PWM2_LVL < target) PWM2_LVL ++;
- else if (PWM2_LVL > target) PWM2_LVL --;
- #endif
- #if PWM_CHANNELS >= 3
- target = PWM_GET(pwm3_levels, gt);
- if (PWM3_LVL < target) PWM3_LVL ++;
- else if (PWM3_LVL > target) PWM3_LVL --;
- #endif
- #if PWM_CHANNELS >= 4
- target = PWM_GET(pwm4_levels, gt);
- if (PWM4_LVL < target) PWM4_LVL ++;
- else if (PWM4_LVL > target) PWM4_LVL --;
- #endif
+// tick the top layer of the stack
+#define GRADUAL_ADJUST_1CH(TABLE,PWM) \
+ target = PWM_GET(TABLE, gt); \
+ if (PWM < target) PWM ++; \
+ else if (PWM > target) PWM --;
+
+// tick a base level of the stack
+// (with support for special DD FET behavior
+// like "low=0, high=255" --> "low=255, high=254")
+#define GRADUAL_ADJUST(TABLE,PWM,TOP) \
+ target = PWM_GET(TABLE, gt); \
+ if ((gt < actual_level) \
+ && (PWM == 0) \
+ && (target == TOP)) PWM = TOP; \
+ else \
+ if (PWM < target) PWM ++; \
+ else if (PWM > target) PWM --;
+
+// do this when output exactly matches a ramp level
+#define GRADUAL_IS_ACTUAL() \
+ uint8_t orig = gradual_target; \
+ set_level(gt + 1); \
+ gradual_target = orig;
+
+#ifdef USE_GRADUAL_TICK_1CH
+void gradual_tick_1ch() {
+ GRADUAL_TICK_SETUP();
+
+ GRADUAL_ADJUST_1CH(low_pwm_levels, LOW_PWM_LVL);
// did we go far enough to hit the next defined ramp level?
// if so, update the main ramp level tracking var
- if ((PWM1_LVL == PWM_GET(pwm1_levels, gt))
- #if PWM_CHANNELS >= 2
- && (PWM2_LVL == PWM_GET(pwm2_levels, gt))
- #endif
- #if PWM_CHANNELS >= 3
- && (PWM3_LVL == PWM_GET(pwm3_levels, gt))
- #endif
- #if PWM_CHANNELS >= 4
- && (PWM4_LVL == PWM_GET(pwm4_levels, gt))
- #endif
+ if ((LOW_PWM_LVL == PWM_GET(low_pwm_levels, gt)))
+ {
+ GRADUAL_IS_ACTUAL();
+ }
+}
+#endif
+
+#ifdef USE_GRADUAL_TICK_2CH_STACKED
+void gradual_tick_2ch_stacked() {
+ GRADUAL_TICK_SETUP();
+
+ GRADUAL_ADJUST(low_pwm_levels, LOW_PWM_LVL, PWM_TOP);
+ GRADUAL_ADJUST_1CH(high_pwm_levels, HIGH_PWM_LVL);
+
+ // did we go far enough to hit the next defined ramp level?
+ // if so, update the main ramp level tracking var
+ if ( (LOW_PWM_LVL == PWM_GET(low_pwm_levels, gt))
+ && (HIGH_PWM_LVL == PWM_GET(high_pwm_levels, gt))
+ )
+ {
+ GRADUAL_IS_ACTUAL();
+ }
+}
+#endif
+
+#ifdef USE_GRADUAL_TICK_3CH_STACKED
+void gradual_tick_3ch_stacked() {
+ GRADUAL_TICK_SETUP();
+
+ GRADUAL_ADJUST(low_pwm_levels, LOW_PWM_LVL, PWM_TOP);
+ GRADUAL_ADJUST(med_pwm_levels, MED_PWM_LVL, PWM_TOP);
+ GRADUAL_ADJUST_1CH(high_pwm_levels, HIGH_PWM_LVL);
+
+ // did we go far enough to hit the next defined ramp level?
+ // if so, update the main ramp level tracking var
+ if ( (LOW_PWM_LVL == PWM_GET(low_pwm_levels, gt))
+ && (MED_PWM_LVL == PWM_GET(med_pwm_levels, gt))
+ && (HIGH_PWM_LVL == PWM_GET(high_pwm_levels, gt))
)
{
- //actual_level = gt + 1;
- uint8_t orig = gradual_target;
- set_level(gt + 1);
- gradual_target = orig;
+ GRADUAL_IS_ACTUAL();
}
- // is handled in set_level()
- //#ifdef USE_TINT_RAMPING
- //update_tint();
- //#endif
- // is handled in set_level()
- //#ifdef USE_DYNAMIC_UNDERCLOCKING
- //auto_clock_speed();
- //#endif
}
-#endif // ifdef OVERRIDE_GRADUAL_TICK
+#endif
#endif // ifdef USE_SET_LEVEL_GRADUALLY
#if defined(USE_TINT_RAMPING) && (!defined(TINT_RAMP_TOGGLE_ONLY))
-void update_tint() {
+void set_level_2ch_blend() {
#ifndef TINT_RAMPING_CORRECTION
#define TINT_RAMPING_CORRECTION 26 // 140% brightness at middle tint
#endif
@@ -340,7 +443,7 @@ void update_tint() {
//PWM_DATATYPE brightness = PWM_GET(pwm1_levels, level);
uint16_t brightness = PWM1_LVL;
uint16_t warm_PWM, cool_PWM;
- #ifdef USE_DYN_PWM
+ #ifdef USE_STACKED_DYN_PWM
uint16_t top = PWM1_TOP;
//uint16_t top = PWM_GET(pwm_tops, actual_level-1);
#else
@@ -414,5 +517,12 @@ void update_tint() {
#endif // ifdef USE_TINT_RAMPING
-#endif // ifdef USE_RAMPING
+// define the channel mode lists
+// TODO: move to progmem
+SetLevelFuncPtr channel_modes[NUM_CHANNEL_MODES] = { SET_LEVEL_MODES };
+#ifdef USE_SET_LEVEL_GRADUALLY
+GradualTickFuncPtr gradual_tick_modes[NUM_CHANNEL_MODES] = { GRADUAL_TICK_MODES };
#endif
+
+
+#endif // ifdef USE_RAMPING
diff --git a/spaghetti-monster/fsm-ramping.h b/spaghetti-monster/fsm-ramping.h
index de090c2..3021ff2 100644
--- a/spaghetti-monster/fsm-ramping.h
+++ b/spaghetti-monster/fsm-ramping.h
@@ -18,23 +18,80 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
-#ifndef FSM_RAMPING_H
-#define FSM_RAMPING_H
+#pragma once
#ifdef USE_RAMPING
// actual_level: last ramp level set by set_level()
uint8_t actual_level = 0;
-#ifdef USE_TINT_RAMPING
-#ifdef TINT_RAMP_TOGGLE_ONLY
-uint8_t tint = 0;
-#else
-uint8_t tint = 128;
+// TODO: size-optimize the case with only 1 channel mode
+// (the arrays and stuff shouldn't be needed)
+
+// current multi-channel mode
+uint8_t channel_mode = DEFAULT_CHANNEL_MODE;
+#ifdef USE_MANUAL_MEMORY
+// reset w/ manual memory
+uint8_t manual_memory_channel_mode = DEFAULT_CHANNEL_MODE;
#endif
-#define USE_TRIANGLE_WAVE
+
+#if NUM_CHANNEL_MODES > 1
+#define USE_CHANNEL_MODES
#endif
+// one function per channel mode
+typedef void SetLevelFunc(uint8_t level);
+typedef SetLevelFunc * SetLevelFuncPtr;
+// TODO: move to progmem
+SetLevelFuncPtr channel_modes[NUM_CHANNEL_MODES];
+
+#ifdef USE_SET_LEVEL_GRADUALLY
+// the gradual tick mechanism may be different per channel
+typedef void GradualTickFunc();
+typedef GradualTickFunc * GradualTickFuncPtr;
+// TODO: move to progmem
+GradualTickFuncPtr gradual_tick_modes[NUM_CHANNEL_MODES];
+#endif
+
+#ifdef USE_CUSTOM_CHANNEL_3H_MODES
+// different 3H behavior per channel?
+// TODO: move to progmem
+StatePtr channel_3H_modes[NUM_CHANNEL_MODES];
+#endif
+
+//#ifdef USE_CHANNEL_MODE_TOGGLES
+#if NUM_CHANNEL_MODES > 1
+// user can take unwanted modes out of the rotation
+// TODO: save to eeprom
+// array
+//uint8_t channel_modes_enabled[NUM_CHANNEL_MODES] = { CHANNEL_MODES_ENABLED };
+// bitmask
+uint8_t channel_modes_enabled = CHANNEL_MODES_ENABLED;
+#define channel_mode_enabled(n) ((channel_modes_enabled >> n) & 1)
+#define channel_mode_enable(n) channel_modes_enabled |= (1 << n)
+#define channel_mode_disable(n) channel_modes_enabled &= ((1 << n) ^ 0xff)
+#endif
+
+#ifdef USE_CHANNEL_MODE_ARGS
+// one byte of extra data per channel mode, like for tint value
+uint8_t channel_mode_args[NUM_CHANNEL_MODES] = { CHANNEL_MODE_ARGS };
+#endif
+
+// TODO: remove this after implementing channel modes
+//#ifdef USE_TINT_RAMPING
+//#ifdef TINT_RAMP_TOGGLE_ONLY
+//uint8_t tint = 0;
+//#else
+//uint8_t tint = 128;
+//#endif
+//#define USE_TRIANGLE_WAVE
+//#endif
+
+void set_channel_mode(uint8_t mode);
+
+void set_level(uint8_t level);
+//void set_level_smooth(uint8_t level);
+
#ifdef USE_SET_LEVEL_GRADUALLY
// adjust brightness very smoothly
uint8_t gradual_target;
@@ -42,21 +99,24 @@ inline void set_level_gradually(uint8_t lvl);
void gradual_tick();
#endif
-#if defined(USE_TINT_RAMPING) && (!defined(TINT_RAMP_TOGGLE_ONLY))
-void update_tint();
-#endif
-
// auto-detect the data type for PWM tables
-#ifndef PWM_BITS
- #define PWM_BITS 8
- #define PWM_TOP 255
+// FIXME: PWM bits and data type should be per PWM table
+#ifndef PWM1_BITS
+ #define PWM1_BITS 8
+ #define PWM1_TOP 255
+ #define STACKED_PWM_TOP 255
#endif
#if PWM_BITS <= 8
+ #define STACKED_PWM_DATATYPE uint8_t
#define PWM_DATATYPE uint8_t
#define PWM_DATATYPE2 uint16_t
#define PWM_TOP 255
+ #define STACKED_PWM_TOP 255
+ #ifndef PWM_GET
#define PWM_GET(x,y) pgm_read_byte(x+y)
+ #endif
#else
+ #define STACKED_PWM_DATATYPE uint16_t
#define PWM_DATATYPE uint16_t
#ifndef PWM_DATATYPE2
#define PWM_DATATYPE2 uint32_t
@@ -64,32 +124,55 @@ void update_tint();
#ifndef PWM_TOP
#define PWM_TOP 1023 // 10 bits by default
#endif
+ #ifndef STACKED_PWM_TOP
+ #define STACKED_PWM_TOP 1023
+ #endif
// pointer plus 2*y bytes
//#define PWM_GET(x,y) pgm_read_word(x+(2*y))
// nope, the compiler was already doing the math correctly
+ #ifndef PWM_GET
#define PWM_GET(x,y) pgm_read_word(x+y)
+ #endif
#endif
+#define PWM_GET8(x,y) pgm_read_byte(x+y)
+#define PWM_GET16(x,y) pgm_read_word(x+y)
// use UI-defined ramp tables if they exist
#ifdef PWM1_LEVELS
-PROGMEM const PWM_DATATYPE pwm1_levels[] = { PWM1_LEVELS };
+PROGMEM const PWM1_DATATYPE pwm1_levels[] = { PWM1_LEVELS };
#endif
#ifdef PWM2_LEVELS
-PROGMEM const PWM_DATATYPE pwm2_levels[] = { PWM2_LEVELS };
+PROGMEM const PWM2_DATATYPE pwm2_levels[] = { PWM2_LEVELS };
#endif
#ifdef PWM3_LEVELS
-PROGMEM const PWM_DATATYPE pwm3_levels[] = { PWM3_LEVELS };
+PROGMEM const PWM3_DATATYPE pwm3_levels[] = { PWM3_LEVELS };
+#endif
+
+// convenience defs for 1 LED with stacked channels
+#ifdef LOW_PWM_LEVELS
+PROGMEM const PWM_DATATYPE low_pwm_levels[] = { LOW_PWM_LEVELS };
+#endif
+#ifdef MED_PWM_LEVELS
+PROGMEM const PWM_DATATYPE med_pwm_levels[] = { MED_PWM_LEVELS };
+#endif
+#ifdef HIGH_PWM_LEVELS
+PROGMEM const PWM_DATATYPE high_pwm_levels[] = { HIGH_PWM_LEVELS };
#endif
-#ifdef PWM4_LEVELS
-PROGMEM const PWM_DATATYPE pwm4_levels[] = { PWM4_LEVELS };
+
+// 2 channel CCT blending ramp
+#ifdef BLEND_PWM_LEVELS
+PROGMEM const PWM_DATATYPE blend_pwm_levels[] = { BLEND_PWM_LEVELS };
#endif
+
// 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
PROGMEM const PWM_DATATYPE pwm_tops[] = { PWM_TOPS };
#endif
+// FIXME: jump start should be per channel / channel mode
#ifdef USE_JUMP_START
#ifndef JUMP_START_TIME
#define JUMP_START_TIME 8 // in ms, should be 4, 8, or 12
@@ -100,78 +183,11 @@ PROGMEM const PWM_DATATYPE pwm_tops[] = { PWM_TOPS };
uint8_t jump_start_level = DEFAULT_JUMP_START_LEVEL;
#endif
-// default / example ramps
-#ifndef PWM1_LEVELS
-#if PWM_CHANNELS == 1
- #if RAMP_LENGTH == 50
- // ../../bin/level_calc.py 1 50 7135 3 0.25 980
- PROGMEM const PWM_DATATYPE pwm1_levels[] = { 3,3,3,3,4,4,4,5,5,6,7,8,9,11,12,14,16,18,20,23,25,28,32,35,39,43,47,52,57,62,68,74,80,87,94,102,110,118,127,136,146,156,167,178,189,201,214,227,241,255 };
- #elif RAMP_LENGTH == 75
- // ../../bin/level_calc.py 1 75 7135 3 0.25 980
- PROGMEM const PWM_DATATYPE pwm1_levels[] = { 3,3,3,3,3,3,4,4,4,4,5,5,5,6,6,7,8,8,9,10,11,12,13,14,15,17,18,20,21,23,25,27,29,31,33,36,38,41,44,47,50,53,56,59,63,67,71,75,79,83,88,93,98,103,108,113,119,125,131,137,143,150,157,164,171,178,186,194,202,210,219,227,236,246,255 };
- #elif RAMP_LENGTH == 150
- // ../../bin/level_calc.py 1 150 7135 3 0.25 980
- PROGMEM const PWM_DATATYPE pwm1_levels[] = { 3,3,3,3,3,3,3,3,3,3,3,4,4,4,4,4,4,4,4,4,5,5,5,5,5,6,6,6,6,7,7,7,8,8,8,9,9,9,10,10,11,11,12,12,13,13,14,15,15,16,17,17,18,19,19,20,21,22,23,24,24,25,26,27,28,29,31,32,33,34,35,36,38,39,40,42,43,44,46,47,49,50,52,53,55,57,58,60,62,64,66,68,70,72,74,76,78,80,82,84,86,89,91,93,96,98,101,103,106,109,111,114,117,120,123,125,128,131,134,138,141,144,147,151,154,157,161,164,168,171,175,179,183,186,190,194,198,202,206,210,215,219,223,228,232,236,241,246,250,255 };
- #endif
-#elif PWM_CHANNELS == 2
- #if RAMP_LENGTH == 50
- // ../../bin/level_calc.py 2 50 7135 4 0.33 150 FET 1 10 1500
- PROGMEM const PWM_DATATYPE pwm1_levels[] = { 4,5,6,8,10,13,17,22,28,35,44,54,65,78,93,109,128,149,171,197,224,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,0 };
- PROGMEM const PWM_DATATYPE pwm2_levels[] = { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,7,11,15,20,26,31,37,44,51,58,65,73,82,91,100,110,121,132,143,155,168,181,194,209,224,239,255 };
- #define MAX_1x7135 22
- #elif RAMP_LENGTH == 75
- // ../../bin/level_calc.py 2 75 7135 4 0.33 150 FET 1 10 1500
- PROGMEM const PWM_DATATYPE pwm1_levels[] = { 4,4,5,6,7,8,10,12,14,17,20,24,28,32,37,43,49,56,64,72,82,91,102,114,126,139,153,168,184,202,220,239,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,0 };
- PROGMEM const PWM_DATATYPE pwm2_levels[] = { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,5,7,10,13,16,19,23,26,30,34,38,42,47,51,56,61,66,72,77,83,89,95,101,108,115,122,129,136,144,152,160,168,177,186,195,204,214,224,234,244,255 };
- #define MAX_1x7135 33
- #elif RAMP_LENGTH == 150
- // ../../bin/level_calc.py 1 65 7135 1 0.8 150
- // ... mixed with this:
- // ../../bin/level_calc.py 2 150 7135 4 0.33 150 FET 1 10 1500
- PROGMEM const PWM_DATATYPE pwm1_levels[] = { 1,1,2,2,3,3,4,4,5,6,7,8,9,10,12,13,14,15,17,19,20,22,24,26,29,31,34,36,39,42,45,48,51,55,59,62,66,70,75,79,84,89,93,99,104,110,115,121,127,134,140,147,154,161,168,176,184,192,200,209,217,226,236,245,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,0 };
- PROGMEM const PWM_DATATYPE pwm2_levels[] = { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,3,4,5,7,8,9,11,12,14,15,17,19,20,22,24,25,27,29,31,33,35,37,39,41,43,45,48,50,52,55,57,59,62,64,67,70,72,75,78,81,84,87,90,93,96,99,102,105,109,112,115,119,122,126,129,133,137,141,144,148,152,156,160,165,169,173,177,182,186,191,195,200,205,209,214,219,224,229,234,239,244,250,255 };
- #define MAX_1x7135 65
- #define HALFSPEED_LEVEL 14
- #define QUARTERSPEED_LEVEL 5
- #endif
-#elif PWM_CHANNELS == 3
- #if RAMP_LENGTH == 50
- // ../../bin/level_calc.py 3 50 7135 4 0.33 150 7135 4 1 840 FET 1 10 2000
- PROGMEM const PWM_DATATYPE pwm1_levels[] = { 4,5,6,8,11,15,20,26,34,43,54,67,82,99,118,140,165,192,221,254,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,0 };
- PROGMEM const PWM_DATATYPE pwm2_levels[] = { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,10,17,25,33,42,52,62,73,85,97,111,125,140,157,174,192,210,230,251,255,255,255,255,255,255,255,255,255,255,0 };
- PROGMEM const PWM_DATATYPE pwm3_levels[] = { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,14,34,54,76,98,122,146,172,198,226,255 };
- #define MAX_1x7135 20
- #define MAX_Nx7135 39
- #elif RAMP_LENGTH == 75
- // ../../bin/level_calc.py 3 75 7135 4 0.33 150 7135 4 1 840 FET 1 10 2000
- PROGMEM const PWM_DATATYPE pwm1_levels[] = { 4,4,5,6,7,9,11,14,16,20,24,28,34,40,46,54,62,71,81,92,104,117,130,146,162,179,198,218,239,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,0 };
- PROGMEM const PWM_DATATYPE pwm2_levels[] = { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,5,9,14,18,23,29,34,40,47,53,60,67,75,83,91,99,108,117,127,137,148,158,170,181,193,206,219,232,246,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,0 };
- PROGMEM const PWM_DATATYPE pwm3_levels[] = { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,15,28,42,55,70,84,99,115,131,147,164,181,199,217,236,255 };
- #define MAX_1x7135 30
- #define MAX_Nx7135 59
- #elif RAMP_LENGTH == 150
- // ../../bin/level_calc.py 1 65 7135 1 0.8 150
- // ... mixed with this:
- // ../../../bin/level_calc.py 3 150 7135 1 0.33 150 7135 1 1 850 FET 1 10 1500
- PROGMEM const PWM_DATATYPE pwm1_levels[] = { 1,1,2,2,3,3,4,4,5,6,7,8,9,10,12,13,14,15,17,19,20,22,24,26,29,31,34,36,39,42,45,48,51,55,59,62,66,70,75,79,84,89,93,99,104,110,115,121,127,134,140,147,154,161,168,176,184,192,200,209,217,226,236,245,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,0 };
- PROGMEM const PWM_DATATYPE pwm2_levels[] = { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,4,6,8,10,13,15,17,19,22,24,26,29,31,34,37,39,42,45,48,51,54,57,60,64,67,70,74,77,81,85,88,92,96,100,104,108,112,116,121,125,130,134,139,143,148,153,158,163,168,173,179,184,189,195,201,206,212,218,224,230,236,243,249,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,0 };
- PROGMEM const PWM_DATATYPE pwm3_levels[] = { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,8,19,31,43,55,67,79,91,104,117,130,143,157,170,184,198,212,226,240,255 };
- #define MAX_1x7135 65
- #define MAX_Nx7135 130
- #define HALFSPEED_LEVEL 14
- #define QUARTERSPEED_LEVEL 5
- #endif
-#elif PWM_CHANNELS == 4
- 4-channel PWM not really supported yet, sorry.
-#endif
-#endif
-
// RAMP_SIZE / MAX_LVL
-#define RAMP_SIZE (sizeof(pwm1_levels)/sizeof(PWM_DATATYPE))
+// cfg-*.h should define RAMP_SIZE
+//#define RAMP_SIZE (sizeof(stacked_pwm1_levels)/sizeof(STACKED_PWM_DATATYPE))
#define MAX_LEVEL RAMP_SIZE
-void set_level(uint8_t level);
-//void set_level_smooth(uint8_t level);
#endif // ifdef USE_RAMPING
-#endif
+
diff --git a/spaghetti-monster/fsm-states.h b/spaghetti-monster/fsm-states.h
index 9964bc1..2c51d0a 100644
--- a/spaghetti-monster/fsm-states.h
+++ b/spaghetti-monster/fsm-states.h
@@ -16,7 +16,7 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
-
+
#ifndef FSM_STATES_H
#define FSM_STATES_H
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.