1use crate::interpolate::interpolate;
2use core::fmt;
3use embedded_hal::adc::{Channel, OneShot};
4
5pub 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 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 pub fn free(self) -> Pin {
112 self.pin
113 }
114
115 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 pub fn min_value(&self) -> u32 {
177 self.first_value().min(self.last_value())
178 }
179
180 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}