ADC raw values calibration (#555)

* adc_cal: c2: Add efuse functions for reading calibration

* adc_cal: c3: Add efuse functions for reading calibration

* adc_cal: c6: Add efuse functions for reading calibration

* adc_cal: Add extra traits to support calibration

- `AdcCalScheme<ADCI>` implemented for each calibration scheme (basic, linear, curved)
- `AdcCalEfuse` implemented for each ADC unit to get calibration data from efuse bits

* adc_cal: Add basic ADC calibration scheme

Basic calibration is related to setting some initial bias value to ADC unit.
Such values usually is stored in efuse bit fields but also can be measured
in runtime by connecting ADC input to ground internally.

* adc_cal: Add line fitting ADC calibration scheme

This scheme also includes basic calibration and implements gain correction based
on reference point.

Reference point is a pair of reference voltage and corresponding mean raw ADC
value. Such raw values usually is stored in efuse bit fields for each supported
attenuation.

Possibly it also can be measured in runtime by connecting ADC to reference
voltage internally.

* adc_cal: Add curve fitting ADC calibration scheme

This scheme also includes basic and linear and implements final polynomial error
correction.

* adc_cal: riscv: Add ADC calibration implementation for riscv chips

* adc_cal: c2: Add calibrated ADC reading example

This example uses line fitting calibration scheme by default.
It periodically prints both raw measured value and computed millivolts.

* adc_cal: c3: Add calibrated ADC reading example

This example uses curve fitting calibration scheme by default.
It periodically prints both raw measured value and computed millivolts.

* adc_cal: c6: Add calibrated ADC reading example

This example uses curve fitting calibration scheme by default.
It periodically prints both raw measured value and computed millivolts.

* adc_cal: riscv: Add changelog entry for ADC calibration
This commit is contained in:
Kayo Phoenix 2023-07-04 22:14:27 +05:00 committed by GitHub
parent 996da27f30
commit 74438fcec5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 1311 additions and 17 deletions

View File

@ -32,6 +32,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Add MD5 functions from ESP ROM (#618)
- Add embassy async `read` support for `uart` (#620)
- Add bare-bones support to run code on ULP-RISCV / LP core (#631)
- Add ADC calibration implementation for a riscv chips (#555)
### Changed

View File

@ -0,0 +1,40 @@
use core::marker::PhantomData;
use crate::adc::{AdcCalEfuse, AdcCalScheme, AdcCalSource, AdcConfig, Attenuation, RegisterAccess};
/// 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.
#[derive(Clone, Copy)]
pub struct AdcCalBasic<ADCI> {
/// Calibration value to set to ADC unit
cal_val: u16,
_phantom: PhantomData<ADCI>,
}
impl<ADCI> AdcCalScheme<ADCI> for AdcCalBasic<ADCI>
where
ADCI: AdcCalEfuse + RegisterAccess,
{
fn new_cal(atten: Attenuation) -> Self {
// Try to get init code (Dout0) from efuse
// Dout0 means mean raw ADC value when zero voltage applied to input.
let cal_val = ADCI::get_init_code(atten).unwrap_or_else(|| {
// As a fallback try to calibrate via connecting input to ground internally.
AdcConfig::<ADCI>::adc_calibrate(atten, AdcCalSource::Gnd)
});
Self {
cal_val,
_phantom: PhantomData,
}
}
fn adc_cal(&self) -> u16 {
self.cal_val
}
}

View File

@ -0,0 +1,240 @@
use core::marker::PhantomData;
use crate::adc::{
AdcCalEfuse,
AdcCalLine,
AdcCalScheme,
AdcHasLineCal,
Attenuation,
RegisterAccess,
};
const COEFF_MUL: i64 = 1 << 52;
type CurveCoeff = i64;
/// Polynomial coefficients for specified attenuation.
pub struct CurveCoeffs {
/// Attenuation
atten: Attenuation,
/// Polynomial coefficients
coeff: &'static [CurveCoeff],
}
type CurvesCoeffs = &'static [CurveCoeffs];
/// Marker trait for ADC which support curve futting
///
/// See also [`AdcCalCurve`].
pub trait AdcHasCurveCal {
/// Coefficients for calculating the reading voltage error.
///
/// A sets of coefficients for each attenuation.
const CURVES_COEFFS: CurvesCoeffs;
}
/// Curve fitting ADC calibration scheme
///
/// This scheme implements final polynomial error correction using predefined
/// coefficient sets for each attenuation.
///
/// This scheme also includes basic calibration ([`AdcCalBasic`]) and line
/// fitting ([`AdcCalLine`]).
#[derive(Clone, Copy)]
pub struct AdcCalCurve<ADCI> {
line: AdcCalLine<ADCI>,
/// Coefficients for each term (3..=5)
coeff: &'static [CurveCoeff],
_phantom: PhantomData<ADCI>,
}
impl<ADCI> AdcCalScheme<ADCI> for AdcCalCurve<ADCI>
where
ADCI: AdcCalEfuse + AdcHasLineCal + AdcHasCurveCal + RegisterAccess,
{
fn new_cal(atten: Attenuation) -> Self {
let line = AdcCalLine::<ADCI>::new_cal(atten);
let coeff = ADCI::CURVES_COEFFS
.iter()
.find(|item| item.atten == atten)
.expect("No curve coefficients for given attenuation")
.coeff;
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 err = if val == 0 {
0
} else {
// err = coeff[0] + coeff[1] * val + coeff[2] * val^2 + ... + coeff[n] * val^n
let mut var = 1i64;
let mut err = (var * self.coeff[0] as i64 / COEFF_MUL) as i32;
for coeff in &self.coeff[1..] {
var = var * val as i64;
err += (var * *coeff as i64 / COEFF_MUL) as i32;
}
err
};
(val as i32 - err) as u16
}
}
macro_rules! coeff_tables {
($($(#[$($meta:meta)*])* $name:ident [ $($att:ident => [ $($val:literal,)* ],)* ];)*) => {
$(
$(#[$($meta)*])*
const $name: CurvesCoeffs = &[
$(CurveCoeffs {
atten: Attenuation::$att,
coeff: &[
$(($val as f64 * COEFF_MUL as f64 * 4096f64 / Attenuation::$att.ref_mv() as f64) as CurveCoeff,)*
],
},)*
];
)*
};
}
#[cfg(any(esp32c3, esp32c6, esp32s3))]
mod impls {
use super::*;
impl AdcHasCurveCal for crate::adc::ADC1 {
const CURVES_COEFFS: CurvesCoeffs = CURVES_COEFFS1;
}
#[cfg(esp32c3)]
impl AdcHasCurveCal for crate::adc::ADC2 {
const CURVES_COEFFS: CurvesCoeffs = CURVES_COEFFS1;
}
#[cfg(esp32s3)]
impl AdcHasCurveCal for crate::adc::ADC2 {
const CURVES_COEFFS: CurvesCoeffs = CURVES_COEFFS2;
}
coeff_tables! {
/// Error curve coefficients derived from https://github.com/espressif/esp-idf/blob/903af13e8/components/esp_adc/esp32c3/curve_fitting_coefficients.c
#[cfg(esp32c3)]
CURVES_COEFFS1 [
Attenuation0dB => [
-0.2259664705000430,
-0.0007265418501948,
0.0000109410402681,
],
Attenuation2p5dB => [
0.4229623392600516,
-0.0000731527490903,
0.0000088166562521,
],
Attenuation6dB => [
-1.0178592392364350,
-0.0097159265299153,
0.0000149794028038,
],
Attenuation11dB => [
-1.4912262772850453,
-0.0228549975564099,
0.0000356391935717,
-0.0000000179964582,
0.0000000000042046,
],
];
/// Error curve coefficients derived from https://github.com/espressif/esp-idf/blob/903af13e8/components/esp_adc/esp32c6/curve_fitting_coefficients.c
#[cfg(esp32c6)]
CURVES_COEFFS1 [
Attenuation0dB => [
-0.0487166399931449,
0.0006436483033201,
0.0000030410131806,
],
Attenuation2p5dB => [
-0.8665498165817785,
0.0015239070452946,
0.0000013818878844,
],
Attenuation6dB => [
-1.2277821756674387,
0.0022275554717885,
0.0000005924302667,
],
Attenuation11dB => [
-0.3801417550380255,
-0.0006020352420772,
0.0000012442478488,
],
];
/// Error curve coefficients derived from https://github.com/espressif/esp-idf/blob/903af13e8/components/esp_adc/esp32s3/curve_fitting_coefficients.c
#[cfg(esp32s3)]
CURVES_COEFFS1 [
Attenuation0dB => [
-2.7856531419538344,
-0.0050871540569528,
0.0000097982495890,
],
Attenuation2p5dB => [
-2.9831022915028695,
-0.0049393185868806,
0.0000101379430548,
],
Attenuation6dB => [
-2.3285545746296417,
-0.0147640181047414,
0.0000208385525314,
],
Attenuation11dB => [
-0.6444034182694780,
-0.0644334888647536,
0.0001297891447611,
-0.0000000707697180,
0.0000000000135150,
],
];
/// Error curve coefficients derived from https://github.com/espressif/esp-idf/blob/903af13e8/components/esp_adc/esp32s3/curve_fitting_coefficients.c
#[cfg(esp32s3)]
CURVES_COEFFS2 [
Attenuation0dB => [
-2.5668651654328927,
0.0001353548869615,
0.0000036615265189,
],
Attenuation2p5dB => [
-2.3690184690298404,
-0.0066319894226185,
0.0000118964995959,
],
Attenuation6dB => [
-0.9452499397020617,
-0.0200996773954387,
0.00000259011467956,
],
Attenuation11dB => [
1.2247719764336924,
-0.0755717904943462,
0.0001478791187119,
-0.0000000796725280,
0.0000000000150380,
],
];
}
}

View File

@ -0,0 +1,100 @@
use core::marker::PhantomData;
use crate::adc::{
AdcCalBasic,
AdcCalEfuse,
AdcCalScheme,
AdcCalSource,
AdcConfig,
Attenuation,
RegisterAccess,
};
/// Marker trait for ADC units which support line fitting
///
/// Usually it means that reference points are stored in efuse.
/// See also [`AdcCalLine`].
pub trait AdcHasLineCal {}
/// Coefficients is actually a fixed-point numbers.
/// It is scaled to put them into integer.
const GAIN_SCALE: u32 = 1 << 16;
/// Line fitting ADC calibration scheme
///
/// This scheme implements gain correction based on reference points.
///
/// 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
/// fields for each supported attenuation.
///
/// Also it can be measured in runtime by connecting ADC to reference voltage
/// internally but this method is not so good because actual reference voltage
/// may varies in range 1.0..=1.2 V. Currently this method is used as a fallback
/// (with 1.1 V by default) when calibration data is missing.
///
/// This scheme also includes basic calibration ([`AdcCalBasic`]).
#[derive(Clone, Copy)]
pub struct AdcCalLine<ADCI> {
basic: AdcCalBasic<ADCI>,
/// Gain of ADC-value
gain: u32,
_phantom: PhantomData<ADCI>,
}
impl<ADCI> AdcCalScheme<ADCI> for AdcCalLine<ADCI>
where
ADCI: AdcCalEfuse + AdcHasLineCal + RegisterAccess,
{
fn new_cal(atten: Attenuation) -> Self {
let basic = AdcCalBasic::<ADCI>::new_cal(atten);
// Try get the reference point (Dout, Vin) from efuse
// Dout means mean raw ADC value when specified Vin applied to input.
let (code, mv) = ADCI::get_cal_code(atten)
.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.
(
AdcConfig::<ADCI>::adc_calibrate(atten, AdcCalSource::Ref),
1100, // use 1100 mV as a middle of typical reference voltage range
)
});
// Estimate the (assumed) linear relationship between the measured raw value and
// 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;
Self {
basic,
gain,
_phantom: PhantomData,
}
}
fn adc_cal(&self) -> u16 {
self.basic.adc_cal()
}
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
}
}
#[cfg(any(esp32c2, esp32c3, esp32c6))]
impl AdcHasLineCal for crate::adc::ADC1 {}
#[cfg(esp32c3)]
impl AdcHasLineCal for crate::adc::ADC2 {}

View File

@ -4,6 +4,10 @@ use embedded_hal::adc::{Channel, OneShot};
#[cfg(esp32c3)]
use crate::analog::ADC2;
#[cfg(any(esp32c6, esp32h2))]
use crate::clock::clocks_ll::regi2c_write_mask;
#[cfg(any(esp32c2, esp32c3, esp32c6))]
use crate::efuse::Efuse;
use crate::{
analog::ADC1,
peripheral::PeripheralRef,
@ -11,6 +15,99 @@ use crate::{
system::{Peripheral, PeripheralClockControl},
};
#[cfg(any(esp32c2, esp32c3, esp32c6))]
mod cal_basic;
#[cfg(any(esp32c3, esp32c6))]
mod cal_curve;
#[cfg(any(esp32c2, esp32c3, esp32c6))]
mod cal_line;
#[cfg(any(esp32c2, esp32c3, esp32c6))]
pub use cal_basic::AdcCalBasic;
#[cfg(any(esp32c3, esp32c6))]
pub use cal_curve::{AdcCalCurve, AdcHasCurveCal};
#[cfg(any(esp32c2, esp32c3, esp32c6))]
pub use cal_line::{AdcCalLine, AdcHasLineCal};
pub use crate::analog::{AdcCalEfuse, AdcCalScheme};
// polyfill for c2 and c3
#[cfg(any(esp32c2, esp32c3))]
#[inline(always)]
fn regi2c_write_mask(block: u8, host_id: u8, reg_add: u8, msb: u8, lsb: u8, data: u8) {
unsafe {
crate::rom::rom_i2c_writeReg_Mask(
block as _,
host_id as _,
reg_add as _,
msb as _,
lsb as _,
data as _,
);
}
}
// Constants taken from:
// https://github.com/espressif/esp-idf/blob/903af13e8/components/soc/esp32c2/include/soc/regi2c_saradc.h
// https://github.com/espressif/esp-idf/blob/903af13e8/components/soc/esp32c3/include/soc/regi2c_saradc.h
// https://github.com/espressif/esp-idf/blob/903af13e8/components/soc/esp32c6/include/soc/regi2c_saradc.h
// https://github.com/espressif/esp-idf/blob/903af13e8/components/soc/esp32h2/include/soc/regi2c_saradc.h
// https://github.com/espressif/esp-idf/blob/903af13e8/components/soc/esp32h4/include/soc/regi2c_saradc.h
cfg_if::cfg_if! {
if #[cfg(any(esp32c2, esp32c3, esp32c6, esp32h2))] {
const I2C_SAR_ADC: u8 = 0x69;
const I2C_SAR_ADC_HOSTID: u8 = 0;
const ADC_VAL_MASK: u16 = 0xfff;
const ADC_CAL_CNT_MAX: u16 = 32;
const ADC_CAL_CHANNEL: u32 = 0xf;
const ADC_SAR1_ENCAL_GND_ADDR: u8 = 0x7;
const ADC_SAR1_ENCAL_GND_ADDR_MSB: u8 = 5;
const ADC_SAR1_ENCAL_GND_ADDR_LSB: u8 = 5;
const ADC_SAR1_INITIAL_CODE_HIGH_ADDR: u8 = 0x1;
const ADC_SAR1_INITIAL_CODE_HIGH_ADDR_MSB: u8 = 0x3;
const ADC_SAR1_INITIAL_CODE_HIGH_ADDR_LSB: u8 = 0x0;
const ADC_SAR1_INITIAL_CODE_LOW_ADDR: u8 = 0x0;
const ADC_SAR1_INITIAL_CODE_LOW_ADDR_MSB: u8 = 0x7;
const ADC_SAR1_INITIAL_CODE_LOW_ADDR_LSB: u8 = 0x0;
const ADC_SAR1_DREF_ADDR: u8 = 0x2;
const ADC_SAR1_DREF_ADDR_MSB: u8 = 0x6;
const ADC_SAR1_DREF_ADDR_LSB: u8 = 0x4;
const ADC_SARADC1_ENCAL_REF_ADDR: u8 = 0x7;
const ADC_SARADC1_ENCAL_REF_ADDR_MSB: u8 = 4;
const ADC_SARADC1_ENCAL_REF_ADDR_LSB: u8 = 4;
}
}
cfg_if::cfg_if! {
if #[cfg(esp32c3)] {
const ADC_SAR2_ENCAL_GND_ADDR: u8 = 0x7;
const ADC_SAR2_ENCAL_GND_ADDR_MSB: u8 = 7;
const ADC_SAR2_ENCAL_GND_ADDR_LSB: u8 = 7;
const ADC_SAR2_INITIAL_CODE_HIGH_ADDR: u8 = 0x4;
const ADC_SAR2_INITIAL_CODE_HIGH_ADDR_MSB: u8 = 0x3;
const ADC_SAR2_INITIAL_CODE_HIGH_ADDR_LSB: u8 = 0x0;
const ADC_SAR2_INITIAL_CODE_LOW_ADDR: u8 = 0x3;
const ADC_SAR2_INITIAL_CODE_LOW_ADDR_MSB: u8 = 0x7;
const ADC_SAR2_INITIAL_CODE_LOW_ADDR_LSB: u8 = 0x0;
const ADC_SAR2_DREF_ADDR: u8 = 0x5;
const ADC_SAR2_DREF_ADDR_MSB: u8 = 0x6;
const ADC_SAR2_DREF_ADDR_LSB: u8 = 0x4;
const ADC_SARADC2_ENCAL_REF_ADDR: u8 = 0x7;
const ADC_SARADC2_ENCAL_REF_ADDR_MSB: u8 = 6;
const ADC_SARADC2_ENCAL_REF_ADDR_LSB: u8 = 6;
}
}
/// The sampling/readout resolution of the ADC
#[derive(PartialEq, Eq, Clone, Copy)]
pub enum Resolution {
@ -20,18 +117,56 @@ pub enum Resolution {
/// The attenuation of the ADC pin
#[derive(PartialEq, Eq, Clone, Copy)]
pub enum Attenuation {
/// 0 dB attenuation, measurement range: 0 - 800 mV
Attenuation0dB = 0b00,
/// 2.5 dB attenuation, measurement range: 0 - 1100 mV
#[cfg(not(esp32c2))]
Attenuation2p5dB = 0b01,
/// 6 dB attenuation, measurement range: 0 - 1350 mV
#[cfg(not(esp32c2))]
Attenuation6dB = 0b10,
/// 11 dB attenuation, measurement range: 0 - 2600 mV
Attenuation11dB = 0b11,
}
pub struct AdcPin<PIN, ADCI> {
impl Attenuation {
/// List of all supported attenuations
pub const ALL: &'static [Attenuation] = &[
Attenuation::Attenuation0dB,
#[cfg(not(esp32c2))]
Attenuation::Attenuation2p5dB,
#[cfg(not(esp32c2))]
Attenuation::Attenuation6dB,
Attenuation::Attenuation11dB,
];
/// Reference voltage in millivolts
///
/// Vref = 10 ^ (Att / 20) * Vref0
/// where Vref0 = 1.1 V, Att - attenuation in dB
///
/// To colvert raw value to millivolts use folmula:
/// 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 pin: PIN,
pub cal_scheme: CS,
_phantom: PhantomData<ADCI>,
}
impl<PIN: Channel<ADCI, ID = u8>, ADCI> Channel<ADCI> for AdcPin<PIN, ADCI> {
impl<PIN: Channel<ADCI, ID = u8>, ADCI, CS> Channel<ADCI> for AdcPin<PIN, ADCI, CS> {
type ID = u8;
fn channel() -> Self::ID {
@ -57,14 +192,69 @@ where
&mut self,
pin: PIN,
attenuation: Attenuation,
) -> AdcPin<PIN, ADCI> {
) -> 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,
) -> AdcPin<PIN, ADCI, CS> {
self.attenuations[PIN::channel() as usize] = Some(attenuation);
AdcPin {
pin,
cal_scheme: CS::new_cal(attenuation),
_phantom: PhantomData::default(),
}
}
/// Calibrate ADC with specified attenuation and voltage source
pub fn adc_calibrate(atten: Attenuation, source: AdcCalSource) -> u16 {
let mut adc_max: u16 = 0;
let mut adc_min: u16 = u16::MAX;
let mut adc_sum: u32 = 0;
ADCI::enable_vdef(true);
// Start sampling
ADCI::config_onetime_sample(ADC_CAL_CHANNEL as u8, atten as u8);
// Connect calibration source
ADCI::connect_cal(source, true);
for _ in 0..ADC_CAL_CNT_MAX {
ADCI::set_init_code(0);
// Trigger ADC sampling
ADCI::start_onetime_sample();
// Wait until ADC1 sampling is done
while !ADCI::is_done() {}
let adc = ADCI::read_data() & ADC_VAL_MASK;
ADCI::reset();
adc_sum += adc as u32;
adc_max = adc.max(adc_max);
adc_min = adc.min(adc_min);
}
let cal_val = (adc_sum - adc_max as u32 - adc_min as u32) as u16 / (ADC_CAL_CNT_MAX - 2);
// Disconnect calibration source
ADCI::connect_cal(source, false);
cal_val
}
}
impl<ADCI> Default for AdcConfig<ADCI> {
@ -77,19 +267,40 @@ impl<ADCI> Default for AdcConfig<ADCI> {
}
}
#[derive(Clone, Copy)]
pub enum AdcCalSource {
Gnd,
Ref,
}
#[doc(hidden)]
pub trait RegisterAccess {
fn start_onetime_sample(channel: u8, attenuation: u8);
/// Configure onetime sampling parameters
fn config_onetime_sample(channel: u8, attenuation: u8);
/// Start onetime sampling
fn start_onetime_sample();
/// Check if sampling is done
fn is_done() -> bool;
/// Read sample data
fn read_data() -> u16;
/// Reset flags
fn reset();
fn enable_vdef(enable: bool);
/// Enable internal connect GND (for calibration)
fn connect_cal(source: AdcCalSource, enable: bool);
/// Set calibration parameter to ADC hardware
fn set_init_code(data: u16);
}
impl RegisterAccess for ADC1 {
fn start_onetime_sample(channel: u8, attenuation: u8) {
fn config_onetime_sample(channel: u8, attenuation: u8) {
let sar_adc = unsafe { &*APB_SARADC::PTR };
sar_adc.onetime_sample.modify(|_, w| unsafe {
@ -99,11 +310,17 @@ impl RegisterAccess for ADC1 {
.bits(channel)
.saradc_onetime_atten()
.bits(attenuation)
.saradc_onetime_start()
.set_bit()
});
}
fn start_onetime_sample() {
let sar_adc = unsafe { &*APB_SARADC::PTR };
sar_adc
.onetime_sample
.modify(|_, w| w.saradc_onetime_start().set_bit());
}
fn is_done() -> bool {
let sar_adc = unsafe { &*APB_SARADC::PTR };
@ -119,19 +336,76 @@ impl RegisterAccess for ADC1 {
fn reset() {
let sar_adc = unsafe { &*APB_SARADC::PTR };
// Clear ADC1 sampling done interrupt bit
sar_adc
.int_clr
.write(|w| w.apb_saradc1_done_int_clr().set_bit());
// Disable ADC sampling
sar_adc
.onetime_sample
.modify(|_, w| w.saradc_onetime_start().clear_bit());
}
fn enable_vdef(enable: bool) {
let value = enable as _;
regi2c_write_mask(
I2C_SAR_ADC,
I2C_SAR_ADC_HOSTID,
ADC_SAR1_DREF_ADDR,
ADC_SAR1_DREF_ADDR_MSB,
ADC_SAR1_DREF_ADDR_LSB,
value,
);
}
fn connect_cal(source: AdcCalSource, enable: bool) {
let value = enable as _;
match source {
AdcCalSource::Gnd => regi2c_write_mask(
I2C_SAR_ADC,
I2C_SAR_ADC_HOSTID,
ADC_SAR1_ENCAL_GND_ADDR,
ADC_SAR1_ENCAL_GND_ADDR_MSB,
ADC_SAR1_ENCAL_GND_ADDR_LSB,
value,
),
AdcCalSource::Ref => regi2c_write_mask(
I2C_SAR_ADC,
I2C_SAR_ADC_HOSTID,
ADC_SARADC1_ENCAL_REF_ADDR,
ADC_SARADC1_ENCAL_REF_ADDR_MSB,
ADC_SARADC1_ENCAL_REF_ADDR_LSB,
value,
),
}
}
fn set_init_code(data: u16) {
let [msb, lsb] = data.to_be_bytes();
regi2c_write_mask(
I2C_SAR_ADC,
I2C_SAR_ADC_HOSTID,
ADC_SAR1_INITIAL_CODE_HIGH_ADDR,
ADC_SAR1_INITIAL_CODE_HIGH_ADDR_MSB,
ADC_SAR1_INITIAL_CODE_HIGH_ADDR_LSB,
msb as _,
);
regi2c_write_mask(
I2C_SAR_ADC,
I2C_SAR_ADC_HOSTID,
ADC_SAR1_INITIAL_CODE_LOW_ADDR,
ADC_SAR1_INITIAL_CODE_LOW_ADDR_MSB,
ADC_SAR1_INITIAL_CODE_LOW_ADDR_LSB,
lsb as _,
);
}
}
#[cfg(esp32c3)]
impl RegisterAccess for ADC2 {
fn start_onetime_sample(channel: u8, attenuation: u8) {
fn config_onetime_sample(channel: u8, attenuation: u8) {
let sar_adc = unsafe { &*APB_SARADC::PTR };
sar_adc.onetime_sample.modify(|_, w| unsafe {
@ -141,11 +415,17 @@ impl RegisterAccess for ADC2 {
.bits(channel)
.saradc_onetime_atten()
.bits(attenuation)
.saradc_onetime_start()
.set_bit()
});
}
fn start_onetime_sample() {
let sar_adc = unsafe { &*APB_SARADC::PTR };
sar_adc
.onetime_sample
.modify(|_, w| w.saradc_onetime_start().set_bit());
}
fn is_done() -> bool {
let sar_adc = unsafe { &*APB_SARADC::PTR };
@ -169,6 +449,61 @@ impl RegisterAccess for ADC2 {
.onetime_sample
.modify(|_, w| w.saradc_onetime_start().clear_bit());
}
fn enable_vdef(enable: bool) {
let value = enable as _;
regi2c_write_mask(
I2C_SAR_ADC,
I2C_SAR_ADC_HOSTID,
ADC_SAR2_DREF_ADDR,
ADC_SAR2_DREF_ADDR_MSB,
ADC_SAR2_DREF_ADDR_LSB,
value,
);
}
fn connect_cal(source: AdcCalSource, enable: bool) {
let value = enable as _;
match source {
AdcCalSource::Gnd => regi2c_write_mask(
I2C_SAR_ADC,
I2C_SAR_ADC_HOSTID,
ADC_SAR2_ENCAL_GND_ADDR,
ADC_SAR2_ENCAL_GND_ADDR_MSB,
ADC_SAR2_ENCAL_GND_ADDR_LSB,
value,
),
AdcCalSource::Ref => regi2c_write_mask(
I2C_SAR_ADC,
I2C_SAR_ADC_HOSTID,
ADC_SARADC2_ENCAL_REF_ADDR,
ADC_SARADC2_ENCAL_REF_ADDR_MSB,
ADC_SARADC2_ENCAL_REF_ADDR_LSB,
value,
),
}
}
fn set_init_code(data: u16) {
let [msb, lsb] = data.to_be_bytes();
regi2c_write_mask(
I2C_SAR_ADC,
I2C_SAR_ADC_HOSTID,
ADC_SAR2_INITIAL_CODE_HIGH_ADDR,
ADC_SAR2_INITIAL_CODE_HIGH_ADDR_MSB,
ADC_SAR2_INITIAL_CODE_HIGH_ADDR_LSB,
msb as _,
);
regi2c_write_mask(
I2C_SAR_ADC,
I2C_SAR_ADC_HOSTID,
ADC_SAR2_INITIAL_CODE_LOW_ADDR,
ADC_SAR2_INITIAL_CODE_LOW_ADDR_MSB,
ADC_SAR2_INITIAL_CODE_LOW_ADDR_LSB,
lsb as _,
);
}
}
pub struct ADC<'d, ADCI> {
@ -179,7 +514,7 @@ pub struct ADC<'d, ADCI> {
impl<'d, ADCI> ADC<'d, ADCI>
where
ADCI: RegisterAccess,
ADCI: RegisterAccess + 'd,
{
pub fn adc(
peripheral_clock_controller: &mut PeripheralClockControl,
@ -209,15 +544,46 @@ where
}
}
impl<'d, ADCI, WORD, PIN> OneShot<ADCI, WORD, AdcPin<PIN, ADCI>> for ADC<'d, ADCI>
#[cfg(any(esp32c2, esp32c3, esp32c6))]
impl AdcCalEfuse for ADC1 {
fn get_init_code(atten: Attenuation) -> Option<u16> {
Efuse::get_rtc_calib_init_code(1, atten)
}
fn get_cal_mv(atten: Attenuation) -> u16 {
Efuse::get_rtc_calib_cal_mv(1, atten)
}
fn get_cal_code(atten: Attenuation) -> Option<u16> {
Efuse::get_rtc_calib_cal_code(1, atten)
}
}
#[cfg(esp32c3)]
impl AdcCalEfuse for ADC2 {
fn get_init_code(atten: Attenuation) -> Option<u16> {
Efuse::get_rtc_calib_init_code(2, atten)
}
fn get_cal_mv(atten: Attenuation) -> u16 {
Efuse::get_rtc_calib_cal_mv(2, atten)
}
fn get_cal_code(atten: Attenuation) -> Option<u16> {
Efuse::get_rtc_calib_cal_code(2, atten)
}
}
impl<'d, ADCI, WORD, PIN, CS> OneShot<ADCI, WORD, AdcPin<PIN, ADCI, CS>> for ADC<'d, ADCI>
where
WORD: From<u16>,
PIN: Channel<ADCI, ID = u8>,
ADCI: RegisterAccess,
CS: AdcCalScheme<ADCI>,
{
type Error = ();
fn read(&mut self, _pin: &mut AdcPin<PIN, ADCI>) -> nb::Result<WORD, Self::Error> {
fn read(&mut self, pin: &mut AdcPin<PIN, ADCI, CS>) -> nb::Result<WORD, Self::Error> {
if self.attenuations[AdcPin::<PIN, ADCI>::channel() as usize] == None {
panic!(
"Channel {} is not configured reading!",
@ -236,9 +602,13 @@ where
// If no conversions are in progress, start a new one for given channel
self.active_channel = Some(AdcPin::<PIN, ADCI>::channel());
// Set ADC unit calibration according used scheme for pin
ADCI::set_init_code(pin.cal_scheme.adc_cal());
let channel = self.active_channel.unwrap();
let attenuation = self.attenuations[channel as usize].unwrap() as u8;
ADCI::start_onetime_sample(channel, attenuation);
ADCI::config_onetime_sample(channel, attenuation);
ADCI::start_onetime_sample();
}
// Wait for ADC to finish conversion
@ -251,6 +621,9 @@ where
let converted_value = ADCI::read_data();
ADCI::reset();
// Postprocess converted value according to calibration scheme used for pin
let converted_value = pin.cal_scheme.adc_val(converted_value);
// There is a hardware limitation. If the APB clock frequency is high, the step
// of this reg signal: ``onetime_start`` may not be captured by the
// ADC digital controller (when its clock frequency is too slow). A rough

View File

@ -5,6 +5,46 @@ pub mod adc;
#[cfg(dac)]
pub mod dac;
/// A helper trait to do calibrated samples fitting
pub trait AdcCalScheme<ADCI>: Sized {
/// Instantiate scheme
fn new_cal(atten: adc::Attenuation) -> Self;
/// Get ADC calibration value to set to ADC unit
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 {
()
}
}
/// A helper trait to get access to ADC calibration efuses
pub trait AdcCalEfuse {
/// Get ADC calibration init code
///
/// Returns digital value for zero voltage for a given attenuation
fn get_init_code(atten: adc::Attenuation) -> Option<u16>;
/// Get ADC calibration reference point voltage
///
/// Returns reference voltage (millivolts) for a given attenuation
fn get_cal_mv(atten: adc::Attenuation) -> u16;
/// Get ADC calibration reference point digital value
///
/// Returns digital value for reference voltage for a given attenuation
fn get_cal_code(atten: adc::Attenuation) -> Option<u16>;
}
pub struct ADC1 {
_private: (),
}

View File

@ -1,7 +1,7 @@
//! Reading of eFuses
use crate::peripherals::EFUSE;
pub use crate::soc::efuse_field::*;
use crate::{adc::Attenuation, peripherals::EFUSE};
pub struct Efuse;
@ -36,6 +36,102 @@ impl Efuse {
pub fn get_rwdt_multiplier() -> u8 {
Self::read_field_le::<u8>(WDT_DELAY_SEL)
}
/// Get efuse block version
///
/// see https://github.com/espressif/esp-idf/blob/dc016f5987/components/hal/efuse_hal.c#L27-L30
pub fn get_block_version() -> (u8, u8) {
// see https://github.com/espressif/esp-idf/blob/dc016f5987/components/hal/esp32c2/include/hal/efuse_ll.h#L65-L73
// https://github.com/espressif/esp-idf/blob/903af13e8/components/efuse/esp32c2/esp_efuse_table.csv#L90-L91
(
Self::read_field_le::<u8>(BLK_VERSION_MAJOR),
Self::read_field_le::<u8>(BLK_VERSION_MINOR),
)
}
/// Get version of RTC calibration block
///
/// see https://github.com/espressif/esp-idf/blob/903af13e8/components/efuse/esp32c2/esp_efuse_rtc_calib.c#L14
pub fn get_rtc_calib_version() -> u8 {
let (major, _minor) = Self::get_block_version();
if major == 0 {
1
} else {
0
}
}
/// Get ADC initial code for specified attenuation from efuse
///
/// see https://github.com/espressif/esp-idf/blob/903af13e8/components/efuse/esp32c2/esp_efuse_rtc_calib.c#L27
pub fn get_rtc_calib_init_code(_unit: u8, atten: Attenuation) -> Option<u16> {
let version = Self::get_rtc_calib_version();
if version != 1 {
return None;
}
// see https://github.com/espressif/esp-idf/blob/903af13e8/components/efuse/esp32c2/esp_efuse_table.csv#L94
let diff_code0: u16 = Self::read_field_le(ADC1_INIT_CODE_ATTEN0);
let code0 = if diff_code0 & (1 << 7) != 0 {
2160 - (diff_code0 & 0x7f)
} else {
2160 + diff_code0
};
if matches!(atten, Attenuation::Attenuation0dB) {
return Some(code0);
}
// see https://github.com/espressif/esp-idf/blob/903af13e8/components/efuse/esp32c2/esp_efuse_table.csv#L95
let diff_code11: u16 = Self::read_field_le(ADC1_INIT_CODE_ATTEN3);
let code11 = code0 + diff_code11;
Some(code11)
}
/// Get ADC reference point voltage for specified attenuation in millivolts
///
/// see https://github.com/espressif/esp-idf/blob/903af13e8/components/efuse/esp32c2/esp_efuse_rtc_calib.c#L65
pub fn get_rtc_calib_cal_mv(_unit: u8, atten: Attenuation) -> u16 {
match atten {
Attenuation::Attenuation0dB => 400,
Attenuation::Attenuation11dB => 1370,
}
}
/// Get ADC reference point digital code for specified attenuation
///
/// see https://github.com/espressif/esp-idf/blob/903af13e8/components/efuse/esp32c2/esp_efuse_rtc_calib.c#L65
pub fn get_rtc_calib_cal_code(_unit: u8, atten: Attenuation) -> Option<u16> {
let version = Self::get_rtc_calib_version();
if version != 1 {
return None;
}
// see https://github.com/espressif/esp-idf/blob/903af13e8/components/efuse/esp32c2/esp_efuse_table.csv#L96
let diff_code0: u16 = Self::read_field_le(ADC1_CAL_VOL_ATTEN0);
let code0 = if diff_code0 & (1 << 7) != 0 {
1540 - (diff_code0 & 0x7f)
} else {
1540 + diff_code0
};
if matches!(atten, Attenuation::Attenuation0dB) {
return Some(code0);
}
// see https://github.com/espressif/esp-idf/blob/903af13e8/components/efuse/esp32c2/esp_efuse_table.csv#L97
let diff_code11: u16 = Self::read_field_le(ADC1_CAL_VOL_ATTEN3);
let code11 = if diff_code0 & (1 << 5) != 0 {
code0 - (diff_code11 & 0x1f)
} else {
code0 + diff_code11
} - 123;
Some(code11)
}
}
#[derive(Copy, Clone)]

View File

@ -1,7 +1,7 @@
//! Reading of eFuses
use crate::peripherals::EFUSE;
pub use crate::soc::efuse_field::*;
use crate::{adc::Attenuation, peripherals::EFUSE};
pub struct Efuse;
@ -36,6 +36,91 @@ impl Efuse {
pub fn get_rwdt_multiplier() -> u8 {
Self::read_field_le::<u8>(WDT_DELAY_SEL)
}
/// Get efuse block version
///
/// see https://github.com/espressif/esp-idf/blob/dc016f5987/components/hal/efuse_hal.c#L27-L30
pub fn get_block_version() -> (u8, u8) {
// see https://github.com/espressif/esp-idf/blob/dc016f5987/components/hal/esp32c3/include/hal/efuse_ll.h#L70-L78
// https://github.com/espressif/esp-idf/blob/903af13e8/components/efuse/esp32c3/esp_efuse_table.csv#L163
// https://github.com/espressif/esp-idf/blob/903af13e8/components/efuse/esp32c3/esp_efuse_table.csv#L173
(
Self::read_field_le::<u8>(BLK_VERSION_MAJOR),
Self::read_field_le::<u8>(BLK_VERSION_MINOR),
)
}
/// Get version of RTC calibration block
///
/// see https://github.com/espressif/esp-idf/blob/903af13e8/components/efuse/esp32c3/esp_efuse_rtc_calib.c#L12
pub fn get_rtc_calib_version() -> u8 {
let (major, _minor) = Self::get_block_version();
if major == 1 {
1
} else {
0
}
}
/// Get ADC initial code for specified attenuation from efuse
///
/// see https://github.com/espressif/esp-idf/blob/903af13e8/components/efuse/esp32c3/esp_efuse_rtc_calib.c#L25
pub fn get_rtc_calib_init_code(_unit: u8, atten: Attenuation) -> Option<u16> {
let version = Self::get_rtc_calib_version();
if version != 1 {
return None;
}
// See https://github.com/espressif/esp-idf/blob/903af13e8/components/efuse/esp32c3/esp_efuse_table.csv#L176-L179
let init_code: u16 = Self::read_field_le(match atten {
Attenuation::Attenuation0dB => ADC1_INIT_CODE_ATTEN0,
Attenuation::Attenuation2p5dB => ADC1_INIT_CODE_ATTEN1,
Attenuation::Attenuation6dB => ADC1_INIT_CODE_ATTEN2,
Attenuation::Attenuation11dB => ADC1_INIT_CODE_ATTEN3,
});
Some(init_code + 1000) // version 1 logic
}
/// Get ADC reference point voltage for specified attenuation in millivolts
///
/// see https://github.com/espressif/esp-idf/blob/903af13e8/components/efuse/esp32c3/esp_efuse_rtc_calib.c#L49
pub fn get_rtc_calib_cal_mv(_unit: u8, atten: Attenuation) -> u16 {
match atten {
Attenuation::Attenuation0dB => 400,
Attenuation::Attenuation2p5dB => 550,
Attenuation::Attenuation6dB => 750,
Attenuation::Attenuation11dB => 1370,
}
}
/// Get ADC reference point digital code for specified attenuation
///
/// see https://github.com/espressif/esp-idf/blob/903af13e8/components/efuse/esp32c3/esp_efuse_rtc_calib.c#L49
pub fn get_rtc_calib_cal_code(_unit: u8, atten: Attenuation) -> Option<u16> {
let version = Self::get_rtc_calib_version();
if version != 1 {
return None;
}
// See https://github.com/espressif/esp-idf/blob/903af13e8/components/efuse/esp32c3/esp_efuse_table.csv#L180-L183
let cal_code: u16 = Self::read_field_le(match atten {
Attenuation::Attenuation0dB => ADC1_CAL_VOL_ATTEN0,
Attenuation::Attenuation2p5dB => ADC1_CAL_VOL_ATTEN1,
Attenuation::Attenuation6dB => ADC1_CAL_VOL_ATTEN2,
Attenuation::Attenuation11dB => ADC1_CAL_VOL_ATTEN3,
});
let cal_code = if cal_code & (1 << 9) != 0 {
2000 - (cal_code & !(1 << 9))
} else {
2000 + cal_code
};
Some(cal_code)
}
}
#[derive(Copy, Clone)]

View File

@ -1,7 +1,7 @@
//! Reading of eFuses
use crate::peripherals::EFUSE;
pub use crate::soc::efuse_field::*;
use crate::{adc::Attenuation, peripherals::EFUSE};
pub struct Efuse;
@ -36,6 +36,90 @@ impl Efuse {
pub fn get_rwdt_multiplier() -> u8 {
Self::read_field_le::<u8>(WDT_DELAY_SEL)
}
/// Get efuse block version
///
/// see https://github.com/espressif/esp-idf/blob/dc016f5987/components/hal/efuse_hal.c#L27-L30
pub fn get_block_version() -> (u8, u8) {
// see https://github.com/espressif/esp-idf/blob/dc016f5987/components/hal/esp32c6/include/hal/efuse_ll.h#L65-L73
// https://github.com/espressif/esp-idf/blob/903af13e8/components/efuse/esp32c6/esp_efuse_table.csv#L156
(
Self::read_field_le::<u8>(BLK_VERSION_MAJOR),
Self::read_field_le::<u8>(BLK_VERSION_MINOR),
)
}
/// Get version of RTC calibration block
///
/// see https://github.com/espressif/esp-idf/blob/903af13e8/components/efuse/esp32c6/esp_efuse_rtc_calib.c#L20
pub fn get_rtc_calib_version() -> u8 {
let (_major, minor) = Self::get_block_version();
if minor >= 1 {
1
} else {
0
}
}
/// Get ADC initial code for specified attenuation from efuse
///
/// see https://github.com/espressif/esp-idf/blob/903af13e8/components/efuse/esp32c6/esp_efuse_rtc_calib.c#L32
pub fn get_rtc_calib_init_code(_unit: u8, atten: Attenuation) -> Option<u16> {
let version = Self::get_rtc_calib_version();
if version != 1 {
return None;
}
// See https://github.com/espressif/esp-idf/blob/903af13e8/components/efuse/esp32c6/esp_efuse_table.csv#L147-L152
let init_code: u16 = Self::read_field_le(match atten {
Attenuation::Attenuation0dB => ADC1_INIT_CODE_ATTEN0,
Attenuation::Attenuation2p5dB => ADC1_INIT_CODE_ATTEN1,
Attenuation::Attenuation6dB => ADC1_INIT_CODE_ATTEN2,
Attenuation::Attenuation11dB => ADC1_INIT_CODE_ATTEN3,
});
Some(init_code + 1600) // version 1 logic
}
/// Get ADC reference point voltage for specified attenuation in millivolts
///
/// see https://github.com/espressif/esp-idf/blob/903af13e8/components/efuse/esp32c6/esp_efuse_rtc_calib.c#L42
pub fn get_rtc_calib_cal_mv(_unit: u8, atten: Attenuation) -> u16 {
match atten {
Attenuation::Attenuation0dB => 400,
Attenuation::Attenuation2p5dB => 550,
Attenuation::Attenuation6dB => 750,
Attenuation::Attenuation11dB => 1370,
}
}
/// Get ADC reference point digital code for specified attenuation
///
/// see https://github.com/espressif/esp-idf/blob/903af13e8/components/efuse/esp32c6/esp_efuse_rtc_calib.c#L42
pub fn get_rtc_calib_cal_code(_unit: u8, atten: Attenuation) -> Option<u16> {
let version = Self::get_rtc_calib_version();
if version != 1 {
return None;
}
// See https://github.com/espressif/esp-idf/blob/903af13e8/components/efuse/esp32c6/esp_efuse_table.csv#L153-L156
let cal_code: u16 = Self::read_field_le(match atten {
Attenuation::Attenuation0dB => ADC1_CAL_VOL_ATTEN0,
Attenuation::Attenuation2p5dB => ADC1_CAL_VOL_ATTEN1,
Attenuation::Attenuation6dB => ADC1_CAL_VOL_ATTEN2,
Attenuation::Attenuation11dB => ADC1_CAL_VOL_ATTEN3,
});
let cal_code = if cal_code & (1 << 9) != 0 {
1500 - (cal_code & !(1 << 9))
} else {
1500 + cal_code
};
Some(cal_code)
}
}
#[derive(Copy, Clone)]

View File

@ -0,0 +1,73 @@
//! Connect a potentiometer to PIN2 and see the read values change when
//! rotating the shaft. Alternatively you could also connect the PIN to GND or
//! 3V3 to see the maximum and minimum raw values read.
#![no_std]
#![no_main]
use esp32c2_hal::{
adc,
adc::{AdcConfig, Attenuation, ADC, ADC1},
clock::ClockControl,
gpio::IO,
peripherals::Peripherals,
prelude::*,
timer::TimerGroup,
Delay,
Rtc,
};
use esp_backtrace as _;
use esp_println::println;
#[entry]
fn main() -> ! {
let peripherals = Peripherals::take();
let mut system = peripherals.SYSTEM.split();
let clocks = ClockControl::boot_defaults(system.clock_control).freeze();
// Disable the watchdog timers. For the ESP32-C2, this includes the Super WDT,
// the RTC WDT, and the TIMG WDTs.
let mut rtc = Rtc::new(peripherals.RTC_CNTL);
let timer_group0 = TimerGroup::new(
peripherals.TIMG0,
&clocks,
&mut system.peripheral_clock_control,
);
let mut wdt0 = timer_group0.wdt;
rtc.swd.disable();
rtc.rwdt.disable();
wdt0.disable();
let io = IO::new(peripherals.GPIO, peripherals.IO_MUX);
// Create ADC instances
let analog = peripherals.APB_SARADC.split();
let mut adc1_config = AdcConfig::new();
let atten = Attenuation::Attenuation11dB;
// You can try any of the following calibration methods by uncommenting them
// type AdcCal = ();
// type AdcCal = adc::AdcCalBasic<ADC1>;
type AdcCal = adc::AdcCalLine<ADC1>;
let mut pin = adc1_config.enable_pin_with_cal::<_, AdcCal>(io.pins.gpio2.into_analog(), atten);
let mut adc1 = ADC::<ADC1>::adc(
&mut system.peripheral_clock_control,
analog.adc1,
adc1_config,
)
.unwrap();
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)");
delay.delay_ms(1500u32);
}
}

View File

@ -0,0 +1,81 @@
//! Connect a potentiometer to PIN2 and see the read values change when
//! rotating the shaft. Alternatively you could also connect the PIN to GND or
//! 3V3 to see the maximum and minimum raw values read.
#![no_std]
#![no_main]
use esp32c3_hal::{
adc,
adc::{AdcConfig, Attenuation, ADC, ADC1},
clock::ClockControl,
gpio::IO,
peripherals::Peripherals,
prelude::*,
timer::TimerGroup,
Delay,
Rtc,
};
use esp_backtrace as _;
use esp_println::println;
#[entry]
fn main() -> ! {
let peripherals = Peripherals::take();
let mut system = peripherals.SYSTEM.split();
let clocks = ClockControl::boot_defaults(system.clock_control).freeze();
// Disable the watchdog timers. For the ESP32-C3, this includes the Super WDT,
// the RTC WDT, and the TIMG WDTs.
let mut rtc = Rtc::new(peripherals.RTC_CNTL);
let timer_group0 = TimerGroup::new(
peripherals.TIMG0,
&clocks,
&mut system.peripheral_clock_control,
);
let mut wdt0 = timer_group0.wdt;
let timer_group1 = TimerGroup::new(
peripherals.TIMG1,
&clocks,
&mut system.peripheral_clock_control,
);
let mut wdt1 = timer_group1.wdt;
rtc.swd.disable();
rtc.rwdt.disable();
wdt0.disable();
wdt1.disable();
let io = IO::new(peripherals.GPIO, peripherals.IO_MUX);
// Create ADC instances
let analog = peripherals.APB_SARADC.split();
let mut adc1_config = AdcConfig::new();
let atten = Attenuation::Attenuation11dB;
// You can try any of the following calibration methods by uncommenting them
// type AdcCal = ();
// type AdcCal = adc::AdcCalBasic<ADC1>;
// type AdcCal = adc::AdcCalLine<ADC1>;
type AdcCal = adc::AdcCalCurve<ADC1>;
let mut pin = adc1_config.enable_pin_with_cal::<_, AdcCal>(io.pins.gpio2.into_analog(), atten);
let mut adc1 = ADC::<ADC1>::adc(
&mut system.peripheral_clock_control,
analog.adc1,
adc1_config,
)
.unwrap();
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)");
delay.delay_ms(1500u32);
}
}

View File

@ -0,0 +1,81 @@
//! Connect a potentiometer to PIN2 and see the read values change when
//! rotating the shaft. Alternatively you could also connect the PIN to GND or
//! 3V3 to see the maximum and minimum raw values read.
#![no_std]
#![no_main]
use esp32c6_hal::{
adc,
adc::{AdcConfig, Attenuation, ADC, ADC1},
clock::ClockControl,
gpio::IO,
peripherals::Peripherals,
prelude::*,
timer::TimerGroup,
Delay,
Rtc,
};
use esp_backtrace as _;
use esp_println::println;
#[entry]
fn main() -> ! {
let peripherals = Peripherals::take();
let mut system = peripherals.PCR.split();
let clocks = ClockControl::boot_defaults(system.clock_control).freeze();
// Disable the watchdog timers. For the ESP32-C6, this includes the Super WDT,
// and the TIMG WDTs.
let mut rtc = Rtc::new(peripherals.LP_CLKRST);
let timer_group0 = TimerGroup::new(
peripherals.TIMG0,
&clocks,
&mut system.peripheral_clock_control,
);
let mut wdt0 = timer_group0.wdt;
let timer_group1 = TimerGroup::new(
peripherals.TIMG1,
&clocks,
&mut system.peripheral_clock_control,
);
let mut wdt1 = timer_group1.wdt;
rtc.swd.disable();
rtc.rwdt.disable();
wdt0.disable();
wdt1.disable();
let io = IO::new(peripherals.GPIO, peripherals.IO_MUX);
// Create ADC instances
let analog = peripherals.APB_SARADC.split();
let mut adc1_config = AdcConfig::new();
let atten = Attenuation::Attenuation11dB;
// You can try any of the following calibration methods by uncommenting them
// type AdcCal = ();
// type AdcCal = adc::AdcCalBasic<ADC1>;
// type AdcCal = adc::AdcCalLine<ADC1>;
type AdcCal = adc::AdcCalCurve<ADC1>;
let mut pin = adc1_config.enable_pin_with_cal::<_, AdcCal>(io.pins.gpio2.into_analog(), atten);
let mut adc1 = ADC::<ADC1>::adc(
&mut system.peripheral_clock_control,
analog.adc1,
adc1_config,
)
.unwrap();
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)");
delay.delay_ms(1500u32);
}
}