Protocol rework

This commit is contained in:
ThePetrovich 2026-05-10 15:33:08 +08:00
parent 3e77d34ccc
commit 2b713a3e3a
15 changed files with 1347 additions and 1155 deletions

View file

@ -1,6 +1,7 @@
/*
* @file adc.cpp
* @brief
* @brief ATmega4809 ADC0 driver: configuration profiles, oversampling reads,
* and conversions to engineering units.
*
* Created: 27.09.2025 05:06:33
* Author: ThePetrovich
@ -13,19 +14,38 @@
#include "adc.h"
#include "config.h"
#include "utils.h"
#include <Arduino.h>
#include <util/atomic.h>
uint8_t adc_flags = 0;
/**
* @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 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));
/*
* 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); }
@ -33,14 +53,11 @@ uint16_t adc_to_voltage_mv(uint16_t adc_value) { return (uint16_t)(adc_value * V
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);
}
@ -50,21 +67,21 @@ void adc_enable_fast(void)
{
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
/* Oversampling factor selected by config.flags.adc_oversample_bits. */
ADC0.CTRLB |= (config.flags.fields.adc_oversample_bits << 2);
ADC0.CTRLC = 0; // reset
ADC0.CTRLC |= ADC_PRESC_DIV32_gc | ADC_REFSEL_VREFA_gc; // CLK_PER/32, VREFA as reference
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 to start conversion
ADC0.INTCTRL |= ADC_RESRDY_bm; // Enable interrupt on conversion complete
ADC0.EVCTRL |= ADC_STARTEI_bm; /* EVSYS input starts conversion */
ADC0.INTCTRL |= ADC_RESRDY_bm; /* result-ready interrupt */
// Configure VREF, Microchip DS40002015B page 424
/* Microchip DS40002015B p. 424. */
VREF.CTRLA |= VREF_ADC0REFSEL_4V34_gc;
ADC0.MUXPOS = ADC_MUXPOS_AIN3_gc; // DET_SIG_PIN
ADC0.MUXPOS = ADC_MUXPOS_AIN3_gc; /* DET_SIG_PIN */
adc_flags |= 0x01; // Fast mode enabled
adc_flags |= 0x01;
}
}
@ -72,19 +89,21 @@ 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.CTRLC |= ADC_REFSEL_VREFA_gc;
ADC0.EVCTRL &= ~ADC_STARTEI_bm;
ADC0.INTCTRL &= ~ADC_RESRDY_bm;
adc_flags &= ~0x01; // Fast mode disabled
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;
@ -93,48 +112,45 @@ static uint16_t adc_conversion(void)
return ADC0.RES;
}
static uint16_t adc_read_tempsense(void)
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.
*/
/*
* 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; // reset
ADC0.CTRLC = 0;
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
int8_t sigrow_offset = SIGROW.TEMPSENSE1;
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
/* 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; // Result might overflow 16 bit variable (10bit+8bit)
temp += 0x80;
// Add 1/2 to get correct rounding on division below
temp *= sigrow_gain;
temp += 0x80; /* Round-half-up before the >>8 division. */
temp >>= 8;
// Divide result to get Kelvin
uint16_t temperature_in_K = temp;
uint16_t temperature_in_C = temp - 273; /* Convert from Kelvin to Celsius. */
adc_restore_default();
@ -142,73 +158,5 @@ static uint16_t adc_read_tempsense(void)
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();
}
return temperature_in_C * 10; /* Return in 0.1 °C for consistency */
}