atsamd_hal/peripherals/adc/
builder.rs

1use atsamd_hal_macros::hal_cfg;
2
3#[hal_cfg("adc-d5x")]
4use crate::pac::adc0;
5
6#[hal_cfg(any("adc-d21", "adc-d11"))]
7use crate::pac::adc as adc0;
8
9#[hal_cfg(any("adc-d21", "adc-d11"))]
10pub use adc0::ctrlb::Prescalerselect as Prescaler;
11
12#[hal_cfg("adc-d5x")]
13pub use adc0::ctrla::Prescalerselect as Prescaler;
14
15pub use adc0::avgctrl::Samplenumselect as SampleCount;
16
17pub use adc0::ctrlb::Resselselect as Resolution;
18
19pub use adc0::refctrl::Refselselect as Reference;
20
21use super::{Adc, AdcInstance};
22
23#[derive(Copy, Clone, PartialEq, Eq)]
24pub enum AdcResolution {
25    _8,
26    _10,
27    _12,
28}
29
30impl From<AdcResolution> for Resolution {
31    fn from(val: AdcResolution) -> Self {
32        match val {
33            AdcResolution::_8 => Resolution::_8bit,
34            AdcResolution::_10 => Resolution::_10bit,
35            AdcResolution::_12 => Resolution::_12bit,
36        }
37    }
38}
39
40/// Result accumulation strategy for the ADC
41#[derive(Copy, Clone, PartialEq, Eq)]
42pub enum Accumulation {
43    /// The ADC will read once and then the result is ready.
44    ///
45    /// The result will be in the users chosen bitwidth
46    Single(AdcResolution),
47    /// The ADC will read [SampleCount] samples, average them out
48    /// and then the result is ready.
49    ///
50    /// The result will be in the range of 0-4095 (12bit)
51    Average(SampleCount),
52    /// The ADC will read [SampleCount] samples, sum them
53    /// into a 16 bit wide value, and then the result is ready.
54    ///
55    /// The result will be in the range of 0-65535 (16bit),
56    /// but will consist of the sum of multiple 12bit reads
57    Summed(SampleCount),
58}
59
60impl Accumulation {
61    /// Read the ADC once
62    pub const fn single(res: AdcResolution) -> Self {
63        Self::Single(res)
64    }
65
66    /// Accumulate multiple samples and average together
67    pub const fn average(count: SampleCount) -> Self {
68        Self::Average(count)
69    }
70
71    /// Accumulate multiple samples and add them together
72    pub const fn summed(count: SampleCount) -> Self {
73        Self::Summed(count)
74    }
75
76    pub(crate) fn resolution(&self) -> Resolution {
77        if let Self::Single(res) = self {
78            (*res).into()
79        } else {
80            Resolution::_16bit
81        }
82    }
83
84    pub(crate) fn output_resolution(&self) -> Resolution {
85        if let Self::Single(res) = self {
86            (*res).into()
87        } else if let Self::Average(_) = self {
88            Resolution::_12bit
89        } else {
90            Resolution::_16bit
91        }
92    }
93
94    pub(crate) fn samples(&self) -> u16 {
95        match self {
96            Accumulation::Single(_) => 1,
97            // Samplenumselect is 2^n to get number of samples
98            Accumulation::Average(samplenumselect) => 2u16.pow(*samplenumselect as u32),
99            Accumulation::Summed(samplenumselect) => 2u16.pow(*samplenumselect as u32),
100        }
101    }
102}
103
104#[derive(Debug, Copy, Clone)]
105#[cfg_attr(feature = "defmt", derive(defmt::Format))]
106pub enum BuilderError {
107    /// Clock divider missing
108    MissingClockDiv,
109    /// Samples per clock missing
110    MissingSampleClocks,
111    /// Vref missing
112    MissingVref,
113    AdcError(super::Error),
114}
115
116impl From<super::Error> for BuilderError {
117    fn from(value: super::Error) -> Self {
118        Self::AdcError(value)
119    }
120}
121
122/// # ADC Configuration Builder
123///
124/// This structure provides configuration of multiple factors which affect the
125/// ADC's sampling characteristics.
126///
127/// The ADC clock is driven by the peripheral clock divided with a divider
128/// selected via [AdcBuilder::with_clock_divider()].
129///
130/// A sample is taken over a number of ADC clock cycles configured by
131/// [AdcBuilder::with_clock_cycles_per_sample()], and then transmitted to the
132/// ADC register 1 clock cycle per bit of resolution - resolution is determined
133/// by the accumulation mode selected by [AdcBuilder::new()].
134///
135/// The ADC can be configured to combine multiple readings in either an average
136/// or summed mode (See [Accumulation]).
137///
138/// The formula for calculating Sample rate (SPS) is shown below, and
139/// implemented in a helper method [AdcBuilder::calculate_sps()]:
140///
141/// ## For single sample
142/// ```
143/// SPS = (GCLK_ADC / clk_divider) / (sample_clock_cycles + bit_width)
144/// ```
145/// ## For multiple samples 'n' (Averaging or Summed)
146/// ```
147/// SPS = (GCLK_ADC / clk_divider) / (n * (sample_clock_cycles + 12))
148/// ```
149#[derive(Copy, Clone)]
150pub struct AdcBuilder {
151    pub clk_divider: Option<Prescaler>,
152    pub sample_clock_cycles: Option<u8>,
153    pub accumulation: Accumulation,
154    pub vref: Option<Reference>,
155}
156
157/// Version of [AdcBuilder] without any optional settings.
158/// [AdcBuilder] is converted to this when passed to the ADC
159#[derive(Copy, Clone, PartialEq)]
160pub(crate) struct AdcSettings {
161    pub clk_divider: Prescaler,
162    pub sample_clock_cycles: u8,
163    pub accumulation: Accumulation,
164    pub vref: Reference,
165}
166
167impl AdcBuilder {
168    /// Create a new settings builder
169    pub fn new(accumulation_method: Accumulation) -> Self {
170        Self {
171            clk_divider: None,
172            sample_clock_cycles: None,
173            accumulation: accumulation_method,
174            vref: None,
175        }
176    }
177
178    pub(crate) fn check_params(&self) -> Result<(), BuilderError> {
179        self.clk_divider.ok_or(BuilderError::MissingClockDiv)?;
180        self.sample_clock_cycles
181            .ok_or(BuilderError::MissingSampleClocks)?;
182        self.vref.ok_or(BuilderError::MissingVref)?;
183        Ok(())
184    }
185
186    pub(crate) fn to_settings(self) -> Result<AdcSettings, BuilderError> {
187        self.check_params()?;
188        Ok(AdcSettings {
189            clk_divider: self.clk_divider.unwrap(),
190            sample_clock_cycles: self.sample_clock_cycles.unwrap(),
191            accumulation: self.accumulation,
192            vref: self.vref.unwrap(),
193        })
194    }
195
196    /// This setting adjusts the ADC clock frequency by dividing the input clock
197    /// for the ADC.
198    ///
199    /// ## Example:
200    /// * Input clock 48MHz, div 32 => ADC Clock is 1.5MHz
201    pub fn with_clock_divider(mut self, div: Prescaler) -> Self {
202        self.clk_divider = Some(div);
203        self
204    }
205
206    /// Sets the ADC reference voltage source
207    pub fn with_vref(mut self, reference: Reference) -> Self {
208        self.vref = Some(reference);
209        self
210    }
211
212    /// Sets the number of ADC clock cycles taken to sample a single sample. The
213    /// higher this number, the longer it will take the ADC to sample each
214    /// sample. Smaller values will make the ADC perform more samples per
215    /// second, but there may be more noise in each sample leading to erratic
216    /// values.
217    ///
218    /// ## Safety
219    /// * This function clamps input value between 1 and 63, to conform to the
220    ///   ADC registers min and max values.
221    pub fn with_clock_cycles_per_sample(mut self, num: u8) -> Self {
222        self.sample_clock_cycles = Some(num.clamp(1, 63)); // Clamp in range
223        self
224    }
225
226    /// Returns a calculated sample rate based on the settings used
227    pub fn calculate_sps(&self, clock_freq: u32) -> Result<u32, BuilderError> {
228        self.check_params()?;
229
230        let div = self.clk_divider.unwrap() as u32;
231        let adc_clk_freq = clock_freq / div;
232
233        let bit_width = match self.accumulation.resolution() {
234            Resolution::_16bit => 16,
235            Resolution::_12bit => 12,
236            Resolution::_10bit => 10,
237            Resolution::_8bit => 8,
238        };
239
240        let mut clocks_per_sample = self.sample_clock_cycles.unwrap() as u32 + bit_width;
241
242        let samples = self.accumulation.samples();
243        clocks_per_sample *= samples as u32;
244        Ok(adc_clk_freq / clocks_per_sample)
245    }
246
247    /// Turn the builder into an ADC
248    #[hal_cfg("adc-d5x")]
249    #[inline]
250    pub fn enable<I: AdcInstance, PS: crate::clock::v2::pclk::PclkSourceId>(
251        self,
252        adc: I::Instance,
253        clk: crate::clock::v2::apb::ApbClk<I::ClockId>,
254        pclk: &crate::clock::v2::pclk::Pclk<I::ClockId, PS>,
255    ) -> Result<Adc<I>, BuilderError> {
256        let settings = self.to_settings()?;
257        Adc::new(adc, settings, clk, pclk).map_err(|e| e.into())
258    }
259
260    #[hal_cfg(any("adc-d11", "adc-d21"))]
261    #[inline]
262    pub fn enable<I: AdcInstance>(
263        self,
264        adc: I::Instance,
265        pm: &mut crate::pac::Pm,
266        clock: &crate::clock::AdcClock,
267    ) -> Result<Adc<I>, BuilderError> {
268        let settings = self.to_settings()?;
269        Adc::new(adc, settings, pm, clock).map_err(|e| e.into())
270    }
271}