scl3300/
lib.rs

1//! This is a driver for [SCL3300](https://www.murata.com/en-global/products/sensor/inclinometer/overview/lineup/scl3300)
2//! inclinometers, implemented using platform-agnostic [`embedded-hal`](https://docs.rs/embedded-hal/latest/embedded_hal/) traits.
3//!
4//! # Usage
5//!
6//! ```rust
7//! # fn main() -> Result<(), scl3300::Error<embedded_hal::spi::ErrorKind>> {
8//! # use embedded_hal_mock::eh1::{spi::{Mock as SpiMock, Transaction as SpiTransaction}};
9//! # let spi = SpiMock::new(&[
10//! #   // Reset.
11//! #   SpiTransaction::transaction_start(),
12//! #   SpiTransaction::transfer_in_place(vec![0xB4, 0x00, 0x20, 0x98], vec![3, 0, 0, 125]),
13//! #   SpiTransaction::delay(1000000),
14//! #   SpiTransaction::transaction_end(),
15//! #
16//! #   // Change to inclination mode.
17//! #   SpiTransaction::transaction_start(),
18//! #   SpiTransaction::transfer_in_place(vec![0xB4, 0x00, 0x02, 0x25], vec![3, 0, 0, 125]),
19//! #   SpiTransaction::delay(10000),
20//! #   SpiTransaction::transaction_end(),
21//! #
22//! #   // Enable angle outputs.
23//! #   SpiTransaction::transaction_start(),
24//! #   SpiTransaction::transfer_in_place(vec![0xB0, 0x00, 0x1F, 0x6F], vec![183, 0, 2, 169]),
25//! #   SpiTransaction::delay(100000000),
26//! #   SpiTransaction::transaction_end(),
27//! #
28//! #   // Read status.
29//! #   SpiTransaction::transaction_start(),
30//! #   SpiTransaction::transfer_in_place(vec![0x18, 0x00, 0x00, 0xE5], vec![179, 0, 31, 227]),
31//! #   SpiTransaction::delay(10000),
32//! #   SpiTransaction::transaction_end(),
33//! #
34//! #   // Read status.
35//! #   SpiTransaction::transaction_start(),
36//! #   SpiTransaction::transfer_in_place(vec![0x18, 0x00, 0x00, 0xE5], vec![27, 0, 18, 158]),
37//! #   SpiTransaction::delay(10000),
38//! #   SpiTransaction::transaction_end(),
39//! #
40//! #   // Read status.
41//! #   SpiTransaction::transaction_start(),
42//! #   SpiTransaction::transfer_in_place(vec![0x18, 0x00, 0x00, 0xE5], vec![25, 0, 18, 157]),
43//! #   SpiTransaction::delay(10000),
44//! #   SpiTransaction::transaction_end(),
45//! #
46//! #   // Read WHOAMI.
47//! #   SpiTransaction::transaction_start(),
48//! #   SpiTransaction::transfer_in_place(vec![0x40, 0x00, 0x00, 0x91], vec![25, 0, 0, 106]),
49//! #   SpiTransaction::delay(10000),
50//! #   SpiTransaction::transaction_end(),
51//! #
52//! #   // Switch to bank 0.
53//! #   SpiTransaction::transaction_start(),
54//! #   SpiTransaction::transfer_in_place(vec![0xFC, 0x00, 0x00, 0x73], vec![65, 0, 193, 54]),
55//! #   SpiTransaction::delay(10000),
56//! #   SpiTransaction::transaction_end(),
57//! #
58//! #   // Read X-axis acceleration.
59//! #   SpiTransaction::transaction_start(),
60//! #   SpiTransaction::transfer_in_place(vec![0x04, 0x00, 0x00, 0xF7], vec![25, 0, 0, 106]),
61//! #   SpiTransaction::delay(10000),
62//! #   SpiTransaction::transaction_end(),
63//! #
64//! #   // Read Y-axis acceleration.
65//! #   SpiTransaction::transaction_start(),
66//! #   SpiTransaction::transfer_in_place(vec![0x08, 0x00, 0x00, 0xFD], vec![5, 255, 230, 197]),
67//! #   SpiTransaction::delay(10000),
68//! #   SpiTransaction::transaction_end(),
69//! #
70//! #   // Read Z-axis acceleration.
71//! #   SpiTransaction::transaction_start(),
72//! #   SpiTransaction::transfer_in_place(vec![0x0C, 0x00, 0x00, 0xFB], vec![9, 0, 141, 213]),
73//! #   SpiTransaction::delay(10000),
74//! #   SpiTransaction::transaction_end(),
75//! #
76//! #   // Read X-axis inclination.
77//! #   SpiTransaction::transaction_start(),
78//! #   SpiTransaction::transfer_in_place(vec![0x24, 0x00, 0x00, 0xC7], vec![13, 46, 112, 183]),
79//! #   SpiTransaction::delay(10000),
80//! #   SpiTransaction::transaction_end(),
81//! #
82//! #   // Read Y-axis inclination.
83//! #   SpiTransaction::transaction_start(),
84//! #   SpiTransaction::transfer_in_place(vec![0x28, 0x00, 0x00, 0xCD], vec![37, 255, 233, 78]),
85//! #   SpiTransaction::delay(10000),
86//! #   SpiTransaction::transaction_end(),
87//! #
88//! #   // Read Z-axis inclination.
89//! #   SpiTransaction::transaction_start(),
90//! #   SpiTransaction::transfer_in_place(vec![0x2C, 0x00, 0x00, 0xCB], vec![41, 0, 123, 212]),
91//! #   SpiTransaction::delay(10000),
92//! #   SpiTransaction::transaction_end(),
93//! #
94//! #   // Read temperature.
95//! #   SpiTransaction::transaction_start(),
96//! #   SpiTransaction::transfer_in_place(vec![0x14, 0x00, 0x00, 0xEF], vec![45, 63, 129, 29]),
97//! #   SpiTransaction::delay(10000),
98//! #   SpiTransaction::transaction_end(),
99//! #
100//! #   // Switch to bank 0.
101//! #   SpiTransaction::transaction_start(),
102//! #   SpiTransaction::transfer_in_place(vec![0xFC, 0x00, 0x00, 0x73], vec![21, 22, 20, 216]),
103//! #   SpiTransaction::delay(10000),
104//! #   SpiTransaction::transaction_end(),
105//! #
106//! #   // Power down.
107//! #   SpiTransaction::transaction_start(),
108//! #   SpiTransaction::transfer_in_place(vec![0xB4, 0x00, 0x04, 0x6B], vec![253, 0, 0, 252]),
109//! #   SpiTransaction::delay(10000),
110//! #   SpiTransaction::transaction_end(),
111//! # ]);
112//! use scl3300::{Scl3300, Acceleration, ComponentId, Inclination, MeasurementMode, Temperature};
113//!
114//! let inclinometer = Scl3300::new(spi);
115//!
116//! // Start the inclinometer and switch to inclination mode.
117//! let mut inclinometer = inclinometer.start_up(MeasurementMode::Inclination)?;
118//!
119//! // Read the component ID.
120//! let id: ComponentId = inclinometer.read()?;
121//! assert_eq!(id, ComponentId::WHOAMI);
122//!
123//! // Read acceleration, inclination and temperature.
124//! let (acc, inc, temp): (Acceleration, Inclination, Temperature) = inclinometer.read()?;
125//!
126//! # assert_eq!(acc.x_g(), -0.0021666666);
127//! # assert_eq!(acc.y_g(), 0.01175);
128//! # assert_eq!(acc.z_g(), 0.9906667);
129//! println!("Acceleration: {}g, {}g, {}g", acc.x_g(), acc.y_g(), acc.z_g());
130//! #
131//! # assert_eq!(inc.x_degrees(), 359.87366);
132//! # assert_eq!(inc.y_degrees(), 0.6756592);
133//! # assert_eq!(inc.z_degrees(), 89.30237);
134//! println!("Inclination: {}°, {}°, {}°", inc.x_degrees(), inc.y_degrees(), inc.z_degrees());
135//! #
136//! # assert_eq!(temp.degrees_celsius(), 26.047638);
137//! println!("Temperature: {}°C", temp.degrees_celsius());
138//!
139//! // Switch to power-down mode.
140//! let inclinometer = inclinometer.power_down()?;
141//!
142//! // Release the SPI peripheral again.
143//! let spi = inclinometer.release();
144//! # let mut spi = spi;
145//! # spi.done();
146//! drop(spi);
147//! # Ok(())
148//! # }
149//! ```
150#![cfg_attr(not(test), no_std)]
151#![warn(missing_debug_implementations)]
152#![warn(missing_docs)]
153
154use core::{marker::PhantomData, num::NonZeroU32};
155
156use embedded_hal::spi::{Operation as SpiOperation, SpiDevice};
157
158mod error;
159pub use error::*;
160mod frame;
161use frame::*;
162pub mod output;
163pub use output::*;
164mod measurement_mode;
165pub use measurement_mode::*;
166mod operation;
167use operation::*;
168mod off_frame_read;
169pub use off_frame_read::*;
170
171/// [`Scl3300`](crate::Scl3300) operation modes.
172pub mod mode {
173  use super::*;
174
175  /// Marker type for an uninitialized [`Scl3300`](crate::Scl3300).
176  #[derive(Debug)]
177  pub struct Uninitialized {
178    pub(crate) _0: PhantomData<()>,
179  }
180
181  /// Marker type for a [`Scl3300`](crate::Scl3300) in normal operation mode.
182  #[derive(Debug)]
183  pub struct Normal {
184    pub(crate) mode: MeasurementMode,
185  }
186
187  /// Marker type for a [`Scl3300`](crate::Scl3300) in power down mode.
188  #[derive(Debug)]
189  pub struct PowerDown {
190    pub(crate) _0: PhantomData<()>,
191  }
192}
193pub use mode::*;
194
195const MIN_WAIT_TIME_NS: NonZeroU32 = match NonZeroU32::new(10_000) {
196  Some(v) => v,
197  None => unreachable!(),
198};
199const WAKE_UP_TIME_NS: NonZeroU32 = match NonZeroU32::new(1_000_000) {
200  Some(v) => v,
201  None => unreachable!(),
202};
203const RESET_TIME_NS: NonZeroU32 = match NonZeroU32::new(1_000_000) {
204  Some(v) => v,
205  None => unreachable!(),
206};
207
208/// An SCL3300 inclinometer.
209#[derive(Debug, Clone)]
210pub struct Scl3300<SPI, MODE = Uninitialized> {
211  pub(crate) spi: SPI,
212  pub(crate) mode: MODE,
213}
214
215impl<SPI> Scl3300<SPI> {
216  /// Create a new `Scl3300` with the given `SPI` instance.
217  pub const fn new(spi: SPI) -> Self {
218    Scl3300 { spi, mode: Uninitialized { _0: PhantomData } }
219  }
220}
221
222impl<SPI, E, MODE> Scl3300<SPI, MODE>
223where
224  SPI: SpiDevice<u8, Error = E>,
225{
226  /// Start the inclinometer in the given [`MeasurementMode`](enum.MeasurementMode.html).
227  fn start_up_inner(mut self, mode: MeasurementMode) -> Result<Scl3300<SPI, Normal>, Error<E>> {
228    // Software reset the device.
229    self.write(Operation::Reset, Some(RESET_TIME_NS))?;
230
231    // Select operation mode.
232    self.write(Operation::ChangeMode(mode), None)?;
233    // Enable angle outputs.
234    self.write(Operation::EnableAngleOutputs, Some(mode.start_up_wait_time_ns()))?;
235
236    // Clear status summary.
237    self.write(Operation::Read(Output::Status), None)?;
238    // Read status summary.
239    self.write(Operation::Read(Output::Status), None)?;
240    // Ensure successful start-up.
241    self.transfer(Operation::Read(Output::Status), None)?;
242
243    Ok(Scl3300 { spi: self.spi, mode: Normal { mode } })
244  }
245
246  #[inline]
247  fn write(&mut self, operation: Operation, wait_us: Option<NonZeroU32>) -> Result<(), Error<E>> {
248    self.transfer_inner(operation, wait_us)?;
249    Ok(())
250  }
251
252  #[inline]
253  fn transfer(&mut self, operation: Operation, wait_us: Option<NonZeroU32>) -> Result<Frame, Error<E>> {
254    let frame = self.transfer_inner(operation, wait_us)?;
255    frame.check_crc()?;
256
257    match frame.return_status() {
258      ReturnStatus::StartupInProgress => Err(Error::Startup),
259      ReturnStatus::Error => Err(Error::ReturnStatus),
260      ReturnStatus::NormalOperation => Ok(frame),
261    }
262  }
263
264  #[inline]
265  fn transfer_inner(&mut self, operation: Operation, wait_us: Option<NonZeroU32>) -> Result<Frame, Error<E>> {
266    let mut frame = operation.to_frame();
267
268    let res = self.spi.transaction(&mut [
269      SpiOperation::TransferInPlace(frame.as_bytes_mut()),
270      SpiOperation::DelayNs(wait_us.unwrap_or(MIN_WAIT_TIME_NS).get()),
271    ]);
272    if let Err(err) = res {
273      return Err(Error::Spi(err))
274    }
275
276    Ok(frame)
277  }
278}
279
280impl<SPI, E> Scl3300<SPI, Uninitialized>
281where
282  SPI: SpiDevice<u8, Error = E>,
283{
284  /// Start the inclinometer in the given [`MeasurementMode`](enum.MeasurementMode.html).
285  ///
286  /// When the inclinometer is in power down mode, use [`wake_up`](Scl3300::wake_up) instead.
287  #[inline(always)]
288  pub fn start_up(self, mode: MeasurementMode) -> Result<Scl3300<SPI, Normal>, Error<E>> {
289    self.start_up_inner(mode)
290  }
291}
292
293impl<SPI, E> Scl3300<SPI, Normal>
294where
295  SPI: SpiDevice<u8, Error = E>,
296{
297  /// Read a value.
298  ///
299  /// The following outputs are supported:
300  ///
301  /// - [`Acceleration`](output::Acceleration)
302  /// - [`Inclination`](output::Inclination)
303  /// - [`Temperature`](output::Temperature)
304  /// - [`SelfTest`](output::SelfTest)
305  /// - [`ComponentId`](output::ComponentId)
306  /// - [`Serial`](output::Serial)
307  /// - [`Status`](output::Status)
308  /// - [`Error1`](output::Error1)
309  /// - [`Error2`](output::Error2)
310  ///
311  /// Additinally, multiple outputs can be read by specifying a tuple.
312  pub fn read<V>(&mut self) -> Result<V, Error<E>>
313  where
314    V: OffFrameRead<SPI, E>,
315  {
316    let mut current_bank = Bank::Zero;
317
318    let (_, mut partial) = V::start_read(self, &mut current_bank)?;
319
320    let last_value = self.transfer(Operation::SwitchBank(Bank::Zero), None)?.data();
321
322    partial.finish_read(last_value);
323
324    Ok(partial)
325  }
326
327  /// Put the inclinometer into power down mode.
328  pub fn power_down(mut self) -> Result<Scl3300<SPI, PowerDown>, Error<E>> {
329    self.transfer(Operation::PowerDown, None)?;
330    Ok(Scl3300 { spi: self.spi, mode: PowerDown { _0: PhantomData } })
331  }
332}
333
334impl<SPI, E> Scl3300<SPI, PowerDown>
335where
336  SPI: SpiDevice<u8, Error = E>,
337{
338  /// Wake the inclinometer up from power down mode and switch to the given [`MeasurementMode`](enum.MeasurementMode.html).
339  #[inline(always)]
340  pub fn wake_up(mut self, mode: MeasurementMode) -> Result<Scl3300<SPI, Normal>, Error<E>> {
341    self.write(Operation::WakeUp, Some(WAKE_UP_TIME_NS))?;
342    self.start_up_inner(mode)
343  }
344}
345
346impl<SPI, MODE> Scl3300<SPI, MODE> {
347  /// Release the contained SPI peripheral.
348  pub fn release(self) -> SPI {
349    self.spi
350  }
351}