edu16_sbc/sbc_fw/adc.cpp
2026-05-10 15:33:08 +08:00

162 lines
4.3 KiB
C++
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*
* @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 */
}