Skip to main content

sensor_tlv493d/
lib.rs

1#![no_std]
2
3/// Rust tlv493d 3-DoF I2C Hall Effect Sensor Driver
4///
5/// Copyright 2020 Ryan Kurte
6///
7/// - https://www.infineon.com/dgdl/Infineon-TLV493D-A1B6-DataSheet-v01_10-EN.pdf?fileId=5546d462525dbac40152a6b85c760e80
8/// - https://www.infineon.com/dgdl/Infineon-TLV493D-A1B6_3DMagnetic-UM-v01_03-EN.pdf?fileId=5546d46261d5e6820161e75721903ddd
9use bitflags::bitflags;
10use core::fmt::Debug;
11use core::marker::PhantomData;
12use maybe_async_cfg::maybe;
13
14#[cfg(feature = "async")]
15use embedded_hal_async::{delay::DelayNs, i2c, i2c::Error as I2cError};
16
17#[cfg(feature = "blocking")]
18use embedded_hal::{delay::DelayNs, i2c, i2c::Error as I2cError};
19
20#[cfg(feature = "std")]
21extern crate std;
22
23#[cfg(feature = "defmt")]
24#[allow(unused_imports)]
25#[macro_use]
26extern crate defmt;
27
28pub struct Tlv493d<I2c, I2cErr, Delay> {
29    i2c: I2c,
30    delay: Delay,
31    addr: u8,
32    initial: [u8; 10],
33    last_frm: Option<u8>,
34    _e_i2c: PhantomData<I2cErr>,
35}
36
37/// Base address for Tlv493d, bottom bit set during power up
38/// based on value of SDA.
39pub const ADDRESS_BASE: u8 = 0b1011110;
40
41#[derive(Debug, PartialEq, Clone)]
42#[cfg_attr(feature = "defmt", derive(defmt::Format))]
43/// Read registers for the Tlv493d
44pub enum ReadRegisters {
45    Rx = 0x00,    // X direction flux (Bx[11..4])
46    By = 0x01,    // Y direction flux (By[11..4])
47    Bz = 0x02,    // X direction flux (Bz[11..4])
48    Temp = 0x03, // Temperature high bits, frame counter, channel, (Temp[11..8] | FRM[1..0] | CH[1..0])
49    Bx2 = 0x04,  // Lower X and Y flux (Bx[3..0] | Bx[3..0])
50    Bz2 = 0x05,  // Flags + Lower Z flux (Reserved | T | FF | PD | Bz[3..0])
51    Temp2 = 0x06, // Temperature low bits (Temp[7..0])
52
53    FactSet1 = 0x07,
54    FactSet2 = 0x08,
55    FactSet3 = 0x09,
56}
57
58#[derive(Debug, PartialEq, Clone)]
59#[cfg_attr(feature = "defmt", derive(defmt::Format))]
60/// Write registers for the Tlv493d
61pub enum WriteRegisters {
62    Res = 0x00,   // Reserved
63    Mode1 = 0x01, // Mode 1 register (P | IICAddr[1..0] | Reserved[1..0] | INT | FAST | LOW)
64    Res2 = 0x02,  // Reserved
65    Mode2 = 0x03, // Mode 2 register (T | LP | PT | Reserved[4..0])
66}
67
68/// TLV493D Measurement values
69#[derive(Debug, PartialEq, Clone)]
70#[cfg_attr(feature = "defmt", derive(defmt::Format))]
71pub struct Values {
72    pub x: f32,    // X axis magnetic flux (mT)
73    pub y: f32,    // Y axis magnetic flux (mT)
74    pub z: f32,    // Z axis magnetic flux (mT)
75    pub temp: f32, // Device temperature (C)
76}
77
78/// Device operating mode
79/// Note that in most cases the mode is a combination of mode and IRQ flags
80#[derive(Debug, PartialEq, Clone)]
81#[cfg_attr(feature = "defmt", derive(defmt::Format))]
82pub enum Mode {
83    Disabled,      // Reading disabled
84    Master,        // Master initiated mode (reading occurs after readout)
85    Fast,          // Fast mode (3.3kHz)
86    LowPower,      // Low power mode (100Hz)
87    UltraLowPower, // Ultra low power mode (10Hz)
88}
89
90bitflags! {
91    /// Device Mode1 register
92    pub struct Mode1: u8 {
93        const PARITY     = 0b1000_0000;     // Parity of configuration map, must be calculated prior to write command
94        const I2C_ADDR_1 = 0b0100_0000;     // Set I2C address top bit in bus configuration
95        const I2C_ADDR_0 = 0b0010_0000;     // Set I2C address bottom bit in bus configuration
96        const IRQ_EN     = 0b0000_0100;      // Enable read-complete interrupts
97        const FAST       = 0b0000_0010;      // Enable fast mode (must be disabled for power-down)
98        const LOW        = 0b0000_0001;      // Low power mode
99    }
100}
101
102bitflags! {
103    /// Device Mode2 register
104    pub struct Mode2: u8 {
105        const TEMP_DISABLE   = 0b1000_0000;     // DISABLE temperature measurement
106        const LOW_POW_PERIOD = 0b0100_0000;     // Set low power period ("0": 100ms, "1": 12ms)
107        const PARITY_TEST_EN = 0b0010_0000;     // Enable / Disable parity test
108    }
109}
110
111/// Tlv493d related errors
112#[derive(Debug)]
113#[cfg_attr(feature = "defmt", derive(defmt::Format))]
114#[cfg_attr(feature = "std", derive(thiserror::Error))]
115pub enum Error<I2cErr: I2cError + Debug> {
116    // No device found with specified i2c bus and address
117    #[cfg_attr(
118        feature = "std",
119        error("No device found with specified i2c bus and address")
120    )]
121    NoDevice,
122
123    // Device ADC locked up and must be reset
124    #[cfg_attr(feature = "std", error("Device ADC lockup, reset required"))]
125    AdcLockup,
126
127    // Underlying I2C device error
128    #[cfg_attr(feature = "std", error("I2C device error: {0:?}"))]
129    I2c(I2cErr),
130}
131
132maybe_async_cfg::content! {
133    impl<I2c, I2cErr, Delay> Tlv493d<I2c, I2cErr, Delay>
134    where
135        I2c: i2c::I2c,
136        I2cErr: I2cError + Debug,
137        Error<I2cErr>: From<Error<<I2c as i2c::ErrorType>::Error>>,
138        Delay: DelayNs,
139    {
140        /// Create a new TLV493D instance
141        #[maybe(
142            sync(feature = "blocking"),
143            async(feature = "async")
144        )]
145        pub async fn new(
146            i2c: I2c,
147            delay: Delay,
148            addr: u8,
149            mode: Mode,
150        ) -> Result<Self, (Error<I2cErr>, I2c)> {
151            #[cfg(feature = "defmt")]
152            debug!("New Tlv493d with address: 0x{:02x}", addr);
153
154            // Construct object
155            let mut s = Self {
156                i2c,
157                delay,
158                addr,
159                initial: [0u8; 10],
160                last_frm: None,
161                _e_i2c: PhantomData,
162            };
163
164            // Reset and configure
165            #[cfg(feature = "async")]
166            if let Err(err) = s.configure_async(mode, true).await {
167                return Err((err, s.i2c));
168            };
169            #[cfg(feature = "blocking")]
170            if let Err(err) = s.configure_sync(mode, true) {
171                return Err((err, s.i2c));
172            };
173
174            // Return object
175            Ok(s)
176        }
177
178        pub fn into_i2c(self) -> I2c {
179            self.i2c
180        }
181
182        /// Configure the device into the specified mode
183        #[maybe(
184            sync(feature = "blocking"),
185            async(feature = "async")
186        )]
187        pub async fn configure(&mut self, mode: Mode, reset: bool) -> Result<(), Error<I2cErr>> {
188            // Startup per fig. 5.1 in TLV493D-A1B6 user manual
189
190            // Reset if enabled
191            if reset {
192                #[cfg(feature = "defmt")]
193                debug!("Resetting device");
194
195                // Wait for startup delay
196                #[cfg(feature = "async")]
197                self.delay.delay_ms(1).await;
198                #[cfg(feature = "blocking")]
199                self.delay.delay_ms(1);
200
201                // Write recovery value
202                #[cfg(feature = "async")]
203                self.i2c.write(0xff, &[]).await
204                    .map_err(Error::I2c).ok();
205                #[cfg(feature = "blocking")]
206                self.i2c.write(0xff, &[])
207                    .map_err(Error::I2c).ok();
208
209                #[cfg(feature = "defmt")]
210                debug!("Setting device address");
211
212                // Write reset
213                #[cfg(feature = "async")]
214                self.i2c.write(0x00, &[0xff]).await
215                    .map_err(Error::I2c).ok();
216                #[cfg(feature = "blocking")]
217                self.i2c.write(0x00, &[0xff])
218                    .map_err(Error::I2c).ok();
219
220                #[cfg(feature = "defmt")]
221                debug!("Read device initial state");
222
223                // Read initial bitmap from device
224                #[cfg(feature = "async")]
225                self.i2c.read(self.addr, &mut self.initial[..]).await
226                    .map_err(Error::I2c)?;
227                #[cfg(feature = "blocking")]
228                self.i2c.read(self.addr, &mut self.initial[..])
229                    .map_err(Error::I2c)?;
230
231                #[cfg(feature = "defmt")]
232                debug!("Initial state: {:02x}", self.initial);
233            }
234
235            // Parse out initial mode settings
236            let Some(mut mod1) = Mode1::from_bits(self.initial[7]) else {
237                panic!("implementation error");
238            };
239            let Some(mod2) = Mode2::from_bits(self.initial[9]) else {
240                panic!("implementation error");
241            };
242
243            // Clear mode flags
244            mod1.remove(Mode1::PARITY);
245            mod1.remove(Mode1::FAST | Mode1::LOW);
246
247            match mode {
248                Mode::Disabled => (),
249                Mode::Master => mod1 |= Mode1::FAST | Mode1::LOW,
250                Mode::Fast => mod1 |= Mode1::FAST | Mode1::IRQ_EN,
251                Mode::LowPower => mod1 |= Mode1::LOW | Mode1::IRQ_EN,
252                Mode::UltraLowPower => mod1 |= Mode1::IRQ_EN,
253            }
254
255            let mut cfg = [0x00, mod1.bits(), self.initial[8], mod2.bits()];
256
257            self.initial[7] = mod1.bits();
258            self.initial[9] = mod2.bits();
259
260            let mut parity = 0;
261            for v in &cfg {
262                for i in 0..8 {
263                    if v & (1 << i) != 0 {
264                        parity += 1;
265                    }
266                }
267            }
268            if parity % 2 == 0 {
269                mod1 |= Mode1::PARITY;
270                cfg[1] = mod1.bits();
271            }
272
273            #[cfg(feature = "async")]
274            self.i2c.write(self.addr, &cfg).await
275                .map_err(Error::I2c)?;
276            #[cfg(feature = "blocking")]
277            self.i2c.write(self.addr, &cfg)
278                .map_err(Error::I2c)?;
279
280            Ok(())
281        }
282
283        /// Read raw values from the sensor
284        #[maybe(
285            sync(feature = "blocking"),
286            async(feature = "async")
287        )]
288        pub async fn read_raw(&mut self) -> Result<[i16; 4], Error<I2cErr>> {
289            let mut v = [0i16; 4];
290
291            // Read data from device
292            let mut b = [0u8; 7];
293            #[cfg(feature = "async")]
294            self.i2c.read(self.addr, &mut b[..]).await
295                .map_err(Error::I2c)?;
296            #[cfg(feature = "blocking")]
297            self.i2c.read(self.addr, &mut b[..])
298                .map_err(Error::I2c)?;
299
300            // Detect ADC lockup (stalled FRM field)
301            let frm = b[3] & 0b0000_1100;
302            if let Some(last_frm) = self.last_frm
303            && last_frm == frm
304            {
305                return Err(Error::AdcLockup);
306            }
307            self.last_frm = Some(frm);
308
309            // Convert to values
310            // Double-cast here required for sign-extension
311            v[0] = (b[0] as i8 as i16) << 4 | ((b[4] & 0xf0) >> 4) as i16;
312            v[1] = (b[1] as i8 as i16) << 4 | (b[4] & 0x0f) as i16;
313            v[2] = (b[2] as i8 as i16) << 4 | (b[5] & 0x0f) as i16;
314            v[3] = (b[3] as i8 as i16 & 0xf0) << 4 | (b[6] as i16 & 0xff);
315
316            #[cfg(feature = "defmt")]
317            debug!("Read data {:02x} values: {:04x}", b, v);
318
319            Ok(v)
320        }
321
322        /// Read and convert values from the sensor
323        #[maybe(
324            sync(feature = "blocking"),
325            async(feature = "async")
326        )]
327        pub async fn read(&mut self) -> Result<Values, Error<I2cErr>> {
328            #[cfg(feature = "async")]
329            let raw = self.read_raw_async().await?;
330            #[cfg(feature = "blocking")]
331            let raw = self.read_raw_sync()?;
332
333            Ok(Values {
334                x: raw[0] as f32 * 0.098f32,
335                y: raw[1] as f32 * 0.098f32,
336                z: raw[2] as f32 * 0.098f32,
337                temp: (raw[3] - 340) as f32 * 1.1f32 + 24.2f32,
338            })
339        }
340
341        #[cfg(feature = "math")]
342        #[maybe(
343            sync(feature = "blocking"),
344            async(feature = "async")
345        )]
346        pub async fn read_angle_f32(&mut self) -> Result<f32, Error<I2cErr>> {
347            // Read values
348            #[cfg(feature = "async")]
349            let v = self.read_async().await?;
350            #[cfg(feature = "blocking")]
351            let v = self.read_sync()?;
352
353            // https://en.wikipedia.org/wiki/Atan2
354            Ok(libm::Libm::<f32>::atan2(v.x, v.y))
355        }
356    }
357}