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;