# atm90e32-async
[](https://github.com/jethub-iot/atm90e32-async-rs/actions/workflows/ci.yml)
[](https://crates.io/crates/atm90e32-async)
[](https://docs.rs/atm90e32-async)
[](#license)
[](#msrv)
Async `no_std` driver for the Microchip / Atmel **ATM90E32** 3-phase
SPI power metering IC.
Built on top of [`embedded-hal-async`](https://crates.io/crates/embedded-hal-async),
with no hard dependency on any specific async runtime — works with
Embassy, RTIC2, or any executor that provides `SpiDevice` and `DelayNs`.
## Architecture
The crate is split into a **sans-I/O core** and an **async transport** layer:
* `proto` — pure functions: byte-frame building, response parsing,
raw → engineering-unit conversions, and the post-reset init sequence
materialised as data (`[InitStep; 22]`). 100% host-testable.
* `driver` — the `Atm90e32<SPI, D>` struct: wraps SPI reads/writes,
drives the init sequence, issues delays through a generic `DelayNs`.
The practical effect is that the tricky parts — wire format, 32-bit
power-register assembly, signed power-factor decoding, line-frequency
dependent MMode0 bit flip — are covered by 18 host unit tests and
don't need a mock SPI bus.
## Features
What the current version does:
* Chip presence probe via `SysStatus0`
* Full init sequence (soft reset, unlock, enable, calibration gains,
startup thresholds, freq thresholds, PGA, MMode0/1, lock) driven by
a single `Config` struct
* Bulk 3-phase readout: RMS voltage, RMS current, active power,
reactive power, power factor, mains frequency, **phase angle**
* Per-phase reads for each of the above
* **Phase status** decoding: per-phase overcurrent, overvoltage,
voltage sag, phase loss, frequency warnings — from `EMMState0`/`EMMState1`
* **Chip temperature** readout
* Raw register read/write escape hatch (`read_register` / `write_register`)
* Typed errors: `Error::Spi(E)`, `Error::NotPresent`,
`Error::InitFailed(InitStage)` with a per-step breakdown
* Optional `defmt::Format` derives behind the `defmt` feature
* No heap, no global state, no hard runtime dependency
What is **not** yet implemented (PRs welcome):
* Energy accumulation (`EPosA`/`EPosT`/…)
* Apparent power (S) registers
* Harmonic analysis registers
* Zero-crossing interrupts
* Calibration assist helpers (auto-gain)
* ATM90E36 family support (planned — the code layout anticipates it)
* ATM90E26 (8-bit addressing, out of scope)
## Quick start
```rust,no_run
use atm90e32_async::{Atm90e32, Config, LineFreq, PgaGain};
async fn run<SPI, D>(spi: SPI, delay: D) -> Result<(), atm90e32_async::Error<SPI::Error>>
where
SPI: embedded_hal_async::spi::SpiDevice,
D: embedded_hal_async::delay::DelayNs,
{
let mut meter = Atm90e32::new(spi, delay);
meter.probe().await?;
let cfg = Config::default()
.with_voltage_gain([39470, 39470, 39470])
.with_current_gain([65327, 65327, 65327])
.with_line_freq(LineFreq::Hz50)
.with_pga_gain(PgaGain::X2);
meter.init(&cfg).await?;
let r = meter.read_all_phases().await?;
// Raw values — no f32 conversion until you need it:
// r.voltage[0] is hundredths of a volt (u16)
// r.power[0] is signed combined register word (i32)
// Convert on demand:
use atm90e32_async::proto;
let volts = proto::voltage_raw_to_volts(r.voltage[0]);
let status = meter.read_status().await?;
// status.overcurrent, status.overvoltage, status.voltage_sag, status.phase_loss
// status.is_ok()
let temp_raw = meter.read_chip_temperature().await?;
let temp_c = proto::temperature_raw_to_celsius(temp_raw);
Ok(())
}
```
For a real integration on an ESP32 + Embassy + esp-hal, see the sketch
in [`examples/basic.rs`](examples/basic.rs).
## Hardware requirements
* SPI mode 3 (CPOL=1, CPHA=1), MSB-first
* SPI clock ≤ 16 MHz (datasheet limit)
* One GPIO for chip select — exposed through the caller's
`SpiDevice` implementation (e.g. `embedded-hal-bus::spi::ExclusiveDevice`)
The driver itself is transport-agnostic and works with any
`embedded-hal-async::spi::SpiDevice` implementation.
## Datasheet
[ATM90E32AS datasheet (Microchip)](https://ww1.microchip.com/downloads/en/DeviceDoc/Atmel-46002-SE-M90E32AS-Datasheet.pdf)
## MSRV
Minimum supported Rust version is **1.75**, required by
`embedded-hal-async` 1.0 (async-fn-in-traits). MSRV is part of the
semver contract and will only be bumped with a minor version release.
## License
Copyright (c) Viacheslav Bocharov \<v@baodeep.com\> and JetHome (r).
Dual-licensed under either of:
* Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or
<http://www.apache.org/licenses/LICENSE-2.0>)
* GNU General Public License, Version 2.0 or later
([LICENSE-GPL](LICENSE-GPL) or
<https://www.gnu.org/licenses/old-licenses/gpl-2.0.html>)
at your option.
### Contribution
Unless you explicitly state otherwise, any contribution intentionally
submitted for inclusion in this project by you shall be dual-licensed
as above, without any additional terms or conditions.