Skip to main content

imxrt_hal/chip/drivers/
adc.rs

1//! Analog to digital converters.
2//!
3//! # Example
4//!
5//! ```no_run
6//! use imxrt_hal as hal;
7//! use imxrt_ral as ral;
8//! use hal::adc;
9//!
10//! let mut pads = // Handle to all processor pads
11//!     # unsafe { imxrt_iomuxc::imxrt1060::Pads::new() };
12//!
13//! # || -> Option<()> {
14//! // Read by allocating an analog input object:
15//! let adc1 = unsafe { ral::adc::ADC1::instance() };
16//! let mut adc1 = adc::Adc::new(adc1, adc::ClockSelect::ADACK, adc::ClockDivision::Div2);
17//!
18//! // Specify the ADC instance (1) in the turbofish when the pin supports multiple ADCs.
19//! let mut a1 = adc1.input::<_, 1>(pads.gpio_ad_b1.p02).ok()?;
20//!
21//! let reading: u16 = adc1.read_blocking(&mut a1);
22//!
23//! // Read without constructing an analog pin:
24//! let adc2 = unsafe { ral::adc::ADC2::instance() };
25//! let mut adc2 = adc::Adc::new(adc2, adc::ClockSelect::ADACK, adc::ClockDivision::Div2);
26//!
27//! let reading = adc2.read_blocking_channel(7);
28//! # Some(()) }();
29//! ```
30
31use crate::iomuxc::adc::{Pin, prepare};
32use crate::ral;
33
34/// Any ADC instance.
35type AnyInstance = crate::AnyInstance<ral::adc::RegisterBlock>;
36
37/// The clock input for an ADC
38#[allow(non_camel_case_types)]
39#[cfg_attr(feature = "defmt", derive(defmt::Format))]
40#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
41pub enum ClockSelect {
42    /// IPG clock
43    IPG,
44    /// IPG clock / 2
45    IPG_2,
46    /// ADC Asynchronous clock
47    #[default]
48    ADACK,
49}
50
51/// How much to divide the clock input
52#[cfg_attr(feature = "defmt", derive(defmt::Format))]
53#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
54pub enum ClockDivision {
55    /// Input clock / 1
56    Div1,
57    /// Input clock
58    #[default]
59    Div2,
60    /// Input clock / 4
61    Div4,
62    /// Input clock / 8
63    Div8,
64}
65
66/// Conversion speeds done by clock cycles
67#[cfg_attr(feature = "defmt", derive(defmt::Format))]
68#[derive(Debug, Clone, Copy, PartialEq, Eq)]
69pub enum ConversionSpeed {
70    /// 25 ADC clock cycles (24 on imxrt102x)
71    Slow,
72    /// 17 ADC clock cycles (16 on imxrt102x)
73    Medium,
74    /// 9 ADC clock cycles (8 on imxrt102x)
75    Fast,
76    /// 3 ADC clock cycles (2 on imxrt102x)
77    VeryFast,
78}
79
80/// Denotes how much hardware averaging to do
81#[cfg_attr(feature = "defmt", derive(defmt::Format))]
82#[derive(Debug, Clone, Copy, PartialEq, Eq)]
83pub enum AveragingCount {
84    /// 1 sample average.
85    Avg1,
86    /// 4 sample average.
87    Avg4,
88    /// 8 sample average.
89    Avg8,
90    /// 16 sample average.
91    Avg16,
92    /// 32 sample average.
93    Avg32,
94}
95
96/// Specifies the resolution the ADC
97#[cfg_attr(feature = "defmt", derive(defmt::Format))]
98#[derive(Debug, Clone, Copy, PartialEq, Eq)]
99pub enum ResolutionBits {
100    /// 8 bit resolution.
101    Res8,
102    /// 10 bit resolution.
103    Res10,
104    /// 12 bit resolution.
105    Res12,
106}
107
108pub use crate::PinPortIncompatibleError;
109
110/// A pin representing an analog input for an ADC.
111///
112/// The analog input stores the ADC channel number at runtime.
113/// The pin is consumed during construction to ensure it's properly
114/// configured, but it is not stored in the driver.
115pub struct AnalogInput {
116    channel: u32,
117}
118
119impl AnalogInput {
120    /// Returns the ADC channel for this analog input.
121    pub fn channel(&self) -> u32 {
122        self.channel
123    }
124}
125
126/// The ADC driver.
127///
128/// The ADC starts out with a default configuration of 4 hardware samples, a conversion speed of
129/// medium, a resolution of 10 bits, and low power mode disabled. It's also pre-calibrated using
130/// 32 averages and a slow conversion speed.
131pub struct Adc {
132    reg: AnyInstance,
133}
134
135impl Adc {
136    /// Construct an ADC from a RAL ADC instance.
137    pub fn new<const N: u8>(
138        reg: ral::adc::Instance<N>,
139        clock: ClockSelect,
140        division: ClockDivision,
141    ) -> Self {
142        let reg: AnyInstance = crate::into_any(reg);
143
144        // Enable asynchronous clock if applicable
145        ral::modify_reg!(ral::adc, reg, GC, ADACKEN: match clock {
146            ClockSelect::ADACK => ADACKEN_1,
147            _ => ADACKEN_0
148        });
149
150        // Select the clock selection, division, and enable ADHSC if applicable
151        ral::modify_reg!(ral::adc, reg, CFG,
152            ADICLK: match clock {
153                ClockSelect::IPG => ADICLK_0,
154                ClockSelect::IPG_2 => ADICLK_1,
155                ClockSelect::ADACK => ADICLK_3
156            },
157            ADIV: match division {
158                ClockDivision::Div1 => ADIV_0,
159                ClockDivision::Div2 => ADIV_1,
160                ClockDivision::Div4 => ADIV_2,
161                ClockDivision::Div8 => ADIV_3
162            },
163            ADHSC: ADHSC_1
164        );
165
166        let mut inst = Self { reg };
167
168        inst.set_resolution(ResolutionBits::Res10);
169        inst.set_low_power_mode(false);
170
171        // Calibrate w/ slow settings initially
172        inst.set_averaging(AveragingCount::Avg32);
173        inst.set_conversion_speed(ConversionSpeed::Slow);
174        inst.calibrate();
175
176        // Set to default of 4 hardware averages & medium conversion speed
177        inst.set_averaging(AveragingCount::Avg4);
178        inst.set_conversion_speed(ConversionSpeed::Medium);
179
180        inst
181    }
182
183    /// Returns the instance number for this ADC peripheral.
184    fn instance(&self) -> u8 {
185        ral::adc::number(&*self.reg).unwrap()
186    }
187
188    /// Creates a new analog input from a pin.
189    ///
190    /// The pin is consumed to ensure it's properly configured as an
191    /// ADC input. If the pin isn't compatible with this ADC bank,
192    /// the pin is returned inside the error so you can recover it.
193    pub fn input<P, const N: u8>(
194        &self,
195        mut pin: P,
196    ) -> Result<AnalogInput, PinPortIncompatibleError<P>>
197    where
198        P: Pin<N>,
199    {
200        if self.instance() != N {
201            return Err(PinPortIncompatibleError(pin));
202        }
203        prepare(&mut pin);
204        Ok(AnalogInput { channel: P::INPUT })
205    }
206
207    /// Sets the resolution that analog reads return, in bits.
208    pub fn set_resolution(&mut self, bits: ResolutionBits) {
209        ral::modify_reg!(ral::adc, self.reg, CFG, MODE: match bits {
210            ResolutionBits::Res8 => MODE_0,
211            ResolutionBits::Res10 => MODE_1,
212            ResolutionBits::Res12 => MODE_2
213        });
214    }
215
216    /// Sets the number of hardware averages taken by the ADC.
217    pub fn set_averaging(&mut self, avg: AveragingCount) {
218        ral::modify_reg!(ral::adc, self.reg, GC, AVGE: match avg {
219            AveragingCount::Avg1 => AVGE_0,
220            _ => AVGE_1
221        });
222        ral::modify_reg!(ral::adc, self.reg, CFG, AVGS: match avg {
223            AveragingCount::Avg32 => AVGS_3,
224            AveragingCount::Avg16 => AVGS_2,
225            AveragingCount::Avg8 => AVGS_1,
226            _ => AVGS_0,
227        });
228    }
229
230    /// Sets the conversion speed for this ADC, see ConversionSpeed for clock cycle counts.
231    pub fn set_conversion_speed(&mut self, conversion_speed: ConversionSpeed) {
232        ral::modify_reg!(ral::adc, self.reg, CFG,
233            ADSTS: match conversion_speed {
234                ConversionSpeed::Slow => ADSTS_3,
235                ConversionSpeed::Medium => ADSTS_1,
236                ConversionSpeed::Fast => ADSTS_3,
237                ConversionSpeed::VeryFast => ADSTS_0
238            },
239            ADLSMP: match conversion_speed {
240                ConversionSpeed::Slow => ADLSMP_1,
241                ConversionSpeed::Medium => ADLSMP_1,
242                ConversionSpeed::Fast => ADLSMP_0,
243                ConversionSpeed::VeryFast => ADLSMP_0
244            }
245        );
246    }
247
248    /// Enables or disables the low power configuration in the ADC. This does limit the
249    /// ADACK clock frequency (<= 20MHz)
250    pub fn set_low_power_mode(&mut self, state: bool) {
251        ral::modify_reg!(ral::adc, self.reg, CFG, ADLPC: if state { ADLPC_1 } else { ADLPC_0 });
252    }
253
254    /// Calibrates the ADC, will wait for finish.
255    pub fn calibrate(&mut self) {
256        ral::modify_reg!(ral::adc, self.reg, GC, CAL: 0b1);
257        while (ral::read_reg!(ral::adc, self.reg, CAL, CAL_CODE) != 0) {}
258    }
259
260    /// Perform a blocking read for an ADC sample.
261    ///
262    /// You're responsible for ensuring the analog input was created from
263    /// a pin compatible with this ADC instance.
264    pub fn read_blocking(&mut self, input: &mut AnalogInput) -> u16 {
265        self.read_blocking_channel(input.channel())
266    }
267
268    /// Perform a blocking read using the specified ADC channel.
269    ///
270    /// Unlike [`read_blocking()`](Self::read_blocking), which uses a
271    /// pre-configured analog input, you're responsible for configuring
272    /// the pin as an ADC input before using this method. Otherwise,
273    /// this method may not produce a (correct) value.
274    ///
275    /// # Panics
276    ///
277    /// Panics if the ADC channel is greater than 15.
278    pub fn read_blocking_channel(&mut self, channel: u32) -> u16 {
279        // There's only 15 channels on the 1010 (0 through 14).
280        // Nevertheless, the HC0 register documents that you can
281        // pass in channel 15.
282        assert!(channel < 16);
283        ral::modify_reg!(ral::adc, self.reg, HC0, |_| channel);
284        while (ral::read_reg!(ral::adc, self.reg, HS, COCO0) == 0) {}
285
286        ral::read_reg!(ral::adc, self.reg, R0) as u16
287    }
288}
289
290/// Adapter for using an ADC input as a DMA source.
291///
292/// This adapter exposes the lower-level DMA interface. However, you may
293/// find it easier to use the interface available in [`dma`](crate::dma).
294pub struct DmaSource {
295    adc: Adc,
296    channel: u32,
297}
298
299impl DmaSource {
300    /// Create a new DMA source object for a DMA transfer.
301    ///
302    /// The analog input is consumed to extract its channel, but is not
303    /// stored in the driver.
304    pub fn new(adc: Adc, input: AnalogInput) -> Self {
305        Self {
306            adc,
307            channel: input.channel(),
308        }
309    }
310
311    /// Create an ADC DMA source without a configured ADC input.
312    ///
313    /// You're responsible for configuring the pin as an ADC input.
314    pub fn without_pin(adc: Adc, channel: u32) -> Self {
315        Self { adc, channel }
316    }
317
318    /// Returns a pointer to the ADC's `R0` register.
319    ///
320    /// You should use this pointer when coordinating a DMA transfer.
321    /// You're not expected to explicitly read from this pointer in software.
322    pub fn r0(&self) -> *const ral::RORegister<u32> {
323        core::ptr::addr_of!(self.adc.reg.R0)
324    }
325
326    /// Enable the ADC's DMA support.
327    ///
328    /// This is necessary to start a transfer. However, this in itself
329    /// does not start a DMA transfer.
330    pub fn enable_dma(&mut self) {
331        ral::modify_reg!(ral::adc, self.adc.reg, GC, ADCO: 1, DMAEN: 1);
332        ral::modify_reg!(ral::adc, self.adc.reg, HC0, |_| self.channel);
333    }
334
335    /// Disable the ADC's DMA support.
336    ///
337    /// See the DMA chapter in the reference manual to understand when this
338    /// should be called in the DMA transfer lifecycle.
339    pub fn disable_dma(&mut self) {
340        ral::modify_reg!(ral::adc, self.adc.reg, GC, ADCO: 0, DMAEN: 0);
341    }
342
343    /// Returns the instance number for this ADC peripheral.
344    pub(crate) fn instance(&self) -> u8 {
345        self.adc.instance()
346    }
347}