esp_hal/analog/adc/
mod.rs

1//! # Analog to Digital Converter (ADC)
2//!
3//! ## Overview
4//!
5//! The ADC is integrated on the chip, and is capable of measuring analog
6//! signals from specific analog I/O pins. One or more ADC units are available,
7//! depending on the device being used.
8//!
9//! ## Configuration
10//!
11//! The ADC can be configured to measure analog signals from specific pins. The
12//! configuration includes the resolution of the ADC, the attenuation of the
13//! input signal, and the pins to be measured.
14//!
15//! Some targets also support ADC calibration via different schemes like
16//! basic calibration, curve fitting or linear interpolation. The calibration
17//! schemes can be used to improve the accuracy of the ADC readings.
18//!
19//! ## Examples
20//!
21//! ### Read an analog signal from a pin
22//!
23//! ```rust, no_run
24#![doc = crate::before_snippet!()]
25//! # use esp_hal::analog::adc::AdcConfig;
26//! # use esp_hal::peripherals::ADC1;
27//! # use esp_hal::analog::adc::Attenuation;
28//! # use esp_hal::analog::adc::Adc;
29//! # use esp_hal::delay::Delay;
30#![cfg_attr(esp32, doc = "let analog_pin = peripherals.GPIO32;")]
31#![cfg_attr(any(esp32s2, esp32s3), doc = "let analog_pin = peripherals.GPIO3;")]
32#![cfg_attr(
33    not(any(esp32, esp32s2, esp32s3)),
34    doc = "let analog_pin = peripherals.GPIO2;"
35)]
36//! let mut adc1_config = AdcConfig::new();
37//! let mut pin = adc1_config.enable_pin(
38//!     analog_pin,
39//!     Attenuation::_11dB,
40//! );
41//! let mut adc1 = Adc::new(peripherals.ADC1, adc1_config);
42//!
43//! let mut delay = Delay::new();
44//!
45//! loop {
46//!     let pin_value: u16 = nb::block!(adc1.read_oneshot(&mut pin))?;
47//!
48//!     delay.delay_millis(1500);
49//! }
50//! # }
51//! ```
52//! 
53//! ## Implementation State
54//!
55//!  - [ADC calibration is not implemented for all targets].
56//!
57//! [ADC calibration is not implemented for all targets]: https://github.com/esp-rs/esp-hal/issues/326
58use core::marker::PhantomData;
59
60use crate::gpio::AnalogPin;
61
62#[cfg_attr(esp32, path = "esp32.rs")]
63#[cfg_attr(riscv, path = "riscv.rs")]
64#[cfg_attr(any(esp32s2, esp32s3), path = "xtensa.rs")]
65#[cfg(feature = "unstable")]
66mod implementation;
67
68#[cfg(feature = "unstable")]
69pub use self::implementation::*;
70
71/// The attenuation of the ADC pin.
72///
73/// The effective measurement range for a given attenuation is dependent on the
74/// device being targeted. Please refer to "ADC Characteristics" section of your
75/// device's datasheet for more information.
76#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
77#[cfg_attr(feature = "defmt", derive(defmt::Format))]
78#[allow(clippy::enum_variant_names, reason = "peripheral is unstable")]
79pub enum Attenuation {
80    /// 0dB attenuation
81    _0dB   = 0b00,
82    /// 2.5dB attenuation
83    #[cfg(not(esp32c2))]
84    _2p5dB = 0b01,
85    /// 6dB attenuation
86    #[cfg(not(esp32c2))]
87    _6dB   = 0b10,
88    /// 11dB attenuation
89    _11dB  = 0b11,
90}
91
92/// Calibration source of the ADC.
93#[cfg(not(esp32))]
94#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
95#[cfg_attr(feature = "defmt", derive(defmt::Format))]
96pub enum AdcCalSource {
97    /// Use Ground as the calibration source
98    Gnd,
99    /// Use Vref as the calibration source
100    Ref,
101}
102
103/// An I/O pin which can be read using the ADC.
104pub struct AdcPin<PIN, ADCI, CS = ()> {
105    /// The underlying GPIO pin
106    pub pin: PIN,
107    /// Calibration scheme used for the configured ADC pin
108    #[cfg_attr(esp32, allow(unused))]
109    pub cal_scheme: CS,
110    _phantom: PhantomData<ADCI>,
111}
112
113/// Configuration for the ADC.
114#[cfg(feature = "unstable")]
115pub struct AdcConfig<ADCI> {
116    #[cfg_attr(not(esp32), allow(unused))]
117    resolution: Resolution,
118    attenuations: [Option<Attenuation>; NUM_ATTENS],
119    _phantom: PhantomData<ADCI>,
120}
121
122#[cfg(feature = "unstable")]
123impl<ADCI> AdcConfig<ADCI> {
124    /// Create a new configuration struct with its default values
125    pub fn new() -> Self {
126        Self::default()
127    }
128
129    /// Enable the specified pin with the given attenuation
130    pub fn enable_pin<PIN>(&mut self, pin: PIN, attenuation: Attenuation) -> AdcPin<PIN, ADCI>
131    where
132        PIN: AdcChannel + AnalogPin,
133    {
134        // TODO revert this on drop
135        pin.set_analog(crate::private::Internal);
136        self.attenuations[PIN::CHANNEL as usize] = Some(attenuation);
137
138        AdcPin {
139            pin,
140            cal_scheme: AdcCalScheme::<()>::new_cal(attenuation),
141            _phantom: PhantomData,
142        }
143    }
144
145    /// Enable the specified pin with the given attenuation and calibration
146    /// scheme
147    #[cfg(not(esp32))]
148    #[cfg(feature = "unstable")]
149    pub fn enable_pin_with_cal<PIN, CS>(
150        &mut self,
151        pin: PIN,
152        attenuation: Attenuation,
153    ) -> AdcPin<PIN, ADCI, CS>
154    where
155        ADCI: CalibrationAccess,
156        PIN: AdcChannel + AnalogPin,
157        CS: AdcCalScheme<ADCI>,
158    {
159        // TODO revert this on drop
160        pin.set_analog(crate::private::Internal);
161        self.attenuations[PIN::CHANNEL as usize] = Some(attenuation);
162
163        AdcPin {
164            pin,
165            cal_scheme: CS::new_cal(attenuation),
166            _phantom: PhantomData,
167        }
168    }
169}
170
171#[cfg(feature = "unstable")]
172impl<ADCI> Default for AdcConfig<ADCI> {
173    fn default() -> Self {
174        Self {
175            resolution: Resolution::default(),
176            attenuations: [None; NUM_ATTENS],
177            _phantom: PhantomData,
178        }
179    }
180}
181
182#[cfg(not(esp32))]
183#[doc(hidden)]
184#[cfg(feature = "unstable")]
185pub trait CalibrationAccess: RegisterAccess {
186    const ADC_CAL_CNT_MAX: u16;
187    const ADC_CAL_CHANNEL: u16;
188    const ADC_VAL_MASK: u16;
189
190    fn enable_vdef(enable: bool);
191
192    /// Enable internal calibration voltage source
193    fn connect_cal(source: AdcCalSource, enable: bool);
194}
195
196/// A helper trait to get the ADC channel of a compatible GPIO pin.
197pub trait AdcChannel {
198    /// Channel number used by the ADC
199    const CHANNEL: u8;
200}
201
202/// A trait abstracting over calibration methods.
203///
204/// The methods in this trait are mostly for internal use. To get
205/// calibrated ADC reads, all you need to do is call `enable_pin_with_cal`
206/// and specify some implementor of this trait.
207pub trait AdcCalScheme<ADCI>: Sized + crate::private::Sealed {
208    /// Create a new calibration scheme for the given attenuation.
209    fn new_cal(atten: Attenuation) -> Self;
210
211    /// Return the basic ADC bias value.
212    fn adc_cal(&self) -> u16 {
213        0
214    }
215
216    /// Convert ADC value.
217    fn adc_val(&self, val: u16) -> u16 {
218        val
219    }
220}
221
222impl crate::private::Sealed for () {}
223
224impl<ADCI> AdcCalScheme<ADCI> for () {
225    fn new_cal(_atten: Attenuation) -> Self {}
226}
227
228/// A helper trait to get access to ADC calibration efuses.
229#[cfg(not(any(esp32, esp32s2)))]
230trait AdcCalEfuse {
231    /// Get ADC calibration init code
232    ///
233    /// Returns digital value for zero voltage for a given attenuation
234    fn init_code(atten: Attenuation) -> Option<u16>;
235
236    /// Get ADC calibration reference point voltage
237    ///
238    /// Returns reference voltage (millivolts) for a given attenuation
239    fn cal_mv(atten: Attenuation) -> u16;
240
241    /// Get ADC calibration reference point digital value
242    ///
243    /// Returns digital value for reference voltage for a given attenuation
244    fn cal_code(atten: Attenuation) -> Option<u16>;
245}
246
247macro_rules! impl_adc_interface {
248    ($adc:ident [
249        $( ($pin:ident<'_>, $channel:expr) ,)+
250    ]) => {
251        $(
252            impl $crate::analog::adc::AdcChannel for $crate::peripherals::$pin<'_> {
253                const CHANNEL: u8 = $channel;
254            }
255        )+
256    }
257}
258
259pub(crate) use impl_adc_interface;