/* * fsm-adc.c: ADC (voltage, temperature) functions for SpaghettiMonster. * * 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 . */ #ifndef FSM_ADC_C #define FSM_ADC_C inline void ADC_on() { // read voltage on VCC by default // disable digital input on VCC pin to reduce power consumption //DIDR0 |= (1 << ADC_DIDR); // FIXME: unsure how to handle for VCC pin // VCC / 1.1V reference ADMUX = ADMUX_VCC; // enable, start, prescale ADCSRA = (1 << ADEN) | (1 << ADSC) | ADC_PRSCL; } inline void ADC_off() { ADCSRA &= ~(1<> 2; voltage = (uint16_t)(1.1*1024*10)/total + VOLTAGE_FUDGE_FACTOR; } #else // no USE_LVP_AVG // 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; #endif // if low, callback EV_voltage_low / EV_voltage_critical // (but only if it has been more than N ticks 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; } } } #endif // ifdef USE_LVP #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> 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 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 (unless voltage is low) // (LVP and underheat warnings fight each other) if (voltage > VOLTAGE_LOW) 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 #ifdef USE_LVP if (adc_step < 2) ADMUX = ADMUX_VCC; else ADMUX = ADMUX_THERM; #else ADMUX = ADMUX_THERM; #endif #else #ifdef USE_LVP ADMUX = ADMUX_VCC; #endif #endif } #ifdef USE_BATTCHECK #ifdef BATTCHECK_4bars PROGMEM const uint8_t voltage_blinks[] = { 30, 35, 38, 40, 42, 99, }; #endif #ifdef BATTCHECK_8bars PROGMEM const uint8_t voltage_blinks[] = { 30, 33, 35, 37, 38, 39, 40, 41, 42, 99, }; #endif void battcheck() { #ifdef BATTCHECK_VpT blink_num(voltage); #else uint8_t i; for(i=0; voltage >= pgm_read_byte(voltage_blinks + i); i++) {} #ifdef DONT_DELAY_AFTER_BATTCHECK blink_digit(i); #else if (blink_digit(i)) nice_delay_ms(1000); #endif #endif } #endif #endif