Skip to content

Commit

Permalink
Change ADC calibration API
Browse files Browse the repository at this point in the history
- Include basic calibration into linear
- Include linear calibration into curved
- Add `enable_pin_with_cal` to avoid breaking changes
  • Loading branch information
katyo committed May 26, 2023
1 parent b45332f commit 464e46a
Show file tree
Hide file tree
Showing 5 changed files with 63 additions and 76 deletions.
17 changes: 15 additions & 2 deletions esp-hal-common/src/analog/adc/cal_curve.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use crate::adc::{AdcCalScheme, AdcUnitId, Attenuation};
use crate::adc::{AdcCalLine, AdcCalScheme, AdcHasLineCal, AdcUnitId, Attenuation, RegisterAccess};
use core::marker::PhantomData;

const COEFF_MUL: i64 = 1 << 52;
Expand Down Expand Up @@ -26,6 +26,8 @@ pub trait AdcHasCurveCal {

#[derive(Clone, Copy)]
pub struct AdcCalCurve<ADCI> {
line: AdcCalLine<ADCI>,

/// Coeff of each term. See `adc_error_coef_atten` for details (and the magic number 2)
coeff: &'static [CurveCoeff],

Expand Down Expand Up @@ -61,17 +63,28 @@ impl<ADCI> AdcCalCurve<ADCI> {
}
}

impl<ADCI: AdcUnitId + AdcHasCurveCal> AdcCalScheme<ADCI> for AdcCalCurve<ADCI> {
impl<ADCI: AdcUnitId + AdcHasCurveCal + AdcHasLineCal + RegisterAccess> AdcCalScheme<ADCI>
for AdcCalCurve<ADCI>
{
fn new_cal(atten: Attenuation) -> Self {
let line = AdcCalLine::<ADCI>::new_cal(atten);

let coeff = ADCI::CURVES_COEFFS[atten as usize];

Self {
line,
coeff,
_phantom: PhantomData,
}
}

fn adc_cal(&self) -> u16 {
self.line.adc_cal()
}

fn adc_val(&self, val: u16) -> u16 {
let val = self.line.adc_val(val);

let error = self.calc_error(val);

(val as i32 - error) as u16
Expand Down
53 changes: 24 additions & 29 deletions esp-hal-common/src/analog/adc/cal_line.rs
Original file line number Diff line number Diff line change
@@ -1,64 +1,59 @@
use crate::adc::{AdcCalScheme, AdcUnitId, Attenuation};
use crate::adc::{AdcCalBasic, AdcCalScheme, AdcUnitId, Attenuation, RegisterAccess};
#[cfg(any(esp32c3, esp32s3))]
use crate::efuse::Efuse;
use core::marker::PhantomData;

/// Marker trait for ADC unit which has linear calibration
///
/// Usually it means that reference points is stored in efuse.
/// Each reference point represented by mean digital value which measured for some reference voltage.
/// Reference points given for each supported attenuation values.
pub trait AdcHasLineCal {}

// coeff_a is actually a float number
// it is scaled to put them into uint32_t so that the headers do not have to be changed
const COEFF_A_SCALING: u32 = 1 << 16;

#[derive(Clone, Copy)]
struct AdcCalibDataV1 {
voltage: u32,
digi: u32,
}
const GAIN_SCALE: u32 = 1 << 16;

#[derive(Clone, Copy)]
pub struct AdcCalLine<ADCI> {
/// Gradient of ADC-Voltage
coeff_a: u16,
/// Offset of ADC-Voltage
coeff_b: u16,
basic: AdcCalBasic<ADCI>,

/// Gain of ADC-Voltage
gain: u16,

_phantom: PhantomData<ADCI>,
}

impl<ADCI: AdcUnitId + AdcHasLineCal> AdcCalScheme<ADCI> for AdcCalLine<ADCI> {
impl<ADCI: AdcUnitId + RegisterAccess + AdcHasLineCal> AdcCalScheme<ADCI> for AdcCalLine<ADCI> {
fn new_cal(atten: Attenuation) -> Self {
// Set first step calibration context
let ref_data_v1 = AdcCalibDataV1::get_ref_point::<ADCI>(atten)
let basic = AdcCalBasic::<ADCI>::new_cal(atten);

//To get the reference point (Dout, Vin)
let (voltage, digi) = Efuse::get_rtc_calib_cal_voltage::<ADCI>(atten)
.expect("No reference point V1 found in efuse");

/*
* Estimate the (assumed) linear relationship btwn the measured raw value and the voltage
* with the previously done measurement when the chip was manufactured.
*/
let coeff_a =
(ref_data_v1.voltage as u32 * COEFF_A_SCALING / ref_data_v1.digi as u32) as u16;
let coeff_b = 0;
let gain = (voltage * GAIN_SCALE / digi) as u16;

Self {
coeff_a,
coeff_b,
basic,
gain,
_phantom: PhantomData,
}
}

fn adc_val(&self, val: u16) -> u16 {
//pointers are checked in the upper layer
(val as u32 * self.coeff_a as u32 / COEFF_A_SCALING) as u16 + self.coeff_b as u16
fn adc_cal(&self) -> u16 {
self.basic.adc_cal()
}
}

impl AdcCalibDataV1 {
//To get the reference point (Dout, Vin)
fn get_ref_point<ADCI: AdcUnitId>(atten: Attenuation) -> Option<Self> {
let (voltage, digi) = Efuse::get_rtc_calib_cal_voltage::<ADCI>(atten)?;
fn adc_val(&self, val: u16) -> u16 {
let val = self.basic.adc_val(val);

Some(Self { voltage, digi })
//pointers are checked in the upper layer
(val as u32 * self.gain as u32 / GAIN_SCALE) as u16
}
}

Expand Down
16 changes: 15 additions & 1 deletion esp-hal-common/src/analog/adc/riscv.rs
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,21 @@ where
Self::default()
}

pub fn enable_pin<PIN: Channel<ADCI, ID = u8>, CS: AdcCalScheme<ADCI>>(
pub fn enable_pin<PIN: Channel<ADCI, ID = u8>>(
&mut self,
pin: PIN,
attenuation: Attenuation,
) -> AdcPin<PIN, ADCI, ()> {
self.attenuations[PIN::channel() as usize] = Some(attenuation);

AdcPin {
pin,
cal_scheme: AdcCalScheme::<()>::new_cal(attenuation),
_phantom: PhantomData::default(),
}
}

pub fn enable_pin_with_cal<PIN: Channel<ADCI, ID = u8>, CS: AdcCalScheme<ADCI>>(
&mut self,
pin: PIN,
attenuation: Attenuation,
Expand Down
41 changes: 2 additions & 39 deletions esp-hal-common/src/analog/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,56 +14,19 @@ pub trait AdcCalScheme<ADCI>: Sized {
fn adc_cal(&self) -> u16 {
0
}

/// Convert ADC value
fn adc_val(&self, val: u16) -> u16 {
val
}
}

impl<ADCI> AdcCalScheme<ADCI> for () {
fn new_cal(atten: adc::Attenuation) -> Self {
fn new_cal(_atten: adc::Attenuation) -> Self {
()
}
}

impl<ADCI, A: AdcCalScheme<ADCI>> AdcCalScheme<ADCI> for (A,) {
fn new_cal(atten: adc::Attenuation) -> Self {
(A::new_cal(atten),)
}
fn adc_cal(&self) -> u16 {
self.0.adc_cal()
}
fn adc_val(&self, val: u16) -> u16 {
self.0.adc_val(val)
}
}

impl<ADCI, A: AdcCalScheme<ADCI>, B: AdcCalScheme<ADCI>> AdcCalScheme<ADCI> for (A, B) {
fn new_cal(atten: adc::Attenuation) -> Self {
(A::new_cal(atten), B::new_cal(atten))
}
fn adc_cal(&self) -> u16 {
self.0.adc_cal()
}
fn adc_val(&self, val: u16) -> u16 {
self.1.adc_val(self.0.adc_val(val))
}
}

impl<ADCI, A: AdcCalScheme<ADCI>, B: AdcCalScheme<ADCI>, C: AdcCalScheme<ADCI>> AdcCalScheme<ADCI>
for (A, B, C)
{
fn new_cal(atten: adc::Attenuation) -> Self {
(A::new_cal(atten), B::new_cal(atten), C::new_cal(atten))
}
fn adc_cal(&self) -> u16 {
self.0.adc_cal()
}
fn adc_val(&self, val: u16) -> u16 {
self.2.adc_val(self.1.adc_val(self.0.adc_val(val)))
}
}

/// A helper trait for identifying peripheral unit number
pub trait AdcUnitId {
const UNIT_ID: u8;
Expand Down
12 changes: 7 additions & 5 deletions esp32c3-hal/examples/adc_cal.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,12 +53,14 @@ fn main() -> ! {
let mut adc1_config = AdcConfig::new();

// type AdcCal = ();
// type AdcCal = (AdcCalBasic<ADC1>,);
// type AdcCal = (AdcCalBasic<ADC1>, AdcCalLine<ADC1>);
type AdcCal = (AdcCalBasic<ADC1>, AdcCalLine<ADC1>, AdcCalCurve<ADC1>);
// type AdcCal = AdcCalBasic<ADC1>;
// type AdcCal = AdcCalLine<ADC1>;
type AdcCal = AdcCalCurve<ADC1>;

let mut pin = adc1_config
.enable_pin::<_, AdcCal>(io.pins.gpio2.into_analog(), Attenuation::Attenuation11dB);
let mut pin = adc1_config.enable_pin_with_cal::<_, AdcCal>(
io.pins.gpio2.into_analog(),
Attenuation::Attenuation11dB,
);

let mut adc1 = ADC::<ADC1>::adc(
&mut system.peripheral_clock_control,
Expand Down

0 comments on commit 464e46a

Please sign in to comment.