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
60pub use self::implementation::*;
61use crate::gpio::AnalogPin;
62
63#[cfg_attr(esp32, path = "esp32.rs")]
64#[cfg_attr(riscv, path = "riscv.rs")]
65#[cfg_attr(any(esp32s2, esp32s3), path = "xtensa.rs")]
66mod implementation;
67
68/// The attenuation of the ADC pin.
69///
70/// The effective measurement range for a given attenuation is dependent on the
71/// device being targeted. Please refer to "ADC Characteristics" section of your
72/// device's datasheet for more information.
73#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
74#[cfg_attr(feature = "defmt", derive(defmt::Format))]
75#[allow(clippy::enum_variant_names, reason = "peripheral is unstable")]
76pub enum Attenuation {
77    /// 0dB attenuation
78    _0dB   = 0b00,
79    /// 2.5dB attenuation
80    #[cfg(not(esp32c2))]
81    _2p5dB = 0b01,
82    /// 6dB attenuation
83    #[cfg(not(esp32c2))]
84    _6dB   = 0b10,
85    /// 11dB attenuation
86    _11dB  = 0b11,
87}
88
89/// Calibration source of the ADC.
90#[cfg(not(esp32))]
91#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
92#[cfg_attr(feature = "defmt", derive(defmt::Format))]
93pub enum AdcCalSource {
94    /// Use Ground as the calibration source
95    Gnd,
96    /// Use Vref as the calibration source
97    Ref,
98}
99
100/// An I/O pin which can be read using the ADC.
101pub struct AdcPin<PIN, ADCI, CS = ()> {
102    /// The underlying GPIO pin
103    pub pin: PIN,
104    /// Calibration scheme used for the configured ADC pin
105    #[cfg_attr(esp32, allow(unused))]
106    pub cal_scheme: CS,
107    _phantom: PhantomData<ADCI>,
108}
109
110/// Configuration for the ADC.
111pub struct AdcConfig<ADCI> {
112    #[cfg_attr(not(esp32), allow(unused))]
113    resolution: Resolution,
114    attenuations: [Option<Attenuation>; NUM_ATTENS],
115    _phantom: PhantomData<ADCI>,
116}
117
118impl<ADCI> AdcConfig<ADCI> {
119    /// Create a new configuration struct with its default values
120    pub fn new() -> Self {
121        Self::default()
122    }
123
124    /// Enable the specified pin with the given attenuation
125    pub fn enable_pin<PIN>(&mut self, pin: PIN, attenuation: Attenuation) -> AdcPin<PIN, ADCI>
126    where
127        PIN: AdcChannel + AnalogPin,
128    {
129        // TODO revert this on drop
130        pin.set_analog(crate::private::Internal);
131        self.attenuations[PIN::CHANNEL as usize] = Some(attenuation);
132
133        AdcPin {
134            pin,
135            cal_scheme: AdcCalScheme::<()>::new_cal(attenuation),
136            _phantom: PhantomData,
137        }
138    }
139
140    /// Enable the specified pin with the given attenuation and calibration
141    /// scheme
142    #[cfg(not(esp32))]
143    pub fn enable_pin_with_cal<PIN, CS>(
144        &mut self,
145        pin: PIN,
146        attenuation: Attenuation,
147    ) -> AdcPin<PIN, ADCI, CS>
148    where
149        ADCI: CalibrationAccess,
150        PIN: AdcChannel + AnalogPin,
151        CS: AdcCalScheme<ADCI>,
152    {
153        // TODO revert this on drop
154        pin.set_analog(crate::private::Internal);
155        self.attenuations[PIN::CHANNEL as usize] = Some(attenuation);
156
157        AdcPin {
158            pin,
159            cal_scheme: CS::new_cal(attenuation),
160            _phantom: PhantomData,
161        }
162    }
163}
164
165impl<ADCI> Default for AdcConfig<ADCI> {
166    fn default() -> Self {
167        Self {
168            resolution: Resolution::default(),
169            attenuations: [None; NUM_ATTENS],
170            _phantom: PhantomData,
171        }
172    }
173}
174
175#[cfg(not(esp32))]
176#[doc(hidden)]
177pub trait CalibrationAccess: RegisterAccess {
178    const ADC_CAL_CNT_MAX: u16;
179    const ADC_CAL_CHANNEL: u16;
180    const ADC_VAL_MASK: u16;
181
182    fn enable_vdef(enable: bool);
183
184    /// Enable internal calibration voltage source
185    fn connect_cal(source: AdcCalSource, enable: bool);
186}
187
188/// A helper trait to get the ADC channel of a compatible GPIO pin.
189pub trait AdcChannel {
190    /// Channel number used by the ADC
191    const CHANNEL: u8;
192}
193
194/// A trait abstracting over calibration methods.
195///
196/// The methods in this trait are mostly for internal use. To get
197/// calibrated ADC reads, all you need to do is call `enable_pin_with_cal`
198/// and specify some implementor of this trait.
199pub trait AdcCalScheme<ADCI>: Sized + crate::private::Sealed {
200    /// Create a new calibration scheme for the given attenuation.
201    fn new_cal(atten: Attenuation) -> Self;
202
203    /// Return the basic ADC bias value.
204    fn adc_cal(&self) -> u16 {
205        0
206    }
207
208    /// Convert ADC value.
209    fn adc_val(&self, val: u16) -> u16 {
210        val
211    }
212}
213
214impl crate::private::Sealed for () {}
215
216impl<ADCI> AdcCalScheme<ADCI> for () {
217    fn new_cal(_atten: Attenuation) -> Self {}
218}
219
220/// A helper trait to get access to ADC calibration efuses.
221#[cfg(not(any(esp32, esp32s2, esp32h2)))]
222trait AdcCalEfuse {
223    /// Get ADC calibration init code
224    ///
225    /// Returns digital value for zero voltage for a given attenuation
226    fn init_code(atten: Attenuation) -> Option<u16>;
227
228    /// Get ADC calibration reference point voltage
229    ///
230    /// Returns reference voltage (millivolts) for a given attenuation
231    fn cal_mv(atten: Attenuation) -> u16;
232
233    /// Get ADC calibration reference point digital value
234    ///
235    /// Returns digital value for reference voltage for a given attenuation
236    fn cal_code(atten: Attenuation) -> Option<u16>;
237}
238
239macro_rules! impl_adc_interface {
240    ($adc:ident [
241        $( (GpioPin<$pin:literal>, $channel:expr) ,)+
242    ]) => {
243        $(
244            impl $crate::analog::adc::AdcChannel for crate::gpio::GpioPin<$pin> {
245                const CHANNEL: u8 = $channel;
246            }
247        )+
248    }
249}
250
251pub(crate) use impl_adc_interface;