aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--spaghetti-monster/baton.c36
-rw-r--r--spaghetti-monster/fsm-adc.c136
-rw-r--r--spaghetti-monster/fsm-adc.h41
-rw-r--r--spaghetti-monster/fsm-states.c2
4 files changed, 206 insertions, 9 deletions
diff --git a/spaghetti-monster/baton.c b/spaghetti-monster/baton.c
index 5ab35be..6b694e4 100644
--- a/spaghetti-monster/baton.c
+++ b/spaghetti-monster/baton.c
@@ -19,6 +19,8 @@
#define FSM_EMISAR_D4_LAYOUT
#define USE_LVP
+#define USE_THERMAL_REGULATION
+#define DEFAULT_THERM_CEIL 45
#define USE_DEBUG_BLINK
#define USE_DELAY_MS
#define USE_DELAY_ZERO
@@ -37,6 +39,9 @@ uint8_t party_strobe_state(EventPtr event, uint16_t arg);
// brightness control
uint8_t memorized_level = 1;
uint8_t actual_level = 0;
+#ifdef USE_THERMAL_REGULATION
+uint8_t target_level = 0;
+#endif
void set_level(uint8_t lvl) {
actual_level = lvl;
@@ -94,6 +99,9 @@ uint8_t steady_state(EventPtr event, uint16_t arg) {
if ((arg > 0) && (arg < MAX_LEVEL))
memorized_level = arg;
// use the requested level even if not memorized
+ #ifdef USE_THERMAL_REGULATION
+ target_level = arg;
+ #endif
set_level(arg);
return 0;
}
@@ -106,10 +114,17 @@ uint8_t steady_state(EventPtr event, uint16_t arg) {
else if (event == EV_2clicks) {
if (actual_level < MAX_LEVEL) {
memorized_level = actual_level; // in case we're on moon
+ #ifdef USE_THERMAL_REGULATION
+ target_level = MAX_LEVEL;
+ #endif
set_level(MAX_LEVEL);
}
- else
+ else {
+ #ifdef USE_THERMAL_REGULATION
+ target_level = memorized_level;
+ #endif
set_level(memorized_level);
+ }
return 0;
}
// 3 clicks: go to strobe modes
@@ -121,10 +136,29 @@ uint8_t steady_state(EventPtr event, uint16_t arg) {
else if (event == EV_click1_hold) {
if ((arg % HOLD_TIMEOUT) == 0) {
memorized_level = (actual_level+1) % (MAX_LEVEL+1);
+ #ifdef USE_THERMAL_REGULATION
+ target_level = memorized_level;
+ #endif
set_level(memorized_level);
}
return 0;
}
+ #ifdef USE_THERMAL_REGULATION
+ // overheating: drop by 1 level
+ else if (event == EV_temperature_high) {
+ if (actual_level > 1) {
+ set_level(actual_level - 1);
+ }
+ return 0;
+ }
+ // underheating: increase by 1 level if we're lower than the target
+ else if (event == EV_temperature_low) {
+ if (actual_level < target_level) {
+ set_level(actual_level + 1);
+ }
+ return 0;
+ }
+ #endif
return 1;
}
diff --git a/spaghetti-monster/fsm-adc.c b/spaghetti-monster/fsm-adc.c
index 11468b9..8af3487 100644
--- a/spaghetti-monster/fsm-adc.c
+++ b/spaghetti-monster/fsm-adc.c
@@ -35,9 +35,18 @@ inline void ADC_off() {
ADCSRA &= ~(1<<ADEN); //ADC off
}
+// Each full cycle runs 7.8X per second with just voltage enabled,
+// or 3.9X per second with voltage and temperature.
+#if defined(USE_LVP) && defined(USE_THERMAL_REGULATION)
+#define ADC_CYCLES_PER_SECOND 4
+#else
+#define ADC_CYCLES_PER_SECOND 8
+#endif
// TODO: is this better done in main() or WDT()?
ISR(ADC_vect) {
static uint8_t adc_step = 0;
+
+ // LVP declarations
#ifdef USE_LVP
#ifdef USE_LVP_AVG
#define NUM_VOLTAGE_VALUES 4
@@ -45,14 +54,24 @@ ISR(ADC_vect) {
#endif
static uint8_t lvp_timer = 0;
static uint8_t lvp_lowpass = 0;
- #define LVP_TIMER_START 50 // ticks between LVP warnings
- #define LVP_LOWPASS_STRENGTH 4
+ #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
#endif
+ // thermal declarations
#ifdef USE_THERMAL_REGULATION
#define NUM_THERMAL_VALUES 4
+ #define NUM_THERMAL_VALUES_HISTORY 16
#define ADC_STEPS 4
- static int16_t temperature_values[NUM_THERMAL_VALUES];
+ static uint8_t first_temp_reading = 1;
+ static int16_t temperature_values[NUM_THERMAL_VALUES]; // last few readings in C
+ static int16_t temperature_history[NUM_THERMAL_VALUES_HISTORY]; // 13.2 fixed-point
+ 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*ADC_CYCLES_PER_SECOND) // N seconds between thermal regulation events
+ #define OVERHEAT_LOWPASS_STRENGTH ADC_CYCLES_PER_SECOND // lowpass for one second
+ #define UNDERHEAT_LOWPASS_STRENGTH ADC_CYCLES_PER_SECOND // lowpass for one second
#else
#define ADC_STEPS 2
#endif
@@ -116,7 +135,116 @@ ISR(ADC_vect) {
}
#endif // ifdef USE_LVP
- // TODO: temperature
+
+ #ifdef USE_THERMAL_REGULATION
+ // temperature
+ else if (adc_step == 3) {
+ // Convert ADC units to Celsius (ish)
+ int16_t temp = measurement - 275 + THERM_CAL_OFFSET;
+
+ // prime on first execution
+ if (first_temp_reading) {
+ first_temp_reading = 0;
+ for(uint8_t i=0; i<NUM_THERMAL_VALUES; i++)
+ temperature_values[i] = temp;
+ for(uint8_t i=0; i<NUM_THERMAL_VALUES_HISTORY; i++)
+ temperature_history[i] = temp;
+ temperature = temp;
+ } else { // update our current temperature estimate
+ uint8_t i;
+ int16_t total=0;
+
+ // rotate array
+ for(i=0; i<NUM_THERMAL_VALUES-1; i++) {
+ temperature_values[i] = temperature_values[i+1];
+ total += temperature_values[i];
+ }
+ temperature_values[i] = temp;
+ total += temp;
+
+ // Divide back to original range:
+ //temperature = total >> 2;
+ // More precise method: use noise as extra precision
+ // (values are now basically fixed-point, signed 13.2)
+ temperature = total;
+ }
+
+ // guess what the temperature will be in a few seconds
+ {
+ uint8_t i;
+ int16_t diff;
+
+ // algorithm tweaking; not really intended to be modified
+ // how far ahead should we predict?
+ #define THERM_PREDICTION_STRENGTH 4
+ // how proportional should the adjustments be?
+ #define THERM_DIFF_ATTENUATION 4
+ // acceptable temperature window size in C
+ #define THERM_WINDOW_SIZE 8
+ // highest temperature allowed
+ // (convert configured value to 13.2 fixed-point)
+ #define THERM_CEIL (therm_ceil<<2)
+ // bottom of target temperature window (13.2 fixed-point)
+ #define THERM_FLOOR (THERM_CEIL - (THERM_WINDOW_SIZE<<2))
+
+ // rotate measurements and add a new one
+ for (i=0; i<NUM_THERMAL_VALUES_HISTORY-1; i++) {
+ temperature_history[i] = temperature_history[i+1];
+ }
+ temperature_history[NUM_THERMAL_VALUES_HISTORY-1] = temperature;
+
+ // guess what the temp will be several seconds in the future
+ diff = temperature_history[NUM_THERMAL_VALUES_HISTORY-1] - temperature_history[0];
+ projected_temperature = temperature_history[NUM_THERMAL_VALUES_HISTORY-1] + (diff<<THERM_PREDICTION_STRENGTH);
+
+ }
+
+ // cancel counters if necessary
+ if (projected_temperature > THERM_FLOOR) {
+ underheat_lowpass = 0; // we're definitely not too cold
+ } else if (projected_temperature < THERM_CEIL) {
+ overheat_lowpass = 0; // we're definitely not too hot
+ }
+
+ if (temperature_timer) {
+ temperature_timer --;
+ } else { // it has been long enough since the last warning
+
+ // Too hot?
+ if (projected_temperature > THERM_CEIL) {
+ if (overheat_lowpass < OVERHEAT_LOWPASS_STRENGTH) {
+ overheat_lowpass ++;
+ } else {
+ // how far above the ceiling?
+ int16_t howmuch = (projected_temperature - THERM_CEIL) >> THERM_DIFF_ATTENUATION;
+ if (howmuch < 1) howmuch = 1;
+ // try to send out a warning
+ emit(EV_temperature_high, howmuch);
+ // reset counters
+ temperature_timer = TEMPERATURE_TIMER_START;
+ overheat_lowpass = 0;
+ }
+ }
+
+ // Too cold?
+ else if (projected_temperature < THERM_FLOOR) {
+ if (underheat_lowpass < UNDERHEAT_LOWPASS_STRENGTH) {
+ underheat_lowpass ++;
+ } else {
+ // how far below the floor?
+ int16_t howmuch = (THERM_FLOOR - projected_temperature) >> THERM_DIFF_ATTENUATION;
+ if (howmuch < 1) howmuch = 1;
+ // try to send out a warning
+ emit(EV_temperature_low, howmuch);
+ // reset counters
+ temperature_timer = TEMPERATURE_TIMER_START;
+ underheat_lowpass = 0;
+ }
+ }
+ }
+ }
+ #endif // ifdef USE_THERMAL_REGULATION
+
// start another measurement for next time
#ifdef USE_THERMAL_REGULATION
diff --git a/spaghetti-monster/fsm-adc.h b/spaghetti-monster/fsm-adc.h
index ac42333..43d52a6 100644
--- a/spaghetti-monster/fsm-adc.h
+++ b/spaghetti-monster/fsm-adc.h
@@ -20,21 +20,54 @@
#ifndef FSM_ADC_H
#define FSM_ADC_H
+
#ifdef USE_LVP
-// volts * 10
-#define VOLTAGE_LOW 30
+// default 5 seconds between low-voltage warning events
+#ifndef VOLTAGE_WARNING_SECONDS
+#define VOLTAGE_WARNING_SECONDS 5
+#endif
+// low-battery threshold in volts * 10
+#ifndef VOLTAGE_LOW
+#define VOLTAGE_LOW 29
+#endif
// MCU sees voltage 0.X volts lower than actual, add X to readings
+#ifndef VOLTAGE_FUDGE_FACTOR
#define VOLTAGE_FUDGE_FACTOR 2
+#endif
volatile uint8_t voltage;
void low_voltage();
#endif
+
+
#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
+#endif
+// don't allow user to set ceiling above 70 C
+#ifndef MAX_THERM_CEIL
+#define MAX_THERM_CEIL 70
+#endif
+// Local per-MCU calibration value
+#ifndef THERM_CAL_OFFSET
+#define THERM_CAL_OFFSET 0
+#endif
+// temperature now, in C (ish)
volatile int16_t temperature;
-void low_temperature();
-void high_temperature();
+// temperature in a few seconds, in C (ish) * 4 (13.2 fixed-point)
+volatile int16_t projected_temperature; // Fight the future!
+uint8_t therm_ceil = DEFAULT_THERM_CEIL;
+//void low_temperature();
+//void high_temperature();
#endif
+
inline void ADC_on();
inline void ADC_off();
+
#endif
diff --git a/spaghetti-monster/fsm-states.c b/spaghetti-monster/fsm-states.c
index 652a9f2..ec24dc8 100644
--- a/spaghetti-monster/fsm-states.c
+++ b/spaghetti-monster/fsm-states.c
@@ -89,6 +89,7 @@ uint8_t default_state(EventPtr event, uint16_t arg) {
}
#endif
+ #if 0
#ifdef USE_THERMAL_REGULATION
else if (event == EV_temperature_high) {
high_temperature();
@@ -100,6 +101,7 @@ uint8_t default_state(EventPtr event, uint16_t arg) {
return 0;
}
#endif
+ #endif
// event not handled
return 1;