aboutsummaryrefslogtreecommitdiff
path: root/fsm/adc.c
diff options
context:
space:
mode:
authorSelene ToyKeeper2023-11-02 17:16:25 -0600
committerSelene ToyKeeper2023-11-02 17:16:25 -0600
commit7cb4fe0944b839f28dfd96a88a772cd6a8b58019 (patch)
tree8d3b203f1650edc28b1f67e1589e3bc870b33fa6 /fsm/adc.c
parentadded LICENSE (GPLv3) (diff)
downloadanduril-7cb4fe0944b839f28dfd96a88a772cd6a8b58019.tar.gz
anduril-7cb4fe0944b839f28dfd96a88a772cd6a8b58019.tar.bz2
anduril-7cb4fe0944b839f28dfd96a88a772cd6a8b58019.zip
reorganized project files (part 1)
(just moved files, didn't change the contents yet, and nothing will work without updating #includes and build scripts and stuff)
Diffstat (limited to 'fsm/adc.c')
-rw-r--r--fsm/adc.c573
1 files changed, 573 insertions, 0 deletions
diff --git a/fsm/adc.c b/fsm/adc.c
new file mode 100644
index 0000000..31b250f
--- /dev/null
+++ b/fsm/adc.c
@@ -0,0 +1,573 @@
+// fsm-adc.c: ADC (voltage, temperature) functions for SpaghettiMonster.
+// Copyright (C) 2017-2023 Selene ToyKeeper
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#pragma once
+
+// override onboard temperature sensor definition, if relevant
+#ifdef USE_EXTERNAL_TEMP_SENSOR
+#ifdef ADMUX_THERM
+#undef ADMUX_THERM
+#endif
+#define ADMUX_THERM ADMUX_THERM_EXTERNAL_SENSOR
+#endif
+
+#include <avr/sleep.h>
+
+
+static inline void set_admux_therm() {
+ #if (ATTINY == 1634)
+ ADMUX = ADMUX_THERM;
+ #elif (ATTINY == 25) || (ATTINY == 45) || (ATTINY == 85)
+ ADMUX = ADMUX_THERM | (1 << ADLAR);
+ #elif (ATTINY == 841) // FIXME: not tested
+ ADMUXA = ADMUXA_THERM;
+ ADMUXB = ADMUXB_THERM;
+ #elif defined(AVRXMEGA3) // ATTINY816, 817, etc
+ ADC0.MUXPOS = ADC_MUXPOS_TEMPSENSE_gc; // read temperature
+ ADC0.CTRLC = ADC_SAMPCAP_bm | ADC_PRESC_DIV64_gc | ADC_REFSEL_INTREF_gc; // Internal ADC reference
+ #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 == 1634)
+ #ifdef USE_VOLTAGE_DIVIDER // 1.1V / pin7
+ ADMUX = ADMUX_VOLTAGE_DIVIDER;
+ #else // VCC / 1.1V reference
+ ADMUX = ADMUX_VCC;
+ #endif
+ #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
+ #elif defined(AVRXMEGA3) // ATTINY816, 817, etc
+ #ifdef USE_VOLTAGE_DIVIDER // 1.1V / ADC input pin
+ // verify that this is correct!!! untested
+ ADC0.MUXPOS = ADMUX_VOLTAGE_DIVIDER; // read the requested ADC pin
+ ADC0.CTRLC = ADC_SAMPCAP_bm | ADC_PRESC_DIV64_gc | ADC_REFSEL_INTREF_gc; // Use internal ADC reference
+ #else // VCC / 1.1V reference
+ ADC0.MUXPOS = ADC_MUXPOS_INTREF_gc; // read internal reference
+ ADC0.CTRLC = ADC_SAMPCAP_bm | ADC_PRESC_DIV64_gc | ADC_REFSEL_VDDREF_gc; // Vdd (Vcc) be ADC reference
+ #endif
+ #else
+ #error Unrecognized MCU type
+ #endif
+ adc_channel = 0;
+ adc_sample_count = 0; // first result is unstable
+ ADC_start_measurement();
+}
+
+
+#ifdef TICK_DURING_STANDBY
+inline void adc_sleep_mode() {
+ // needs a special sleep mode to get accurate measurements quickly
+ // ... full power-down ends up using more power overall, and causes
+ // some weird issues when the MCU doesn't stay awake enough cycles
+ // to complete a reading
+ #ifdef SLEEP_MODE_ADC
+ // attiny1634
+ set_sleep_mode(SLEEP_MODE_ADC);
+ #elif defined(AVRXMEGA3) // ATTINY816, 817, etc
+ set_sleep_mode(SLEEP_MODE_STANDBY);
+ #else
+ #error No ADC sleep mode defined for this hardware.
+ #endif
+}
+#endif
+
+inline void ADC_start_measurement() {
+ #if (ATTINY == 25) || (ATTINY == 45) || (ATTINY == 85) || (ATTINY == 841) || (ATTINY == 1634)
+ ADCSRA |= (1 << ADSC) | (1 << ADIE);
+ #elif defined(AVRXMEGA3) // ATTINY816, 817, etc
+ ADC0.INTCTRL |= ADC_RESRDY_bm; // enable interrupt
+ ADC0.COMMAND |= ADC_STCONV_bm; // Start the ADC conversions
+ #else
+ #error unrecognized MCU type
+ #endif
+}
+
+// set up ADC for reading battery voltage
+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);
+ #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
+ #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
+ #endif
+ // enable, start, auto-retrigger, prescale
+ ADCSRA = (1 << ADEN) | (1 << ADSC) | (1 << ADATE) | ADC_PRSCL;
+ // end tiny25/45/85
+ #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, auto-retrigger, prescale
+ ADCSRA = (1 << ADEN) | (1 << ADSC) | (1 << ADATE) | ADC_PRSCL;
+ //ADCSRA |= (1 << ADSC); // start measuring
+ #elif defined(AVRXMEGA3) // ATTINY816, 817, etc
+ VREF.CTRLA |= VREF_ADC0REFSEL_1V1_gc; // Set Vbg ref to 1.1V
+ // Enabled, free-running (aka, auto-retrigger), run in standby
+ ADC0.CTRLA = ADC_ENABLE_bm | ADC_FREERUN_bm | ADC_RUNSTBY_bm;
+ // set a INITDLY value because the AVR manual says so (section 30.3.5)
+ // (delay 1st reading until Vref is stable)
+ ADC0.CTRLD |= ADC_INITDLY_DLY16_gc;
+ set_admux_voltage();
+ #else
+ #error Unrecognized MCU type
+ #endif
+}
+
+inline void ADC_off() {
+ #ifdef AVRXMEGA3 // ATTINY816, 817, etc
+ ADC0.CTRLA &= ~(ADC_ENABLE_bm); // disable the ADC
+ #else
+ ADCSRA &= ~(1<<ADEN); //ADC off
+ #endif
+}
+
+#ifdef USE_VOLTAGE_DIVIDER
+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);
+ // shift incoming value into a matching position
+ uint8_t result = ((value / adc_per_volt)
+ + VOLTAGE_FUDGE_FACTOR
+ #ifdef USE_VOLTAGE_CORRECTION
+ + VOLT_CORR - 7
+ #endif
+ ) >> 1;
+ return result;
+}
+#endif
+
+// 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 1
+#else
+#define ADC_CYCLES_PER_SECOND 2
+#endif
+
+#ifdef AVRXMEGA3 // ATTINY816, 817, etc
+#define ADC_vect ADC0_RESRDY_vect
+#endif
+// happens every time the ADC sampler finishes a measurement
+ISR(ADC_vect) {
+
+ #ifdef AVRXMEGA3 // ATTINY816, 817, etc
+ ADC0.INTFLAGS = ADC_RESRDY_bm; // clear the interrupt
+ #endif
+
+ if (adc_sample_count) {
+
+ uint16_t m; // latest measurement
+ uint16_t s; // smoothed measurement
+ uint8_t channel = adc_channel;
+
+ // update the latest value
+ #ifdef AVRXMEGA3 // ATTINY816, 817, etc
+ // Use the factory calibrated values in SIGROW.TEMPSENSE0 and SIGROW.TEMPSENSE1
+ // to calculate a temperature reading in Kelvin, then left-align it.
+ if (channel == 1) { // thermal, convert ADC reading to left-aligned Kelvin
+ int8_t sigrow_offset = SIGROW.TEMPSENSE1; // Read signed value from signature row
+ uint8_t sigrow_gain = SIGROW.TEMPSENSE0; // Read unsigned value from signature row
+ uint32_t temp = ADC0.RES - sigrow_offset;
+ temp *= sigrow_gain; // Result might overflow 16 bit variable (10bit+8bit)
+ temp += 0x80; // Add 1/2 to get correct rounding on division below
+ temp >>= 8; // Divide result to get Kelvin
+ m = (temp << 6); // left align it
+ }
+ else { m = (ADC0.RES << 6); } // voltage, force left-alignment
+
+ #else
+ m = ADC;
+ #endif
+ 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;
+
+ }
+
+ // the next measurement isn't the first
+ adc_sample_count = 1;
+ // rollover doesn't really matter
+ //adc_sample_count ++;
+
+}
+
+void adc_deferred() {
+ irq_adc = 0; // event handled
+
+ #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.
+ #ifdef AVRXMEGA3 // ATTINY816, 817, etc
+ pseudo_rand_seed += ADC0.RESL; // right aligned, not left... so should be equivalent?
+ #else
+ pseudo_rand_seed += (ADCL >> 6) + (ADCH << 2);
+ #endif
+ #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
+ adc_deferred_enable = 0;
+
+ // 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 any measurements were in progress, they're done now
+ adc_active_now = 0;
+ // also, only check the battery while asleep, not the temperature
+ adc_channel = 0;
+ }
+ #endif
+
+ if (0) {} // placeholder for easier syntax
+
+ #ifdef USE_LVP
+ 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();
+ #ifdef USE_LVP
+ // set the correct type of measurement for next time
+ set_admux_voltage();
+ #endif
+ }
+ #endif
+
+ if (adc_reset) adc_reset --;
+}
+
+
+#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;
+ #define LVP_TIMER_START (VOLTAGE_WARNING_SECONDS*ADC_CYCLES_PER_SECOND) // N seconds between LVP warnings
+
+ #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
+
+ uint16_t measurement;
+
+ // latest ADC value
+ if (adc_reset) { // just after waking, don't lowpass
+ measurement = adc_raw[0];
+ adc_smooth[0] = measurement; // no lowpass, just use the latest value
+ }
+ #ifdef USE_LOWPASS_WHILE_ASLEEP
+ else if (go_to_standby) { // weaker lowpass while asleep
+ // occasionally the aux LED color can oscillate during standby,
+ // while using "voltage" mode ... so try to reduce the oscillation
+ uint16_t r = adc_raw[0];
+ uint16_t s = adc_smooth[0];
+ #if 0
+ // fixed-rate lowpass, stable but very slow
+ // (move by only 0.5 ADC units per measurement, 1 ADC unit = 64)
+ if (r < s) { s -= 32; }
+ if (r > s) { s += 32; }
+ #elif 1
+ // 1/8th proportional lowpass, faster but less stable
+ int16_t diff = (r/8) - (s/8);
+ s += diff;
+ #else
+ // 50% proportional lowpass, fastest but least stable
+ s = (r>>1) + (s>>1);
+ #endif
+ adc_smooth[0] = s;
+ measurement = s;
+ }
+ #endif
+ 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
+ // calculate actual voltage: volts * 10
+ // ADC = 1.1 * 1024 / volts
+ // volts = 1.1 * 1024 / ADC
+ voltage = ((uint16_t)(2*1.1*1024*10)/(measurement>>6)
+ + VOLTAGE_FUDGE_FACTOR
+ #ifdef USE_VOLTAGE_CORRECTION
+ + VOLT_CORR - 7
+ #endif
+ ) >> 1;
+ #endif
+
+ // if low, callback EV_voltage_low / EV_voltage_critical
+ // (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
+ #ifdef DUAL_VOLTAGE_FLOOR
+ if (((voltage < VOLTAGE_LOW) && (voltage > DUAL_VOLTAGE_FLOOR)) || (voltage < DUAL_VOLTAGE_LOW_LOW)) {
+ #else
+ if (voltage < VOLTAGE_LOW) {
+ #endif
+ // send out a warning
+ emit(EV_voltage_low, 0);
+ // reset rate-limit counter
+ lvp_timer = LVP_TIMER_START;
+ }
+ }
+}
+#endif
+
+
+#ifdef USE_THERMAL_REGULATION
+// generally happens once per second while awake
+static inline void ADC_temperature_handler() {
+ // coarse adjustment
+ #ifndef THERM_LOOKAHEAD
+ #define THERM_LOOKAHEAD 4
+ #endif
+ // 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 64
+ #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 (adc_reset) { // wipe out old data
+ // ignore average, use latest sample
+ 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] = (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)
+ #ifndef USE_EXTERNAL_TEMP_SENSOR
+ // onboard sensor for attiny25/45/85/1634
+ temperature = (measurement>>1) + THERM_CAL_OFFSET + (int16_t)TH_CAL - 275;
+ #else
+ // external sensor
+ temperature = EXTERN_TEMP_FORMULA(measurement>>1) + THERM_CAL_OFFSET + (int16_t)TH_CAL;
+ #endif
+
+ // 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 + TH_CAL;
+ // ... so ...
+ // (C + 275 - THERM_CAL_OFFSET - TH_CAL) << 6 = ADC;
+ uint16_t ceil = (TH_CEIL + 275 - TH_CAL - 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 ++;
+ }
+ }
+
+ // 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?
+ // (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
+
+
+#ifdef USE_BATTCHECK
+#ifdef BATTCHECK_4bars
+PROGMEM const uint8_t voltage_blinks[] = {
+ 30, 35, 38, 40, 42, 99,
+};
+#endif
+#ifdef BATTCHECK_6bars
+PROGMEM const uint8_t voltage_blinks[] = {
+ 30, 34, 36, 38, 40, 41, 43, 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
+