embedded_devices/devices/belling/bl0942/
mod.rs

1//! The BL0942 is a built-in clock calibration-free energy metering IC, which is suitable for
2//! single-phase multi-function electricity meters, smart sockets, smart home appliances and other
3//! applications.
4//!
5//! The BL0942 incorporates two sigma delta ADCs with a high accuracy energy measurement core. It measures
6//! line voltage and current and calculate active energy as well as instantaneous voltage and current.
7//!
8//! ## Usage (sync)
9//!
10//! ```rust
11//! # #[cfg(feature = "sync")] mod test {
12//! # fn test<I, D>(mut spi: I, delay: D) -> Result<(), embedded_interfaces::TransportError<embedded_devices::devices::belling::ChecksumError, I::Error>>
13//! # where
14//! #   I: embedded_hal::spi::SpiDevice,
15//! #   D: embedded_hal::delay::DelayNs
16//! # {
17//! use embedded_devices::devices::belling::bl0942::BL0942Sync;
18//! use embedded_devices::sensor::OneshotSensorSync;
19//! use uom::si::electric_current::ampere;
20//! use uom::si::electric_potential::volt;
21//! use uom::si::electrical_resistance::ohm;
22//! use uom::si::energy::watt_hour;
23//! use uom::si::f64::{ElectricCurrent, ElectricalResistance};
24//! use uom::si::frequency::hertz;
25//! use uom::si::power::watt;
26//! use uom::si::thermodynamic_temperature::degree_celsius;
27//!
28//! // Create and initialize the device
29//! let mut bl0942 = BL0942Sync::new_spi(delay, spi,
30//!     ElectricalResistance::new::<ohm>(0.001),
31//!     ElectricalResistance::new::<ohm>(510.0),
32//!     ElectricalResistance::new::<ohm>(5.0 * 390_000.0),
33//! );
34//! bl0942.init().unwrap();
35//! let measurement = bl0942.measure().unwrap();
36//! let voltage = measurement.voltage.get::<volt>();
37//! let current = measurement.current.get::<ampere>();
38//! let power = measurement.power.get::<watt>();
39//! let energy = measurement.energy.get::<watt_hour>();
40//! let frequency = measurement.frequency.get::<hertz>();
41//! println!("Current measurement: {:?}V, {:?}A, {:?}W, {:?}Wh, {:?}Hz", voltage, current, power, energy, frequency);
42//! # Ok(())
43//! # }
44//! # }
45//! ```
46//!
47//! ## Usage (async)
48//!
49//! ```rust
50//! # #[cfg(feature = "async")] mod test {
51//! # async fn test<I, D>(mut spi: I, delay: D) -> Result<(), embedded_interfaces::TransportError<embedded_devices::devices::belling::ChecksumError, I::Error>>
52//! # where
53//! #   I: embedded_hal_async::spi::SpiDevice,
54//! #   D: embedded_hal_async::delay::DelayNs
55//! # {
56//! use embedded_devices::devices::belling::bl0942::BL0942Async;
57//! use embedded_devices::sensor::OneshotSensorAsync;
58//! use uom::si::electric_current::ampere;
59//! use uom::si::electric_potential::volt;
60//! use uom::si::electrical_resistance::ohm;
61//! use uom::si::energy::watt_hour;
62//! use uom::si::f64::{ElectricCurrent, ElectricalResistance};
63//! use uom::si::frequency::hertz;
64//! use uom::si::power::watt;
65//! use uom::si::thermodynamic_temperature::degree_celsius;
66//!
67//! // Create and initialize the device
68//! let mut bl0942 = BL0942Async::new_spi(delay, spi,
69//!     ElectricalResistance::new::<ohm>(0.001),
70//!     ElectricalResistance::new::<ohm>(510.0),
71//!     ElectricalResistance::new::<ohm>(5.0 * 390_000.0),
72//! );
73//! bl0942.init().await.unwrap();
74//! let measurement = bl0942.measure().await.unwrap();
75//! let voltage = measurement.voltage.get::<volt>();
76//! let current = measurement.current.get::<ampere>();
77//! let power = measurement.power.get::<watt>();
78//! let energy = measurement.energy.get::<watt_hour>();
79//! let frequency = measurement.frequency.get::<hertz>();
80//! println!("Current measurement: {:?}V, {:?}A, {:?}W, {:?}Wh, {:?}Hz", voltage, current, power, energy, frequency);
81//! # Ok(())
82//! # }
83//! # }
84//! ```
85
86use super::ChecksumError;
87use embedded_devices_derive::forward_register_fns;
88use embedded_devices_derive::sensor;
89use embedded_interfaces::TransportError;
90use uom::si::electrical_resistance::ohm;
91use uom::si::f64::{ElectricCurrent, ElectricPotential, ElectricalResistance, Energy, Frequency, Power};
92
93pub mod registers;
94
95pub const V_REF: f64 = 1.218;
96pub const V_RMS_COEFF: f64 = V_REF / (73989.0 * 1000.0);
97pub const I_RMS_COEFF: f64 = V_REF / (305978.0 * 1000.0);
98pub const I_FAST_RMS_FACTOR: f64 = 0.363;
99pub const P_COEFF: f64 = V_REF * V_REF / (3537.0 * 1_000_000.0);
100pub const E_COEFF: f64 = 1638.4 * 256.0 / 3600.0;
101
102/// Measurement data
103#[derive(Debug, embedded_devices_derive::Measurement)]
104pub struct Measurement {
105    /// Measured voltage
106    #[measurement(Voltage)]
107    pub voltage: ElectricPotential,
108    /// Measured current
109    #[measurement(Current)]
110    pub current: ElectricCurrent,
111    /// Measured power
112    #[measurement(Power)]
113    pub power: Power,
114    /// Measured energy
115    #[measurement(Energy)]
116    pub energy: Energy,
117    /// Measured line frequency
118    #[measurement(Frequency)]
119    pub frequency: Frequency,
120}
121
122/// The BL0942 is a built-in clock calibration-free energy metering IC, which is suitable for
123/// single-phase multi-function electricity meters, smart sockets, smart home appliances and other
124/// applications.
125///
126/// For a full description and usage examples, refer to the [module documentation](self).
127#[maybe_async_cfg::maybe(
128    idents(hal(sync = "embedded_hal", async = "embedded_hal_async"), RegisterInterface),
129    sync(feature = "sync"),
130    async(feature = "async")
131)]
132pub struct BL0942<D: hal::delay::DelayNs, I: embedded_interfaces::registers::RegisterInterface> {
133    /// The delay provider
134    delay: D,
135    /// The interface to communicate with the device
136    interface: I,
137    /// The shunt resistor value in Ohms.
138    pub shunt_resistance: ElectricalResistance,
139    /// The value of the voltage divider calculated as (R1 + R2) / R1
140    /// where R1 is the small value (e.g. 1k) and R2 is the large value (e.g. 6*390k).
141    pub voltage_divider_coeff: f64,
142    /// Precomputed LSB value of the current register
143    pub i_lsb: f64,
144    /// Precomputed LSB value of the voltage register
145    pub v_lsb: f64,
146    /// Precomputed LSB value of the power register
147    pub p_lsb: f64,
148    /// Precomputed LSB value of the energy register
149    pub e_lsb: f64,
150}
151
152pub trait BL0942Register {}
153
154#[maybe_async_cfg::maybe(
155    idents(hal(sync = "embedded_hal", async = "embedded_hal_async"), SpiDevice),
156    sync(feature = "sync"),
157    async(feature = "async")
158)]
159impl<D, I> BL0942<D, embedded_interfaces::spi::SpiDevice<I>>
160where
161    I: hal::spi::r#SpiDevice,
162    D: hal::delay::DelayNs,
163{
164    /// Initializes a new device from the specified SPI device.
165    /// This consumes the SPI device `I`.
166    ///
167    /// The value of the voltage divider calculated as (R1 + R2) / R1
168    /// where R1 is the small value (e.g. 1k) and R2 is the large value (e.g. 6*390k).
169    ///
170    /// The device requires SPI mode 1 (CPOL=0 and CPHA=1).
171    #[inline]
172    pub fn new_spi(
173        delay: D,
174        interface: I,
175        shunt_resistance: ElectricalResistance,
176        voltage_divider_r1: ElectricalResistance,
177        voltage_divider_r2: ElectricalResistance,
178    ) -> Self {
179        let r1 = voltage_divider_r1.get::<ohm>();
180        let r2 = voltage_divider_r2.get::<ohm>();
181        let voltage_divider_coeff = (r1 + r2) / r1;
182        let p_lsb = P_COEFF * voltage_divider_coeff / shunt_resistance.get::<ohm>();
183        Self {
184            delay,
185            interface: embedded_interfaces::spi::SpiDevice::new(interface),
186            shunt_resistance,
187            voltage_divider_coeff,
188            i_lsb: I_RMS_COEFF / shunt_resistance.get::<ohm>(),
189            v_lsb: V_RMS_COEFF * voltage_divider_coeff,
190            p_lsb,
191            e_lsb: E_COEFF * p_lsb,
192        }
193    }
194}
195
196#[forward_register_fns]
197#[sensor(Voltage, Current, Power, Energy)]
198#[maybe_async_cfg::maybe(
199    idents(
200        hal(sync = "embedded_hal", async = "embedded_hal_async"),
201        RegisterInterface,
202        ResettableDevice
203    ),
204    sync(feature = "sync"),
205    async(feature = "async")
206)]
207impl<D: hal::delay::DelayNs, I: embedded_interfaces::registers::RegisterInterface> BL0942<D, I> {
208    /// Initializes the sensor by resetting it.
209    pub async fn init(&mut self) -> Result<(), TransportError<ChecksumError, I::BusError>> {
210        use crate::device::ResettableDevice;
211
212        // Reset the device and wait until it is ready. The datasheet specifies
213        // no startup time, we use 1 millisecond for good measure.
214        self.reset().await?;
215        self.delay.delay_ms(1).await;
216
217        Ok(())
218    }
219}
220
221#[maybe_async_cfg::maybe(
222    idents(
223        hal(sync = "embedded_hal", async = "embedded_hal_async"),
224        RegisterInterface,
225        ResettableDevice
226    ),
227    sync(feature = "sync"),
228    async(feature = "async")
229)]
230impl<D: hal::delay::DelayNs, I: embedded_interfaces::registers::RegisterInterface> crate::device::ResettableDevice
231    for BL0942<D, I>
232{
233    type Error = TransportError<ChecksumError, I::BusError>;
234
235    /// Performs a soft-reset of the device, restoring internal registers to power-on reset values.
236    async fn reset(&mut self) -> Result<(), Self::Error> {
237        self.write_register(self::registers::WriteProtection::default().with_key(registers::ProtectionKey::Unlocked))
238            .await?;
239        self.write_register(self::registers::SoftReset::default().with_magic(registers::ResetMagic::Reset))
240            .await?;
241        Ok(())
242    }
243}
244
245#[maybe_async_cfg::maybe(
246    idents(
247        hal(sync = "embedded_hal", async = "embedded_hal_async"),
248        RegisterInterface,
249        OneshotSensor
250    ),
251    sync(feature = "sync"),
252    async(feature = "async")
253)]
254impl<D: hal::delay::DelayNs, I: embedded_interfaces::registers::RegisterInterface> crate::sensor::OneshotSensor
255    for BL0942<D, I>
256{
257    type Error = TransportError<ChecksumError, I::BusError>;
258    type Measurement = Measurement;
259
260    /// Performs a one-shot measurement.
261    async fn measure(&mut self) -> Result<Self::Measurement, Self::Error> {
262        use uom::si::{electric_current::ampere, electric_potential::volt, energy::watt_hour, power::watt};
263
264        let i_rms = self.read_register::<self::registers::CurrentRms>().await?.read_value() as f64 * self.i_lsb;
265        let v_rms = self.read_register::<self::registers::VoltageRms>().await?.read_value() as f64 * self.v_lsb;
266        let power = self.read_register::<self::registers::ActivePower>().await?.read_value() as f64 * self.p_lsb;
267        let energy = self
268            .read_register::<self::registers::EnergyCounter>()
269            .await?
270            .read_value() as f64
271            * self.e_lsb;
272        let frequency = self
273            .read_register::<self::registers::Frequency>()
274            .await?
275            .read_frequency();
276        let measurement = Measurement {
277            voltage: ElectricPotential::new::<volt>(v_rms),
278            current: ElectricCurrent::new::<ampere>(i_rms),
279            power: Power::new::<watt>(power),
280            energy: Energy::new::<watt_hour>(energy),
281            frequency,
282        };
283
284        Ok(measurement)
285    }
286}
287
288#[maybe_async_cfg::maybe(
289    idents(
290        hal(sync = "embedded_hal", async = "embedded_hal_async"),
291        RegisterInterface,
292        OneshotSensor,
293        ContinuousSensor
294    ),
295    sync(feature = "sync"),
296    async(feature = "async")
297)]
298impl<D: hal::delay::DelayNs, I: embedded_interfaces::registers::RegisterInterface> crate::sensor::ContinuousSensor
299    for BL0942<D, I>
300{
301    type Error = TransportError<ChecksumError, I::BusError>;
302    type Measurement = Measurement;
303
304    /// Starts continuous measurement. No-op, always enabled.
305    async fn start_measuring(&mut self) -> Result<(), Self::Error> {
306        Ok(())
307    }
308
309    /// Stops continuous measurement. No-op, cannot disable.
310    async fn stop_measuring(&mut self) -> Result<(), Self::Error> {
311        Ok(())
312    }
313
314    /// Expected amount of time between measurements in microseconds.
315    async fn measurement_interval_us(&mut self) -> Result<u32, Self::Error> {
316        Ok(self
317            .read_register::<self::registers::UserMode>()
318            .await?
319            .read_rms_update_rate()
320            .as_us())
321    }
322
323    /// Returns the most recent measurement. Will never return None.
324    async fn current_measurement(&mut self) -> Result<Option<Self::Measurement>, Self::Error> {
325        use crate::sensor::OneshotSensor;
326        Ok(Some(self.measure().await?))
327    }
328
329    /// Not supported, always returns true.
330    async fn is_measurement_ready(&mut self) -> Result<bool, Self::Error> {
331        Ok(true)
332    }
333
334    /// Opportunistically waits one conversion interval and returns the measurement.
335    async fn next_measurement(&mut self) -> Result<Self::Measurement, Self::Error> {
336        use crate::sensor::OneshotSensor;
337        let interval = self.measurement_interval_us().await?;
338        self.delay.delay_us(interval).await;
339        self.measure().await
340    }
341}