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;