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}