cu_ads7883_new/
lib.rs

1#[cfg(mock)]
2mod mock;
3
4use bincode::{Decode, Encode};
5use cu29::prelude::*;
6use serde::{Deserialize, Serialize};
7
8#[cfg(hardware)]
9use spidev::{SpiModeFlags, Spidev, SpidevOptions, SpidevTransfer};
10
11#[cfg(mock)]
12use mock::Spidev;
13
14pub struct ADS7883 {
15    spi: Spidev,
16    integrated_value: u64,
17}
18
19const INTEGRATION_FACTOR: u64 = 8;
20
21/// This opens a SPI device at the given path.
22/// The path is usually something like "/dev/spidev0.0" (the default)
23/// The max_speed_hz is the maximum speed of the SPI bus in Hz. The ADS7883 can handle up to 48MHz.
24/// i.e. 48_000_000 (the default)
25#[cfg(hardware)]
26fn open_spi(dev_device: Option<&str>, max_speed_hz: Option<u32>) -> std::io::Result<Spidev> {
27    let dev_device = dev_device.unwrap_or("/dev/spidev0.0");
28    let max_speed_hz = max_speed_hz.unwrap_or(48_000_000);
29    let mut spi = Spidev::open(dev_device)?;
30    let options = SpidevOptions::new()
31        .bits_per_word(8)
32        .max_speed_hz(max_speed_hz)
33        .mode(SpiModeFlags::SPI_MODE_1)
34        .build();
35    spi.configure(&options)?;
36    Ok(spi)
37}
38
39#[derive(Debug, Clone, Copy, Default, Encode, Decode, PartialEq, Serialize, Deserialize)]
40pub struct ADCReadingPayload<T>
41where
42    T: Into<u128> + Copy, // Trick to say all unsigned integers.
43{
44    pub analog_value: T,
45}
46
47/// This is the type of message that the ADS7883 driver will send.
48pub type ADSReadingPayload = ADCReadingPayload<u16>;
49
50// Some convenience function.
51impl From<ADSReadingPayload> for u16 {
52    fn from(msg: ADSReadingPayload) -> Self {
53        msg.analog_value
54    }
55}
56
57impl From<&ADCReadingPayload<u16>> for f32 {
58    fn from(payload: &ADCReadingPayload<u16>) -> f32 {
59        payload.analog_value as f32
60    }
61}
62
63impl Freezable for ADS7883 {} // This device is stateless.
64
65/// Reads one sample from the ADC.
66/// The value is a 12-bit number. i.e. 0-4095
67/// 0    -> 0v
68/// 4095 -> VDD
69#[inline]
70#[cfg(hardware)]
71fn read_adc(spi: &mut Spidev) -> std::io::Result<u16> {
72    let mut rx_buf = [0u8; 2];
73
74    let mut transfer = SpidevTransfer::read(&mut rx_buf);
75    spi.transfer(&mut transfer)?;
76
77    // There are 2 bits of padding in the 12-bit ADC value
78    // from the datasheet:
79    //
80    // "The device outputs data while the
81    // conversion is in progress. The data word contains two leading zeros, followed by 12-bit data in MSB first format
82    // and padded by two lagging zeros."
83    //
84    let adc_value = (((rx_buf[0] as u16) << 8) | (rx_buf[1] as u16)) >> 2;
85
86    Ok(adc_value)
87}
88
89#[cfg(mock)]
90use mock::read_adc;
91
92impl CuSrcTask for ADS7883 {
93    type Resources<'r> = ();
94    type Output<'m> = output_msg!(ADSReadingPayload);
95
96    fn new(config: Option<&ComponentConfig>, _resources: Self::Resources<'_>) -> CuResult<Self>
97    where
98        Self: Sized,
99    {
100        match config {
101            #[allow(unused_variables)]
102            Some(config) => {
103                let maybe_string: Option<String> = config.get::<String>("spi_dev");
104                let maybe_spidev: Option<&str> = maybe_string.as_deref();
105                let maybe_max_speed_hz: Option<u32> = config.get("max_speed_hz");
106
107                #[cfg(hardware)]
108                let spi = open_spi(maybe_spidev, maybe_max_speed_hz).map_err(|e| {
109                    CuError::new_with_cause("Could not open the ADS7883 SPI device", e)
110                })?;
111
112                #[cfg(mock)]
113                let spi = Spidev {};
114
115                Ok(ADS7883 {
116                    spi,
117                    integrated_value: 0,
118                })
119            }
120            None => {
121                #[cfg(hardware)]
122                let spi = open_spi(None, None).map_err(|e| {
123                    CuError::new_with_cause("Could not open the ADS7883 SPI device (Note: no config specified for the node so it took the default config)", e)
124                })?;
125
126                #[cfg(mock)]
127                let spi = Spidev {};
128                Ok(ADS7883 {
129                    spi,
130                    integrated_value: 0,
131                })
132            }
133        }
134    }
135    fn start(&mut self, clock: &RobotClock) -> CuResult<()> {
136        debug!("ADS7883 started at {}", clock.now());
137        // initialize the integrated value.
138        self.integrated_value = read_adc(&mut self.spi).map_err(|e| {
139            CuError::new_with_cause("Could not read the ADC value from the ADS7883", e)
140        })? as u64;
141        self.integrated_value *= INTEGRATION_FACTOR;
142        Ok(())
143    }
144    fn process(&mut self, clock: &RobotClock, new_msg: &mut Self::Output<'_>) -> CuResult<()> {
145        let bf = clock.now();
146        let analog_value = read_adc(&mut self.spi).map_err(|e| {
147            CuError::new_with_cause("Could not read the ADC value from the ADS7883", e)
148        })?;
149        // hard to know exactly when the value was read.
150        // Should be within a couple of microseconds with the ioctl opverhead.
151        let af = clock.now();
152        new_msg.tov = Some((af + bf) / 2u64).into();
153
154        self.integrated_value = ((self.integrated_value + analog_value as u64)
155            * INTEGRATION_FACTOR)
156            / (INTEGRATION_FACTOR + 1);
157
158        let result = (self.integrated_value / INTEGRATION_FACTOR) as u16;
159        let output = ADSReadingPayload {
160            analog_value: result,
161        };
162        new_msg.set_payload(output);
163        new_msg.tov = ((clock.now() + bf) / 2u64).into();
164        new_msg.metadata.set_status(result);
165        Ok(())
166    }
167}
168
169pub mod test_support {
170    use super::*;
171
172    pub struct ADS78883TestSink;
173
174    impl Freezable for ADS78883TestSink {}
175
176    impl CuSinkTask for ADS78883TestSink {
177        type Resources<'r> = ();
178        type Input<'m> = input_msg!(ADSReadingPayload);
179
180        fn new(
181            _config: Option<&ComponentConfig>,
182            _resources: Self::Resources<'_>,
183        ) -> CuResult<Self> {
184            Ok(Self {})
185        }
186
187        fn process(&mut self, _clock: &RobotClock, new_msg: &Self::Input<'_>) -> CuResult<()> {
188            debug!("Received: {}", &new_msg.payload());
189            Ok(())
190        }
191    }
192}