adc_interpolator/
adc_interpolator.rs

1use crate::interpolate::interpolate;
2use core::fmt;
3use embedded_hal::adc::{Channel, OneShot};
4
5/// Configuration for an `AdcInterpolator`.
6///
7/// - `max_voltage`: The voltage corresponding to the largest value possible for the ADC (mV)
8/// - `precision`: The precision of the ADC in bits (eg. for 10-bit precision, use `10`)
9/// - `voltage_to_values`: An array of tuples of `(voltage in mV, value)` which will be used for the interpolation
10///
11/// # Examples
12///
13/// ```
14/// use adc_interpolator::Config;
15///
16/// let config = Config {
17///     max_voltage: 3300, // 3.3 V
18///     precision: 10,     // 10 bits of precision
19///     voltage_to_values: [
20///         (100, 5),   // 100 mV  -> 5
21///         (500, 10),  // 500 mV  -> 10
22///         (2000, 15), // 2000 mV -> 15
23///     ],
24/// };
25/// ```
26pub struct Config<const LENGTH: usize> {
27    pub max_voltage: u32,
28    pub precision: u32,
29    pub voltage_to_values: [(u32, u32); LENGTH],
30}
31
32impl<const LENGTH: usize> Config<LENGTH> {
33    fn table<Word>(&self) -> [(Word, u32); LENGTH]
34    where
35        Word: Copy + PartialOrd + TryFrom<u32>,
36        <Word as TryFrom<u32>>::Error: fmt::Debug,
37    {
38        let mut table: [(Word, u32); LENGTH] = [(0.try_into().unwrap(), 0); LENGTH];
39
40        for (index, (voltage, value)) in self.voltage_to_values.into_iter().enumerate() {
41            let max_adc_value = 2u32.pow(self.precision);
42            let adc_value = voltage * max_adc_value / self.max_voltage;
43
44            table[index] = (adc_value.try_into().unwrap(), value);
45        }
46
47        table
48    }
49}
50
51#[derive(Debug)]
52pub struct AdcInterpolator<Pin, Word, const LENGTH: usize> {
53    pin: Pin,
54    table: [(Word, u32); LENGTH],
55}
56
57type Error<Adc, ADC, Word, Pin> = nb::Error<<Adc as OneShot<ADC, Word, Pin>>::Error>;
58
59impl<Pin, Word, const LENGTH: usize> AdcInterpolator<Pin, Word, LENGTH> {
60    /// Returns an interpolator using the provided `config`.
61    ///
62    /// The values in `config`'s `voltage_to_values` field must be in
63    /// ascending order by voltage or this function will panic when
64    /// running in debug mode.
65    ///
66    /// # Examples
67    ///
68    /// ```
69    /// use adc_interpolator::{AdcInterpolator, Config};
70    /// # use embedded_hal_mock::{
71    /// #     adc::{Mock, MockChan0, Transaction},
72    /// #     common::Generic,
73    /// #     MockError,
74    /// # };
75    /// #
76    /// # let pin = MockChan0 {};
77    ///
78    /// let config = Config {
79    ///     max_voltage: 1000,
80    ///     precision: 12,
81    ///     voltage_to_values: [
82    ///         (100, 40),
83    ///         (200, 30),
84    ///         (300, 10),
85    ///     ],
86    /// };
87    ///
88    /// let interpolator = AdcInterpolator::new(pin, config);
89    /// # let interpolator_u16: AdcInterpolator<MockChan0, u16, 3> = interpolator;
90    pub fn new<ADC>(pin: Pin, config: Config<LENGTH>) -> Self
91    where
92        Word: Copy + PartialOrd + TryFrom<u32>,
93        <Word as TryFrom<u32>>::Error: fmt::Debug,
94        Pin: Channel<ADC>,
95    {
96        debug_assert!(
97            config
98                .voltage_to_values
99                .windows(2)
100                .all(|w| w[0].0 <= w[1].0),
101            "The values in table must be in ascending order by voltage"
102        );
103
104        Self {
105            pin,
106            table: config.table(),
107        }
108    }
109
110    /// Destroys the interpolator and returns the `Pin`.
111    pub fn free(self) -> Pin {
112        self.pin
113    }
114
115    /// Returns a value based on the table, using linear interpolation
116    /// between values in the table if necessary. If `adc_value` falls
117    /// outside the range of the table, returns `Ok(None)`.
118    ///
119    /// # Examples
120    ///
121    /// ```
122    /// use adc_interpolator::{AdcInterpolator, Config};
123    /// # use embedded_hal_mock::{
124    /// #     adc::{Mock, MockChan0, Transaction},
125    /// #     common::Generic,
126    /// #     MockError,
127    /// # };
128    /// #
129    /// # let expectations: [Transaction<u16>; 1] = [Transaction::read(0, 614)];
130    /// # let mut adc = Mock::new(&expectations);
131    /// # let pin = MockChan0 {};
132    ///
133    /// let config = Config {
134    ///     max_voltage: 1000,
135    ///     precision: 12,
136    ///     voltage_to_values: [
137    ///         (100, 40),
138    ///         (200, 30),
139    ///         (300, 10),
140    ///     ],
141    /// };
142    ///
143    /// let mut interpolator = AdcInterpolator::new(pin, config);
144    ///
145    /// // With voltage at 150 mV, the value is 35
146    /// assert_eq!(interpolator.read(&mut adc), Ok(Some(35)));
147    /// ```
148    pub fn read<Adc, ADC>(
149        &mut self,
150        adc: &mut Adc,
151    ) -> Result<Option<u32>, Error<Adc, ADC, Word, Pin>>
152    where
153        Word: Copy + Into<u32> + PartialEq + PartialOrd,
154        Pin: Channel<ADC>,
155        Adc: OneShot<ADC, Word, Pin>,
156    {
157        let adc_value = adc.read(&mut self.pin)?;
158
159        let result = self.table.iter().enumerate().find_map(|(index, (x0, y0))| {
160            let (x1, y1) = self.table.get(index + 1)?;
161
162            if adc_value >= *x0 && adc_value <= *x1 {
163                Some((x0, y0, x1, y1))
164            } else {
165                None
166            }
167        });
168
169        Ok(result.map(|(x0, y0, x1, y1)| {
170            interpolate((*x0).into(), (*x1).into(), *y0, *y1, adc_value.into())
171        }))
172    }
173
174    /// Returns the smallest value that can be returned by
175    /// [`read`](AdcInterpolator::read).
176    pub fn min_value(&self) -> u32 {
177        self.first_value().min(self.last_value())
178    }
179
180    /// Returns the largest value that can be returned by
181    /// [`read`](AdcInterpolator::read).
182    pub fn max_value(&self) -> u32 {
183        self.first_value().max(self.last_value())
184    }
185
186    fn first_value(&self) -> u32 {
187        self.table.first().unwrap().1
188    }
189
190    fn last_value(&self) -> u32 {
191        self.table.last().unwrap().1
192    }
193}
194
195#[cfg(test)]
196mod tests {
197    use super::*;
198    use embedded_hal_mock::{
199        adc::{Mock, MockChan0, Transaction},
200        common::Generic,
201        MockError,
202    };
203    use std::io::ErrorKind;
204
205    fn table_positive() -> Config<3> {
206        Config {
207            max_voltage: 1000,
208            precision: 12,
209            voltage_to_values: [(100, 10), (200, 30), (300, 40)],
210        }
211    }
212
213    fn table_negative() -> Config<3> {
214        Config {
215            max_voltage: 1000,
216            precision: 12,
217            voltage_to_values: [(100, 40), (200, 30), (300, 10)],
218        }
219    }
220
221    fn table_invalid() -> Config<3> {
222        Config {
223            max_voltage: 1000,
224            precision: 12,
225            voltage_to_values: [(300, 40), (200, 30), (100, 10)],
226        }
227    }
228
229    fn interpolator<const LENGTH: usize>(
230        config: Config<LENGTH>,
231    ) -> AdcInterpolator<MockChan0, u16, LENGTH> {
232        let pin = MockChan0 {};
233        AdcInterpolator::new(pin, config)
234    }
235
236    fn adc(expectations: &[Transaction<u16>]) -> Generic<Transaction<u16>> {
237        Mock::new(expectations)
238    }
239
240    fn assert_read_ok<const LENGTH: usize>(
241        config: Config<LENGTH>,
242        value: u16,
243        expected: Option<u32>,
244    ) {
245        let mut interpolator = interpolator(config);
246        let expectations = [Transaction::read(0, value)];
247        let mut adc = adc(&expectations);
248
249        assert_eq!(interpolator.read(&mut adc), Ok(expected))
250    }
251
252    #[test]
253    #[should_panic]
254    fn panics_if_unsorted_tabled() {
255        interpolator(table_invalid());
256    }
257
258    #[test]
259    fn matching_exact_values() {
260        assert_read_ok(table_negative(), 409, Some(40));
261        assert_read_ok(table_negative(), 819, Some(30));
262        assert_read_ok(table_negative(), 1228, Some(10));
263    }
264
265    #[test]
266    fn interpolates() {
267        assert_read_ok(table_negative(), 502, Some(38));
268        assert_read_ok(table_negative(), 614, Some(35));
269        assert_read_ok(table_negative(), 1023, Some(21));
270    }
271
272    #[test]
273    fn outside_range() {
274        assert_read_ok(table_negative(), 0, None);
275        assert_read_ok(table_negative(), 408, None);
276        assert_read_ok(table_negative(), 1229, None);
277        assert_read_ok(table_negative(), 10000, None);
278    }
279
280    #[test]
281    fn error() {
282        let mut adc =
283            adc(&[Transaction::read(0, 0).with_error(MockError::Io(ErrorKind::InvalidData))]);
284        assert!(interpolator(table_positive()).read(&mut adc).is_err());
285    }
286
287    #[test]
288    fn min_value() {
289        assert_eq!(interpolator(table_positive()).min_value(), 10);
290        assert_eq!(interpolator(table_negative()).min_value(), 10);
291    }
292
293    #[test]
294    fn max_value() {
295        assert_eq!(interpolator(table_positive()).max_value(), 40);
296        assert_eq!(interpolator(table_negative()).max_value(), 40);
297    }
298}