diff options
| author | Selene ToyKeeper | 2023-11-21 02:52:00 -0700 |
|---|---|---|
| committer | Selene ToyKeeper | 2023-11-21 02:52:00 -0700 |
| commit | fdb47b96e86924bf81852205b2b3078b8b21d040 (patch) | |
| tree | ed8337b4619bb86ffb40c63767a32ae58e9b4dcc | |
| parent | 1.55V AA battery should not show as "white" voltage color, only purple (diff) | |
| download | anduril-fdb47b96e86924bf81852205b2b3078b8b21d040.tar.gz anduril-fdb47b96e86924bf81852205b2b3078b8b21d040.tar.bz2 anduril-fdb47b96e86924bf81852205b2b3078b8b21d040.zip | |
got ADC voltage+temp working on avrdd... but broke all other builds/MCUs
This patch changes the ADC code to use two internal standard units, and
everything else must convert to these units:
- FSM Volts: centiVolts << 6 (range 0 to 10.24 V per cell)
- FSM Kelvin: Kelvin << 6 (range 0 to 1024 K)
UI-level voltage is still "Volts * 10", and temperature is still Celsius.
FSM expects functions to be provided, to convert from the hardware's
raw ADC measurements to these internal units: `voltage_raw2cooked()`
and `temp_raw2cooked()`. Defaults will be provided by arch/*.[ch] for
each MCU type, or the hwdef can make its own.
Anyway, gotta go fix all the other MCUs and builds now. :(
Diffstat (limited to '')
| -rw-r--r-- | arch/avr32dd20.c | 60 | ||||
| -rw-r--r-- | arch/avr32dd20.h | 15 | ||||
| -rw-r--r-- | fsm/adc.c | 31 | ||||
| -rw-r--r-- | hw/thefreeman/avr32dd20-devkit/hwdef.c | 13 | ||||
| -rw-r--r-- | hw/thefreeman/avr32dd20-devkit/hwdef.h | 12 |
5 files changed, 97 insertions, 34 deletions
diff --git a/arch/avr32dd20.c b/arch/avr32dd20.c index 45923a8..998e9f6 100644 --- a/arch/avr32dd20.c +++ b/arch/avr32dd20.c @@ -52,19 +52,19 @@ inline void mcu_set_admux_therm() { ADC0.SAMPCTRL = 32; // set single-ended or differential // set resolution to 12 bits - // set left- or right-adjust (right) + // set left- or right-adjust // set free-running mode or not (yes) ADC0.CTRLA = ADC_CONVMODE_SINGLEENDED_gc | ADC_RESSEL_12BIT_gc - //| ADC_LEFTADJ_bm // not in temperature mode + | ADC_LEFTADJ_bm | ADC_FREERUN_bm; // set number of samples (requires adjustment in formula too) ADC0.CTRLB = ADC_SAMPNUM_NONE_gc; - // TODO: accumulate more samples for more resolution - // (and probably set the prescale faster too) - //ADC0.CTRLB = ADC_SAMPNUM_ACC16_gc; // 16 samples per result + // accumulate more samples for more resolution + ADC0.CTRLB = ADC_SAMPNUM_ACC16_gc; // 16 samples per result // set a clock prescaler - ADC0.CTRLC = ADC_PRESC_DIV64_gc; + //ADC0.CTRLC = ADC_PRESC_DIV64_gc; // use this when no accumulation + ADC0.CTRLC = ADC_PRESC_DIV4_gc; // measure faster when oversampling // enable the ADC ADC0.CTRLA |= ADC_ENABLE_bm; // actually start measuring (happens in another function) @@ -90,7 +90,7 @@ inline void mcu_set_admux_voltage() { // set number of samples ADC0.CTRLB = ADC_SAMPNUM_ACC16_gc; // 16 samples per result // set a clock prescaler - ADC0.CTRLC = ADC_PRESC_DIV16_gc; // not too fast, not too slow + ADC0.CTRLC = ADC_PRESC_DIV4_gc; // measure faster when oversampling // select the positive ADC input with MUXPOS #ifdef USE_VOLTAGE_DIVIDER // external voltage divider // ADC input pin / Vref @@ -113,11 +113,8 @@ inline void mcu_adc_sleep_mode() { } inline void mcu_adc_start_measurement() { - // FIXME: enable this after getting ADC stuff fixed - #if 0 ADC0.INTCTRL |= ADC_RESRDY_bm; // enable interrupt ADC0.COMMAND |= ADC_STCONV_bm; // actually start measuring - #endif } /* @@ -135,24 +132,35 @@ inline void mcu_adc_vect_clear() { ADC0.INTFLAGS = ADC_RESRDY_bm; // clear the interrupt } -inline uint16_t mcu_adc_result_temp() { - // FIXME: better math, higher precision - // Use the factory calibrated values in SIGROW.TEMPSENSE0 and - // SIGROW.TEMPSENSE1 to calculate a temperature reading in Kelvin, then - // left-align it. - 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 - //return temp << 6; // left align it - return temp >> 2; // left-aligned uint16_t +inline uint16_t mcu_adc_result() { + // value is 12-bit left-aligned + 16x oversampling = 16 bits total + return ADC0.RES; } -inline uint16_t mcu_adc_result_volts() { - // voltage is 12-bit right-aligned + 16x oversampling = 16 bits total - return ADC0.RES; +inline uint16_t mcu_vdd_raw2cooked(uint16_t measurement) { + // In : 65535 * (Vbat / 10) / 1.024V + // Out: 65535 * (Vbat / 10) / 1.024V + // This MCU's native format is already correct + return measurement; +} + +inline uint16_t mcu_temp_raw2cooked(uint16_t measurement) { + // convert raw ADC values to calibrated temperature + // In: ADC raw temperature (16-bit, or 12-bit left-aligned) + // Out: Kelvin << 6 + // Precision: 1/64th Kelvin (but noisy) + // AVR DD datasheet section 33.3.3.8 + uint16_t sigrow_slope = SIGROW.TEMPSENSE0; // factory calibration data + uint16_t sigrow_offset = SIGROW.TEMPSENSE1; // 12-bit value + //const uint32_t scaling_factor = 4096; // use top 12 bits of ADC data + //uint32_t temp = sigrow_offset - (measurement >> 4); + const uint32_t scaling_factor = 65536; // use all 16 bits of ADC data + uint32_t temp = (sigrow_offset << 4) - measurement; + temp *= sigrow_slope; // 24-bit result + temp += scaling_factor / 8; // Add 1/8th K to get correct rounding on later divisions + //temp = temp >> 6; // change (K << 12) to (K << 6) + temp = temp >> 10; // change (K << 16) to (K << 6) + return temp; // left-aligned uint16_t, 0 to 1023.98 Kelvin } inline uint8_t mcu_adc_lsb() { diff --git a/arch/avr32dd20.h b/arch/avr32dd20.h index 7a6b8f1..3ebb05a 100644 --- a/arch/avr32dd20.h +++ b/arch/avr32dd20.h @@ -50,12 +50,21 @@ inline void mcu_adc_off(); #define ADC_vect ADC0_RESRDY_vect inline void mcu_adc_vect_clear(); +// both readings are left-aligned +inline uint16_t mcu_adc_result(); + // read ADC differently for temperature and voltage -#define MCU_ADC_RESULT_PER_TYPE +//#define MCU_ADC_RESULT_PER_TYPE +//inline uint16_t mcu_adc_result_temp(); +//inline uint16_t mcu_adc_result_volts(); -inline uint16_t mcu_adc_result_temp(); +// return (centiVolts << 6), range 0 to 10.24V +#define voltage_raw2cooked mcu_vdd_raw2cooked +inline uint16_t mcu_vdd_raw2cooked(uint16_t measurement); -inline uint16_t mcu_adc_result_volts(); +// return (temp in Kelvin << 6) +#define temp_raw2cooked mcu_temp_raw2cooked +inline uint16_t mcu_temp_raw2cooked(uint16_t measurement); inline uint8_t mcu_adc_lsb(); @@ -30,6 +30,7 @@ void adc_voltage_mode() { } +#if 0 #ifdef USE_VOLTAGE_DIVIDER static inline uint8_t calc_voltage_divider(uint16_t value) { // use 9.7 fixed-point to get sufficient precision @@ -44,6 +45,7 @@ static inline uint8_t calc_voltage_divider(uint16_t value) { return result; } #endif +#endif // Each full cycle runs ~2X per second with just voltage enabled, // or ~1X per second with voltage and temperature. @@ -67,7 +69,6 @@ ISR(ADC_vect) { // update the latest value #ifdef MCU_ADC_RESULT_PER_TYPE - // thermal, convert ADC reading to left-aligned Kelvin if (channel) m = mcu_adc_result_temp(); else m = mcu_adc_result_volts(); #else @@ -208,6 +209,19 @@ static inline void ADC_voltage_handler() { #endif else measurement = adc_smooth[0]; + // convert raw ADC value to FSM voltage units: (V * 100) << 6 + // 0 .. 65535 = 0.0V .. 10.24V + measurement = voltage_raw2cooked(measurement) / (10 << 5); + + // calculate actual voltage: volts * 10 + // TODO: should be (volts * 40) for extra precision + voltage = (measurement + VOLTAGE_FUDGE_FACTOR + #ifdef USE_VOLTAGE_CORRECTION + + VOLT_CORR - 7 + #endif + ) >> 1; + + #if 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, @@ -231,6 +245,7 @@ static inline void ADC_voltage_handler() { #endif ) >> 1; #endif + #endif // if low, callback EV_voltage_low / EV_voltage_critical // (but only if it has been more than N seconds since last call) @@ -290,8 +305,14 @@ static inline void ADC_temperature_handler() { } // latest 16-bit ADC reading - uint16_t measurement = adc_smooth[1]; + // convert raw ADC value to Kelvin << 6 + // 0 .. 65535 = 0 K .. 1024 K + uint16_t measurement = temp_raw2cooked(adc_smooth[1]); + // (Kelvin << 6) to Celsius + temperature = (measurement>>6) + THERM_CAL_OFFSET + (int16_t)TH_CAL - 275; + + #if 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, @@ -313,6 +334,12 @@ static inline void ADC_temperature_handler() { // external sensor temperature = EXTERN_TEMP_FORMULA(measurement>>1) + THERM_CAL_OFFSET + (int16_t)TH_CAL; #endif + #endif + + // instead of (K << 6), use (K << 1) now + // TODO: use more precision, if it can be done without overflow in 16 bits + // (and still work on attiny85 without increasing ROM size) + measurement = measurement >> 5; // how much has the temperature changed between now and a few seconds ago? int16_t diff; diff --git a/hw/thefreeman/avr32dd20-devkit/hwdef.c b/hw/thefreeman/avr32dd20-devkit/hwdef.c index e5b347f..133baca 100644 --- a/hw/thefreeman/avr32dd20-devkit/hwdef.c +++ b/hw/thefreeman/avr32dd20-devkit/hwdef.c @@ -103,3 +103,16 @@ bool gradual_tick_main(uint8_t gt) { return false; // not done yet } + +uint16_t voltage_raw2cooked(uint16_t measurement) { + // In : 65535 * BATTLVL pin / 1.024 Vref + // Out: 65535 * (Vbat / 10) / 1.024V (i.e. FSM Volt units) + // BATTLVL = Vbat * (100.0/(330+100)) = Vbat / 4.3 + // So, Out = In * 4.3 / 10.24 + // (plus 1.5% based on measured hardware) + // (plus a fudge factor of +0.04V to round up to nearest 1/10th Volt) + uint16_t result = ((uint32_t)measurement * 436 / 1024) + + (65535 * 4 / 1024); + return result; +} + diff --git a/hw/thefreeman/avr32dd20-devkit/hwdef.h b/hw/thefreeman/avr32dd20-devkit/hwdef.h index 7e093f9..7e1ad3d 100644 --- a/hw/thefreeman/avr32dd20-devkit/hwdef.h +++ b/hw/thefreeman/avr32dd20-devkit/hwdef.h @@ -35,7 +35,7 @@ * and low value Rsense (high current range, pin high) * IN- NFET : pull up after BST enable to eliminate startup flash, pull down otherwise * CH senses the status of the onboard charger - * LVL : ??? (unused?) + * BATT LVL : Vbat * (100.0/(330+100)) * LVB is for OTSM firmware, not used here */ @@ -109,8 +109,14 @@ enum CHANNEL_MODES { // TODO: define stuff for the voltage divider // AVR datasheet table 3.1 I/O Multiplexing, PA6 ADC0 = AIN26 -//#define USE_VOLTAGE_DIVIDER // use a dedicated pin, not VCC, because VCC input is regulated +#define USE_VOLTAGE_DIVIDER // use a dedicated pin, not VCC, because VCC input is regulated #define ADMUX_VOLTAGE_DIVIDER ADC_MUXPOS_AIN26_gc +#define DUAL_VOLTAGE_FLOOR 21 // for AA/14500 boost drivers, don't indicate low voltage if below this level +#define DUAL_VOLTAGE_LOW_LOW 7 // the lower voltage range's danger zone 0.7 volts (NiMH) +// convert BATT LVL pin readings to FSM volt units +#undef voltage_raw2cooked +uint16_t voltage_raw2cooked(uint16_t measurement); + // average drop across diode on this hardware #ifndef VOLTAGE_FUDGE_FACTOR @@ -148,7 +154,7 @@ inline void hwdef_setup() { //PORTA.PIN3CTRL = PORT_PULLUPEN_bm; // CH PORTA.PIN4CTRL = PORT_PULLUPEN_bm; PORTA.PIN5CTRL = PORT_PULLUPEN_bm; - PORTA.PIN6CTRL = PORT_PULLUPEN_bm; + //PORTA.PIN6CTRL = PORT_PULLUPEN_bm; // BATT LVL //PORTA.PIN7CTRL = PORT_PULLUPEN_bm; // HDR //PORTC.PIN0CTRL = PORT_PULLUPEN_bm; // doesn't exist |
