diff options
| author | Selene ToyKeeper | 2020-02-28 02:06:53 -0700 |
|---|---|---|
| committer | Selene ToyKeeper | 2020-02-28 02:06:53 -0700 |
| commit | 930752b496ad8a1d9f3db96184839022c16a5c7f (patch) | |
| tree | 840ff47b53f38ba422bbf2367ee8c3c93c3de1b6 | |
| parent | brute force method for reducing ADC noise -- average a ridiculous number of s... (diff) | |
| download | anduril-930752b496ad8a1d9f3db96184839022c16a5c7f.tar.gz anduril-930752b496ad8a1d9f3db96184839022c16a5c7f.tar.bz2 anduril-930752b496ad8a1d9f3db96184839022c16a5c7f.zip | |
went back to continuous lowpass because it had the best noise reduction
(also, now treating smoothed ADC values as 11-bit, with the lowest 5 bits chopped off to eliminate noise)
| -rw-r--r-- | spaghetti-monster/fsm-adc.c | 124 | ||||
| -rw-r--r-- | spaghetti-monster/fsm-wdt.c | 2 |
2 files changed, 74 insertions, 52 deletions
diff --git a/spaghetti-monster/fsm-adc.c b/spaghetti-monster/fsm-adc.c index 65669b3..59d4e5c 100644 --- a/spaghetti-monster/fsm-adc.c +++ b/spaghetti-monster/fsm-adc.c @@ -25,7 +25,7 @@ static inline void set_admux_therm() { #if (ATTINY == 1634) ADMUX = ADMUX_THERM; #elif (ATTINY == 25) || (ATTINY == 45) || (ATTINY == 85) - ADMUX = ADMUX_THERM; + ADMUX = ADMUX_THERM | (1 << ADLAR); #elif (ATTINY == 841) // FIXME: not tested ADMUXA = ADMUXA_THERM; ADMUXB = ADMUXB_THERM; @@ -46,9 +46,9 @@ inline void set_admux_voltage() { #endif #elif (ATTINY == 25) || (ATTINY == 45) || (ATTINY == 85) #ifdef USE_VOLTAGE_DIVIDER // 1.1V / pin7 - ADMUX = ADMUX_VOLTAGE_DIVIDER; + ADMUX = ADMUX_VOLTAGE_DIVIDER | (1 << ADLAR); #else // VCC / 1.1V reference - ADMUX = ADMUX_VCC; + ADMUX = ADMUX_VCC | (1 << ADLAR); #endif #elif (ATTINY == 841) // FIXME: not tested #ifdef USE_VOLTAGE_DIVIDER // 1.1V / pin7 @@ -88,7 +88,7 @@ inline void ADC_on() #endif #if (ATTINY == 1634) //ACSRA |= (1 << ACD); // turn off analog comparator to save power - //ADCSRB |= (1 << ADLAR); // left-adjust flag is here instead of ADMUX + ADCSRB |= (1 << ADLAR); // left-adjust flag is here instead of ADMUX #endif // enable, start, auto-retrigger, prescale ADCSRA = (1 << ADEN) | (1 << ADSC) | (1 << ADATE) | ADC_PRSCL; @@ -122,46 +122,42 @@ static inline uint8_t calc_voltage_divider(uint16_t value) { // Each full cycle runs ~4X per second with just voltage enabled, // or ~2X per second with voltage and temperature. #if defined(USE_LVP) && defined(USE_THERMAL_REGULATION) -#define ADC_CYCLES_PER_SECOND 2 +#define ADC_CYCLES_PER_SECOND 1 #else -#define ADC_CYCLES_PER_SECOND 4 +#define ADC_CYCLES_PER_SECOND 2 #endif // happens every time the ADC sampler finishes a measurement -// collects a rolling average of 64+ samples, which increases effective number -// of bits from 10 to about 16 (ish, probably more like 14 really) (64 was -// chosen because it's the largest sample size which allows the sum to still -// fit into a 16-bit integer, and for speed and size reasons, we want to avoid -// doing 32-bit math) ISR(ADC_vect) { - static uint32_t adc_sum; + if (adc_sample_count) { - // keep this moving along - adc_sample_count ++; + uint16_t m; // latest measurement + uint16_t s; // smoothed measurement + uint8_t channel = adc_channel; - // reset on first sample - // also, ignore first value since it's probably junk - if (1 == adc_sample_count) { - adc_sum = 0; - return; - } - // 2048 samples collected, save the result - else if (2050 == adc_sample_count) { - // save the latest result - adc_smooth[adc_channel] = adc_sum >> 5; - } - // add the latest measurement to the pile - else { - uint16_t m = ADC; - // add to the running total - adc_sum += m; // update the latest value - adc_raw[adc_channel] = m; + 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; + } - // track what woke us up, and enable deferred logic - irq_adc = 1; + // the next measurement isn't the first + //adc_sample_count = 1; + adc_sample_count ++; + } void adc_deferred() { @@ -171,7 +167,7 @@ void adc_deferred() { // 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; + pseudo_rand_seed += (ADCL >> 6) + (ADCH << 2); #endif // the ADC triggers repeatedly when it's on, but we only need to run the @@ -237,9 +233,22 @@ static inline void ADC_voltage_handler() { uint16_t measurement; // latest ADC value - if (go_to_standby) measurement = adc_raw[0] << 6; + 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); #else @@ -286,27 +295,35 @@ static inline void ADC_temperature_handler() { // N seconds between thermal regulation events #define TEMPERATURE_TIMER_START (THERMAL_WARNING_SECONDS*ADC_CYCLES_PER_SECOND) - // latest 16-bit ADC reading - uint16_t measurement; - - if (! reset_thermal_history) { - // average of recent samples - measurement = adc_smooth[1] >> 1; - } else { // wipe out old data + if (reset_thermal_history) { // wipe out old data // don't keep resetting reset_thermal_history = 0; // ignore average, use latest sample - measurement = adc_raw[1] << 5; + uint16_t foo = adc_raw[1]; + adc_smooth[1] = foo; // forget any past measurements for(uint8_t i=0; i<NUM_TEMP_HISTORY_STEPS; i++) - temperature_history[i] = measurement; + temperature_history[i] = (foo + 16) >> 5; } + // latest 16-bit ADC reading + uint16_t measurement = adc_smooth[1]; + + // 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 + // let the UI see the current temperature in C // Convert ADC units to Celsius (ish) - temperature = (measurement>>5) - 275 + THERM_CAL_OFFSET + (int16_t)therm_cal_offset; + 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; @@ -320,22 +337,27 @@ static inline void ADC_temperature_handler() { uint16_t pt; // predicted temperature pt = measurement + (diff * THERM_LOOKAHEAD); + /* seems unnecessary; simply sending repeated warnings has a similar effect // P[I]D: average of recent measurements uint16_t avg = 0; for(uint8_t i=0; i<NUM_TEMP_HISTORY_STEPS; i++) avg += (temperature_history[i]>>3); + */ // 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) << 5; + uint16_t ceil = (therm_ceil + 275 - therm_cal_offset - THERM_CAL_OFFSET) << 1; //uint16_t floor = ceil - (THERM_WINDOW_SIZE << 6); + /* average of I and D terms int16_t offset_pt, offset_avg; offset_pt = (pt - ceil) >> 1; offset_avg = (avg - ceil) >> 1; int16_t offset = offset_pt + offset_avg; //int16_t offset = (pt - ceil) + (avg - ceil); + */ + int16_t offset = pt - ceil; if (temperature_timer) { @@ -344,25 +366,25 @@ static inline void ADC_temperature_handler() { // Too hot? // (if it's too hot and not getting colder...) - if ((offset > 0) && (diff > (-1 << 4))) { + if ((offset > 0) && (diff > (-1))) { // reset counters temperature_timer = TEMPERATURE_TIMER_START; // how far above the ceiling? //int16_t howmuch = (offset >> 6) * THERM_RESPONSE_MAGNITUDE / 128; - int16_t howmuch = (offset >> 8); + int16_t howmuch = (offset >> 1); // send a warning emit(EV_temperature_high, howmuch); } // Too cold? // (if it's too cold and not getting warmer...) - else if ((offset < -(THERM_WINDOW_SIZE << 5)) - && (diff < (1 << 3))) { + else if ((offset < -(THERM_WINDOW_SIZE << 1)) + && (diff < (1))) { // reset counters temperature_timer = TEMPERATURE_TIMER_START; // how far below the floor? //int16_t howmuch = (((-offset) - (THERM_WINDOW_SIZE<<6)) >> 7) * THERM_WINDOW_SIZE / 128; - int16_t howmuch = ((-offset) - (THERM_WINDOW_SIZE<<5)) >> 8; + int16_t howmuch = ((-offset) - (THERM_WINDOW_SIZE<<1)) >> 1; // send a notification (unless voltage is low) // (LVP and underheat warnings fight each other) if (voltage > VOLTAGE_LOW) diff --git a/spaghetti-monster/fsm-wdt.c b/spaghetti-monster/fsm-wdt.c index 459010f..12aab7b 100644 --- a/spaghetti-monster/fsm-wdt.c +++ b/spaghetti-monster/fsm-wdt.c @@ -180,7 +180,7 @@ void WDT_inner() { #if defined(USE_LVP) || defined(USE_THERMAL_REGULATION) // start a new ADC measurement every 16 ticks adc_trigger ++; - if (0 == (adc_trigger & 15)) { + if (0 == (adc_trigger & 31)) { // in case we're in standby mode and the ADC is turned off if (go_to_standby) { //set_admux_voltage(); |
