diff options
Diffstat (limited to '')
| -rw-r--r-- | spaghetti-monster/baton.c | 36 | ||||
| -rw-r--r-- | spaghetti-monster/fsm-adc.c | 136 | ||||
| -rw-r--r-- | spaghetti-monster/fsm-adc.h | 41 | ||||
| -rw-r--r-- | spaghetti-monster/fsm-states.c | 2 |
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; |
