mirror of
https://github.com/esp-rs/esp-hal.git
synced 2025-10-02 22:55:26 +00:00
Provide ADC values in mV instead of requiring the user to scale them (#836)
* Provide ADC values in mV instead of requiring the user to scale them * Changelog * Try converting poly calibration also * Update changelog and comments * Fix example
This commit is contained in:
parent
0304a10b2f
commit
ff80b69183
@ -37,6 +37,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
|
|
||||||
- Bumped MSRV to 1.67 (#798)
|
- Bumped MSRV to 1.67 (#798)
|
||||||
- Optimised multi-core critical section implementation (#797)
|
- Optimised multi-core critical section implementation (#797)
|
||||||
|
- Changed linear- and curve-calibrated ADC to provide readings in mV (#836)
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
|
@ -11,10 +11,14 @@ use crate::adc::{
|
|||||||
|
|
||||||
/// Basic ADC calibration scheme
|
/// Basic ADC calibration scheme
|
||||||
///
|
///
|
||||||
/// Basic calibration is related to setting some initial bias value in ADC.
|
/// Basic calibration sets the initial ADC bias value so that a zero voltage
|
||||||
/// Such values usually is stored in efuse bit fields but also can be measured
|
/// gives a reading of zero. The correct bias value is usually stored in efuse,
|
||||||
/// in runtime by connecting ADC input to ground internally a fallback when
|
/// but can also be measured at runtime by connecting the ADC input to ground
|
||||||
/// it is not available.
|
/// 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)]
|
#[derive(Clone, Copy)]
|
||||||
pub struct AdcCalBasic<ADCI> {
|
pub struct AdcCalBasic<ADCI> {
|
||||||
/// Calibration value to set to ADC unit
|
/// Calibration value to set to ADC unit
|
||||||
|
@ -11,6 +11,8 @@ use crate::adc::{
|
|||||||
|
|
||||||
const COEFF_MUL: i64 = 1 << 52;
|
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;
|
type CurveCoeff = i64;
|
||||||
|
|
||||||
/// Polynomial coefficients for specified attenuation.
|
/// Polynomial coefficients for specified attenuation.
|
||||||
@ -35,8 +37,8 @@ pub trait AdcHasCurveCal {
|
|||||||
|
|
||||||
/// Curve fitting ADC calibration scheme
|
/// Curve fitting ADC calibration scheme
|
||||||
///
|
///
|
||||||
/// This scheme implements final polynomial error correction using predefined
|
/// This scheme implements polynomial error correction using predefined
|
||||||
/// coefficient sets for each attenuation.
|
/// coefficient sets for each attenuation. It returns readings in mV.
|
||||||
///
|
///
|
||||||
/// This scheme also includes basic calibration ([`super::AdcCalBasic`]) and
|
/// This scheme also includes basic calibration ([`super::AdcCalBasic`]) and
|
||||||
/// line fitting ([`AdcCalLine`]).
|
/// line fitting ([`AdcCalLine`]).
|
||||||
@ -44,7 +46,15 @@ pub trait AdcHasCurveCal {
|
|||||||
pub struct AdcCalCurve<ADCI> {
|
pub struct AdcCalCurve<ADCI> {
|
||||||
line: AdcCalLine<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],
|
coeff: &'static [CurveCoeff],
|
||||||
|
|
||||||
_phantom: PhantomData<ADCI>,
|
_phantom: PhantomData<ADCI>,
|
||||||
@ -104,7 +114,7 @@ macro_rules! coeff_tables {
|
|||||||
$(CurveCoeffs {
|
$(CurveCoeffs {
|
||||||
atten: Attenuation::$att,
|
atten: Attenuation::$att,
|
||||||
coeff: &[
|
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,)*
|
||||||
],
|
],
|
||||||
},)*
|
},)*
|
||||||
];
|
];
|
||||||
|
@ -16,13 +16,13 @@ use crate::adc::{
|
|||||||
/// See also [`AdcCalLine`].
|
/// See also [`AdcCalLine`].
|
||||||
pub trait AdcHasLineCal {}
|
pub trait AdcHasLineCal {}
|
||||||
|
|
||||||
/// Coefficients is actually a fixed-point numbers.
|
/// We store the gain as a u32, but it's really a fixed-point number.
|
||||||
/// It is scaled to put them into integer.
|
|
||||||
const GAIN_SCALE: u32 = 1 << 16;
|
const GAIN_SCALE: u32 = 1 << 16;
|
||||||
|
|
||||||
/// Line fitting ADC calibration scheme
|
/// 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
|
/// 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
|
/// mean raw digital ADC value. Such values are usually stored in efuse bit
|
||||||
@ -38,7 +38,11 @@ const GAIN_SCALE: u32 = 1 << 16;
|
|||||||
pub struct AdcCalLine<ADCI> {
|
pub struct AdcCalLine<ADCI> {
|
||||||
basic: AdcCalBasic<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,
|
gain: u32,
|
||||||
|
|
||||||
_phantom: PhantomData<ADCI>,
|
_phantom: PhantomData<ADCI>,
|
||||||
@ -57,8 +61,8 @@ where
|
|||||||
.map(|code| (code, ADCI::get_cal_mv(atten)))
|
.map(|code| (code, ADCI::get_cal_mv(atten)))
|
||||||
.unwrap_or_else(|| {
|
.unwrap_or_else(|| {
|
||||||
// As a fallback try to calibrate using reference voltage source.
|
// As a fallback try to calibrate using reference voltage source.
|
||||||
// This methos is no to good because actual reference voltage may varies
|
// This method is not too good because actual reference voltage may varies
|
||||||
// in range 1000..=1200 mV and this value currently cannot be given from efuse.
|
// in range 1000..=1200 mV and this value currently cannot be read from efuse.
|
||||||
(
|
(
|
||||||
AdcConfig::<ADCI>::adc_calibrate(atten, AdcCalSource::Ref),
|
AdcConfig::<ADCI>::adc_calibrate(atten, AdcCalSource::Ref),
|
||||||
1100, // use 1100 mV as a middle of typical reference voltage range
|
1100, // use 1100 mV as a middle of typical reference voltage range
|
||||||
@ -69,10 +73,9 @@ where
|
|||||||
// the voltage with the previously done measurement when the chip was
|
// the voltage with the previously done measurement when the chip was
|
||||||
// manufactured.
|
// manufactured.
|
||||||
//
|
//
|
||||||
// Rounding formula: R = (OP(A * 2) + 1) / 2
|
// Note that the constant term is zero because the basic calibration takes care
|
||||||
// where R - result, A - argument, O - operation
|
// of it already.
|
||||||
let gain =
|
let gain = mv as u32 * GAIN_SCALE / code as u32;
|
||||||
((mv as u32 * GAIN_SCALE * 2 / code as u32 + 1) * 4096 / atten.ref_mv() as u32 + 1) / 2;
|
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
basic,
|
basic,
|
||||||
@ -88,7 +91,6 @@ where
|
|||||||
fn adc_val(&self, val: u16) -> u16 {
|
fn adc_val(&self, val: u16) -> u16 {
|
||||||
let val = self.basic.adc_val(val);
|
let val = self.basic.adc_val(val);
|
||||||
|
|
||||||
// pointers are checked in the upper layer
|
|
||||||
(val as u32 * self.gain / GAIN_SCALE) as u16
|
(val as u32 * self.gain / GAIN_SCALE) as u16
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -149,25 +149,6 @@ impl Attenuation {
|
|||||||
Attenuation::Attenuation6dB,
|
Attenuation::Attenuation6dB,
|
||||||
Attenuation::Attenuation11dB,
|
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 = ()> {
|
pub struct AdcPin<PIN, ADCI, CS = ()> {
|
||||||
|
@ -112,23 +112,6 @@ impl Attenuation {
|
|||||||
Attenuation::Attenuation6dB,
|
Attenuation::Attenuation6dB,
|
||||||
Attenuation::Attenuation11dB,
|
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 = ()> {
|
pub struct AdcPin<PIN, ADCI, CS = ()> {
|
||||||
|
@ -84,12 +84,17 @@ pub mod adc;
|
|||||||
#[cfg(dac)]
|
#[cfg(dac)]
|
||||||
pub mod 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 {
|
pub trait AdcCalScheme<ADCI>: Sized {
|
||||||
/// Instantiate scheme
|
/// Create a new calibration scheme for the given attenuation.
|
||||||
fn new_cal(atten: adc::Attenuation) -> Self;
|
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 {
|
fn adc_cal(&self) -> u16 {
|
||||||
0
|
0
|
||||||
}
|
}
|
||||||
|
@ -32,7 +32,10 @@ fn main() -> ! {
|
|||||||
|
|
||||||
let atten = Attenuation::Attenuation11dB;
|
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 = ();
|
||||||
// type AdcCal = adc::AdcCalBasic<ADC1>;
|
// type AdcCal = adc::AdcCalBasic<ADC1>;
|
||||||
type AdcCal = adc::AdcCalLine<ADC1>;
|
type AdcCal = adc::AdcCalLine<ADC1>;
|
||||||
@ -44,9 +47,8 @@ fn main() -> ! {
|
|||||||
let mut delay = Delay::new(&clocks);
|
let mut delay = Delay::new(&clocks);
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
let pin_value: u16 = nb::block!(adc1.read(&mut pin)).unwrap();
|
let pin_mv = 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_mv} mV");
|
||||||
println!("PIN2 ADC reading = {pin_value} ({pin_value_mv} mV)");
|
|
||||||
delay.delay_ms(1500u32);
|
delay.delay_ms(1500u32);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -32,7 +32,10 @@ fn main() -> ! {
|
|||||||
|
|
||||||
let atten = Attenuation::Attenuation11dB;
|
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 = ();
|
||||||
// type AdcCal = adc::AdcCalBasic<ADC1>;
|
// type AdcCal = adc::AdcCalBasic<ADC1>;
|
||||||
// type AdcCal = adc::AdcCalLine<ADC1>;
|
// type AdcCal = adc::AdcCalLine<ADC1>;
|
||||||
@ -45,9 +48,8 @@ fn main() -> ! {
|
|||||||
let mut delay = Delay::new(&clocks);
|
let mut delay = Delay::new(&clocks);
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
let pin_value = nb::block!(adc1.read(&mut pin)).unwrap();
|
let pin_mv = 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_mv} mV");
|
||||||
println!("PIN2 ADC reading = {pin_value} ({pin_value_mv} mV)");
|
|
||||||
delay.delay_ms(1500u32);
|
delay.delay_ms(1500u32);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -32,7 +32,10 @@ fn main() -> ! {
|
|||||||
|
|
||||||
let atten = Attenuation::Attenuation11dB;
|
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 = ();
|
||||||
// type AdcCal = adc::AdcCalBasic<ADC1>;
|
// type AdcCal = adc::AdcCalBasic<ADC1>;
|
||||||
// type AdcCal = adc::AdcCalLine<ADC1>;
|
// type AdcCal = adc::AdcCalLine<ADC1>;
|
||||||
@ -45,9 +48,8 @@ fn main() -> ! {
|
|||||||
let mut delay = Delay::new(&clocks);
|
let mut delay = Delay::new(&clocks);
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
let pin_value: u16 = nb::block!(adc1.read(&mut pin)).unwrap();
|
let pin_mv = 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_mv} mV");
|
||||||
println!("PIN2 ADC reading = {pin_value} ({pin_value_mv} mV)");
|
|
||||||
delay.delay_ms(1500u32);
|
delay.delay_ms(1500u32);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -31,7 +31,10 @@ fn main() -> ! {
|
|||||||
|
|
||||||
let atten = Attenuation::Attenuation11dB;
|
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 = ();
|
||||||
// type AdcCal = adc::AdcCalBasic<ADC1>;
|
// type AdcCal = adc::AdcCalBasic<ADC1>;
|
||||||
// type AdcCal = adc::AdcCalLine<ADC1>;
|
// type AdcCal = adc::AdcCalLine<ADC1>;
|
||||||
@ -44,9 +47,8 @@ fn main() -> ! {
|
|||||||
let mut delay = Delay::new(&clocks);
|
let mut delay = Delay::new(&clocks);
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
let pin_value: u16 = nb::block!(adc1.read(&mut pin)).unwrap();
|
let pin_mv = 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_mv} mV");
|
||||||
println!("PIN2 ADC reading = {pin_value} ({pin_value_mv} mV)");
|
|
||||||
delay.delay_ms(1500u32);
|
delay.delay_ms(1500u32);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user