1#![cfg_attr(not(feature = "std"), no_std)]
7
8extern crate alloc;
9
10use alloc::{format, string::String};
11use core::fmt::Debug;
12
13pub use cu_sensor_payloads::MagnetometerPayload;
14use cu29::prelude::*;
15use cu29::units::si::magnetic_flux_density::microtesla;
16
17const REG_WHOAMI: u8 = 0x00;
19const REG_STAT1: u8 = 0x02;
20const REG_DATA_X_L: u8 = 0x03;
21const REG_CNTRL1: u8 = 0x0A;
22const REG_AVGCNTL: u8 = 0x41;
23const REG_PDCNTL: u8 = 0x42;
24
25const IST8310_CHIP_ID: u8 = 0x10;
27const STAT1_DRDY: u8 = 0x01;
28
29const CNTRL1_SINGLE_MEASURE: u8 = 0x01;
31const AVGCNTL_16X: u8 = 0x24;
32const PDCNTL_PULSE_DURATION_NORMAL: u8 = 0xC0;
33
34const UT_PER_LSB: f32 = 0.3;
36
37const DETECT_RETRY_LIMIT: usize = 5_000;
38const I2C_TRANSFER_RETRY_LIMIT: usize = 8;
39const OUTPUT_RATE_HZ: u64 = 100;
40const OUTPUT_PERIOD_NS: u64 = 1_000_000_000 / OUTPUT_RATE_HZ;
41const MEASURE_MIN_DELAY_NS: u64 = 6_000_000;
42const MEASURE_TIMEOUT_NS: u64 = 20_000_000;
43const DRIVER_LOG_PERIOD_NS: u64 = 1_000_000_000;
44
45pub trait Ist8310Bus: Send + Sync + 'static {
50 type Error: Debug + Send + 'static;
51
52 fn write(&mut self, write: &[u8]) -> Result<(), Self::Error>;
53 fn write_read(&mut self, write: &[u8], read: &mut [u8]) -> Result<(), Self::Error>;
54}
55
56struct Ist8310Driver<BUS>
57where
58 BUS: Ist8310Bus,
59{
60 bus: BUS,
61}
62
63impl<BUS> Ist8310Driver<BUS>
64where
65 BUS: Ist8310Bus,
66{
67 fn new(bus: BUS) -> CuResult<Self> {
68 let mut driver = Self { bus };
69 driver.detect_chip_id()?;
70 driver.configure()?;
71 Ok(driver)
72 }
73
74 fn detect_chip_id(&mut self) -> CuResult<()> {
75 let mut last_id: Option<u8> = None;
76 let mut read_buf = [0u8; 1];
77
78 for _ in 0..DETECT_RETRY_LIMIT {
79 if self.read_reg_buf(REG_WHOAMI, &mut read_buf).is_ok() {
80 let id = read_buf[0];
81 last_id = Some(id);
82 if id == IST8310_CHIP_ID {
83 debug!("ist8310: detected id=0x{:02X}", id);
84 return Ok(());
85 }
86 }
87 backoff_spin();
88 }
89
90 let last_id_str = match last_id {
91 Some(id) => format!("0x{id:02X}"),
92 None => String::from("none"),
93 };
94 Err(CuError::from(format!(
95 "ist8310 detect failed: expected=0x{IST8310_CHIP_ID:02X} last_id={last_id_str}"
96 )))
97 }
98
99 fn configure(&mut self) -> CuResult<()> {
100 self.write_reg_value(REG_AVGCNTL, AVGCNTL_16X)
102 .map_err(|err| map_debug_error("ist8310 configure AVGCNTL", err))?;
103 self.write_reg_value(REG_PDCNTL, PDCNTL_PULSE_DURATION_NORMAL)
104 .map_err(|err| map_debug_error("ist8310 configure PDCNTL", err))?;
105 debug!("ist8310: configured AVG16 and pulse duration normal");
106 Ok(())
107 }
108
109 fn trigger_measurement(&mut self) -> CuResult<()> {
110 self.write_reg_value(REG_CNTRL1, CNTRL1_SINGLE_MEASURE)
111 .map_err(|err| map_debug_error("ist8310 trigger single measure", err))
112 }
113
114 fn read_measure_if_ready(&mut self) -> CuResult<Option<MagnetometerPayload>> {
115 let stat1 = self.read_reg(REG_STAT1)?;
116 if (stat1 & STAT1_DRDY) == 0 {
117 return Ok(None);
118 }
119
120 let mut buf = [0u8; 6];
121 self.read_reg_buf(REG_DATA_X_L, &mut buf)?;
122
123 let x_raw = i16::from_le_bytes([buf[0], buf[1]]);
124 let y_raw = i16::from_le_bytes([buf[2], buf[3]]);
125 let z_raw = i16::from_le_bytes([buf[4], buf[5]]);
126
127 let mag_x = (x_raw as f32) * UT_PER_LSB;
129 let mag_y = -(y_raw as f32) * UT_PER_LSB;
130 let mag_z = (z_raw as f32) * UT_PER_LSB;
131
132 Ok(Some(MagnetometerPayload::from_raw([mag_x, mag_y, mag_z])))
133 }
134
135 fn read_reg(&mut self, reg: u8) -> CuResult<u8> {
136 let mut byte = [0u8; 1];
137 self.read_reg_buf(reg, &mut byte)?;
138 Ok(byte[0])
139 }
140
141 fn read_reg_buf(&mut self, reg: u8, read: &mut [u8]) -> CuResult<()> {
142 for _ in 0..I2C_TRANSFER_RETRY_LIMIT {
143 if self.bus.write_read(&[reg], read).is_ok() {
144 return Ok(());
145 }
146 backoff_spin();
147 }
148 Err(CuError::from(format!(
149 "ist8310 i2c write_read failed: reg=0x{reg:02X} len={} retries={I2C_TRANSFER_RETRY_LIMIT}",
150 read.len()
151 )))
152 }
153
154 fn write_reg_value(&mut self, reg: u8, value: u8) -> CuResult<()> {
155 for _ in 0..I2C_TRANSFER_RETRY_LIMIT {
156 if self.bus.write(&[reg, value]).is_ok() {
157 return Ok(());
158 }
159 backoff_spin();
160 }
161 Err(CuError::from(format!(
162 "ist8310 i2c write failed: reg=0x{reg:02X} value=0x{value:02X} retries={I2C_TRANSFER_RETRY_LIMIT}"
163 )))
164 }
165}
166
167fn backoff_spin() {
168 spin_wait(128);
169}
170
171fn spin_wait(iterations: usize) {
172 for _ in 0..iterations {
173 core::hint::spin_loop();
174 }
175}
176
177fn map_debug_error<E: Debug>(context: &str, err: E) -> CuError {
178 CuError::from(format!("{context}: {err:?}"))
179}
180
181resources!(for <BUS>
182where
183 BUS: Ist8310Bus,
184{
185 i2c => Owned<BUS>,
186});
187
188#[derive(Reflect)]
190#[reflect(no_field_bounds, from_reflect = false, type_path = false)]
191pub struct Ist8310Source<BUS>
192where
193 BUS: Ist8310Bus,
194{
195 #[reflect(ignore)]
196 driver: Ist8310Driver<BUS>,
197 measure_state: MeasureState,
198 last_output_ns: Option<u64>,
199 last_log_ns: Option<u64>,
200}
201
202#[derive(Clone, Copy, Reflect)]
203enum MeasureState {
204 Idle,
205 Measuring { started_ns: u64 },
206}
207
208impl<BUS> TypePath for Ist8310Source<BUS>
209where
210 BUS: Ist8310Bus,
211{
212 fn type_path() -> &'static str {
213 "cu_ist8310::Ist8310Source"
214 }
215
216 fn short_type_path() -> &'static str {
217 "Ist8310Source"
218 }
219
220 fn type_ident() -> Option<&'static str> {
221 Some("Ist8310Source")
222 }
223
224 fn crate_name() -> Option<&'static str> {
225 Some("cu_ist8310")
226 }
227
228 fn module_path() -> Option<&'static str> {
229 Some("")
230 }
231}
232
233impl<BUS> Freezable for Ist8310Source<BUS> where BUS: Ist8310Bus {}
234
235impl<BUS> CuSrcTask for Ist8310Source<BUS>
236where
237 BUS: Ist8310Bus,
238{
239 type Resources<'r> = Resources<BUS>;
240 type Output<'m> = output_msg!(MagnetometerPayload);
241
242 fn new(_config: Option<&ComponentConfig>, resources: Self::Resources<'_>) -> CuResult<Self>
243 where
244 Self: Sized,
245 {
246 let driver = Ist8310Driver::new(resources.i2c.0)?;
247 Ok(Self {
248 driver,
249 measure_state: MeasureState::Idle,
250 last_output_ns: None,
251 last_log_ns: None,
252 })
253 }
254
255 fn start(&mut self, ctx: &CuContext) -> CuResult<()> {
256 debug!(ctx, "ist8310: source started");
257 Ok(())
258 }
259
260 fn process<'o>(&mut self, ctx: &CuContext, output: &mut Self::Output<'o>) -> CuResult<()> {
261 let tov = ctx.now();
262 let now_ns = tov.as_nanos();
263
264 let payload = match self.measure_state {
265 MeasureState::Idle => {
266 if let Some(last_output_ns) = self.last_output_ns
267 && now_ns.saturating_sub(last_output_ns) < OUTPUT_PERIOD_NS
268 {
269 output.clear_payload();
270 output.tov = Tov::None;
271 return Ok(());
272 }
273
274 self.driver.trigger_measurement()?;
275 self.measure_state = MeasureState::Measuring { started_ns: now_ns };
276 output.clear_payload();
277 output.tov = Tov::None;
278 return Ok(());
279 }
280 MeasureState::Measuring { started_ns } => {
281 let elapsed_ns = now_ns.saturating_sub(started_ns);
282 if elapsed_ns < MEASURE_MIN_DELAY_NS {
283 output.clear_payload();
284 output.tov = Tov::None;
285 return Ok(());
286 }
287
288 if elapsed_ns > MEASURE_TIMEOUT_NS {
289 self.measure_state = MeasureState::Idle;
290 output.clear_payload();
291 output.tov = Tov::None;
292 return Ok(());
293 }
294
295 match self.driver.read_measure_if_ready()? {
296 Some(payload) => {
297 self.measure_state = MeasureState::Idle;
298 payload
299 }
300 None => {
301 output.clear_payload();
302 output.tov = Tov::None;
303 return Ok(());
304 }
305 }
306 }
307 };
308 self.last_output_ns = Some(now_ns);
309
310 if self
311 .last_log_ns
312 .is_none_or(|last_log_ns| now_ns.saturating_sub(last_log_ns) >= DRIVER_LOG_PERIOD_NS)
313 {
314 self.last_log_ns = Some(now_ns);
315 info!(
316 ctx,
317 "ist8310: mag_x_ut={} mag_y_ut={} mag_z_ut={} rate_hz={}",
318 payload.mag_x.get::<microtesla>(),
319 payload.mag_y.get::<microtesla>(),
320 payload.mag_z.get::<microtesla>(),
321 OUTPUT_RATE_HZ
322 );
323 }
324
325 output.tov = Tov::Time(tov);
326 output.set_payload(payload);
327 Ok(())
328 }
329}