1#![cfg_attr(not(feature = "std"), no_std)]
31#![allow(dead_code)]
32
33use core::fmt::Debug;
34
35pub use cu_sensor_payloads::ImuPayload;
36use cu29::prelude::*;
37use embedded_hal::blocking::delay::DelayMs;
38use embedded_hal::blocking::spi::Transfer;
39use embedded_hal::digital::v2::OutputPin;
40
41const BMI088_ACC_CHIP_ID: u8 = 0x1E;
43const BMI088_GYR_CHIP_ID: u8 = 0x0F;
44
45const BMI088_ACC_REG_CHIP_ID: u8 = 0x00;
47const BMI088_ACC_REG_ACCEL_X_LSB: u8 = 0x12;
48const BMI088_ACC_REG_TEMP_MSB: u8 = 0x22;
49const BMI088_ACC_REG_TEMP_LSB: u8 = 0x23;
50const BMI088_ACC_REG_ACC_RANGE: u8 = 0x41;
51const BMI088_ACC_REG_PWR_CONF: u8 = 0x7C;
52const BMI088_ACC_REG_PWR_CTRL: u8 = 0x7D;
53const BMI088_ACC_REG_SOFT_RESET: u8 = 0x7E;
54
55const BMI088_GYR_REG_CHIP_ID: u8 = 0x00;
57const BMI088_GYR_REG_X_LSB: u8 = 0x02;
58const BMI088_GYR_REG_RANGE: u8 = 0x0F;
59const BMI088_GYR_REG_BANDWIDTH: u8 = 0x10;
60const BMI088_GYR_REG_POWER_MODE: u8 = 0x11;
61const BMI088_GYR_REG_SOFT_RESET: u8 = 0x14;
62
63const BMI088_SOFT_RESET_CMD: u8 = 0xB6;
65const BMI088_ACC_PWR_CONF_ACTIVE: u8 = 0x00;
66const BMI088_ACC_PWR_CTRL_ON: u8 = 0x04;
67const BMI088_GYR_RANGE_2000: u8 = 0x00;
68const BMI088_GYR_BANDWIDTH: u8 = 0x07; const BMI088_GYR_PWR_NORMAL: u8 = 0x00;
70
71const GYRO_RAD_PER_LSB: f32 = (2000.0 / 32_768.0) * (core::f32::consts::PI / 180.0);
73
74resources!(for <SPI, ACC, GYR, D>
81where
82 SPI: Transfer<u8> + Send + Sync + 'static,
83 SPI::Error: Debug + Send + 'static,
84 ACC: OutputPin + Send + Sync + 'static,
85 ACC::Error: Debug + Send + 'static,
86 GYR: OutputPin + Send + Sync + 'static,
87 GYR::Error: Debug + Send + 'static,
88 D: DelayMs<u32> + Send + Sync + 'static,
89{
90 spi => Owned<SPI>,
91 acc_cs => Owned<ACC>,
92 gyr_cs => Owned<GYR>,
93 delay => Owned<D>,
94});
95
96pub struct Bmi088Source<SPI, ACC, GYR, D> {
109 driver: Bmi088Driver<SPI, ACC, GYR, D>,
110}
111
112impl<SPI, ACC, GYR, D> Freezable for Bmi088Source<SPI, ACC, GYR, D>
113where
114 SPI: Transfer<u8> + Send + Sync + 'static,
115 SPI::Error: Debug + Send + 'static,
116 ACC: OutputPin + Send + Sync + 'static,
117 ACC::Error: Debug + Send + 'static,
118 GYR: OutputPin + Send + Sync + 'static,
119 GYR::Error: Debug + Send + 'static,
120 D: DelayMs<u32> + Send + Sync + 'static,
121{
122}
123
124impl<SPI, ACC, GYR, D> CuSrcTask for Bmi088Source<SPI, ACC, GYR, D>
125where
126 SPI: Transfer<u8> + Send + Sync + 'static,
127 SPI::Error: Debug + Send + 'static,
128 ACC: OutputPin + Send + Sync + 'static,
129 ACC::Error: Debug + Send + 'static,
130 GYR: OutputPin + Send + Sync + 'static,
131 GYR::Error: Debug + Send + 'static,
132 D: DelayMs<u32> + Send + Sync + 'static,
133{
134 type Resources<'r> = Resources<SPI, ACC, GYR, D>;
135 type Output<'m> = output_msg!(ImuPayload);
136
137 fn new(_config: Option<&ComponentConfig>, resources: Self::Resources<'_>) -> CuResult<Self>
138 where
139 Self: Sized,
140 {
141 let driver = Bmi088Driver::new(
142 resources.spi.0,
143 resources.acc_cs.0,
144 resources.gyr_cs.0,
145 resources.delay.0,
146 )?;
147 Ok(Self { driver })
148 }
149
150 fn start(&mut self, _clock: &RobotClock) -> CuResult<()> {
151 Ok(())
152 }
153
154 fn process<'o>(&mut self, clock: &RobotClock, output: &mut Self::Output<'o>) -> CuResult<()> {
155 let tov = clock.now();
156 let payload = self.driver.read_measure()?;
157 output.tov = Tov::Time(tov);
158 output.set_payload(payload);
159 Ok(())
160 }
161}
162
163struct Bmi088Driver<SPI, ACC, GYR, D> {
165 spi: SPI,
166 acc_cs: ACC,
167 gyr_cs: GYR,
168 #[allow(dead_code)]
169 delay: D,
170 acc_mps2_per_lsb: f32,
171}
172
173impl<SPI, ACC, GYR, D> Bmi088Driver<SPI, ACC, GYR, D>
174where
175 SPI: Transfer<u8>,
176 SPI::Error: Debug,
177 ACC: OutputPin,
178 ACC::Error: Debug,
179 GYR: OutputPin,
180 GYR::Error: Debug,
181 D: DelayMs<u32>,
182{
183 fn new(mut spi: SPI, mut acc_cs: ACC, mut gyr_cs: GYR, mut delay: D) -> CuResult<Self> {
191 acc_cs
193 .set_high()
194 .map_err(|err| map_error("bmi088 acc cs high", err))?;
195 gyr_cs
196 .set_high()
197 .map_err(|err| map_error("bmi088 gyr cs high", err))?;
198
199 let acc_id = spi_read_reg_2(&mut spi, &mut acc_cs, BMI088_ACC_REG_CHIP_ID)
201 .map_err(|err| map_error("bmi088 acc chip id", err))?;
202 let gyr_id = spi_read_reg_1(&mut spi, &mut gyr_cs, BMI088_GYR_REG_CHIP_ID)
203 .map_err(|err| map_error("bmi088 gyr chip id", err))?;
204
205 if acc_id != BMI088_ACC_CHIP_ID {
206 return Err(CuError::from("bmi088 accel id mismatch"));
207 }
208 if gyr_id != BMI088_GYR_CHIP_ID {
209 return Err(CuError::from("bmi088 gyro id mismatch"));
210 }
211
212 spi_write_reg(
214 &mut spi,
215 &mut acc_cs,
216 BMI088_ACC_REG_SOFT_RESET,
217 BMI088_SOFT_RESET_CMD,
218 )
219 .map_err(|err| map_error("bmi088 acc reset", err))?;
220 delay.delay_ms(10);
221
222 spi_write_reg(
224 &mut spi,
225 &mut acc_cs,
226 BMI088_ACC_REG_PWR_CONF,
227 BMI088_ACC_PWR_CONF_ACTIVE,
228 )
229 .map_err(|err| map_error("bmi088 acc pwr conf", err))?;
230 delay.delay_ms(1);
231 spi_write_reg(
232 &mut spi,
233 &mut acc_cs,
234 BMI088_ACC_REG_PWR_CTRL,
235 BMI088_ACC_PWR_CTRL_ON,
236 )
237 .map_err(|err| map_error("bmi088 acc pwr ctrl", err))?;
238 delay.delay_ms(50);
239
240 spi_write_reg(
242 &mut spi,
243 &mut gyr_cs,
244 BMI088_GYR_REG_SOFT_RESET,
245 BMI088_SOFT_RESET_CMD,
246 )
247 .map_err(|err| map_error("bmi088 gyro reset", err))?;
248 delay.delay_ms(100);
249 spi_write_reg(
250 &mut spi,
251 &mut gyr_cs,
252 BMI088_GYR_REG_RANGE,
253 BMI088_GYR_RANGE_2000,
254 )
255 .map_err(|err| map_error("bmi088 gyro range", err))?;
256 spi_write_reg(
257 &mut spi,
258 &mut gyr_cs,
259 BMI088_GYR_REG_BANDWIDTH,
260 BMI088_GYR_BANDWIDTH,
261 )
262 .map_err(|err| map_error("bmi088 gyro bandwidth", err))?;
263 spi_write_reg(
264 &mut spi,
265 &mut gyr_cs,
266 BMI088_GYR_REG_POWER_MODE,
267 BMI088_GYR_PWR_NORMAL,
268 )
269 .map_err(|err| map_error("bmi088 gyro pwr mode", err))?;
270
271 let acc_range_reg = spi_read_reg_2(&mut spi, &mut acc_cs, BMI088_ACC_REG_ACC_RANGE)
273 .map_err(|err| map_error("bmi088 acc range", err))?;
274 let acc_range_g = accel_range_g_from_reg(acc_range_reg);
275 let acc_mps2_per_lsb = acc_range_g * 9.806_65 / 32_768.0;
276 debug!(
277 "bmi088 accel range reg={} -> ±{}g",
278 acc_range_reg, acc_range_g
279 );
280
281 Ok(Self {
282 spi,
283 acc_cs,
284 gyr_cs,
285 delay,
286 acc_mps2_per_lsb,
287 })
288 }
289
290 fn read_measure(&mut self) -> CuResult<ImuPayload> {
297 let mut acc_buf = [0_u8; 6];
299 spi_read_burst_2(
300 &mut self.spi,
301 &mut self.acc_cs,
302 BMI088_ACC_REG_ACCEL_X_LSB,
303 &mut acc_buf,
304 )
305 .map_err(|err| map_error("bmi088 acc burst", err))?;
306 let ax = bytes_to_i16(acc_buf[0], acc_buf[1]);
307 let ay = bytes_to_i16(acc_buf[2], acc_buf[3]);
308 let az = bytes_to_i16(acc_buf[4], acc_buf[5]);
309
310 let mut gyr_buf = [0_u8; 6];
312 spi_read_burst_1(
313 &mut self.spi,
314 &mut self.gyr_cs,
315 BMI088_GYR_REG_X_LSB,
316 &mut gyr_buf,
317 )
318 .map_err(|err| map_error("bmi088 gyro burst", err))?;
319 let gx = bytes_to_i16(gyr_buf[0], gyr_buf[1]);
320 let gy = bytes_to_i16(gyr_buf[2], gyr_buf[3]);
321 let gz = bytes_to_i16(gyr_buf[4], gyr_buf[5]);
322
323 let temp_msb = spi_read_reg_2(&mut self.spi, &mut self.acc_cs, BMI088_ACC_REG_TEMP_MSB)
325 .map_err(|err| map_error("bmi088 temp msb", err))?;
326 let temp_lsb = spi_read_reg_2(&mut self.spi, &mut self.acc_cs, BMI088_ACC_REG_TEMP_LSB)
327 .map_err(|err| map_error("bmi088 temp lsb", err))?;
328 let temp_raw = (temp_msb as i16) * 8 + (temp_lsb as i16) / 32;
329 let temp_c = (temp_raw as f32) * 0.125 + 23.0;
330
331 let accel_mps2 = [
334 accel_raw_to_mps2(ay, self.acc_mps2_per_lsb),
335 accel_raw_to_mps2(ax, self.acc_mps2_per_lsb),
336 accel_raw_to_mps2(az, self.acc_mps2_per_lsb),
337 ];
338 let gyro_rad = [
339 -gyro_raw_to_rad(gy),
340 -gyro_raw_to_rad(gx),
341 -gyro_raw_to_rad(gz),
342 ];
343
344 Ok(ImuPayload::from_raw(accel_mps2, gyro_rad, temp_c))
345 }
346}
347
348#[derive(Debug)]
351enum SpiCsError<SpiErr, CsErr> {
352 Spi(SpiErr),
353 Cs(CsErr),
354}
355
356fn map_error<E: Debug>(context: &'static str, _err: E) -> CuError {
357 CuError::from(context)
358}
359
360fn spi_transfer<SPI, CS>(
361 spi: &mut SPI,
362 cs: &mut CS,
363 buf: &mut [u8],
364) -> Result<(), SpiCsError<SPI::Error, CS::Error>>
365where
366 SPI: Transfer<u8>,
367 CS: OutputPin,
368{
369 cs.set_low().map_err(SpiCsError::Cs)?;
370 let transfer_res = spi.transfer(buf).map_err(SpiCsError::Spi);
371 let cs_res = cs.set_high().map_err(SpiCsError::Cs);
372 if let Err(err) = transfer_res {
373 let _ = cs_res;
374 return Err(err);
375 }
376 cs_res?;
377 Ok(())
378}
379
380fn spi_write_reg<SPI, CS>(
381 spi: &mut SPI,
382 cs: &mut CS,
383 reg: u8,
384 value: u8,
385) -> Result<(), SpiCsError<SPI::Error, CS::Error>>
386where
387 SPI: Transfer<u8>,
388 CS: OutputPin,
389{
390 let mut buf = [reg & 0x7f, value];
391 spi_transfer(spi, cs, &mut buf)
392}
393
394fn spi_read_reg_1<SPI, CS>(
396 spi: &mut SPI,
397 cs: &mut CS,
398 reg: u8,
399) -> Result<u8, SpiCsError<SPI::Error, CS::Error>>
400where
401 SPI: Transfer<u8>,
402 CS: OutputPin,
403{
404 let mut buf = [reg | 0x80, 0x00];
405 spi_transfer(spi, cs, &mut buf)?;
406 Ok(buf[1])
407}
408
409fn spi_read_reg_2<SPI, CS>(
411 spi: &mut SPI,
412 cs: &mut CS,
413 reg: u8,
414) -> Result<u8, SpiCsError<SPI::Error, CS::Error>>
415where
416 SPI: Transfer<u8>,
417 CS: OutputPin,
418{
419 let mut buf = [reg | 0x80, 0x00, 0x00];
420 spi_transfer(spi, cs, &mut buf)?;
421 Ok(buf[2])
422}
423
424fn spi_read_burst_1<SPI, CS>(
426 spi: &mut SPI,
427 cs: &mut CS,
428 reg: u8,
429 out: &mut [u8; 6],
430) -> Result<(), SpiCsError<SPI::Error, CS::Error>>
431where
432 SPI: Transfer<u8>,
433 CS: OutputPin,
434{
435 let mut buf = [0_u8; 7];
436 buf[0] = reg | 0x80;
437 spi_transfer(spi, cs, &mut buf)?;
438 out.copy_from_slice(&buf[1..7]);
439 Ok(())
440}
441
442fn spi_read_burst_2<SPI, CS>(
444 spi: &mut SPI,
445 cs: &mut CS,
446 reg: u8,
447 out: &mut [u8; 6],
448) -> Result<(), SpiCsError<SPI::Error, CS::Error>>
449where
450 SPI: Transfer<u8>,
451 CS: OutputPin,
452{
453 let mut buf = [0_u8; 8];
454 buf[0] = reg | 0x80;
455 spi_transfer(spi, cs, &mut buf)?;
456 out.copy_from_slice(&buf[2..8]);
457 Ok(())
458}
459
460fn bytes_to_i16(lsb: u8, msb: u8) -> i16 {
463 i16::from_le_bytes([lsb, msb])
464}
465
466fn accel_raw_to_mps2(raw: i16, acc_mps2_per_lsb: f32) -> f32 {
467 raw as f32 * acc_mps2_per_lsb
468}
469
470fn gyro_raw_to_rad(raw: i16) -> f32 {
471 raw as f32 * GYRO_RAD_PER_LSB
472}
473
474fn accel_range_g_from_reg(range_reg: u8) -> f32 {
475 match range_reg & 0x03 {
476 0x00 => 3.0,
477 0x01 => 6.0,
478 0x02 => 12.0,
479 _ => 24.0,
480 }
481}