Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

- Bumped MSRV to 1.67 (#798)
- Optimised multi-core critical section implementation (#797)
- Changed linear- and curve-calibrated ADC to provide readings in mV (#836)

### Fixed

Expand Down
12 changes: 8 additions & 4 deletions esp-hal-common/src/analog/adc/cal_basic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,14 @@ use crate::adc::{

/// Basic ADC calibration scheme
///
/// Basic calibration is related to setting some initial bias value in ADC.
/// Such values usually is stored in efuse bit fields but also can be measured
/// in runtime by connecting ADC input to ground internally a fallback when
/// it is not available.
/// Basic calibration sets the initial ADC bias value so that a zero voltage
/// gives a reading of zero. The correct bias value is usually stored in efuse,
/// but can also be measured at runtime by connecting the ADC input to ground
/// internally.
///
/// Failing to apply basic calibration can substantially reduce the ADC's output
/// range because bias correction is done *before* the ADC's output is truncated
/// to 12 bits.
#[derive(Clone, Copy)]
pub struct AdcCalBasic<ADCI> {
/// Calibration value to set to ADC unit
Expand Down
18 changes: 14 additions & 4 deletions esp-hal-common/src/analog/adc/cal_curve.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ use crate::adc::{

const COEFF_MUL: i64 = 1 << 52;

/// Integer type for the error polynomial's coefficients. Despite
/// the type, this is a fixed-point number with 52 fractional bits.
type CurveCoeff = i64;

/// Polynomial coefficients for specified attenuation.
Expand All @@ -35,16 +37,24 @@ pub trait AdcHasCurveCal {

/// Curve fitting ADC calibration scheme
///
/// This scheme implements final polynomial error correction using predefined
/// coefficient sets for each attenuation.
/// This scheme implements polynomial error correction using predefined
/// coefficient sets for each attenuation. It returns readings in mV.
///
/// This scheme also includes basic calibration ([`super::AdcCalBasic`]) and
/// line fitting ([`AdcCalLine`]).
#[derive(Clone, Copy)]
pub struct AdcCalCurve<ADCI> {
line: AdcCalLine<ADCI>,

/// Coefficients for each term (3..=5)
/// Coefficients of the error estimation polynomial.
///
/// The constant coefficient comes first; the error polynomial is
/// `coeff[0] + coeff[1] * x + ... + coeff[n] * x^n`.
///
/// This calibration works by first applying linear calibration. Then
/// the error polynomial is applied to the output of linear calibration.
/// The output of the polynomial is our estimate of the error; it gets
/// subtracted from linear calibration's output to get the final reading.
coeff: &'static [CurveCoeff],

_phantom: PhantomData<ADCI>,
Expand Down Expand Up @@ -104,7 +114,7 @@ macro_rules! coeff_tables {
$(CurveCoeffs {
atten: Attenuation::$att,
coeff: &[
$(($val as f64 * COEFF_MUL as f64 * 4096f64 / Attenuation::$att.ref_mv() as f64) as CurveCoeff,)*
$(($val as f64 * COEFF_MUL as f64) as CurveCoeff,)*
],
},)*
];
Expand Down
24 changes: 13 additions & 11 deletions esp-hal-common/src/analog/adc/cal_line.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,13 @@ use crate::adc::{
/// See also [`AdcCalLine`].
pub trait AdcHasLineCal {}

/// Coefficients is actually a fixed-point numbers.
/// It is scaled to put them into integer.
/// We store the gain as a u32, but it's really a fixed-point number.
const GAIN_SCALE: u32 = 1 << 16;

/// Line fitting ADC calibration scheme
///
/// This scheme implements gain correction based on reference points.
/// This scheme implements gain correction based on reference points, and
/// returns readings in mV.
///
/// A reference point is a pair of a reference voltage and the corresponding
/// mean raw digital ADC value. Such values are usually stored in efuse bit
Expand All @@ -38,7 +38,11 @@ const GAIN_SCALE: u32 = 1 << 16;
pub struct AdcCalLine<ADCI> {
basic: AdcCalBasic<ADCI>,

/// Gain of ADC-value
/// ADC gain.
///
/// After being de-biased by the basic calibration, the reading is
/// multiplied by this value. Despite the type, it is a fixed-point
/// number with 16 fractional bits.
gain: u32,

_phantom: PhantomData<ADCI>,
Expand All @@ -57,8 +61,8 @@ where
.map(|code| (code, ADCI::get_cal_mv(atten)))
.unwrap_or_else(|| {
// As a fallback try to calibrate using reference voltage source.
// This methos is no to good because actual reference voltage may varies
// in range 1000..=1200 mV and this value currently cannot be given from efuse.
// This method is not too good because actual reference voltage may varies
// in range 1000..=1200 mV and this value currently cannot be read from efuse.
(
AdcConfig::<ADCI>::adc_calibrate(atten, AdcCalSource::Ref),
1100, // use 1100 mV as a middle of typical reference voltage range
Expand All @@ -69,10 +73,9 @@ where
// the voltage with the previously done measurement when the chip was
// manufactured.
//
// Rounding formula: R = (OP(A * 2) + 1) / 2
// where R - result, A - argument, O - operation
let gain =
((mv as u32 * GAIN_SCALE * 2 / code as u32 + 1) * 4096 / atten.ref_mv() as u32 + 1) / 2;
// Note that the constant term is zero because the basic calibration takes care
// of it already.
let gain = mv as u32 * GAIN_SCALE / code as u32;

Self {
basic,
Expand All @@ -88,7 +91,6 @@ where
fn adc_val(&self, val: u16) -> u16 {
let val = self.basic.adc_val(val);

// pointers are checked in the upper layer
(val as u32 * self.gain / GAIN_SCALE) as u16
}
}
Expand Down
19 changes: 0 additions & 19 deletions esp-hal-common/src/analog/adc/riscv.rs
Original file line number Diff line number Diff line change
Expand Up @@ -149,25 +149,6 @@ impl Attenuation {
Attenuation::Attenuation6dB,
Attenuation::Attenuation11dB,
];

/// Reference voltage in millivolts
///
/// Vref = 10 ^ (Att / 20) * Vref0
/// where Vref0 = 1.1 V, Att - attenuation in dB
///
/// To convert raw value to millivolts use formula:
/// V = D * Vref / 2 ^ R
/// where D - raw ADC value, R - resolution in bits
pub const fn ref_mv(&self) -> u16 {
match self {
Attenuation::Attenuation0dB => 1100,
#[cfg(not(esp32c2))]
Attenuation::Attenuation2p5dB => 1467,
#[cfg(not(esp32c2))]
Attenuation::Attenuation6dB => 2195,
Attenuation::Attenuation11dB => 3903,
}
}
}

pub struct AdcPin<PIN, ADCI, CS = ()> {
Expand Down
17 changes: 0 additions & 17 deletions esp-hal-common/src/analog/adc/xtensa.rs
Original file line number Diff line number Diff line change
Expand Up @@ -112,23 +112,6 @@ impl Attenuation {
Attenuation::Attenuation6dB,
Attenuation::Attenuation11dB,
];

/// Reference voltage in millivolts
///
/// Vref = 10 ^ (Att / 20) * Vref0
/// where Vref0 = 1.1 V, Att - attenuation in dB
///
/// To convert raw value to millivolts use formula:
/// V = D * Vref / 2 ^ R
/// where D - raw ADC value, R - resolution in bits
pub const fn ref_mv(&self) -> u16 {
match self {
Attenuation::Attenuation0dB => 1100,
Attenuation::Attenuation2p5dB => 1467,
Attenuation::Attenuation6dB => 2195,
Attenuation::Attenuation11dB => 3903,
}
}
}

pub struct AdcPin<PIN, ADCI, CS = ()> {
Expand Down
11 changes: 8 additions & 3 deletions esp-hal-common/src/analog/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -84,12 +84,17 @@ pub mod adc;
#[cfg(dac)]
pub mod dac;

/// A helper trait to do calibrated samples fitting
/// A trait abstracting over calibration methods.
///
/// The methods in this trait are mostly for internal use. To get
/// calibrated ADC reads, all you need to do is call `enable_pin_with_cal`
/// and specify some implementor of this trait.
pub trait AdcCalScheme<ADCI>: Sized {
/// Instantiate scheme
/// Create a new calibration scheme for the given attenuation.
fn new_cal(atten: adc::Attenuation) -> Self;

/// Get ADC calibration value to set to ADC unit
/// Return the basic ADC bias value. See [`adc::AdcCalBasic`] for
/// details.
fn adc_cal(&self) -> u16 {
0
}
Expand Down
10 changes: 6 additions & 4 deletions esp32c2-hal/examples/adc_cal.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,10 @@ fn main() -> ! {

let atten = Attenuation::Attenuation11dB;

// You can try any of the following calibration methods by uncommenting them
// You can try any of the following calibration methods by uncommenting
// them. Note that only AdcCalLine returns readings in mV; the other two
// return raw readings in some unspecified scale.
//
// type AdcCal = ();
// type AdcCal = adc::AdcCalBasic<ADC1>;
type AdcCal = adc::AdcCalLine<ADC1>;
Expand All @@ -44,9 +47,8 @@ fn main() -> ! {
let mut delay = Delay::new(&clocks);

loop {
let pin_value: u16 = nb::block!(adc1.read(&mut pin)).unwrap();
let pin_value_mv = pin_value as u32 * atten.ref_mv() as u32 / 4096;
println!("PIN2 ADC reading = {pin_value} ({pin_value_mv} mV)");
let pin_mv = nb::block!(adc1.read(&mut pin)).unwrap();
println!("PIN2 ADC reading = {pin_mv} mV");
delay.delay_ms(1500u32);
}
}
10 changes: 6 additions & 4 deletions esp32c3-hal/examples/adc_cal.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,10 @@ fn main() -> ! {

let atten = Attenuation::Attenuation11dB;

// You can try any of the following calibration methods by uncommenting them
// You can try any of the following calibration methods by uncommenting them.
// Note that only AdcCalLine and AdcCalCurve return readings in mV; the other
// two return raw readings in some unspecified scale.
//
// type AdcCal = ();
// type AdcCal = adc::AdcCalBasic<ADC1>;
// type AdcCal = adc::AdcCalLine<ADC1>;
Expand All @@ -45,9 +48,8 @@ fn main() -> ! {
let mut delay = Delay::new(&clocks);

loop {
let pin_value = nb::block!(adc1.read(&mut pin)).unwrap();
let pin_value_mv = pin_value as u32 * atten.ref_mv() as u32 / 4096;
println!("PIN2 ADC reading = {pin_value} ({pin_value_mv} mV)");
let pin_mv = nb::block!(adc1.read(&mut pin)).unwrap();
println!("PIN2 ADC reading = {pin_mv} mV");
delay.delay_ms(1500u32);
}
}
10 changes: 6 additions & 4 deletions esp32c6-hal/examples/adc_cal.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,10 @@ fn main() -> ! {

let atten = Attenuation::Attenuation11dB;

// You can try any of the following calibration methods by uncommenting them
// You can try any of the following calibration methods by uncommenting them.
// Note that only AdcCalLine and AdcCalCurve return readings in mV; the other
// two return raw readings in some unspecified scale.
//
// type AdcCal = ();
// type AdcCal = adc::AdcCalBasic<ADC1>;
// type AdcCal = adc::AdcCalLine<ADC1>;
Expand All @@ -45,9 +48,8 @@ fn main() -> ! {
let mut delay = Delay::new(&clocks);

loop {
let pin_value: u16 = nb::block!(adc1.read(&mut pin)).unwrap();
let pin_value_mv = pin_value as u32 * atten.ref_mv() as u32 / 4096;
println!("PIN2 ADC reading = {pin_value} ({pin_value_mv} mV)");
let pin_mv = nb::block!(adc1.read(&mut pin)).unwrap();
println!("PIN2 ADC reading = {pin_mv} mV");
delay.delay_ms(1500u32);
}
}
10 changes: 6 additions & 4 deletions esp32s3-hal/examples/adc_cal.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,10 @@ fn main() -> ! {

let atten = Attenuation::Attenuation11dB;

// You can try any of the following calibration methods by uncommenting them
// You can try any of the following calibration methods by uncommenting them.
// Note that only AdcCalLine and AdcCalCurve return readings in mV; the other
// two return raw readings in some unspecified scale.
//
// type AdcCal = ();
// type AdcCal = adc::AdcCalBasic<ADC1>;
// type AdcCal = adc::AdcCalLine<ADC1>;
Expand All @@ -44,9 +47,8 @@ fn main() -> ! {
let mut delay = Delay::new(&clocks);

loop {
let pin_value: u16 = nb::block!(adc1.read(&mut pin)).unwrap();
let pin_value_mv = pin_value as u32 * atten.ref_mv() as u32 / 4096;
println!("PIN2 ADC reading = {pin_value} ({pin_value_mv} mV)");
let pin_mv = nb::block!(adc1.read(&mut pin)).unwrap();
println!("PIN2 ADC reading = {pin_mv} mV");
delay.delay_ms(1500u32);
}
}