Skip to main content

atm90e32_async/
driver.rs

1// SPDX-License-Identifier: (GPL-2.0-or-later OR Apache-2.0)
2// Copyright (c) Viacheslav Bocharov <v@baodeep.com> and JetHome (r)
3
4//! Async driver struct for the ATM90E32.
5//!
6//! This module is the thin async transport layer on top of the sans-I/O
7//! `proto` module. Everything that touches the SPI bus or needs a delay
8//! lives here; everything that can be tested on the host lives in `proto`.
9
10use embedded_hal_async::delay::DelayNs;
11use embedded_hal_async::spi::SpiDevice;
12
13use crate::config::Config;
14use crate::error::{Error, InitStage};
15use crate::proto::{self, build_init_sequence};
16use crate::readings::PhaseReadings;
17use crate::registers::*;
18use crate::status::PhaseStatus;
19
20/// Phase selector for per-phase read methods.
21#[derive(Debug, Clone, Copy, PartialEq, Eq)]
22#[cfg_attr(feature = "defmt", derive(defmt::Format))]
23pub enum Phase {
24    /// Phase A (first phase).
25    A,
26    /// Phase B (second phase).
27    B,
28    /// Phase C (third phase).
29    C,
30}
31
32impl Phase {
33    /// Index into a `[T; 3]` array in A, B, C order.
34    #[inline]
35    fn index(self) -> usize {
36        match self {
37            Phase::A => 0,
38            Phase::B => 1,
39            Phase::C => 2,
40        }
41    }
42}
43
44/// Async ATM90E32 driver.
45///
46/// The driver owns a handle to an `embedded-hal-async` [`SpiDevice`] and a
47/// [`DelayNs`] used for the post-reset wait in [`init`](Self::init). Both
48/// are generic so the crate does not pull in any platform-specific runtime.
49///
50/// All measurement methods return **raw register values** without
51/// floating-point conversion. Use the [`proto`] helpers
52/// to convert to engineering units when needed.
53///
54/// ## Example
55///
56/// ```no_run
57/// # use atm90e32_async::{Atm90e32, Config, LineFreq, PgaGain};
58/// # async fn demo<SPI, D>(spi: SPI, delay: D) -> Result<(), atm90e32_async::Error<SPI::Error>>
59/// # where
60/// #     SPI: embedded_hal_async::spi::SpiDevice,
61/// #     D:   embedded_hal_async::delay::DelayNs,
62/// # {
63/// let mut meter = Atm90e32::new(spi, delay);
64/// meter.probe().await?;
65/// let cfg = Config::default()
66///     .with_voltage_gain([39470, 39470, 39470])
67///     .with_current_gain([65327, 65327, 65327])
68///     .with_line_freq(LineFreq::Hz50)
69///     .with_pga_gain(PgaGain::X2);
70/// meter.init(&cfg).await?;
71/// let readings = meter.read_all_phases().await?;
72/// # Ok(())
73/// # }
74/// ```
75pub struct Atm90e32<SPI, D> {
76    spi: SPI,
77    delay: D,
78}
79
80impl<SPI, D> Atm90e32<SPI, D>
81where
82    SPI: SpiDevice,
83    D: DelayNs,
84{
85    /// Create a new driver.
86    ///
87    /// Does no I/O. The SPI device must be already configured for SPI
88    /// mode 3 (CPOL=1, CPHA=1), MSB-first, 16-bit or 8-bit word size,
89    /// clocked at ≤ 16 MHz (datasheet).
90    pub fn new(spi: SPI, delay: D) -> Self {
91        Self { spi, delay }
92    }
93
94    /// Destroy the driver and return the owned SPI device and delay.
95    pub fn release(self) -> (SPI, D) {
96        (self.spi, self.delay)
97    }
98
99    /// Read a 16-bit register.
100    pub async fn read_register(&mut self, addr: u16) -> Result<u16, Error<SPI::Error>> {
101        let tx = proto::build_read_frame(addr);
102        let mut rx = [0u8; 4];
103        self.spi.transfer(&mut rx, &tx).await.map_err(Error::Spi)?;
104        Ok(proto::parse_read_response(&rx))
105    }
106
107    /// Write a 16-bit register.
108    pub async fn write_register(&mut self, addr: u16, value: u16) -> Result<(), Error<SPI::Error>> {
109        let tx = proto::build_write_frame(addr, value);
110        self.spi.write(&tx).await.map_err(Error::Spi)
111    }
112
113    /// Probe for a chip on the bus.
114    ///
115    /// Reads `REG_SYSSTATUS0` and returns its value. If the bus reads back
116    /// as `0x0000` or `0xFFFF` (floating / no device), returns
117    /// [`Error::NotPresent`].
118    pub async fn probe(&mut self) -> Result<u16, Error<SPI::Error>> {
119        let status = self.read_register(REG_SYSSTATUS0).await?;
120        if status == 0x0000 || status == 0xFFFF {
121            Err(Error::NotPresent)
122        } else {
123            Ok(status)
124        }
125    }
126
127    /// Initialise metering with the given configuration.
128    ///
129    /// Performs a soft reset, waits [`Config::post_reset_delay_ms`]
130    /// milliseconds, and then plays the write sequence produced by
131    /// [`build_init_sequence`]. Any SPI failure is wrapped into
132    /// `Error::InitFailed(stage)` with the stage identifying which step
133    /// broke.
134    pub async fn init(&mut self, config: &Config) -> Result<(), Error<SPI::Error>> {
135        // 1. Soft reset: fixed magic value 0x789A per datasheet.
136        self.write_register(REG_SOFTRESET, 0x789A)
137            .await
138            .map_err(|_| Error::InitFailed(InitStage::SoftReset))?;
139        self.delay.delay_ms(config.post_reset_delay_ms).await;
140
141        // 2. Play the data-driven init sequence.
142        for step in build_init_sequence(config).iter() {
143            self.write_register(step.addr, step.value)
144                .await
145                .map_err(|_| Error::InitFailed(step.stage))?;
146        }
147
148        Ok(())
149    }
150
151    // ── 3-phase bulk readout ─────────────────────────────────────────
152
153    /// Read all three phases in one call.
154    ///
155    /// Issues 25 SPI read transactions (3×U + 3×I + 6×P high/low +
156    /// 6×Q high/low + 3×PF + 1×freq + 3×angle) and returns raw
157    /// register values without floating-point conversion.
158    pub async fn read_all_phases(&mut self) -> Result<PhaseReadings, Error<SPI::Error>> {
159        let ua = self.read_register(REG_URMS_A).await?;
160        let ub = self.read_register(REG_URMS_B).await?;
161        let uc = self.read_register(REG_URMS_C).await?;
162
163        let ia = self.read_register(REG_IRMS_A).await?;
164        let ib = self.read_register(REG_IRMS_B).await?;
165        let ic = self.read_register(REG_IRMS_C).await?;
166
167        let pa_h = self.read_register(REG_PMEAN_A).await?;
168        let pa_l = self.read_register(REG_PMEAN_A_LSB).await?;
169        let pb_h = self.read_register(REG_PMEAN_B).await?;
170        let pb_l = self.read_register(REG_PMEAN_B_LSB).await?;
171        let pc_h = self.read_register(REG_PMEAN_C).await?;
172        let pc_l = self.read_register(REG_PMEAN_C_LSB).await?;
173
174        let qa_h = self.read_register(REG_QMEAN_A).await?;
175        let qa_l = self.read_register(REG_QMEAN_A_LSB).await?;
176        let qb_h = self.read_register(REG_QMEAN_B).await?;
177        let qb_l = self.read_register(REG_QMEAN_B_LSB).await?;
178        let qc_h = self.read_register(REG_QMEAN_C).await?;
179        let qc_l = self.read_register(REG_QMEAN_C_LSB).await?;
180
181        let pfa = self.read_register(REG_PFMEAN_A).await?;
182        let pfb = self.read_register(REG_PFMEAN_B).await?;
183        let pfc = self.read_register(REG_PFMEAN_C).await?;
184
185        let freq = self.read_register(REG_FREQ).await?;
186
187        let ang_a = self.read_register(REG_PANGLE_A).await?;
188        let ang_b = self.read_register(REG_PANGLE_B).await?;
189        let ang_c = self.read_register(REG_PANGLE_C).await?;
190
191        Ok(PhaseReadings {
192            voltage: [ua, ub, uc],
193            current: [ia, ib, ic],
194            power: [
195                proto::combine_power_words(pa_h, pa_l),
196                proto::combine_power_words(pb_h, pb_l),
197                proto::combine_power_words(pc_h, pc_l),
198            ],
199            reactive: [
200                proto::combine_power_words(qa_h, qa_l),
201                proto::combine_power_words(qb_h, qb_l),
202                proto::combine_power_words(qc_h, qc_l),
203            ],
204            pf: [pfa as i16, pfb as i16, pfc as i16],
205            frequency: freq,
206            phase_angle: [ang_a, ang_b, ang_c],
207        })
208    }
209
210    // ── Per-phase helpers (raw values) ──────────────────────────────
211
212    /// Read the raw RMS voltage register of a single phase.
213    ///
214    /// Returns hundredths of a volt. Use
215    /// [`proto::voltage_raw_to_volts`]
216    /// to convert to `f32` volts.
217    pub async fn read_voltage(&mut self, phase: Phase) -> Result<u16, Error<SPI::Error>> {
218        const REGS: [u16; 3] = [REG_URMS_A, REG_URMS_B, REG_URMS_C];
219        self.read_register(REGS[phase.index()]).await
220    }
221
222    /// Read the raw RMS current register of a single phase.
223    ///
224    /// Returns thousandths of an amp. Use
225    /// [`proto::current_raw_to_amps`]
226    /// to convert to `f32` amps.
227    pub async fn read_current(&mut self, phase: Phase) -> Result<u16, Error<SPI::Error>> {
228        const REGS: [u16; 3] = [REG_IRMS_A, REG_IRMS_B, REG_IRMS_C];
229        self.read_register(REGS[phase.index()]).await
230    }
231
232    /// Read the raw active power of a single phase.
233    ///
234    /// Returns a signed 32-bit value assembled from the high and low
235    /// register words. Use [`proto::power_combined_to_watts`]
236    /// to convert to `f32` watts.
237    pub async fn read_active_power(&mut self, phase: Phase) -> Result<i32, Error<SPI::Error>> {
238        const HI: [u16; 3] = [REG_PMEAN_A, REG_PMEAN_B, REG_PMEAN_C];
239        const LO: [u16; 3] = [REG_PMEAN_A_LSB, REG_PMEAN_B_LSB, REG_PMEAN_C_LSB];
240        let idx = phase.index();
241        let hi = self.read_register(HI[idx]).await?;
242        let lo = self.read_register(LO[idx]).await?;
243        Ok(proto::combine_power_words(hi, lo))
244    }
245
246    /// Read the raw reactive power of a single phase.
247    ///
248    /// Returns a signed 32-bit value assembled from the high and low
249    /// register words. Use [`proto::power_combined_to_watts`]
250    /// to convert to `f32` vars.
251    pub async fn read_reactive_power(&mut self, phase: Phase) -> Result<i32, Error<SPI::Error>> {
252        const HI: [u16; 3] = [REG_QMEAN_A, REG_QMEAN_B, REG_QMEAN_C];
253        const LO: [u16; 3] = [REG_QMEAN_A_LSB, REG_QMEAN_B_LSB, REG_QMEAN_C_LSB];
254        let idx = phase.index();
255        let hi = self.read_register(HI[idx]).await?;
256        let lo = self.read_register(LO[idx]).await?;
257        Ok(proto::combine_power_words(hi, lo))
258    }
259
260    /// Read the raw power factor of a single phase.
261    ///
262    /// Returns signed thousandths (-1000..=1000). Use
263    /// [`proto::power_factor_raw_to_unitless`]
264    /// to convert to `f32` (-1.0..=1.0).
265    pub async fn read_power_factor(&mut self, phase: Phase) -> Result<i16, Error<SPI::Error>> {
266        const REGS: [u16; 3] = [REG_PFMEAN_A, REG_PFMEAN_B, REG_PFMEAN_C];
267        let raw = self.read_register(REGS[phase.index()]).await?;
268        Ok(raw as i16)
269    }
270
271    /// Read the raw mains line frequency register.
272    ///
273    /// Returns hundredths of a hertz. Use
274    /// [`proto::frequency_raw_to_hz`]
275    /// to convert to `f32` hertz.
276    pub async fn read_frequency(&mut self) -> Result<u16, Error<SPI::Error>> {
277        self.read_register(REG_FREQ).await
278    }
279
280    /// Read the raw mean phase angle register of a single phase.
281    ///
282    /// Returns tenths of a degree. Use
283    /// [`proto::phase_angle_raw_to_degrees`]
284    /// to convert to `f32` degrees.
285    pub async fn read_phase_angle(&mut self, phase: Phase) -> Result<u16, Error<SPI::Error>> {
286        const REGS: [u16; 3] = [REG_PANGLE_A, REG_PANGLE_B, REG_PANGLE_C];
287        self.read_register(REGS[phase.index()]).await
288    }
289
290    /// Read the raw chip temperature register.
291    ///
292    /// Returns the raw `u16` register value (signed when interpreted
293    /// as `i16`). Use [`proto::temperature_raw_to_celsius`]
294    /// to convert to `f32` degrees Celsius.
295    pub async fn read_chip_temperature(&mut self) -> Result<u16, Error<SPI::Error>> {
296        self.read_register(REG_TEMP).await
297    }
298
299    /// Read the EMM status registers and decode phase/frequency conditions.
300    ///
301    /// Returns a [`PhaseStatus`] with per-phase overcurrent, overvoltage,
302    /// voltage sag, and phase loss flags, plus frequency threshold warnings.
303    pub async fn read_status(&mut self) -> Result<PhaseStatus, Error<SPI::Error>> {
304        let s0 = self.read_register(REG_EMMSTATE0).await?;
305        let s1 = self.read_register(REG_EMMSTATE1).await?;
306        Ok(PhaseStatus::from_emm(s0, s1))
307    }
308}