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#[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, {
44 pub analog_value: T,
45}
46
47pub type ADSReadingPayload = ADCReadingPayload<u16>;
49
50impl 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 {} #[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 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 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 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}