162 lines
4.3 KiB
C++
162 lines
4.3 KiB
C++
/*
|
||
* @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 <Arduino.h>
|
||
#include <util/atomic.h>
|
||
|
||
/**
|
||
* @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 */
|
||
}
|