mirror of
https://github.com/esp-rs/esp-hal.git
synced 2025-09-30 05:40:39 +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)
|
||||
- Optimised multi-core critical section implementation (#797)
|
||||
- Changed linear- and curve-calibrated ADC to provide readings in mV (#836)
|
||||
|
||||
### Fixed
|
||||
|
||||
|
@ -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
|
||||
|
@ -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.
|
||||
@ -35,8 +37,8 @@ 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`]).
|
||||
@ -44,7 +46,15 @@ pub trait AdcHasCurveCal {
|
||||
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>,
|
||||
@ -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,)*
|
||||
],
|
||||
},)*
|
||||
];
|
||||
|
@ -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
|
||||
@ -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>,
|
||||
@ -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
|
||||
@ -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,
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
|
@ -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 = ()> {
|
||||
|
@ -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 = ()> {
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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>;
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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>;
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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>;
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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>;
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user