tmag5273/lib.rs
1//! Platform-agnostic `no_std` driver for the TI TMAG5273 3-axis Hall-effect sensor.
2//!
3//! This crate provides a type-safe, zero-allocation driver for the TMAG5273 family
4//! of linear 3D Hall-effect sensors over I2C, using [`embedded-hal`] 1.0 traits.
5//!
6//! # Supported Variants
7//!
8//! All eight TMAG5273 variants are supported (A1, A2, B1, B2, C1, C2, D1, D2),
9//! differing in default I2C address and magnetic field sensitivity range.
10//!
11//! | Variant | Address | XY range (low / high) | Z range (low / high) |
12//! |---------|---------|----------------------|---------------------|
13//! | A1, B1, C1, D1 | 0x35, 0x22, 0x78, 0x44 | ±40 / ±80 mT | ±40 / ±80 mT |
14//! | A2, B2, C2, D2 | 0x35, 0x22, 0x78, 0x44 | ±133 / ±266 mT | ±133 / ±266 mT |
15//!
16//! # Quick Start
17//!
18//! Three ways to construct the driver (simplest first):
19//!
20//! ```no_run
21//! # use embedded_hal_mock::eh1::i2c::{Mock as I2cMock, Transaction};
22//! # let i2c = I2cMock::new(&[]);
23//! use tmag5273::{Tmag5273, DeviceVariant, ConfigBuilder};
24//!
25//! // Option 1: Plug-and-play — scans all known addresses, auto-detects variant
26//! // let sensor = Tmag5273::scan(i2c).expect("no TMAG5273 found");
27//!
28//! // Option 2: Known address, auto-detect variant
29//! // let sensor = Tmag5273::detect(i2c, 0x22).expect("no sensor at 0x22");
30//!
31//! // Option 3: Fully explicit — no I2C activity until init()
32//! let sensor = Tmag5273::new(i2c, DeviceVariant::B1);
33//!
34//! // Configure and initialize
35//! let config = ConfigBuilder::new().build().unwrap();
36//! // let mut sensor = sensor.init(&config).unwrap();
37//!
38//! // Read measurements
39//! // let mag = sensor.read_magnetic().unwrap();
40//! // let temp = sensor.read_temperature().unwrap();
41//! // let all = sensor.read_all().unwrap(); // coherent temp + mag in one burst
42//! ```
43//!
44//! # Features
45//!
46//! - `crc` — Enables CRC-8 validation on I2C reads (disables burst reads)
47//! - `defmt` — Enables `defmt::Format` derives on all public types
48//! - `libm` — Enables the L3 analysis layer: `magnitude_3d()`, `plane_angles()` with
49//! per-plane angle/magnitude, `AxisCalibrator` for auto-detecting the optimal hardware
50//! angle axis pair, and related types (`PlaneAxis`, `PlaneAngle`, `PlaneAngles`,
51//! `AxisRecommendation`)
52//!
53//! [`embedded-hal`]: https://docs.rs/embedded-hal/1.0
54
55#![no_std]
56#![forbid(unsafe_code)]
57#![deny(missing_docs)]
58#![cfg_attr(docsrs, feature(doc_cfg))]
59#![cfg_attr(docsrs, doc(auto_cfg))]
60
61/// Gated `defmt` logging macros.
62///
63/// These wrap `defmt::trace!` / `debug!` / `warn!` / `error!` behind
64/// `#[cfg(all(feature = "defmt", not(test)))]`. The `not(test)` gate is
65/// needed because `defmt` requires a global logger (e.g., `defmt-rtt`)
66/// at link time, which only exists on real hardware — not in host-side
67/// `cargo test`.
68macro_rules! defmt_trace {
69 ($($arg:tt)*) => {
70 #[cfg(all(feature = "defmt", not(test)))]
71 defmt::trace!($($arg)*);
72 };
73}
74macro_rules! defmt_debug {
75 ($($arg:tt)*) => {
76 #[cfg(all(feature = "defmt", not(test)))]
77 defmt::debug!($($arg)*);
78 };
79}
80macro_rules! defmt_warn {
81 ($($arg:tt)*) => {
82 #[cfg(all(feature = "defmt", not(test)))]
83 defmt::warn!($($arg)*);
84 };
85}
86macro_rules! defmt_error {
87 ($($arg:tt)*) => {
88 #[cfg(all(feature = "defmt", not(test)))]
89 defmt::error!($($arg)*);
90 };
91}
92
93/// Generates a `TryFrom<u8>` impl. Two forms:
94///
95/// **Enum form** — maps discriminant values to named variants:
96/// ```ignore
97/// impl_try_from_u8!(MyEnum { 0 => A, 1 => B });
98/// ```
99///
100/// **Struct form** — delegates to `from_code(u8) -> Option<Self>`:
101/// ```ignore
102/// impl_try_from_u8!(MyStruct);
103/// ```
104///
105/// Unknown values return `Err(value)` and emit a `defmt::warn!` when the
106/// `defmt` feature is active.
107macro_rules! impl_try_from_u8 {
108 // Enum arm: discrete variant mapping.
109 ($enum:ty { $($val:expr => $variant:ident),+ $(,)? }) => {
110 impl TryFrom<u8> for $enum {
111 type Error = u8;
112
113 fn try_from(val: u8) -> Result<Self, u8> {
114 match val {
115 $($val => Ok(Self::$variant),)+
116 other => {
117 defmt_warn!("invalid register value: 0x{:02X}", other);
118 Err(other)
119 }
120 }
121 }
122 }
123 };
124 // Struct arm: delegates to `from_code()` for range-validated newtypes.
125 ($struct:ty) => {
126 impl TryFrom<u8> for $struct {
127 type Error = u8;
128
129 fn try_from(val: u8) -> Result<Self, u8> {
130 match Self::from_code(val) {
131 Some(val) => Ok(val),
132 None => {
133 defmt_warn!("invalid register value: 0x{:02X}", val);
134 Err(val)
135 }
136 }
137 }
138 }
139 };
140}
141
142mod config;
143mod device;
144mod error;
145mod register;
146mod rotation;
147mod types;
148
149#[cfg(feature = "libm")]
150mod angle;
151
152#[cfg(feature = "libm")]
153pub use angle::{AxisCalibrator, AxisRecommendation, PlaneAngle, PlaneAngles, PlaneAxis, Welford};
154pub use config::{
155 CalibrationConfig, Config, ConfigBuilder, InterruptConfig, ThresholdConfig, ThresholdHysteresis,
156};
157pub use device::{Configured, Tmag5273, Unconfigured};
158pub use error::{ConfigError, Error, InitError};
159pub use rotation::{Cordic, CordicTracker, RotationTracker, TrackingMode, ZeroCrossing};
160pub use types::{
161 AngleEnable, AngleReading, Axis, Celsius, ConversionAverage, ConversionStatus, CordicMagnitude,
162 Crossings, Degrees, DeviceStatus, DeviceVariant, Diagnostics, I2cReadMode, InterruptMode,
163 InterruptState, Lsb, MagneticChannel, MagneticGainChannel, MagneticReading,
164 MagneticTempCoefficient, MagneticThresholdDirection, MicrosIsr, MicrosRange, MilliTesla,
165 NoDelay, OperatingMode, PoleCount, PowerNoiseMode, Range, Rpm, SensorReading, SignedDegrees,
166 SleepTime, TempThresholdConfig, ThresholdCrossingCount, TriggerMode, WAKE_DELAY,
167 WAKE_RETRY_DELAY,
168};