/* * @file adc.cpp * @brief * * 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 "utils.h" #include #include uint8_t adc_flags = 0; int16_t adc_to_temperature_c(uint16_t adc_value) { // MCP9700: 500 mV at 0°C, 10 mV per degree, vref = 3.0V // Result in 0.1°C units uint16_t adc_resolution = ADC_RESOLUTION << config.flags.adc_oversample_bits; // Adjust resolution based on oversampling return (int16_t)((adc_value * ADC_VREF_MV / adc_resolution - config.tempcal)); } 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; // unrolled loop 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; // Enable oversampling x16 for 12-bit result ADC0.CTRLB |= (config.flags.adc_oversample_bits << 2); // 2, 4, or 6 for 4x, 16x, or 64x oversampling ADC0.CTRLC = 0; // reset ADC0.CTRLC |= ADC_PRESC_DIV32_gc | ADC_REFSEL_VREFA_gc; // CLK_PER/32, VREFA as reference ADC0.EVCTRL |= ADC_STARTEI_bm; // EVSYS input to start conversion ADC0.INTCTRL |= ADC_RESRDY_bm; // Enable interrupt on conversion complete // Configure VREF, Microchip DS40002015B page 424 VREF.CTRLA |= VREF_ADC0REFSEL_4V34_gc; ADC0.MUXPOS = ADC_MUXPOS_AIN3_gc; // DET_SIG_PIN adc_flags |= 0x01; // Fast mode enabled } } void adc_restore_default(void) { ATOMIC_BLOCK(ATOMIC_RESTORESTATE) { // Disable oversampling ADC0.CTRLB &= ~ADC_SAMPNUM_gm; ADC0.CTRLC &= ~ADC_REFSEL_gm; ADC0.CTRLC |= ADC_REFSEL_VREFA_gc; // VREFA as reference ADC0.EVCTRL &= ~ADC_STARTEI_bm; ADC0.INTCTRL &= ~ADC_RESRDY_bm; adc_flags &= ~0x01; // Fast mode disabled } } static uint16_t adc_conversion(void) { ADC0.COMMAND = ADC_STCONV_bm; while (!(ADC0.INTFLAGS & ADC_RESRDY_bm)) ; return ADC0.RES; } static uint16_t adc_read_tempsense(void) { /* DS40002015B-page 425 1. Configure the internal voltage reference to 1.1V by configuring the VREF peripheral. 2. Select the internal voltage reference by writing the REFSEL bits in ADCn.CTRLC to 0x0. 3. Select the ADC temperature sensor channel by configuring the MUXPOS register (ADCn.MUXPOS). This enables the temperature sensor. 5. In ADCn.SAMPCTRL select INITDLY ≥ 32 µs × CLK_ADC 6. In ADCn.CTRLC select SAMPLEN ≥ 32 µs × CLK_ADC 7. Acquire the temperature sensor output voltage by starting a conversion. 8. Process the measurement result as described below. */ ATOMIC_BLOCK(ATOMIC_RESTORESTATE) { ADC0.CTRLA |= ADC_ENABLE_bm; ADC0.CTRLB |= ADC_SAMPNUM_ACC16_gc; ADC0.CTRLC = 0; // reset ADC0.CTRLC |= ADC_PRESC_DIV32_gc | ADC_REFSEL_INTREF_gc; // Configure VREF, Microchip DS40002015B page 424 VREF.CTRLA |= VREF_ADC0REFSEL_1V1_gc; // Select temperature sensor channel ADC0.MUXPOS = ADC_MUXPOS_TEMPSENSE_gc; } delay(10); int8_t sigrow_offset = SIGROW.TEMPSENSE1; // Read signed value from signature row uint8_t sigrow_gain = SIGROW.TEMPSENSE0; // Read unsigned value from signature row uint16_t adc_reading = adc_conversion() >> 4; // ADC conversion result with 1.1 V internal reference uint32_t temp = adc_reading - 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 uint16_t temperature_in_K = temp; adc_restore_default(); if (adc_flags & 0x01) { adc_enable_fast(); } return temperature_in_K; } void adc_cmd_calibrate(void) { ATOMIC_BLOCK(ATOMIC_RESTORESTATE) { ADC0.EVCTRL &= ~ADC_STARTEI_bm; // Start event input ADC0.INTCTRL &= ~ADC_RESRDY_bm; ADC0.CTRLB |= ADC_SAMPNUM_ACC16_gc; // 16 samples for oversampling to get 12-bit result from 10-bit ADC ADC0.CTRLC = 0; // reset ADC0.CTRLC |= ADC_PRESC_DIV32_gc | ADC_REFSEL_VDDREF_gc; // CLK_PER/32, VDD as reference // Configure VREF, Microchip DS40002015B page 424 VREF.CTRLA |= VREF_ADC0REFSEL_4V34_gc; } delay(10); // Wait for VREF to stabilize // set MUX to gnd and read offset ADC0.MUXPOS = ADC_MUXPOS_GND_gc; delay(1); uint16_t offset = adc_conversion() >> 2; // 12-bit result Serial.write(offset & 0xFF); Serial.write(offset >> 8); if (config.adccal0 == 0xFFFF) { config.adccal0 = offset; } // set MUX to external AREF and read 3.0V value ADC0.MUXPOS = ADC_MUXPOS_AIN7_gc; // AREF pin delay(1); uint16_t aref = adc_conversion() >> 2; // 12-bit result Serial.write(aref & 0xFF); Serial.write(aref >> 8); if (config.adccal3 == 0xFFFF) { config.adccal3 = aref; } // set MUX to DACREF0 and read ADC0.MUXPOS = ADC_MUXPOS_DACREF_gc; delay(1); uint16_t dacref = adc_conversion() >> 2; // 12-bit result Serial.write(dacref & 0xFF); Serial.write(dacref >> 8); uint16_t tempsense = adc_read_tempsense(); Serial.write(tempsense & 0xFF); Serial.write(tempsense >> 8); Serial.write(SIGROW.TEMPSENSE1); Serial.write(SIGROW.TEMPSENSE0); Serial.flush(); SERIAL_BUFFER_CLEAR(); adc_restore_default(); if (adc_flags & 0x01) { adc_enable_fast(); } }