/* * @file adc.cpp * @brief ATmega4809 ADC0 driver: configuration profiles, oversampling reads, * and conversions to engineering units. * * Created: 27.09.2025 05:06:33 * Author: ThePetrovich * * Copyright YKSA - Sakha Aerospace Systems, LLC. * See the LICENSE file for details. * * SPDX-License-Identifier: BSD-3-Clause */ #include "adc.h" #include "config.h" #include #include /** * @brief Internal driver state. * Bit 0 = fast mode currently enabled. */ static uint8_t adc_flags = 0; int16_t adc_to_temperature_c(uint16_t adc_value) { /* * MCP9700: 500 mV at 0 °C, 10 mV/°C, result returned in 0.1 °C units. * Use calibration points when available (adccal3 != 0xFFFF): * adccal0 = ADC reading at GND (offset), * adccal3 = ADC reading at 3.0 V reference. * Falls back to nominal constants when uncalibrated. */ uint16_t cal0 = config.calibration.adccal0; uint16_t cal3 = config.calibration.adccal3; int16_t tempcal = (int16_t)config.calibration.tempcal; int32_t mv; if (cal3 != 0xFFFF && cal3 != cal0) { mv = (int32_t)(adc_value - cal0) * ADC_VREF_MV / (int32_t)(cal3 - cal0); } else { uint16_t adc_resolution = ADC_RESOLUTION << config.flags.fields.adc_oversample_bits; mv = (int32_t)adc_value * ADC_VREF_MV / adc_resolution; } /* Result in 0.1 °C: (mv - 500 - tempcal_offset) * 10 / 10. */ return (int16_t)((mv - MCP9700_OFFSET_MV - tempcal) * 10L / MCP9700_SCALE_MV); } uint16_t adc_to_voltage_mv(uint16_t adc_value) { return (uint16_t)(adc_value * VOLTAGE_MV_PER_STEP); } uint16_t adc_read_oversampled(uint8_t pin, uint8_t samples) { uint32_t sum = 0; uint8_t shift = ((samples == 64) ? 3 : (samples == 16) ? 2 : (samples == 4) ? 1 : 0); for (uint8_t i = 0; i < samples; i++) { sum += analogRead(pin); } return (uint16_t)(sum >> shift); } void adc_enable_fast(void) { ATOMIC_BLOCK(ATOMIC_RESTORESTATE) { ADC0.CTRLA |= ADC_ENABLE_bm; /* Oversampling factor selected by config.flags.adc_oversample_bits. */ ADC0.CTRLB |= (config.flags.fields.adc_oversample_bits << 2); ADC0.CTRLC = 0; ADC0.CTRLC |= ADC_PRESC_DIV32_gc | ADC_REFSEL_VREFA_gc; /* CLK_PER/32, VREFA reference */ ADC0.EVCTRL |= ADC_STARTEI_bm; /* EVSYS input starts conversion */ ADC0.INTCTRL |= ADC_RESRDY_bm; /* result-ready interrupt */ /* Microchip DS40002015B p. 424. */ VREF.CTRLA |= VREF_ADC0REFSEL_4V34_gc; ADC0.MUXPOS = ADC_MUXPOS_AIN3_gc; /* DET_SIG_PIN */ adc_flags |= 0x01; } } void adc_restore_default(void) { ATOMIC_BLOCK(ATOMIC_RESTORESTATE) { ADC0.CTRLB &= ~ADC_SAMPNUM_gm; ADC0.CTRLC &= ~ADC_REFSEL_gm; ADC0.CTRLC |= ADC_REFSEL_VREFA_gc; ADC0.EVCTRL &= ~ADC_STARTEI_bm; ADC0.INTCTRL &= ~ADC_RESRDY_bm; adc_flags &= ~0x01; } } /** * @brief Trigger a single conversion and block until the result is ready. */ static uint16_t adc_conversion(void) { ADC0.COMMAND = ADC_STCONV_bm; while (!(ADC0.INTFLAGS & ADC_RESRDY_bm)) ; return ADC0.RES; } uint16_t adc_read_tempsense(void) { /* * Microchip DS40002015B p. 425 procedure: * 1. Configure VREF to 1.1 V via the VREF peripheral. * 2. Select internal voltage reference (REFSEL = 0x0 in ADCn.CTRLC). * 3. Select the temperature sensor channel via ADCn.MUXPOS. * 4. Set INITDLY ≥ 32 µs × CLK_ADC in ADCn.SAMPCTRL. * 5. Set SAMPLEN ≥ 32 µs × CLK_ADC in ADCn.CTRLC. * 6. Start a conversion to acquire the sensor output voltage. * 7. Process the result as below. */ ATOMIC_BLOCK(ATOMIC_RESTORESTATE) { ADC0.CTRLA |= ADC_ENABLE_bm; ADC0.CTRLB |= ADC_SAMPNUM_ACC16_gc; ADC0.CTRLC = 0; ADC0.CTRLC |= ADC_PRESC_DIV32_gc | ADC_REFSEL_INTREF_gc; VREF.CTRLA |= VREF_ADC0REFSEL_1V1_gc; ADC0.MUXPOS = ADC_MUXPOS_TEMPSENSE_gc; } delay(10); int8_t sigrow_offset = SIGROW.TEMPSENSE1; uint8_t sigrow_gain = SIGROW.TEMPSENSE0; uint16_t adc_reading = adc_conversion() >> 4; /* Conversion result with 1.1 V internal reference; result may exceed 16 * bits during the multiply (10-bit reading × 8-bit gain). */ uint32_t temp = adc_reading - sigrow_offset; temp *= sigrow_gain; temp += 0x80; /* Round-half-up before the >>8 division. */ temp >>= 8; uint16_t temperature_in_C = temp - 273; /* Convert from Kelvin to Celsius. */ adc_restore_default(); if (adc_flags & 0x01) { adc_enable_fast(); } return temperature_in_C * 10; /* Return in 0.1 °C for consistency */ }