aboutsummaryrefslogtreecommitdiff
path: root/spaghetti-monster
diff options
context:
space:
mode:
Diffstat (limited to 'spaghetti-monster')
-rw-r--r--spaghetti-monster/anduril/anduril-manual.txt2
-rw-r--r--spaghetti-monster/anduril/anduril.c90
-rw-r--r--spaghetti-monster/anduril/cfg-emisar-d18.h2
-rw-r--r--spaghetti-monster/anduril/cfg-emisar-d4.h4
-rw-r--r--spaghetti-monster/anduril/cfg-emisar-d4s.h5
-rw-r--r--spaghetti-monster/anduril/cfg-emisar-d4sv2.h14
-rw-r--r--spaghetti-monster/anduril/cfg-emisar-d4v2.h12
-rw-r--r--spaghetti-monster/anduril/cfg-ff-pl47.h5
-rw-r--r--spaghetti-monster/anduril/cfg-ff-pl47g2.h5
-rw-r--r--spaghetti-monster/anduril/cfg-ff-rot66.h3
-rw-r--r--spaghetti-monster/anduril/cfg-mateminco-mf01-mini.h5
-rw-r--r--spaghetti-monster/anduril/cfg-noctigon-k1.h12
-rw-r--r--spaghetti-monster/anduril/cfg-noctigon-kr4-219.h11
-rw-r--r--spaghetti-monster/anduril/cfg-noctigon-kr4-nofet.h47
-rw-r--r--spaghetti-monster/anduril/cfg-noctigon-kr4.h57
-rw-r--r--spaghetti-monster/anduril/cfg-sofirn-sp36.h7
-rw-r--r--spaghetti-monster/fsm-adc.c504
-rw-r--r--spaghetti-monster/fsm-adc.h24
-rw-r--r--spaghetti-monster/fsm-events.h1
-rw-r--r--spaghetti-monster/fsm-main.c2
-rw-r--r--spaghetti-monster/fsm-misc.c46
-rw-r--r--spaghetti-monster/fsm-pcint.c4
-rw-r--r--spaghetti-monster/fsm-standby.c4
-rw-r--r--spaghetti-monster/fsm-wdt.c16
-rw-r--r--spaghetti-monster/ramping-ui/ramping-ui.c3
-rw-r--r--spaghetti-monster/rampingios/rampingiosv3.c12
-rw-r--r--spaghetti-monster/spaghetti-monster.txt12
-rw-r--r--spaghetti-monster/werner/werner.c12
28 files changed, 501 insertions, 420 deletions
diff --git a/spaghetti-monster/anduril/anduril-manual.txt b/spaghetti-monster/anduril/anduril-manual.txt
index 3f17f41..97e6589 100644
--- a/spaghetti-monster/anduril/anduril-manual.txt
+++ b/spaghetti-monster/anduril/anduril-manual.txt
@@ -147,7 +147,7 @@ In more detail, this is what each blinky / utility mode does:
can reach before it will start doing thermal regulation to keep
itself from overheating. Click once per degree C above 30. For
example, to set the limit to 50 C, click 20 times. The default
- is 45 C.
+ is 45 C, and the highest value it will allow is 70 C.
Strobe / Mood Modes
diff --git a/spaghetti-monster/anduril/anduril.c b/spaghetti-monster/anduril/anduril.c
index 9348262..e87a30c 100644
--- a/spaghetti-monster/anduril/anduril.c
+++ b/spaghetti-monster/anduril/anduril.c
@@ -22,7 +22,7 @@
// Anduril config file name (set it here or define it at the gcc command line)
//#define CONFIGFILE cfg-blf-q8.h
-#define USE_LVP // FIXME: won't build when this option is turned off
+#define USE_LVP
// parameters for this defined below or per-driver
#define USE_THERMAL_REGULATION
@@ -278,6 +278,7 @@ void sos_blink(uint8_t num, uint8_t dah);
uint8_t battcheck_state(Event event, uint16_t arg);
#endif
#ifdef USE_THERMAL_REGULATION
+#define USE_BLINK_NUM
uint8_t tempcheck_state(Event event, uint16_t arg);
uint8_t thermal_config_state(Event event, uint16_t arg);
#endif
@@ -354,6 +355,9 @@ const PROGMEM uint8_t rgb_led_colors[] = {
#ifndef RGB_LED_LOCKOUT_DEFAULT
#define RGB_LED_LOCKOUT_DEFAULT 0x37 // blinking, rainbow
#endif
+#ifndef RGB_RAINBOW_SPEED
+#define RGB_RAINBOW_SPEED 0x0f // change color every 16 frames
+#endif
uint8_t rgb_led_off_mode = RGB_LED_OFF_DEFAULT;
uint8_t rgb_led_lockout_mode = RGB_LED_LOCKOUT_DEFAULT;
#endif
@@ -511,6 +515,7 @@ volatile uint8_t beacon_seconds = 2;
#endif
#ifdef USE_VERSION_CHECK
+#define USE_BLINK_DIGIT
#include "version.h"
const PROGMEM uint8_t version_number[] = VERSION_NUMBER;
uint8_t version_check_state(Event event, uint16_t arg);
@@ -973,59 +978,34 @@ uint8_t steady_state(Event event, uint16_t arg) {
if (arg == TICKS_PER_SECOND) ramp_direction = 1;
#endif
#ifdef USE_SET_LEVEL_GRADUALLY
- // make thermal adjustment speed scale with magnitude
- // also, adjust slower when going up
- if ((arg & 1) &&
- ((actual_level < THERM_FASTER_LEVEL) ||
- (actual_level < gradual_target))) {
- return MISCHIEF_MANAGED; // adjust slower when not a high mode
- }
- #ifdef THERM_HARD_TURBO_DROP
- else if ((! (actual_level < THERM_FASTER_LEVEL))
- && (actual_level > gradual_target)) {
- gradual_tick();
- }
- else {
- #endif
- // [int(62*4 / (x**0.8)) for x in (1,2,4,8,16,32,64,128)]
- //uint8_t intervals[] = {248, 142, 81, 46, 26, 15, 8, 5};
- // [int(62*4 / (x**0.9)) for x in (1,2,4,8,16,32,64,128)]
- //uint8_t intervals[] = {248, 132, 71, 38, 20, 10, 5, 3};
- // [int(62*4 / (x**0.95)) for x in (1,2,4,8,16,32,64,128)]
- uint8_t intervals[] = {248, 128, 66, 34, 17, 9, 4, 2};
- uint8_t diff;
- static uint8_t ticks_since_adjust = 0;
- if (gradual_target > actual_level) {
- // rise at half speed (skip half the frames)
- if (arg & 2) return MISCHIEF_MANAGED;
- diff = gradual_target - actual_level;
- } else {
- diff = actual_level - gradual_target;
- }
- ticks_since_adjust ++;
- // if there's any adjustment to be made, make it
+ int16_t diff = gradual_target - actual_level;
+ static uint16_t ticks_since_adjust = 0;
+ ticks_since_adjust++;
if (diff) {
- uint8_t magnitude = 0;
- #ifndef THERM_HARD_TURBO_DROP
- // if we're on a really high mode, drop faster
- if ((actual_level >= THERM_FASTER_LEVEL)
- && (actual_level > gradual_target)) { magnitude ++; }
- #endif
+ uint16_t ticks_per_adjust = 256;
+ if (diff < 0) {
+ //diff = -diff;
+ if (actual_level > THERM_FASTER_LEVEL) {
+ #ifdef THERM_HARD_TURBO_DROP
+ ticks_per_adjust >>= 2;
+ #endif
+ ticks_per_adjust >>= 2;
+ }
+ } else {
+ // rise at half speed
+ ticks_per_adjust <<= 1;
+ }
while (diff) {
- magnitude ++;
- diff >>= 1;
+ ticks_per_adjust >>= 1;
+ //diff >>= 1;
+ diff /= 2; // because shifting produces weird behavior
}
- uint8_t ticks_per_adjust = intervals[magnitude];
if (ticks_since_adjust > ticks_per_adjust)
{
gradual_tick();
ticks_since_adjust = 0;
}
- //if (!(arg % ticks_per_adjust)) gradual_tick();
- }
- #ifdef THERM_HARD_TURBO_DROP
}
- #endif
#endif // ifdef USE_SET_LEVEL_GRADUALLY
return MISCHIEF_MANAGED;
}
@@ -1078,6 +1058,18 @@ uint8_t steady_state(Event event, uint16_t arg) {
}
return MISCHIEF_MANAGED;
}
+ #ifdef USE_SET_LEVEL_GRADUALLY
+ // temperature is within target window
+ // (so stop trying to adjust output)
+ else if (event == EV_temperature_okay) {
+ // if we're still adjusting output... stop after the current step
+ if (gradual_target > actual_level)
+ gradual_target = actual_level + 1;
+ else if (gradual_target < actual_level)
+ gradual_target = actual_level - 1;
+ return MISCHIEF_MANAGED;
+ }
+ #endif // ifdef USE_SET_LEVEL_GRADUALLY
#endif // ifdef USE_THERMAL_REGULATION
return EVENT_NOT_HANDLED;
}
@@ -1618,11 +1610,13 @@ uint8_t tempcheck_state(Event event, uint16_t arg) {
set_state(off_state, 0);
return MISCHIEF_MANAGED;
}
+ #ifdef USE_BATTCHECK
// 2 clicks: battcheck mode
else if (event == EV_2clicks) {
set_state(battcheck_state, 0);
return MISCHIEF_MANAGED;
}
+ #endif
// 4 clicks: thermal config mode
else if (event == EV_4clicks) {
push_state(thermal_config_state, 0);
@@ -1648,7 +1642,7 @@ uint8_t beacon_state(Event event, uint16_t arg) {
set_state(sos_state, 0);
#elif defined(USE_THERMAL_REGULATION)
set_state(tempcheck_state, 0);
- #else
+ #elif defined(USE_BATTCHECK)
set_state(battcheck_state, 0);
#endif
return MISCHIEF_MANAGED;
@@ -2040,6 +2034,7 @@ uint8_t muggle_state(Event event, uint16_t arg) {
return MISCHIEF_MANAGED;
}
#endif
+ #ifdef USE_LVP
// low voltage is handled specially in muggle mode
else if(event == EV_voltage_low) {
uint8_t lvl = (actual_level >> 1) + (actual_level >> 2);
@@ -2050,6 +2045,7 @@ uint8_t muggle_state(Event event, uint16_t arg) {
}
return MISCHIEF_MANAGED;
}
+ #endif
return EVENT_NOT_HANDLED;
}
@@ -2407,7 +2403,7 @@ void rgb_led_update(uint8_t mode, uint8_t arg) {
}
else if (color == 7) { // rainbow
uint8_t speed = 0x03; // awake speed
- if (go_to_standby) speed = 0x0f; // asleep speed
+ if (go_to_standby) speed = RGB_RAINBOW_SPEED; // asleep speed
if (0 == (arg & speed)) {
rainbow = (rainbow + 1) % 6;
}
diff --git a/spaghetti-monster/anduril/cfg-emisar-d18.h b/spaghetti-monster/anduril/cfg-emisar-d18.h
index 16fbacd..155a747 100644
--- a/spaghetti-monster/anduril/cfg-emisar-d18.h
+++ b/spaghetti-monster/anduril/cfg-emisar-d18.h
@@ -42,5 +42,3 @@
// stop panicking at about ~40% power or ~5000 lm
#define THERM_FASTER_LEVEL 125
-// optional, makes initial turbo step-down faster so first peak isn't as hot
-//#define THERM_HARD_TURBO_DROP
diff --git a/spaghetti-monster/anduril/cfg-emisar-d4.h b/spaghetti-monster/anduril/cfg-emisar-d4.h
index c86a534..501b9c7 100644
--- a/spaghetti-monster/anduril/cfg-emisar-d4.h
+++ b/spaghetti-monster/anduril/cfg-emisar-d4.h
@@ -24,8 +24,4 @@
// stop panicking at ~30% power or ~1200 lm
#define THERM_FASTER_LEVEL 105
-// respond to thermal changes faster
-#define THERMAL_WARNING_SECONDS 3
-#define THERMAL_UPDATE_SPEED 1
-#define THERM_PREDICTION_STRENGTH 4
diff --git a/spaghetti-monster/anduril/cfg-emisar-d4s.h b/spaghetti-monster/anduril/cfg-emisar-d4s.h
index 6fe95a6..88465da 100644
--- a/spaghetti-monster/anduril/cfg-emisar-d4s.h
+++ b/spaghetti-monster/anduril/cfg-emisar-d4s.h
@@ -42,8 +42,3 @@
#ifdef THERM_HARD_TURBO_DROP
#undef THERM_HARD_TURBO_DROP
#endif
-
-#define THERMAL_WARNING_SECONDS 3
-#define THERMAL_UPDATE_SPEED 2
-#define THERM_PREDICTION_STRENGTH 4
-
diff --git a/spaghetti-monster/anduril/cfg-emisar-d4sv2.h b/spaghetti-monster/anduril/cfg-emisar-d4sv2.h
index c47e774..c578c4a 100644
--- a/spaghetti-monster/anduril/cfg-emisar-d4sv2.h
+++ b/spaghetti-monster/anduril/cfg-emisar-d4sv2.h
@@ -42,16 +42,6 @@
// stop panicking at ~50% power or ~2000 lm
#define THERM_FASTER_LEVEL 130
-// no need to be extra-careful on this light
-#ifdef THERM_HARD_TURBO_DROP
-#undef THERM_HARD_TURBO_DROP
-#endif
-
-// respond to thermal changes faster
-#define THERMAL_WARNING_SECONDS 3
-#define THERMAL_UPDATE_SPEED 2
-#define THERM_PREDICTION_STRENGTH 4
-
// easier access to thermal config mode, for Emisar
#define USE_TENCLICK_THERMAL_CONFIG
@@ -61,7 +51,3 @@
// seems relevant on attiny1634
#define THERM_CAL_OFFSET 5
-
-// attiny1634 has enough space to smooth out voltage readings
-#define USE_VOLTAGE_LOWPASS
-
diff --git a/spaghetti-monster/anduril/cfg-emisar-d4v2.h b/spaghetti-monster/anduril/cfg-emisar-d4v2.h
index 3da877e..241ca7e 100644
--- a/spaghetti-monster/anduril/cfg-emisar-d4v2.h
+++ b/spaghetti-monster/anduril/cfg-emisar-d4v2.h
@@ -37,22 +37,10 @@
#define RAMP_DISCRETE_CEIL RAMP_SMOOTH_CEIL
#define RAMP_DISCRETE_STEPS 7
-// optional, makes initial turbo step-down faster so first peak isn't as hot
-// the D4 runs very very hot, so be extra careful
-//#define THERM_HARD_TURBO_DROP
-
// stop panicking at ~30% power or ~1200 lm
#define THERM_FASTER_LEVEL 105
-// respond to thermal changes faster
-#define THERMAL_WARNING_SECONDS 3
-#define THERMAL_UPDATE_SPEED 1
-#define THERM_PREDICTION_STRENGTH 4
-//#define THERM_RESPONSE_MAGNITUDE 128
// easier access to thermal config mode, for Emisar
#define USE_TENCLICK_THERMAL_CONFIG
#define THERM_CAL_OFFSET 5
-
-// attiny1634 has enough space to smooth out voltage readings
-#define USE_VOLTAGE_LOWPASS
diff --git a/spaghetti-monster/anduril/cfg-ff-pl47.h b/spaghetti-monster/anduril/cfg-ff-pl47.h
index 7a81c25..e6907c1 100644
--- a/spaghetti-monster/anduril/cfg-ff-pl47.h
+++ b/spaghetti-monster/anduril/cfg-ff-pl47.h
@@ -61,11 +61,6 @@
// regulate down faster when the FET is active, slower otherwise
#define THERM_FASTER_LEVEL 135 // throttle back faster when high
-// play it safe, don't try to regulate above the recommended safe level
-#ifdef THERM_HARD_TURBO_DROP
-#undef THERM_HARD_TURBO_DROP
-#endif
-
// don't do this
#undef BLINK_AT_RAMP_MIDDLE
#undef BLINK_AT_RAMP_CEILING
diff --git a/spaghetti-monster/anduril/cfg-ff-pl47g2.h b/spaghetti-monster/anduril/cfg-ff-pl47g2.h
index d5dd79d..cab008c 100644
--- a/spaghetti-monster/anduril/cfg-ff-pl47g2.h
+++ b/spaghetti-monster/anduril/cfg-ff-pl47g2.h
@@ -49,11 +49,6 @@
// regulate down faster when the FET is active, slower otherwise
#define THERM_FASTER_LEVEL 135 // throttle back faster when high
-// hard drop doesn't seem to be needed on this light
-#ifdef THERM_HARD_TURBO_DROP
-#undef THERM_HARD_TURBO_DROP
-#endif
-
// don't do this
#undef BLINK_AT_RAMP_MIDDLE
#undef BLINK_AT_RAMP_CEILING
diff --git a/spaghetti-monster/anduril/cfg-ff-rot66.h b/spaghetti-monster/anduril/cfg-ff-rot66.h
index 2a90343..a87b66d 100644
--- a/spaghetti-monster/anduril/cfg-ff-rot66.h
+++ b/spaghetti-monster/anduril/cfg-ff-rot66.h
@@ -38,9 +38,6 @@
// regulate down faster when the FET is active, slower otherwise
#define THERM_FASTER_LEVEL 130 // throttle back faster when high
-// play it safe, don't try to regulate above the recommended safe level
-//#define THERM_HARD_TURBO_DROP
-
// don't do this
#undef BLINK_AT_RAMP_MIDDLE
#undef BLINK_AT_RAMP_CEILING
diff --git a/spaghetti-monster/anduril/cfg-mateminco-mf01-mini.h b/spaghetti-monster/anduril/cfg-mateminco-mf01-mini.h
index bbf751b..28c77c2 100644
--- a/spaghetti-monster/anduril/cfg-mateminco-mf01-mini.h
+++ b/spaghetti-monster/anduril/cfg-mateminco-mf01-mini.h
@@ -48,8 +48,3 @@
#define USE_TENCLICK_THERMAL_CONFIG // by request
#define THERM_FASTER_LEVEL 130 // throttle back faster when high
-//#define THERM_HARD_TURBO_DROP // this light is massively overpowered
-#define THERMAL_WARNING_SECONDS 1 // FIXME: increase by 2 after merging newer code
-//#define THERMAL_UPDATE_SPEED 1
-//#define THERM_PREDICTION_STRENGTH 4
-
diff --git a/spaghetti-monster/anduril/cfg-noctigon-k1.h b/spaghetti-monster/anduril/cfg-noctigon-k1.h
index 6a8e8ee..4db8787 100644
--- a/spaghetti-monster/anduril/cfg-noctigon-k1.h
+++ b/spaghetti-monster/anduril/cfg-noctigon-k1.h
@@ -46,16 +46,8 @@
#define MUGGLE_FLOOR RAMP_DISCRETE_FLOOR
#define MUGGLE_CEILING 70
-// optional, makes initial turbo step-down faster so first peak isn't as hot
-// the D4 runs very very hot, so be extra careful
-//#define THERM_HARD_TURBO_DROP
-
// stop panicking at ~70% power or ~600 lm
#define THERM_FASTER_LEVEL 130
-// respond to thermal changes faster
-#define THERMAL_WARNING_SECONDS 3
-#define THERMAL_UPDATE_SPEED 1
-#define THERM_PREDICTION_STRENGTH 4
// easier access to thermal config mode, for Noctigon
#define USE_TENCLICK_THERMAL_CONFIG
@@ -65,7 +57,3 @@
#define THERM_CAL_OFFSET 5
-// attiny1634 has enough space to smooth out voltage readings
-// (prevent the button from blinking during use)
-#define USE_VOLTAGE_LOWPASS
-
diff --git a/spaghetti-monster/anduril/cfg-noctigon-kr4-219.h b/spaghetti-monster/anduril/cfg-noctigon-kr4-219.h
new file mode 100644
index 0000000..0cfccf2
--- /dev/null
+++ b/spaghetti-monster/anduril/cfg-noctigon-kr4-219.h
@@ -0,0 +1,11 @@
+// Noctigon KR4 (75% FET) config options for Anduril
+#include "cfg-noctigon-kr4.h"
+// ATTINY: 1634
+
+// don't turn off first channel at turbo level
+#undef PWM1_LEVELS
+#define PWM1_LEVELS 0,0,1,1,2,2,3,3,4,4,5,6,7,8,9,10,11,13,14,15,17,19,20,22,24,26,28,30,33,35,38,40,43,46,49,52,55,59,62,66,70,74,78,82,86,91,96,100,105,111,116,121,127,133,139,145,151,158,165,172,179,186,193,201,209,217,225,234,243,251,261,270,280,289,299,310,320,331,342,353,364,376,388,400,412,425,438,451,464,478,492,506,521,536,551,566,582,597,614,630,647,664,681,699,717,735,754,772,792,811,831,851,871,892,913,935,956,978,1001,1023,1023,1023,1023,1023,1023,1023,1023,1023,1023,1023,1023,1023,1023,1023,1023,1023,1023,1023,1023,1023,1023,1023,1023,1023,1023,1023,1023,1023,1023,1023
+// 75% 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,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,17,39,60,82,104,126,149,172,195,219,243,268,293,318,343,369,396,422,449,476,504,531,560,588,617,646,676,706,737,768
+
diff --git a/spaghetti-monster/anduril/cfg-noctigon-kr4-nofet.h b/spaghetti-monster/anduril/cfg-noctigon-kr4-nofet.h
new file mode 100644
index 0000000..19cbc23
--- /dev/null
+++ b/spaghetti-monster/anduril/cfg-noctigon-kr4-nofet.h
@@ -0,0 +1,47 @@
+// Noctigon KR4 (fetless) config options for Anduril
+#include "cfg-noctigon-kr4.h"
+// ATTINY: 1634
+
+// brightness w/ SST-20 4000K LEDs:
+// 0/1023: 0.35 lm
+// 1/1023: 2.56 lm
+// max regulated: 1740 lm
+// level_calc.py 3.0 1 150 7135 0 5 1740
+#undef PWM_CHANNELS
+#define PWM_CHANNELS 1
+#define RAMP_LENGTH 150
+#undef PWM1_LEVELS
+#define PWM1_LEVELS 0,0,1,1,2,2,3,3,4,4,5,5,6,7,8,9,10,11,12,13,15,16,17,18,20,21,23,24,26,27,29,31,33,35,37,39,41,43,45,48,50,53,55,58,61,63,66,69,72,75,79,82,85,89,92,96,100,104,108,112,116,120,125,129,134,138,143,148,153,158,163,169,174,180,185,191,197,203,209,215,222,228,235,242,248,255,263,270,277,285,292,300,308,316,324,333,341,350,359,368,377,386,395,405,414,424,434,444,454,465,475,486,497,508,519,531,542,554,566,578,590,603,615,628,641,654,667,680,694,708,722,736,750,765,779,794,809,825,840,856,872,888,904,920,937,954,971,988,1005,1023
+#undef PWM2_LEVELS
+#undef DEFAULT_LEVEL
+#define DEFAULT_LEVEL 50
+#undef MAX_1x7135
+#define MAX_1x7135 150
+
+#undef RAMP_SMOOTH_FLOOR
+#undef RAMP_SMOOTH_CEIL
+#undef RAMP_DISCRETE_FLOOR
+#undef RAMP_DISCRETE_CEIL
+#undef RAMP_DISCRETE_STEPS
+
+#define RAMP_SMOOTH_FLOOR 3 // level 1 is unreliable
+#define RAMP_SMOOTH_CEIL 130
+// 10, 30, [50], 70, 90, 110, 130 (plus [150] on turbo)
+#define RAMP_DISCRETE_FLOOR 10
+#define RAMP_DISCRETE_CEIL RAMP_SMOOTH_CEIL
+#define RAMP_DISCRETE_STEPS 7
+
+#undef MUGGLE_FLOOR
+#undef MUGGLE_CEILING
+#define MUGGLE_FLOOR RAMP_DISCRETE_FLOOR
+#define MUGGLE_CEILING 70
+
+// make candle mode wobble more
+#define CANDLE_AMPLITUDE 32
+
+// stop panicking at ~90% power or ~1600 lm
+#undef THERM_FASTER_LEVEL
+#define THERM_FASTER_LEVEL 143
+#undef MIN_THERM_STEPDOWN
+#define MIN_THERM_STEPDOWN DEFAULT_LEVEL
+
diff --git a/spaghetti-monster/anduril/cfg-noctigon-kr4.h b/spaghetti-monster/anduril/cfg-noctigon-kr4.h
new file mode 100644
index 0000000..66c5a28
--- /dev/null
+++ b/spaghetti-monster/anduril/cfg-noctigon-kr4.h
@@ -0,0 +1,57 @@
+// Noctigon KR4 config options for Anduril
+#include "hwdef-Noctigon_KR4.h"
+// ATTINY: 1634
+
+// this light has three aux LED channels: R, G, B
+#define USE_AUX_RGB_LEDS
+//#define USE_AUX_RGB_LEDS_WHILE_ON
+//#define USE_INDICATOR_LED_WHILE_RAMPING
+#define RGB_LED_OFF_DEFAULT 0x17 // low, rainbow
+#define RGB_LED_LOCKOUT_DEFAULT 0x37 // blinking, rainbow
+#define RGB_RAINBOW_SPEED 0x03 // half a second per color
+
+// enable blinking aux LEDs
+#define TICK_DURING_STANDBY
+#define STANDBY_TICK_SPEED 3 // every 0.128 s
+
+
+// brightness w/ SST-20 4000K LEDs:
+// 0/1023: 0.35 lm
+// 1/1023: 2.56 lm
+// max regulated: 1740 lm
+// FET: ~3700 lm
+// maxreg at 130: level_calc.py cube 2 150 7135 0 2.5 1740 FET 1 10 2565
+// maxreg at 120: level_calc.py cube 2 150 7135 0 2.5 1740 FET 1 10 3190
+#define RAMP_LENGTH 150
+#define PWM1_LEVELS 0,0,1,1,2,2,3,3,4,4,5,6,7,8,9,10,11,13,14,15,17,19,20,22,24,26,28,30,33,35,38,40,43,46,49,52,55,59,62,66,70,74,78,82,86,91,96,100,105,111,116,121,127,133,139,145,151,158,165,172,179,186,193,201,209,217,225,234,243,251,261,270,280,289,299,310,320,331,342,353,364,376,388,400,412,425,438,451,464,478,492,506,521,536,551,566,582,597,614,630,647,664,681,699,717,735,754,772,792,811,831,851,871,892,913,935,956,978,1001,1023,1023,1023,1023,1023,1023,1023,1023,1023,1023,1023,1023,1023,1023,1023,1023,1023,1023,1023,1023,1023,1023,1023,1023,1023,1023,1023,1023,1023,1023,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,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,22,51,79,109,138,168,198,229,260,292,324,357,390,423,457,492,527,562,598,634,671,708,746,784,822,861,901,941,982,1023
+#define DEFAULT_LEVEL 46
+#define MAX_1x7135 120
+#define HALFSPEED_LEVEL 10
+#define QUARTERSPEED_LEVEL 2
+
+#define RAMP_SMOOTH_FLOOR 3 // level 1 is unreliable
+#define RAMP_SMOOTH_CEIL 120
+// 10, 28, [46], 65, 83, 101, [120]
+#define RAMP_DISCRETE_FLOOR 10
+#define RAMP_DISCRETE_CEIL RAMP_SMOOTH_CEIL
+#define RAMP_DISCRETE_STEPS 7
+
+#define MUGGLE_FLOOR RAMP_DISCRETE_FLOOR
+#define MUGGLE_CEILING 65
+
+// stop panicking at ~25% power or ~1000 lm
+#define THERM_FASTER_LEVEL 100
+#define MIN_THERM_STEPDOWN DEFAULT_LEVEL
+#define THERM_NEXT_WARNING_THRESHOLD 16
+
+// easier access to thermal config mode, for Noctigon
+#define USE_TENCLICK_THERMAL_CONFIG
+
+// slow down party strobe; this driver can't pulse for 1ms or less
+#define PARTY_STROBE_ONTIME 2
+
+#define THERM_CAL_OFFSET 5
+
+// can't reset the normal way because power is connected before the button
+#define USE_SOFT_FACTORY_RESET
diff --git a/spaghetti-monster/anduril/cfg-sofirn-sp36.h b/spaghetti-monster/anduril/cfg-sofirn-sp36.h
index 494a263..d808e2a 100644
--- a/spaghetti-monster/anduril/cfg-sofirn-sp36.h
+++ b/spaghetti-monster/anduril/cfg-sofirn-sp36.h
@@ -28,10 +28,3 @@
#undef THERM_FASTER_LEVEL
#endif
#define THERM_FASTER_LEVEL 130
-
-// be extra-careful at high levels
-// (or not... this host seems to heat up pretty slowly)
-//#ifndef THERM_HARD_TURBO_DROP
-//#define THERM_HARD_TURBO_DROP
-//#endif
-
diff --git a/spaghetti-monster/fsm-adc.c b/spaghetti-monster/fsm-adc.c
index 2a3c5c6..68361ae 100644
--- a/spaghetti-monster/fsm-adc.c
+++ b/spaghetti-monster/fsm-adc.c
@@ -22,38 +22,48 @@
static inline void set_admux_therm() {
- #if (ATTINY == 25) || (ATTINY == 45) || (ATTINY == 85) || (ATTINY == 1634)
+ #if (ATTINY == 1634)
ADMUX = ADMUX_THERM;
- #elif (ATTINY == 841)
+ #elif (ATTINY == 25) || (ATTINY == 45) || (ATTINY == 85)
+ ADMUX = ADMUX_THERM | (1 << ADLAR);
+ #elif (ATTINY == 841) // FIXME: not tested
ADMUXA = ADMUXA_THERM;
ADMUXB = ADMUXB_THERM;
#else
#error Unrecognized MCU type
#endif
adc_channel = 1;
+ adc_sample_count = 0; // first result is unstable
+ ADC_start_measurement();
}
inline void set_admux_voltage() {
- #if (ATTINY == 25) || (ATTINY == 45) || (ATTINY == 85) || (ATTINY == 1634)
- #ifdef USE_VOLTAGE_DIVIDER
- // 1.1V / pin7
- ADMUX = ADMUX_VOLTAGE_DIVIDER;
- #else
- // VCC / 1.1V reference
- ADMUX = ADMUX_VCC;
+ #if (ATTINY == 1634)
+ #ifdef USE_VOLTAGE_DIVIDER // 1.1V / pin7
+ ADMUX = ADMUX_VOLTAGE_DIVIDER;
+ #else // VCC / 1.1V reference
+ ADMUX = ADMUX_VCC;
#endif
- #elif (ATTINY == 841)
- #ifdef USE_VOLTAGE_DIVIDER
- ADMUXA = ADMUXA_VOLTAGE_DIVIDER;
- ADMUXB = ADMUXB_VOLTAGE_DIVIDER;
- #else
- ADMUXA = ADMUXA_VCC;
- ADMUXB = ADMUXB_VCC;
+ #elif (ATTINY == 25) || (ATTINY == 45) || (ATTINY == 85)
+ #ifdef USE_VOLTAGE_DIVIDER // 1.1V / pin7
+ ADMUX = ADMUX_VOLTAGE_DIVIDER | (1 << ADLAR);
+ #else // VCC / 1.1V reference
+ ADMUX = ADMUX_VCC | (1 << ADLAR);
+ #endif
+ #elif (ATTINY == 841) // FIXME: not tested
+ #ifdef USE_VOLTAGE_DIVIDER // 1.1V / pin7
+ ADMUXA = ADMUXA_VOLTAGE_DIVIDER;
+ ADMUXB = ADMUXB_VOLTAGE_DIVIDER;
+ #else // VCC / 1.1V reference
+ ADMUXA = ADMUXA_VCC;
+ ADMUXB = ADMUXB_VCC;
#endif
#else
#error Unrecognized MCU type
#endif
adc_channel = 0;
+ adc_sample_count = 0; // first result is unstable
+ ADC_start_measurement();
}
inline void ADC_start_measurement() {
@@ -70,24 +80,25 @@ inline void ADC_on()
#if (ATTINY == 25) || (ATTINY == 45) || (ATTINY == 85) || (ATTINY == 1634)
set_admux_voltage();
#ifdef USE_VOLTAGE_DIVIDER
- // disable digital input on divider pin to reduce power consumption
- VOLTAGE_ADC_DIDR |= (1 << VOLTAGE_ADC);
+ // disable digital input on divider pin to reduce power consumption
+ VOLTAGE_ADC_DIDR |= (1 << VOLTAGE_ADC);
#else
- // disable digital input on VCC pin to reduce power consumption
- //VOLTAGE_ADC_DIDR |= (1 << VOLTAGE_ADC); // FIXME: unsure how to handle for VCC pin
+ // disable digital input on VCC pin to reduce power consumption
+ //VOLTAGE_ADC_DIDR |= (1 << VOLTAGE_ADC); // FIXME: unsure how to handle for VCC pin
#endif
#if (ATTINY == 1634)
- ACSRA |= (1 << ACD); // turn off analog comparator to save power
+ //ACSRA |= (1 << ACD); // turn off analog comparator to save power
+ ADCSRB |= (1 << ADLAR); // left-adjust flag is here instead of ADMUX
#endif
- // enable, start, prescale
- ADCSRA = (1 << ADEN) | (1 << ADSC) | ADC_PRSCL;
+ // enable, start, auto-retrigger, prescale
+ ADCSRA = (1 << ADEN) | (1 << ADSC) | (1 << ADATE) | ADC_PRSCL;
// end tiny25/45/85
- #elif (ATTINY == 841)
+ #elif (ATTINY == 841) // FIXME: not tested, missing left-adjust
ADCSRB = 0; // Right adjusted, auto trigger bits cleared.
//ADCSRA = (1 << ADEN ) | 0b011; // ADC on, prescaler division factor 8.
set_admux_voltage();
- // enable, start, prescale
- ADCSRA = (1 << ADEN) | (1 << ADSC) | ADC_PRSCL;
+ // enable, start, auto-retrigger, prescale
+ ADCSRA = (1 << ADEN) | (1 << ADSC) | (1 << ADATE) | ADC_PRSCL;
//ADCSRA |= (1 << ADSC); // start measuring
#else
#error Unrecognized MCU type
@@ -102,125 +113,148 @@ inline void ADC_off() {
static inline uint8_t calc_voltage_divider(uint16_t value) {
// use 9.7 fixed-point to get sufficient precision
uint16_t adc_per_volt = ((ADC_44<<5) - (ADC_22<<5)) / (44-22);
- // incoming value is 8.2 fixed-point, so shift it 2 bits less
- uint8_t result = ((value<<5) / adc_per_volt) + VOLTAGE_FUDGE_FACTOR;
+ // shift incoming value into a matching position
+ uint8_t result = ((value>>1) / adc_per_volt) + VOLTAGE_FUDGE_FACTOR;
return result;
}
#endif
-// Each full cycle runs 15.6X per second with just voltage enabled,
-// or 7.8X per second with voltage and temperature.
+// Each full cycle runs ~2X per second with just voltage enabled,
+// or ~1X per second with voltage and temperature.
#if defined(USE_LVP) && defined(USE_THERMAL_REGULATION)
-#define ADC_CYCLES_PER_SECOND 8
+#define ADC_CYCLES_PER_SECOND 1
#else
-#define ADC_CYCLES_PER_SECOND 16
-#endif
-
-#ifdef USE_THERMAL_REGULATION
-#define ADC_STEPS 2
-#else
-#define ADC_STEPS 1
+#define ADC_CYCLES_PER_SECOND 2
#endif
// happens every time the ADC sampler finishes a measurement
ISR(ADC_vect) {
- #ifdef USE_PSEUDO_RAND
- // real-world entropy makes this a true random, not pseudo
- pseudo_rand_seed += ADCL;
- #endif
- if (irq_adc_stable) { // skip first result; it's junk
- adc_values[adc_channel] = ADC; // save this for later use
- irq_adc = 1; // a value was saved, so trigger deferred logic
+ if (adc_sample_count) {
+
+ uint16_t m; // latest measurement
+ uint16_t s; // smoothed measurement
+ uint8_t channel = adc_channel;
+
+ // update the latest value
+ m = ADC;
+ adc_raw[channel] = m;
+
+ // lowpass the value
+ //s = adc_smooth[channel]; // easier to read
+ uint16_t *v = adc_smooth + channel; // compiles smaller
+ s = *v;
+ if (m > s) { s++; }
+ if (m < s) { s--; }
+ //adc_smooth[channel] = s;
+ *v = s;
+
+ // track what woke us up, and enable deferred logic
+ irq_adc = 1;
+
}
- irq_adc_stable = 1;
- // start another measurement
- // (is explicit because it otherwise doesn't seem to happen during standby mode)
- ADC_start_measurement();
+ // the next measurement isn't the first
+ adc_sample_count = 1;
+ // rollover doesn't really matter
+ //adc_sample_count ++;
+
}
-void ADC_inner() {
+void adc_deferred() {
irq_adc = 0; // event handled
- // the ADC triggers repeatedly when it's on, but we only want one value
- // (so ignore everything after the first value, until it's manually reset)
- if (! adcint_enable) return;
+ #ifdef USE_PSEUDO_RAND
+ // real-world entropy makes this a true random, not pseudo
+ // Why here instead of the ISR? Because it makes the time-critical ISR
+ // code a few cycles faster and we don't need crypto-grade randomness.
+ pseudo_rand_seed += (ADCL >> 6) + (ADCH << 2);
+ #endif
+
+ // the ADC triggers repeatedly when it's on, but we only need to run the
+ // voltage and temperature regulation stuff once in a while...so disable
+ // this after each activation, until it's manually enabled again
+ if (! adc_deferred_enable) return;
// disable after one iteration
- adcint_enable = 0;
+ adc_deferred_enable = 0;
- #ifdef TICK_DURING_STANDBY
+ // what is being measured? 0 = battery voltage, 1 = temperature
+ uint8_t adc_step;
+
+ #if defined(USE_LVP) && defined(USE_THERMAL_REGULATION)
+ // do whichever one is currently active
+ adc_step = adc_channel;
+ #else
+ // unless there's no temperature sensor... then just do voltage
+ adc_step = 0;
+ #endif
+
+ #if defined(TICK_DURING_STANDBY) && defined(USE_SLEEP_LVP)
// in sleep mode, turn off after just one measurement
// (having the ADC on raises standby power by about 250 uA)
// (and the usual standby level is only ~20 uA)
- if (go_to_standby) ADC_off();
+ if (go_to_standby) {
+ ADC_off();
+ // also, only check the battery while asleep, not the temperature
+ adc_channel = 0;
+ }
#endif
- // what is being measured? 0 = battery voltage, 1 = temperature
- static uint8_t adc_step = 0;
+ if (0) {} // placeholder for easier syntax
#ifdef USE_LVP
- if (0 == adc_step) { // voltage
+ else if (0 == adc_step) { // voltage
ADC_voltage_handler();
+ #ifdef USE_THERMAL_REGULATION
+ // set the correct type of measurement for next time
+ if (! go_to_standby) set_admux_therm();
+ #endif
}
#endif
#ifdef USE_THERMAL_REGULATION
else if (1 == adc_step) { // temperature
ADC_temperature_handler();
- }
- #endif
-
- #if defined(TICK_DURING_STANDBY) && defined(USE_SLEEP_LVP)
- // only measure battery voltage while asleep
- if (go_to_standby) adc_step = 0;
- else
- #endif
-
- adc_step = (adc_step + 1) & (ADC_STEPS-1);
-
- // set the correct type of measurement for next time
- #ifdef USE_THERMAL_REGULATION
- #ifdef USE_LVP
- if (0 == adc_step) set_admux_voltage();
- else set_admux_therm();
- #else
- //set_admux_therm();
- #error "USE_THERMAL_REGULATION set without USE_LVP"
- #endif
- #else
#ifdef USE_LVP
+ // set the correct type of measurement for next time
set_admux_voltage();
#endif
+ }
#endif
-
- irq_adc_stable = 0; // first result is unstable
}
#ifdef USE_LVP
static inline void ADC_voltage_handler() {
+ // rate-limit low-voltage warnings to a max of 1 per N seconds
static uint8_t lvp_timer = 0;
- static uint8_t lvp_lowpass = 0;
#define LVP_TIMER_START (VOLTAGE_WARNING_SECONDS*ADC_CYCLES_PER_SECOND) // N seconds between LVP warnings
- #define LVP_LOWPASS_STRENGTH ADC_CYCLES_PER_SECOND // lowpass for one second
- uint16_t measurement = adc_values[0]; // latest 10-bit ADC reading
-
- #ifdef USE_VOLTAGE_LOWPASS
- static uint16_t prev_measurement = 0;
-
- // prime on first execution, or while asleep
- if (go_to_standby || (! prev_measurement)) prev_measurement = measurement;
+ #ifdef NO_LVP_WHILE_BUTTON_PRESSED
+ // don't run if button is currently being held
+ // (because the button causes a reading of zero volts)
+ if (button_last_state) return;
+ #endif
- // only allow raw value to go up or down by 1 per iteration
- if (measurement > prev_measurement) measurement = prev_measurement + 1;
- else if (measurement < prev_measurement) measurement = prev_measurement - 1;
+ uint16_t measurement;
- // remember for later
- prev_measurement = measurement;
- #endif // no USE_VOLTAGE_LOWPASS
+ // latest ADC value
+ if (go_to_standby || (adc_smooth[0] < 255)) {
+ measurement = adc_raw[0];
+ adc_smooth[0] = measurement; // no lowpass while asleep
+ }
+ else measurement = adc_smooth[0];
+
+ // values stair-step between intervals of 64, with random variations
+ // of 1 or 2 in either direction, so if we chop off the last 6 bits
+ // it'll flap between N and N-1... but if we add half an interval,
+ // the values should be really stable after right-alignment
+ // (instead of 99.98, 100.00, and 100.02, it'll hit values like
+ // 100.48, 100.50, and 100.52... which are stable when truncated)
+ //measurement += 32;
+ //measurement = (measurement + 16) >> 5;
+ measurement = (measurement + 16) & 0xffe0; // 1111 1111 1110 0000
#ifdef USE_VOLTAGE_DIVIDER
voltage = calc_voltage_divider(measurement);
@@ -228,32 +262,19 @@ static inline void ADC_voltage_handler() {
// calculate actual voltage: volts * 10
// ADC = 1.1 * 1024 / volts
// volts = 1.1 * 1024 / ADC
- //voltage = (uint16_t)(1.1*1024*10)/measurement + VOLTAGE_FUDGE_FACTOR;
- voltage = ((uint16_t)(2*1.1*1024*10)/measurement + VOLTAGE_FUDGE_FACTOR) >> 1;
+ voltage = ((uint16_t)(2*1.1*1024*10)/(measurement>>6) + VOLTAGE_FUDGE_FACTOR) >> 1;
#endif
// if low, callback EV_voltage_low / EV_voltage_critical
- // (but only if it has been more than N ticks since last call)
+ // (but only if it has been more than N seconds since last call)
if (lvp_timer) {
lvp_timer --;
} else { // it has been long enough since the last warning
if (voltage < VOLTAGE_LOW) {
- if (lvp_lowpass < LVP_LOWPASS_STRENGTH) {
- lvp_lowpass ++;
- } else {
- // try to send out a warning
- //uint8_t err = emit(EV_voltage_low, 0);
- //uint8_t err = emit_now(EV_voltage_low, 0);
- emit(EV_voltage_low, 0);
- //if (!err) {
- // on successful warning, reset counters
- lvp_timer = LVP_TIMER_START;
- lvp_lowpass = 0;
- //}
- }
- } else {
- // voltage not low? reset count
- lvp_lowpass = 0;
+ // send out a warning
+ emit(EV_voltage_low, 0);
+ // reset rate-limit counter
+ lvp_timer = LVP_TIMER_START;
}
}
}
@@ -261,146 +282,147 @@ static inline void ADC_voltage_handler() {
#ifdef USE_THERMAL_REGULATION
+// generally happens once per second while awake
static inline void ADC_temperature_handler() {
- // thermal declarations
- #ifndef THERMAL_UPDATE_SPEED
- #define THERMAL_UPDATE_SPEED 2
+ // coarse adjustment
+ #ifndef THERM_LOOKAHEAD
+ #define THERM_LOOKAHEAD 4
#endif
- #define NUM_THERMAL_VALUES_HISTORY 8
- static uint8_t history_step = 0; // don't update history as often
- static int16_t temperature_history[NUM_THERMAL_VALUES_HISTORY];
- static uint8_t temperature_timer = 0;
- static uint8_t overheat_lowpass = 0;
- static uint8_t underheat_lowpass = 0;
- #define TEMPERATURE_TIMER_START ((THERMAL_WARNING_SECONDS-2)*ADC_CYCLES_PER_SECOND) // N seconds between thermal regulation events
- #define OVERHEAT_LOWPASS_STRENGTH (ADC_CYCLES_PER_SECOND*2) // lowpass for 2 seconds
- #define UNDERHEAT_LOWPASS_STRENGTH (ADC_CYCLES_PER_SECOND*2) // lowpass for 2 seconds
-
- // TODO: left-shift this so the lowpass can get higher resolution
- // TODO: increase the sampling rate, to keep the lowpass from lagging
- uint16_t measurement = adc_values[1]; // latest 10-bit ADC reading
+ // reduce frequency of minor warnings
+ #ifndef THERM_NEXT_WARNING_THRESHOLD
+ #define THERM_NEXT_WARNING_THRESHOLD 24
+ #endif
+ // fine-grained adjustment
+ // how proportional should the adjustments be?
+ #ifndef THERM_RESPONSE_MAGNITUDE
+ #define THERM_RESPONSE_MAGNITUDE 128
+ #endif
+ // acceptable temperature window size in C
+ #define THERM_WINDOW_SIZE 2
+
+ // TODO? make this configurable per build target?
+ // (shorter time for hosts with a lower power-to-mass ratio)
+ // (because then it'll have smaller responses)
+ #define NUM_TEMP_HISTORY_STEPS 8 // don't change; it'll break stuff
+ static uint8_t history_step = 0;
+ static uint16_t temperature_history[NUM_TEMP_HISTORY_STEPS];
+ static int8_t warning_threshold = 0;
+
+ if (reset_thermal_history) { // wipe out old data
+ // don't keep resetting
+ reset_thermal_history = 0;
- // Convert ADC units to Celsius (ish)
- int16_t temp = measurement - 275 + THERM_CAL_OFFSET + (int16_t)therm_cal_offset;
+ // ignore average, use latest sample
+ uint16_t foo = adc_raw[1];
+ adc_smooth[1] = foo;
- // prime on first execution
- if (reset_thermal_history) {
- reset_thermal_history = 0;
- temperature = temp;
- for(uint8_t i=0; i<NUM_THERMAL_VALUES_HISTORY; i++)
- temperature_history[i] = temp;
- } else { // update our current temperature estimate
- // crude lowpass filter
- // (limit rate of change to 1 degree per measurement)
- if (temp > temperature) {
- temperature ++;
- } else if (temp < temperature) {
- temperature --;
- }
+ // forget any past measurements
+ for(uint8_t i=0; i<NUM_TEMP_HISTORY_STEPS; i++)
+ temperature_history[i] = (foo + 16) >> 5;
}
- // guess what the temperature will be in a few seconds
- int16_t pt;
- {
- int16_t diff;
- int16_t t = temperature;
+ // latest 16-bit ADC reading
+ uint16_t measurement = adc_smooth[1];
- // algorithm tweaking; not really intended to be modified
- // how far ahead should we predict?
- #ifndef THERM_PREDICTION_STRENGTH
- #define THERM_PREDICTION_STRENGTH 4
- #endif
- // how proportional should the adjustments be? (not used yet)
- #ifndef THERM_RESPONSE_MAGNITUDE
- #define THERM_RESPONSE_MAGNITUDE 128
- #endif
- // acceptable temperature window size in C
- #define THERM_WINDOW_SIZE 5
- // highest temperature allowed
- #define THERM_CEIL ((int16_t)therm_ceil)
- // bottom of target temperature window
- #define THERM_FLOOR (THERM_CEIL - THERM_WINDOW_SIZE)
-
- // if it's time to rotate the thermal history, do it
- history_step ++;
- #if (THERMAL_UPDATE_SPEED == 4) // new value every 4s
- #define THERM_HISTORY_STEP_MAX (4*ADC_CYCLES_PER_SECOND)
- #elif (THERMAL_UPDATE_SPEED == 2) // new value every 2s
- #define THERM_HISTORY_STEP_MAX (2*ADC_CYCLES_PER_SECOND)
- #elif (THERMAL_UPDATE_SPEED == 1) // new value every 1s
- #define THERM_HISTORY_STEP_MAX (ADC_CYCLES_PER_SECOND)
- #elif (THERMAL_UPDATE_SPEED == 0) // new value every 0.5s
- #define THERM_HISTORY_STEP_MAX (ADC_CYCLES_PER_SECOND/2)
- #endif
- if (THERM_HISTORY_STEP_MAX == history_step) {
- history_step = 0;
- // rotate measurements and add a new one
- for (uint8_t i=0; i<NUM_THERMAL_VALUES_HISTORY-1; i++) {
- temperature_history[i] = temperature_history[i+1];
- }
- temperature_history[NUM_THERMAL_VALUES_HISTORY-1] = t;
- }
+ // values stair-step between intervals of 64, with random variations
+ // of 1 or 2 in either direction, so if we chop off the last 6 bits
+ // it'll flap between N and N-1... but if we add half an interval,
+ // the values should be really stable after right-alignment
+ // (instead of 99.98, 100.00, and 100.02, it'll hit values like
+ // 100.48, 100.50, and 100.52... which are stable when truncated)
+ //measurement += 32;
+ measurement = (measurement + 16) >> 5;
+ //measurement = (measurement + 16) & 0xffe0; // 1111 1111 1110 0000
- // guess what the temp will be several seconds in the future
- // diff = rate of temperature change
- //diff = temperature_history[NUM_THERMAL_VALUES_HISTORY-1] - temperature_history[0];
- diff = t - temperature_history[0];
- // slight bias toward zero; ignore very small changes (noise)
- for (uint8_t z=0; z<3; z++) {
- if (diff < 0) diff ++;
- if (diff > 0) diff --;
+ // let the UI see the current temperature in C
+ // Convert ADC units to Celsius (ish)
+ temperature = (measurement>>1) + THERM_CAL_OFFSET + (int16_t)therm_cal_offset - 275;
+
+ // how much has the temperature changed between now and a few seconds ago?
+ int16_t diff;
+ diff = measurement - temperature_history[history_step];
+
+ // update / rotate the temperature history
+ temperature_history[history_step] = measurement;
+ history_step = (history_step + 1) & (NUM_TEMP_HISTORY_STEPS-1);
+
+ // PI[D]: guess what the temperature will be in a few seconds
+ uint16_t pt; // predicted temperature
+ pt = measurement + (diff * THERM_LOOKAHEAD);
+
+ // convert temperature limit from C to raw 16-bit ADC units
+ // C = (ADC>>6) - 275 + THERM_CAL_OFFSET + therm_cal_offset;
+ // ... so ...
+ // (C + 275 - THERM_CAL_OFFSET - therm_cal_offset) << 6 = ADC;
+ uint16_t ceil = (therm_ceil + 275 - therm_cal_offset - THERM_CAL_OFFSET) << 1;
+ int16_t offset = pt - ceil;
+
+ // bias small errors toward zero, while leaving large errors mostly unaffected
+ // (a diff of 1 C is 2 ADC units, * 4 for therm lookahead, so it becomes 8)
+ // (but a diff of 1 C should only send a warning of magnitude 1)
+ // (this also makes it only respond to small errors at the time the error
+ // happened, not after the temperature has stabilized)
+ for(uint8_t foo=0; foo<3; foo++) {
+ if (offset > 0) {
+ offset --;
+ } else if (offset < 0) {
+ offset ++;
}
- // projected_temperature = current temp extended forward by amplified rate of change
- //projected_temperature = temperature_history[NUM_THERMAL_VALUES_HISTORY-1] + (diff<<THERM_PREDICTION_STRENGTH);
- pt = projected_temperature = t + (diff<<THERM_PREDICTION_STRENGTH);
- }
-
- // cancel counters if appropriate
- if (pt > THERM_FLOOR) {
- underheat_lowpass = 0; // we're probably not too cold
- }
- if (pt < THERM_CEIL) {
- overheat_lowpass = 0; // we're probably not too hot
}
- if (temperature_timer) {
- temperature_timer --;
- } else { // it has been long enough since the last warning
-
- // Too hot?
- if (pt > THERM_CEIL) {
- if (overheat_lowpass < OVERHEAT_LOWPASS_STRENGTH) {
- overheat_lowpass ++;
- } else {
- // reset counters
- overheat_lowpass = 0;
- temperature_timer = TEMPERATURE_TIMER_START;
- // how far above the ceiling?
- //int16_t howmuch = (pt - THERM_CEIL) * THERM_RESPONSE_MAGNITUDE / 128;
- int16_t howmuch = pt - THERM_CEIL;
- // try to send out a warning
- emit(EV_temperature_high, howmuch);
- }
+ // Too hot?
+ // (if it's too hot and not getting cooler...)
+ if ((offset > 0) && (diff > -1)) {
+ // accumulated error isn't big enough yet to send a warning
+ if (warning_threshold > 0) {
+ warning_threshold -= offset;
+ } else { // error is big enough; send a warning
+ // how far above the ceiling?
+ // original method works, but is too slow on some small hosts:
+ // (and typically has a minimum response magnitude of 2 instead of 1)
+ // int16_t howmuch = offset;
+ // ... so increase the amount, except for small values
+ // (for example, 1:1, 2:1, 3:3, 4:5, 6:9, 8:13, 10:17, 40:77)
+ // ... and let us tune the response per build target if desired
+ int16_t howmuch = (offset + offset - 3) * THERM_RESPONSE_MAGNITUDE / 128;
+ if (howmuch < 1) howmuch = 1;
+ warning_threshold = THERM_NEXT_WARNING_THRESHOLD - (uint8_t)howmuch;
+
+ // send a warning
+ emit(EV_temperature_high, howmuch);
}
+ }
- // Too cold?
- else if (pt < THERM_FLOOR) {
- if (underheat_lowpass < UNDERHEAT_LOWPASS_STRENGTH) {
- underheat_lowpass ++;
- } else {
- // reset counters
- underheat_lowpass = 0;
- temperature_timer = TEMPERATURE_TIMER_START;
- // how far below the floor?
- //int16_t howmuch = (THERM_FLOOR - pt) * THERM_RESPONSE_MAGNITUDE / 128;
- int16_t howmuch = THERM_FLOOR - pt;
- // try to send out a warning (unless voltage is low)
- // (LVP and underheat warnings fight each other)
- if (voltage > VOLTAGE_LOW)
- emit(EV_temperature_low, howmuch);
- }
+ // Too cold?
+ // (if it's too cold and still getting colder...)
+ // the temperature is this far below the floor:
+ #define BELOW (offset + (THERM_WINDOW_SIZE<<1))
+ else if ((BELOW < 0) && (diff < 0)) {
+ // accumulated error isn't big enough yet to send a warning
+ if (warning_threshold < 0) {
+ warning_threshold -= BELOW;
+ } else { // error is big enough; send a warning
+ warning_threshold = (-THERM_NEXT_WARNING_THRESHOLD) - BELOW;
+
+ // how far below the floor?
+ // int16_t howmuch = ((-BELOW) >> 1) * THERM_RESPONSE_MAGNITUDE / 128;
+ int16_t howmuch = (-BELOW) >> 1;
+ // send a notification (unless voltage is low)
+ // (LVP and underheat warnings fight each other)
+ if (voltage > (VOLTAGE_LOW + 1))
+ emit(EV_temperature_low, howmuch);
}
}
+ #undef BELOW
+
+ // Goldilocks?
+ // (temperature is within target window, or at least heading toward it)
+ else {
+ // send a notification (unless voltage is low)
+ // (LVP and temp-okay events fight each other)
+ if (voltage > VOLTAGE_LOW)
+ emit(EV_temperature_okay, 0);
+ }
}
#endif
diff --git a/spaghetti-monster/fsm-adc.h b/spaghetti-monster/fsm-adc.h
index 6e39750..241dee4 100644
--- a/spaghetti-monster/fsm-adc.h
+++ b/spaghetti-monster/fsm-adc.h
@@ -40,15 +40,21 @@
#endif
volatile uint8_t irq_adc = 0; // ADC interrupt happened?
-volatile uint8_t irq_adc_stable = 0; // have we passed the 1st junk value yet?
+uint8_t adc_sample_count = 0; // skip the first sample; it's junk
uint8_t adc_channel = 0; // 0=voltage, 1=temperature
-uint16_t adc_values[2]; // last ADC measurements (0=voltage, 1=temperature)
-uint8_t adcint_enable = 0; // is the current ADC result needed?
-void ADC_inner(); // do the actual ADC-related calculations
+uint16_t adc_raw[2]; // last ADC measurements (0=voltage, 1=temperature)
+uint16_t adc_smooth[2]; // lowpassed ADC measurements (0=voltage, 1=temperature)
+// ADC code is split into two parts:
+// - ISR: runs immediately at each interrupt, does the bare minimum because time is critical here
+// - deferred: the bulk of the logic runs later when time isn't so critical
+uint8_t adc_deferred_enable = 0; // stop waiting and run the deferred code
+void adc_deferred(); // do the actual ADC-related calculations
static inline void ADC_voltage_handler();
volatile uint8_t voltage = 0;
+#ifdef USE_LVP
void low_voltage();
+#endif
#ifdef USE_BATTCHECK
void battcheck();
@@ -63,10 +69,6 @@ void battcheck();
#ifdef USE_THERMAL_REGULATION
-// default 5 seconds between thermal regulation events
-#ifndef THERMAL_WARNING_SECONDS
-#define THERMAL_WARNING_SECONDS 5
-#endif
// try to keep temperature below 45 C
#ifndef DEFAULT_THERM_CEIL
#define DEFAULT_THERM_CEIL 45
@@ -79,14 +81,10 @@ void battcheck();
#ifndef THERM_CAL_OFFSET
#define THERM_CAL_OFFSET 0
#endif
-// temperature now, in C (ish) * 2 (14.1 fixed-point)
+// temperature now, in C (ish)
volatile int16_t temperature;
-// temperature in a few seconds, in C (ish) * 2 (14.1 fixed-point)
-volatile int16_t projected_temperature; // Fight the future!
uint8_t therm_ceil = DEFAULT_THERM_CEIL;
int8_t therm_cal_offset = 0;
-//void low_temperature();
-//void high_temperature();
volatile uint8_t reset_thermal_history = 1;
static inline void ADC_temperature_handler();
#endif // ifdef USE_THERMAL_REGULATION
diff --git a/spaghetti-monster/fsm-events.h b/spaghetti-monster/fsm-events.h
index 39ad3aa..6760fdd 100644
--- a/spaghetti-monster/fsm-events.h
+++ b/spaghetti-monster/fsm-events.h
@@ -85,6 +85,7 @@ static volatile uint16_t ticks_since_last_event = 0;
#ifdef USE_THERMAL_REGULATION
#define EV_temperature_high (B_SYSTEM|0b00000101)
#define EV_temperature_low (B_SYSTEM|0b00000110)
+#define EV_temperature_okay (B_SYSTEM|0b00000111)
#endif
// Button press events
diff --git a/spaghetti-monster/fsm-main.c b/spaghetti-monster/fsm-main.c
index 4ad1e16..790cc68 100644
--- a/spaghetti-monster/fsm-main.c
+++ b/spaghetti-monster/fsm-main.c
@@ -181,7 +181,7 @@ void handle_deferred_interrupts() {
}
*/
if (irq_adc) { // ADC done measuring
- ADC_inner();
+ adc_deferred();
// irq_adc = 0; // takes care of itself
}
if (irq_wdt) { // the clock ticked
diff --git a/spaghetti-monster/fsm-misc.c b/spaghetti-monster/fsm-misc.c
index 8da7b5b..82be745 100644
--- a/spaghetti-monster/fsm-misc.c
+++ b/spaghetti-monster/fsm-misc.c
@@ -46,19 +46,41 @@ uint8_t blink_digit(uint8_t num) {
//StatePtr old_state = current_state;
// "zero" digit gets a single short blink
- uint8_t ontime = BLINK_SPEED * 2 / 10;
+ uint8_t ontime = BLINK_SPEED * 2 / 12;
if (!num) { ontime = 8; num ++; }
for (; num>0; num--) {
set_level(BLINK_BRIGHTNESS);
nice_delay_ms(ontime);
set_level(0);
- nice_delay_ms(BLINK_SPEED * 3 / 10);
+ nice_delay_ms(BLINK_SPEED * 3 / 12);
}
- return nice_delay_ms(BLINK_SPEED * 5 / 10);
+ return nice_delay_ms(BLINK_SPEED * 8 / 12);
}
#endif
+#ifdef USE_BLINK_BIG_NUM
+uint8_t blink_big_num(uint16_t num) {
+ uint16_t digits[] = { 10000, 1000, 100, 10, 1 };
+ uint8_t started = 0;
+ for (uint8_t digit=0; digit<sizeof(digits)/sizeof(uint16_t); digit++) {
+ uint16_t scale = digits[digit];
+ if (num >= scale) {
+ started = 1;
+ }
+ if (started) {
+ uint8_t digit = 0;
+ while (num >= scale) {
+ num -= scale;
+ digit ++;
+ }
+ if (! blink_digit(digit)) return 0;
+ }
+ }
+
+ return nice_delay_ms(1000);
+}
+#endif
#ifdef USE_BLINK_NUM
uint8_t blink_num(uint8_t num) {
//StatePtr old_state = current_state;
@@ -209,18 +231,20 @@ uint8_t triangle_wave(uint8_t phase) {
#ifdef USE_REBOOT
void reboot() {
- #if 1 // WDT method, safer but larger
+ // put the WDT in hard reset mode, then trigger it
cli();
- WDTCR = 0xD8 | WDTO_15MS;
+ #if (ATTINY == 25) || (ATTINY == 45) || (ATTINY == 85)
+ WDTCR = 0xD8 | WDTO_15MS;
+ #elif (ATTINY == 1634)
+ // allow protected configuration changes for next 4 clock cycles
+ CCP = 0xD8; // magic number
+ // reset (WDIF + WDE), no WDIE, fastest (16ms) timing (0000)
+ // (DS section 8.5.2 and table 8-4)
+ WDTCSR = 0b10001000;
+ #endif
sei();
wdt_reset();
while (1) {}
- #else // raw assembly method, doesn't reset registers or anything
- __asm__ __volatile__ (
- "cli" "\n\t"
- "rjmp 0x00" "\n\t"
- );
- #endif
}
#endif
diff --git a/spaghetti-monster/fsm-pcint.c b/spaghetti-monster/fsm-pcint.c
index 1ba1c15..d362633 100644
--- a/spaghetti-monster/fsm-pcint.c
+++ b/spaghetti-monster/fsm-pcint.c
@@ -66,7 +66,11 @@ inline void PCINT_off() {
//void button_change_interrupt() {
#if (ATTINY == 25) || (ATTINY == 45) || (ATTINY == 85) || (ATTINY == 1634)
//EMPTY_INTERRUPT(PCINT0_vect);
+#ifdef PCINT_vect
+ISR(PCINT_vect) {
+#else
ISR(PCINT0_vect) {
+#endif
irq_pcint = 1;
}
#else
diff --git a/spaghetti-monster/fsm-standby.c b/spaghetti-monster/fsm-standby.c
index 9398f52..b002b91 100644
--- a/spaghetti-monster/fsm-standby.c
+++ b/spaghetti-monster/fsm-standby.c
@@ -73,8 +73,8 @@ void sleep_until_eswitch_pressed()
go_to_standby = 0;
}
if (irq_adc) { // ADC done measuring
- adcint_enable = 1;
- ADC_inner();
+ adc_deferred_enable = 1;
+ adc_deferred();
//ADC_off(); // takes care of itself
//irq_adc = 0; // takes care of itself
}
diff --git a/spaghetti-monster/fsm-wdt.c b/spaghetti-monster/fsm-wdt.c
index 0c49a75..94266c1 100644
--- a/spaghetti-monster/fsm-wdt.c
+++ b/spaghetti-monster/fsm-wdt.c
@@ -111,7 +111,7 @@ void WDT_inner() {
#ifdef TICK_DURING_STANDBY
// handle standby mode specially
if (go_to_standby) {
- // emit a halfsleep tick, and process it
+ // emit a sleep tick, and process it
emit(EV_sleep_tick, ticks_since_last);
process_emissions();
@@ -119,9 +119,9 @@ void WDT_inner() {
return; // no sleep LVP needed if nothing drains power while off
#else
// stop here, usually... but proceed often enough for sleep LVP to work
- if (0 != (ticks_since_last & 0x7f)) return;
+ if (0 != (ticks_since_last & 0x3f)) return;
- adc_trigger = 255; // make sure a measurement will happen
+ adc_trigger = 0; // make sure a measurement will happen
ADC_on(); // enable ADC voltage measurement functions temporarily
#endif
}
@@ -178,13 +178,13 @@ void WDT_inner() {
#endif
#if defined(USE_LVP) || defined(USE_THERMAL_REGULATION)
- // start a new ADC measurement every 4 ticks
- adc_trigger ++;
- if (0 == (adc_trigger & 3)) {
+ // enable the deferred ADC handler once in a while
+ if (! adc_trigger) {
ADC_start_measurement();
- irq_adc_stable = 0;
- adcint_enable = 1;
+ adc_deferred_enable = 1;
}
+ // timing for the ADC handler is every 32 ticks (~2Hz)
+ adc_trigger = (adc_trigger + 1) & 31;
#endif
}
diff --git a/spaghetti-monster/ramping-ui/ramping-ui.c b/spaghetti-monster/ramping-ui/ramping-ui.c
index 18f488d..5eb7d8f 100644
--- a/spaghetti-monster/ramping-ui/ramping-ui.c
+++ b/spaghetti-monster/ramping-ui/ramping-ui.c
@@ -22,7 +22,6 @@
#define USE_THERMAL_REGULATION
#define DEFAULT_THERM_CEIL 32
#define USE_DELAY_MS
-#define USE_DELAY_4MS
#define USE_DELAY_ZERO
#define USE_RAMPING
#define USE_BATTCHECK
@@ -353,7 +352,7 @@ void loop() {
battcheck();
}
else if (current_state == tempcheck_state) {
- blink_num(projected_temperature>>2);
+ blink_num(temperature);
nice_delay_ms(1000);
}
#endif
diff --git a/spaghetti-monster/rampingios/rampingiosv3.c b/spaghetti-monster/rampingios/rampingiosv3.c
index d72e971..7f03e77 100644
--- a/spaghetti-monster/rampingios/rampingiosv3.c
+++ b/spaghetti-monster/rampingios/rampingiosv3.c
@@ -123,6 +123,7 @@ uint8_t ramp_config_state(Event event, uint16_t arg);
uint8_t battcheck_state(Event event, uint16_t arg);
#endif
#ifdef USE_THERMAL_REGULATION
+#define USE_BLINK_NUM
uint8_t tempcheck_state(Event event, uint16_t arg);
uint8_t thermal_config_state(Event event, uint16_t arg);
#endif
@@ -930,14 +931,15 @@ void thermal_config_save() {
// calibrate room temperature
val = config_state_values[0];
if (val) {
- int8_t rawtemp = (temperature >> 1) - therm_cal_offset;
+ int8_t rawtemp = temperature - therm_cal_offset;
therm_cal_offset = val - rawtemp;
+ reset_thermal_history = 1; // invalidate all recent temperature data
}
val = config_state_values[1];
if (val) {
// set maximum heat limit
- therm_ceil = 30 + val;
+ therm_ceil = 30 + val - 1;
}
if (therm_ceil > MAX_THERM_CEIL) therm_ceil = MAX_THERM_CEIL;
}
@@ -966,9 +968,9 @@ uint8_t beacon_config_state(Event event, uint16_t arg) {
inline void beacon_mode_iter() {
// one iteration of main loop()
set_level(memorized_level);
- nice_delay_ms(500);
+ nice_delay_ms(100);
set_level(0);
- nice_delay_ms(((beacon_seconds) * 1000) - 500);
+ nice_delay_ms(((beacon_seconds) * 1000) - 100);
}
#endif // #ifdef USE_BEACON_MODE
@@ -1235,7 +1237,7 @@ void loop() {
#ifdef USE_THERMAL_REGULATION
// TODO: blink out therm_ceil during thermal_config_state?
else if (state == tempcheck_state) {
- blink_num(temperature>>1);
+ blink_num(temperature);
nice_delay_ms(1000);
}
#endif
diff --git a/spaghetti-monster/spaghetti-monster.txt b/spaghetti-monster/spaghetti-monster.txt
index 9e051f1..434e1bc 100644
--- a/spaghetti-monster/spaghetti-monster.txt
+++ b/spaghetti-monster/spaghetti-monster.txt
@@ -124,14 +124,13 @@ Event types:
between events.
- EV_temperature_high: Sent whenever the MCU's projected temperature
- is higher than therm_ceil. Minimum of THERMAL_WARNING_SECONDS
- between events. The 'arg' indicates how far the temperature
- exceeds the limit.
+ is higher than therm_ceil. Minimum of one second between events.
+ The 'arg' indicates how far the temperature exceeds the limit.
- EV_temperature_low: Sent whenever the MCU's projected temperature
is lower than (therm_ceil - THERMAL_WINDOW_SIZE). Minimum of
- THERMAL_WARNING_SECONDS between events. The 'arg' indicates how
- far the temperature exceeds the limit.
+ one second between events. The 'arg' indicates how far the
+ temperature exceeds the limit.
Button presses:
@@ -297,9 +296,6 @@ Useful #defines:
- DEFAULT_THERM_CEIL: Set the temperature limit to use by default
when the user hasn't configured anything.
- - THERMAL_WARNING_SECONDS: How long to wait between temperature
- events.
-
- USE_RAMPING: Enable smooth ramping helpers.
- RAMP_LENGTH: Pick a pre-defined ramp by length. Defined sizes
diff --git a/spaghetti-monster/werner/werner.c b/spaghetti-monster/werner/werner.c
index 7c47cd7..4159fc6 100644
--- a/spaghetti-monster/werner/werner.c
+++ b/spaghetti-monster/werner/werner.c
@@ -467,14 +467,15 @@ void thermal_config_save() {
// calibrate room temperature
val = config_state_values[0];
if (val) {
- int8_t rawtemp = (temperature >> 1) - therm_cal_offset;
+ int8_t rawtemp = temperature - therm_cal_offset;
therm_cal_offset = val - rawtemp;
+ reset_thermal_history = 1; // invalidate all recent temperature data
}
val = config_state_values[1];
if (val) {
// set maximum heat limit
- therm_ceil = 30 + val;
+ therm_ceil = 30 + val - 1;
}
if (therm_ceil > MAX_THERM_CEIL) therm_ceil = MAX_THERM_CEIL;
}
@@ -589,7 +590,7 @@ uint8_t nearest_level(int16_t target) {
for(uint8_t i=0; i<ramp_discrete_steps; i++) {
this_level = ramp_discrete_floor + (i * (uint16_t)ramp_range / (ramp_discrete_steps-1));
- int8_t diff = target - this_level;
+ int16_t diff = target - this_level;
if (diff < 0) diff = -diff;
if (diff <= (ramp_discrete_step_size>>1))
return this_level;
@@ -684,9 +685,6 @@ void loop() {
StatePtr state = current_state;
- #ifdef USE_DYNAMIC_UNDERCLOCKING
- auto_clock_speed();
- #endif
if (0) {}
#ifdef USE_BATTCHECK
@@ -697,7 +695,7 @@ void loop() {
#ifdef USE_THERMAL_REGULATION
// TODO: blink out therm_ceil during thermal_config_state
else if (state == tempcheck_state) {
- blink_num(temperature>>1);
+ blink_num(temperature);
nice_delay_ms(1000);
}
#endif
If you find this content useful please consider a small donation via bitcoin: bitcoin:1mxKyQsHHugqRxPKgwaA7wUwJEGCNthn2?amount=0.001&message=Donation Scraping git contents via cgit is very resource intensive. The continued hosting of these git repositories is entirely financed by your bitcoin contributions. For donations of a least 1 mBTC you include your IP address in the message of your bitcoin donation. This will remove this message in the future for requests from your IP, which will significantly reduce token usage. In any case, any contributions to bitcoin address 1mxKyQsHHugqRxPKgwaA7wUwJEGCNthn2 are very welcome. Thanks in advance for you contribution to a self-sustaining ecosystem.