Skip to main content

ads111x_rs/
lib.rs

1#![no_std]
2//! ADS1113/ADS1114/ADS1115 driver.
3//!
4//! ## Audit summary (pre-existing flaws fixed by this redesign)
5//! - Async impl busy-polled CONFIG (`write_read` in a tight loop) — wastes bus/CPU.
6//! - `enable_conversion_ready_pin` existed but was disconnected from async flow.
7//! - Sync/async config-byte construction duplicated; raw `u8` MUX allowed invalid values.
8//! - `Config` comparator/PGA fields applied to ADS1113 which lacks them.
9//! - No mode (single-shot/continuous), polarity, timeout, or stale-data protection.
10//!
11//! ## Architecture
12//! ```text
13//! START CONVERSION -> await ALERT/RDY event (preferred) or timed delay -> READ CONVERSION
14//! ```
15//! Core crate depends only on `embedded-hal(-async)` plus two small generic traits
16//! (`ConversionReady`, `AsyncDelayUs`) that Embassy types can implement downstream.
17//! No `embassy_*` dependency. No alloc.
18//!
19//! ## Sampling semantics (control-loop guidance)
20//! Continuous mode is **latest-sample acquisition**, not guaranteed-sample
21//! acquisition: each ADS111x has one conversion register and no FIFO, so a
22//! slow reader loses intermediate samples. Multi-device capture overlaps
23//! conversion windows but does NOT phase-lock sampling. "Differential" means a
24//! differential MUX pair on the single converter — never simultaneous sampling.
25
26#[cfg(feature = "async")]
27#[allow(unused_imports)]
28use embedded_hal_async::i2c::I2c as AsyncI2c;
29use embedded_hal::i2c::I2c;
30
31/// Register pointers.
32const REG_CONVERSION: u8 = 0x00;
33const REG_CONFIG: u8 = 0x01;
34const REG_LO_THRESH: u8 = 0x02;
35const REG_HI_THRESH: u8 = 0x03;
36
37/// OS bit: write 1 to start single-shot conversion; reads 1 when complete/idle.
38const OS_MASK: u8 = 0x80;
39/// Returns true if OS bit indicates conversion complete.
40#[inline]
41pub fn conversion_complete(cfg_hi: u8) -> bool {
42    cfg_hi & OS_MASK != 0
43}
44
45pub struct ADS111x<I2C, const MODEL: u8> {
46    address: Address,
47    i2c: I2C,
48    pub config: Config,
49}
50
51pub type ADS1115<I2C> = ADS111x<I2C, 5>;
52pub type ADS1114<I2C> = ADS111x<I2C, 4>;
53pub type ADS1113<I2C> = ADS111x<I2C, 3>;
54
55/// Unified error: preserves I2C error, adds async wait failures. No alloc.
56///
57/// Note: there is deliberately NO timeout variant. Timeout/cancellation policy
58/// belongs to the application layer (e.g. Embassy's `with_timeout` around the
59/// RDY wait); a `no_std` HAL driver cannot choose a sensible deadline itself.
60#[derive(Debug, Clone, Copy, PartialEq, Eq)]
61pub enum Error<I2cError, WaitError = ()> {
62    I2c(I2cError),
63    Wait(WaitError),
64}
65
66impl<I2cError, WaitError> From<I2cError> for Error<I2cError, WaitError> {
67    fn from(e: I2cError) -> Self {
68        Self::I2c(e)
69    }
70}
71
72/// Input MUX selection (typed; replaces raw `u8`).
73#[derive(Debug, Clone, Copy, PartialEq, Eq)]
74#[repr(u8)]
75pub enum Mux {
76    A0N1 = 0b000,
77    A0N3 = 0b001,
78    A1N3 = 0b010,
79    A2N3 = 0b011,
80    A0 = 0b100,
81    A1 = 0b101,
82    A2 = 0b110,
83    A3 = 0b111,
84}
85
86/// Operating mode.
87#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
88pub enum Mode {
89    #[default]
90    SingleShot,
91    Continuous,
92}
93
94/// Expected ALERT/RDY edge; must match `Config.comp_pol`.
95/// ActiveLow -> wait for falling edge; ActiveHigh -> rising edge.
96#[derive(Debug, Clone, Copy, PartialEq, Eq)]
97pub enum ConversionReadyPolarity {
98    ActiveLow,
99    ActiveHigh,
100}
101
102impl From<CompPol> for ConversionReadyPolarity {
103    fn from(p: CompPol) -> Self {
104        match p {
105            CompPol::ActiveLow => Self::ActiveLow,
106            CompPol::ActiveHigh => Self::ActiveHigh,
107        }
108    }
109}
110
111/// Generic conversion-ready signal (Embassy GPIO input implements this downstream).
112/// Implementations MUST be interrupt/event driven (`wait_for_falling_edge`-style),
113/// never `while pin.is_high() { Timer::after().await }`.
114pub trait ConversionReady {
115    type Error;
116    type WaitFuture<'a>: core::future::Future<Output = Result<(), Self::Error>> + 'a
117    where
118        Self: 'a;
119    /// Synchronously arm the edge detector for `polarity`.
120    ///
121    /// # Contract (required for race safety)
122    /// Must synchronously, in this order:
123    /// 1. clear/consume any already-pending edge/event state,
124    /// 2. configure the requested edge polarity,
125    /// 3. enable/register the event source,
126    /// and return only after the detector is ready to observe the NEXT edge.
127    /// Step 1 is load-bearing: without it a stale pending interrupt (e.g. a
128    /// leftover EXTI flag) makes the subsequent wait return immediately and
129    /// the driver reads the conversion register too early. An implementation
130    /// that merely records the polarity and defers registration to the first
131    /// poll does NOT satisfy this contract: drivers call `arm()` on every RDY
132    /// source BEFORE starting any ADC conversion, precisely so a short (~8µs)
133    /// conversion-ready pulse cannot occur while no detector is listening.
134    fn arm(&mut self, polarity: ConversionReadyPolarity);
135    /// Wait for the conversion-ready edge. At the latest, the detector is
136    /// armed when the returned future is first polled — but full start-before-
137    /// arm safety is only achieved when the driver called [`ConversionReady::arm`]
138    /// before starting the conversion (see `wait_all_ready`).
139    fn wait_for_ready(&mut self, polarity: ConversionReadyPolarity) -> Self::WaitFuture<'_>;
140}
141
142/// Blocking (non-async) conversion-ready signal: the sync counterpart of
143/// [`ConversionReady`], for bare-metal / RTIC-style drivers without an executor.
144/// Implementations MUST block on a hardware event (GPIO interrupt flag, EXTI
145/// wait, etc.), never poll the ADC CONFIG register — OS-bit polling is invalid
146/// in continuous mode (OS reads 0 there) and would hang forever.
147pub trait BlockingReady {
148    type Error;
149    /// Synchronously arm the edge detector for `polarity`, returning only once
150    /// the detector will observe the next edge. Same contract as
151    /// [`ConversionReady::arm`] — including clearing stale pending state
152    /// first: lazy/deferred arming reintroduces the start-before-arm race,
153    /// and a leftover pending flag causes an immediate bogus wake.
154    fn arm(&mut self, polarity: ConversionReadyPolarity);
155    /// Block until the conversion-ready edge for `polarity` is observed.
156    fn wait_for_ready(&mut self, polarity: ConversionReadyPolarity) -> Result<(), Self::Error>;
157}
158
159/// Concurrent wait over N ready sources: ALL `wait_for_ready` futures are
160/// created up front and polled round-robin inside ONE future, so every wait
161/// observes its edge without sequential-await gaps. This fixes the
162/// *sequential-wait* race (`wait(RDY0).await; wait(RDY1).await; ...` missing
163/// the ~8µs continuous-mode pulses of ADC1..N while blocked on ADC0).
164///
165/// NOTE — arming vs. polling: each detector is (at the latest) armed when its
166/// future is first polled, i.e. when THIS future is first polled. That alone
167/// does NOT protect against pulses that fire between ADC START and the first
168/// poll. Drivers must therefore call [`ConversionReady::arm`] on every source
169/// BEFORE starting any conversion; `wait_all_ready` then only observes edges
170/// on already-armed detectors. (`capture_parallel_event`/`next_all_event` do
171/// exactly this: arm all → start → wait all → read all.)
172///
173/// Returns when every source is ready; on failure reports the failing source
174/// index via [`WaitFailed`]. `no_std`, no alloc, no executor-specific join dependency.
175#[cfg(feature = "async")]
176pub fn wait_all_ready<R: ConversionReady, const N: usize>(
177    rdy: &mut [R; N],
178    pol: [ConversionReadyPolarity; N],
179) -> JoinAllReady<'_, R, N> {    // Disjoint elements via raw pointer: each future borrows only its own RDY.
180    let ptr = rdy.as_mut_ptr();
181    let futs = core::array::from_fn(|i| unsafe { (&mut *ptr.add(i)).wait_for_ready(pol[i]) });
182    JoinAllReady { futs, done: [false; N], remaining: N }
183}
184
185/// Wait failure with the source index of the failed RDY future, so callers can
186/// attribute the error to the correct device. `no_std`, no alloc.
187#[cfg(feature = "async")]
188#[derive(Debug, Clone, Copy, PartialEq, Eq)]
189pub struct WaitFailed<E> {
190    pub index: usize,
191    pub error: E,
192}
193
194/// Future returned by [`wait_all_ready`]. See its docs for race-safety rationale.
195#[cfg(feature = "async")]
196pub struct JoinAllReady<'r, R: ConversionReady + 'r, const N: usize> {
197    futs: [R::WaitFuture<'r>; N],
198    done: [bool; N],
199    remaining: usize,
200}
201
202#[cfg(feature = "async")]
203impl<R: ConversionReady, const N: usize> core::future::Future for JoinAllReady<'_, R, N> {    type Output = Result<(), WaitFailed<R::Error>>;
204    fn poll(
205        self: core::pin::Pin<&mut Self>,
206        cx: &mut core::task::Context<'_>,
207    ) -> core::task::Poll<Self::Output> {
208        // SAFETY: futures are never moved after creation; only pinned polls.
209        let this = unsafe { self.get_unchecked_mut() };
210        let mut i = 0;
211        while i < N {
212            if !this.done[i] {
213                let f = unsafe { core::pin::Pin::new_unchecked(&mut this.futs[i]) };
214                match f.poll(cx) {
215                    core::task::Poll::Ready(Ok(())) => {
216                        this.done[i] = true;
217                        this.remaining -= 1;
218                    }
219                    core::task::Poll::Ready(Err(error)) => {
220                        return core::task::Poll::Ready(Err(WaitFailed { index: i, error }))
221                    }
222                    core::task::Poll::Pending => {}
223                }
224            }
225            i += 1;
226        }
227        if this.remaining == 0 {
228            core::task::Poll::Ready(Ok(()))
229        } else {
230            core::task::Poll::Pending
231        }
232    }
233}
234
235/// Generic async delay (microseconds) for timed-wait fallback / timeouts.
236/// Named-future (GAT) style so no `Send` bound is imposed: embedded executors
237/// are not necessarily threaded. Embassy users should prefer the `embassy`
238/// feature's first-class timer APIs instead of implementing this manually.
239pub trait AsyncDelayUs {
240    type DelayFuture<'a>: core::future::Future<Output = ()> + 'a
241    where
242        Self: 'a;
243    fn delay_us(&mut self, us: u32) -> Self::DelayFuture<'_>;
244}
245
246// ---------- shared config-byte construction (no duplication) ----------
247
248/// Build CONFIG high/low bytes. `os_start=true` sets OS=1 (single-shot start only).
249/// Continuous mode MUST use os_start=false (OS is don't-care; MODE=0 runs freely).
250/// Bit layout: OS|MUX[2:0]|PGA[2:0]|MODE | DR[2:0]|COMP_MODE|COMP_POL|COMP_LAT|COMP_QUE[1:0]
251fn build_config_bytes(cfg: &Config, mux_bits: u8, os_start: bool, mode: Mode) -> [u8; 2] {
252    debug_assert!(!(mode == Mode::Continuous && os_start), "continuous must not set OS=1");
253    let os = if os_start { 1u8 << 7 } else { 0 };
254    let mode_bit = match mode {
255        Mode::SingleShot => 1u8,
256        Mode::Continuous => 0u8,
257    };
258    let hi = os | ((mux_bits & 0x07) << 4) | (cfg.gain as u8) | mode_bit;
259    let lo = (cfg.data_rate as u8)
260        | (cfg.comp_mode as u8)
261        | (cfg.comp_pol as u8)
262        | (cfg.comp_lat as u8)
263        | (cfg.comp_que as u8);
264    [hi, lo]
265}
266
267/// ADS1113 has no MUX/PGA/comparator: fixed single channel, gain bits forced 0.
268fn build_config_bytes_ads1113(data_rate: DataRate, os_start: bool) -> [u8; 2] {
269    let os = if os_start { 1u8 << 7 } else { 0 };
270    // Datasheet: ADS1113 config fixed except OS/DR/MODE(=1 single-shot).
271    let hi = os | 1u8;
272    let lo = data_rate as u8 | 0x03; // COMP_QUE=11b disabled
273    [hi, lo]
274}
275
276/// ADS1113 continuous config: MODE=0 free-running, OS not set.
277/// Only consumed by the Embassy timer path.
278#[cfg(feature = "embassy")]
279fn build_config_bytes_ads1113_continuous(data_rate: DataRate) -> [u8; 2] {
280    [0u8, data_rate as u8 | 0x03]
281}
282
283fn write_config<I2C: I2c>(i2c: &mut I2C, addr: u8, bytes: [u8; 2]) -> Result<(), I2C::Error> {
284    i2c.write(addr, &[REG_CONFIG, bytes[0], bytes[1]])
285}
286
287fn read_conversion<I2C: I2c>(i2c: &mut I2C, addr: u8) -> Result<i16, I2C::Error> {
288    let mut data = [0u8; 2];
289    i2c.write_read(addr, &[REG_CONVERSION], &mut data)?;
290    Ok(i16::from_be_bytes(data))
291}
292
293/// Approximate conversion period in microseconds for a data rate (timed-wait fallback).
294/// Nominal `1_000_000/SPS` plus a safety margin of ~12.5% + 100µs. This provides
295/// a conservative delay intended to allow the conversion to complete under
296/// oscillator variation and normal scheduling jitter — deliberately trading a
297/// fraction of throughput (at 860 SPS: ~1.41ms wait, ≈710 reads/s max, instead
298/// of the nominal 1.16ms). It cannot mathematically guarantee completion on
299/// every device under every condition; ALERT/RDY remains the preferred sync
300/// method for full-rate capture. Use this fallback only when RDY is unwired.
301pub fn conversion_period_us(dr: DataRate) -> u32 {
302    let sps = match dr {
303        DataRate::SPS8 => 8,
304        DataRate::SPS16 => 16,
305        DataRate::SPS32 => 32,
306        DataRate::SPS64 => 64,
307        DataRate::SPS128 => 128,
308        DataRate::SPS250 => 250,
309        DataRate::SPS475 => 475,
310        DataRate::SPS860 => 860,
311    };
312    let nominal = 1_000_000 / sps as u32;
313    nominal + nominal / 8 + 100
314}
315
316// ---------- constructors / common accessors ----------
317
318impl<I2C> ADS1113<I2C> {
319    pub fn new(i2c: I2C, config: Config) -> Self {
320        Self { address: Address::Ground, i2c, config }
321    }
322    pub fn release(self) -> I2C { self.i2c }
323}
324
325impl<I2C> ADS1114<I2C> {
326    pub fn new(i2c: I2C, config: Config) -> Self {
327        Self { address: Address::Ground, i2c, config }
328    }
329    pub fn release(self) -> I2C { self.i2c }
330}
331
332impl<I2C> ADS1115<I2C> {
333    pub fn new(address: Address, i2c: I2C, config: Config) -> Self {
334        Self { address, i2c, config }
335    }
336    pub fn release(self) -> I2C { self.i2c }
337}
338
339// ---------- synchronous API (preserved) ----------
340
341/// Blocking OS-bit wait for SINGLE-SHOT mode only (START with OS=1, then OS
342/// reads back 1 when done). MUST NOT be used for continuous mode: there OS
343/// reads 0 and this would poll forever. Continuous sync paths use
344/// [`BlockingReady`] or a caller-supplied delay instead.
345fn wait_complete_sync<I2C: I2c>(i2c: &mut I2C, addr: u8) -> Result<(), I2C::Error> {
346    let mut buf = [0u8; 2];
347    loop {
348        i2c.write_read(addr, &[REG_CONFIG], &mut buf)?;
349        if conversion_complete(buf[0]) { break; }
350    }
351    Ok(())
352}
353
354impl<I2C: I2c> ADS1113<I2C> {
355    /// NOTE: ADS1113 ignores gain/comparator fields in `Config` (no PGA/comparator on HW).
356    fn read_channel_raw(&mut self) -> Result<i16, I2C::Error> {
357        let b = build_config_bytes_ads1113(self.config.data_rate, true);
358        write_config(&mut self.i2c, self.address as u8, b)?;
359        wait_complete_sync(&mut self.i2c, self.address as u8)?;
360        read_conversion(&mut self.i2c, self.address as u8)
361    }
362    pub fn read_adc(&mut self) -> Result<i16, I2C::Error> { self.read_channel_raw() }
363    /// Timed (non-polling) sync variant: blocks on provided `delay_us` closure.
364    pub fn read_adc_timed(&mut self, mut delay_us: impl FnMut(u32)) -> Result<i16, I2C::Error> {
365        let b = build_config_bytes_ads1113(self.config.data_rate, true);
366        write_config(&mut self.i2c, self.address as u8, b)?;
367        delay_us(conversion_period_us(self.config.data_rate));
368        read_conversion(&mut self.i2c, self.address as u8)
369    }
370}
371
372impl<I2C: I2c> ADS1114<I2C> {
373    /// Configure ALERT/RDY as conversion-ready: writes HI=0x8000/LO=0x0000
374    /// AND upgrades `self.config` via `as_conversion_ready()` (COMP_QUE=
375    /// OneConversion, NonLatching), because the default `DisableComparator`
376    /// (11b) would silently keep the pin inactive. Subsequent starts therefore
377    /// emit a usable conversion-ready signal.
378    ///
379    /// If an I2C error occurs mid-setup (HI written, LO not), the hardware may
380    /// hold a partially modified threshold pair while `self.config` remains
381    /// unchanged — a safe software state, but retry/reinitialize the registers
382    /// before relying on RDY rather than assuming either half took effect.
383    pub fn enable_conversion_ready_pin(&mut self) -> Result<(), I2C::Error> {
384        enable_rdy_registers(&mut self.i2c, self.address as u8)?;
385        self.config = self.config.as_conversion_ready();
386        Ok(())
387    }
388    /// A. start conversion (OS=1); returns immediately.
389    pub fn start_conversion(&mut self) -> Result<(), I2C::Error> {
390        let b = build_config_bytes(&self.config, 0b100, true, Mode::SingleShot);
391        write_config(&mut self.i2c, self.address as u8, b)
392    }
393    /// C. read conversion register (call after READY observed).
394    pub fn read_conversion(&mut self) -> Result<i16, I2C::Error> {
395        read_conversion(&mut self.i2c, self.address as u8)
396    }
397    /// Continuous mode: start + streaming reads.
398    /// MODE=0, OS NOT set. First sample valid only after RDY/timed wait.
399    /// (Sync names are kept stable; like the async `_unchecked` starters, this
400    /// does not arm RDY — arm the detector before starting for event-driven use.)
401    pub fn start_continuous(&mut self) -> Result<(), I2C::Error> {
402        let b = build_config_bytes(&self.config, 0b100, false, Mode::Continuous);
403        write_config(&mut self.i2c, self.address as u8, b)
404    }
405    /// Next sample of an already-running continuous stream via a blocking RDY
406    /// wait: re-arm, then wait, then read. No restart, no CONFIG polling (OS
407    /// polling is invalid here: OS reads 0 in continuous mode). Returns the
408    /// latest sample (no FIFO).
409    ///
410    /// # Precondition
411    /// Conversion-ready mode enabled (`enable_conversion_ready_pin()`).
412    pub fn next_conversion_blocking<R: BlockingReady>(
413        &mut self,
414        rdy: &mut R,
415    ) -> Result<i16, Error<I2C::Error, R::Error>> {
416        debug_assert_rdy(&self.config, "ADS1114::next_conversion_blocking");
417        let pol = ConversionReadyPolarity::from(self.config.comp_pol);
418        rdy.arm(pol);
419        rdy.wait_for_ready(pol).map_err(Error::Wait)?;
420        self.read_conversion().map_err(Error::I2c)
421    }
422    /// Next continuous sample via a caller-supplied delay (no RDY wiring
423    /// needed): waits ~one conversion period, then reads the LATEST conversion.
424    /// Not "the conversion from exactly one period ago": the ADC free-runs and
425    /// I2C traffic shifts actual sample instants. No CONFIG polling.
426    pub fn next_conversion_timed(
427        &mut self,
428        mut delay_us: impl FnMut(u32),
429    ) -> Result<i16, I2C::Error> {
430        delay_us(conversion_period_us(self.config.data_rate));
431        self.read_conversion()
432    }
433    fn read_channel_raw(&mut self) -> Result<i16, I2C::Error> {
434        self.start_conversion()?;
435        wait_complete_sync(&mut self.i2c, self.address as u8)?;
436        self.read_conversion()
437    }
438    pub fn read_adc(&mut self) -> Result<i16, I2C::Error> { self.read_channel_raw() }
439}
440
441/// Signed raw -> (negative, magnitude). false=positive (A+>A-), true=negative.
442/// Uses unsigned_abs: handles i16::MIN correctly (no abs() overflow).
443#[inline]
444pub fn signed_to_polarity_magnitude(value: i16) -> (bool, u16) {
445    if value < 0 { (true, value.unsigned_abs()) } else { (false, value as u16) }
446}
447
448/// Zero-cost single-ended channel storage (one ADC+MUX: values filled sequentially).
449#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
450pub struct Channel0(pub u16);
451#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
452pub struct Channel1(pub u16);
453#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
454pub struct Channel2(pub u16);
455#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
456pub struct Channel3(pub u16);
457
458fn enable_rdy_registers<I2C: I2c>(i2c: &mut I2C, addr: u8) -> Result<(), I2C::Error> {
459    // Hi_thresh MSB=1, Lo_thresh MSB=0; requires COMP_QUE != 11b (checked by caller docs).
460    i2c.write(addr, &[REG_HI_THRESH, 0x80, 0x00])?;
461    i2c.write(addr, &[REG_LO_THRESH, 0x00, 0x00])?;
462    Ok(())
463}
464
465impl<I2C: I2c> ADS1115<I2C> {
466    /// Same as ADS1114: threshold registers plus `self.config` upgrade so the
467    /// pin is actually usable (see above, including the partial-write note:
468    /// on I2C error retry/reinitialize before relying on RDY).
469    pub fn enable_conversion_ready_pin(&mut self) -> Result<(), I2C::Error> {
470        enable_rdy_registers(&mut self.i2c, self.address as u8)?;
471        self.config = self.config.as_conversion_ready();
472        Ok(())
473    }
474    pub fn start_conversion(&mut self, mux: Mux) -> Result<(), I2C::Error> {
475        let b = build_config_bytes(&self.config, mux as u8, true, Mode::SingleShot);
476        write_config(&mut self.i2c, self.address as u8, b)
477    }
478    pub fn read_conversion(&mut self) -> Result<i16, I2C::Error> {
479        read_conversion(&mut self.i2c, self.address as u8)
480    }
481    pub fn start_continuous(&mut self, mux: Mux) -> Result<(), I2C::Error> {
482        // NOTE: does not arm RDY (sync names kept stable); arm the detector
483        // before starting for event-driven `next_conversion_blocking()` use.
484        let b = build_config_bytes(&self.config, mux as u8, false, Mode::Continuous);
485        write_config(&mut self.i2c, self.address as u8, b)
486    }
487    /// Next continuous sample via a blocking RDY wait: re-arm, then wait, then
488    /// read. No restart, no CONFIG polling (OS polling is invalid here: OS
489    /// reads 0 in continuous mode). Returns the latest sample (no FIFO).
490    ///
491    /// # Precondition
492    /// Conversion-ready mode enabled (`enable_conversion_ready_pin()`).
493    pub fn next_conversion_blocking<R: BlockingReady>(
494        &mut self,
495        rdy: &mut R,
496    ) -> Result<i16, Error<I2C::Error, R::Error>> {
497        debug_assert_rdy(&self.config, "ADS1115::next_conversion_blocking");
498        let pol = ConversionReadyPolarity::from(self.config.comp_pol);
499        rdy.arm(pol);
500        rdy.wait_for_ready(pol).map_err(Error::Wait)?;
501        self.read_conversion().map_err(Error::I2c)
502    }
503    /// Next continuous sample via a caller-supplied delay (no RDY wiring
504    /// needed): waits ~one conversion period, then reads the LATEST conversion.
505    /// Not "the conversion from exactly one period ago": the ADC free-runs and
506    /// I2C traffic shifts actual sample instants. No CONFIG polling.
507    pub fn next_conversion_timed(
508        &mut self,
509        mut delay_us: impl FnMut(u32),
510    ) -> Result<i16, I2C::Error> {
511        delay_us(conversion_period_us(self.config.data_rate));
512        self.read_conversion()
513    }
514    fn read_channel_raw(&mut self, mux: Mux) -> Result<i16, I2C::Error> {
515        // Exclusive &mut self from start->read guarantees no stale-channel hazard.
516        self.start_conversion(mux)?;
517        wait_complete_sync(&mut self.i2c, self.address as u8)?;
518        self.read_conversion()
519    }
520    /// Typed MUX API (preferred). Raw-bits variant validates range (no silent masking).
521    pub fn read_channel(&mut self, mux: Mux) -> Result<i16, I2C::Error> { self.read_channel_raw(mux) }
522    pub fn read_channel_raw_bits(&mut self, mux: u8) -> Result<i16, MuxOrI2c<I2C::Error>> {
523        Ok(self.read_channel_raw(match_mux(mux).map_err(|_| MuxOrI2c::Mux(MuxError(mux)))?).map_err(MuxOrI2c::I2c)?)
524    }
525    pub fn read_adc_a0(&mut self) -> Result<i16, I2C::Error> { self.read_channel_raw(Mux::A0) }
526    pub fn read_adc_a1(&mut self) -> Result<i16, I2C::Error> { self.read_channel_raw(Mux::A1) }
527    pub fn read_adc_a2(&mut self) -> Result<i16, I2C::Error> { self.read_channel_raw(Mux::A2) }
528    pub fn read_adc_a3(&mut self) -> Result<i16, I2C::Error> { self.read_channel_raw(Mux::A3) }
529    pub fn read_adc_a0n1(&mut self) -> Result<i16, I2C::Error> { self.read_channel_raw(Mux::A0N1) }
530    pub fn read_adc_a0n3(&mut self) -> Result<i16, I2C::Error> { self.read_channel_raw(Mux::A0N3) }
531    pub fn read_adc_a1n3(&mut self) -> Result<i16, I2C::Error> { self.read_channel_raw(Mux::A1N3) }
532    pub fn read_adc_a2n3(&mut self) -> Result<i16, I2C::Error> { self.read_channel_raw(Mux::A2N3) }
533    /// Sequential single-shot conversions (single ADC + MUX: cannot overlap).
534    pub fn read_4adc(&mut self) -> Result<[i16; 4], I2C::Error> {
535        Ok([self.read_adc_a0()?, self.read_adc_a1()?, self.read_adc_a2()?, self.read_adc_a3()?])
536    }
537    /// Typed single-ended channel structs (sequential fills; NOT simultaneous).
538    pub fn read_channels(&mut self) -> Result<(Channel0, Channel1, Channel2, Channel3), I2C::Error> {
539        Ok((Channel0(self.read_adc_a0()? as u16), Channel1(self.read_adc_a1()? as u16),
540            Channel2(self.read_adc_a2()? as u16), Channel3(self.read_adc_a3()? as u16)))
541    }
542    // --- Differential pairs (one converter + MUX; NOT simultaneous sampling) ---
543    // Only HW-valid pairs exposed; reverse polarity = sign of result.
544    pub fn read_a0_a1(&mut self) -> Result<(bool, u16), I2C::Error> {
545        Ok(signed_to_polarity_magnitude(self.read_channel_raw(Mux::A0N1)?))
546    }
547    pub fn read_a0_a3(&mut self) -> Result<(bool, u16), I2C::Error> {
548        Ok(signed_to_polarity_magnitude(self.read_channel_raw(Mux::A0N3)?))
549    }
550    pub fn read_a1_a3(&mut self) -> Result<(bool, u16), I2C::Error> {
551        Ok(signed_to_polarity_magnitude(self.read_channel_raw(Mux::A1N3)?))
552    }
553    pub fn read_a2_a3(&mut self) -> Result<(bool, u16), I2C::Error> {
554        Ok(signed_to_polarity_magnitude(self.read_channel_raw(Mux::A2N3)?))
555    }
556}
557
558/// Rejected raw MUX value (valid: 0..=7 mapping to the 8 ADS1115 selections).
559#[derive(Debug, Clone, Copy, PartialEq, Eq)]
560pub struct MuxError(pub u8);
561
562/// Error wrapper for the compat raw-bits helper only.
563#[derive(Debug, Clone, Copy, PartialEq, Eq)]
564pub enum MuxOrI2c<E> {
565    Mux(MuxError),
566    I2c(E),
567}
568
569fn match_mux(bits: u8) -> Result<Mux, MuxError> {
570    match bits {
571        0 => Ok(Mux::A0N1), 1 => Ok(Mux::A0N3), 2 => Ok(Mux::A1N3), 3 => Ok(Mux::A2N3),
572        4 => Ok(Mux::A0), 5 => Ok(Mux::A1), 6 => Ok(Mux::A2), 7 => Ok(Mux::A3),
573        b => Err(MuxError(b)),
574    }
575}
576
577// ---------- async API (event-driven, no polling) ----------
578
579#[cfg(feature = "async")]
580mod async_api {
581    use super::*;
582    use embedded_hal_async::i2c::I2c as AsyncI2c;
583
584    async fn write_config_async<I2C: AsyncI2c>(i2c: &mut I2C, addr: u8, b: [u8; 2]) -> Result<(), I2C::Error> {
585        i2c.write(addr, &[REG_CONFIG, b[0], b[1]]).await
586    }
587    async fn read_conv_async<I2C: AsyncI2c>(i2c: &mut I2C, addr: u8) -> Result<i16, I2C::Error> {
588        let mut d = [0u8; 2];
589        i2c.write_read(addr, &[REG_CONVERSION], &mut d).await?;
590        Ok(i16::from_be_bytes(d))
591    }
592
593    impl<I2C: AsyncI2c> ADS1113<I2C> {
594        /// ADS1113 has NO ALERT/RDY: timed async wait only (explicit model difference).
595        pub async fn read_adc_async<D: AsyncDelayUs>(&mut self, delay: &mut D) -> Result<i16, I2C::Error> {
596            self.start_conversion_async().await?;
597            delay.delay_us(conversion_period_us(self.config.data_rate)).await;
598            self.read_conversion_async().await
599        }
600        pub async fn start_conversion_async(&mut self) -> Result<(), I2C::Error> {
601            let b = build_config_bytes_ads1113(self.config.data_rate, true);
602            write_config_async(&mut self.i2c, self.address as u8, b).await
603        }
604        pub async fn read_conversion_async(&mut self) -> Result<i16, I2C::Error> {
605            read_conv_async(&mut self.i2c, self.address as u8).await
606        }
607    }
608
609    impl<I2C: AsyncI2c> ADS1114<I2C> {
610        /// Async RDY-register setup (same bytes as sync; async only because I2C is async).
611        /// On I2C error the hardware may hold a partial threshold pair while
612        /// `self.config` is unchanged — retry/reinitialize before relying on RDY.
613        pub async fn enable_conversion_ready_pin_async(&mut self) -> Result<(), I2C::Error> {
614            self.i2c.write(self.address as u8, &[REG_HI_THRESH, 0x80, 0x00]).await?;
615            self.i2c.write(self.address as u8, &[REG_LO_THRESH, 0x00, 0x00]).await?;
616            // Same comparator upgrade as the sync helper: without COMP_QUE !=
617            // 11b the pin would stay inactive despite the thresholds.
618            self.config = self.config.as_conversion_ready();
619            Ok(())
620        }
621        pub async fn start_conversion_async(&mut self) -> Result<(), I2C::Error> {
622            let b = build_config_bytes(&self.config, 0b100, true, Mode::SingleShot);
623            write_config_async(&mut self.i2c, self.address as u8, b).await
624        }
625        pub async fn start_continuous_async_unchecked(&mut self) -> Result<(), I2C::Error> {
626            // MODE=0 runs freely; OS=1 NOT used per conversion.
627            // UNCHECKED: the caller MUST have armed RDY via `rdy.arm()` BEFORE
628            // calling this (see `continuous()`); the name is the warning.
629            let b = build_config_bytes(&self.config, 0b100, false, Mode::Continuous);
630            write_config_async(&mut self.i2c, self.address as u8, b).await
631        }
632        pub async fn read_conversion_async(&mut self) -> Result<i16, I2C::Error> {
633            read_conv_async(&mut self.i2c, self.address as u8).await
634        }
635        /// Preferred Embassy flow. Arm RDY BEFORE start (per `ConversionReady`
636        /// contract) so the ~8µs pulse cannot fire while unlistened; `&mut self`
637        /// held throughout so no other op can interleave (no stale-data hazard).
638        /// Documented ordering: enable RDY regs -> arm -> start -> wait edge -> read.
639        ///
640        /// # Precondition
641        /// Conversion-ready mode must be enabled first (`enable_conversion_ready_pin_async()`,
642        /// which also sets `COMP_QUE != DisableComparator`). Otherwise ALERT/RDY
643        /// stays inactive and this wait hangs forever (debug builds assert this).
644        pub async fn read_adc_event<R: ConversionReady>(&mut self, rdy: &mut R) -> Result<i16, Error<I2C::Error, R::Error>> {
645            debug_assert_rdy(&self.config, "ADS1114::read_adc_event");
646            let pol = ConversionReadyPolarity::from(self.config.comp_pol);
647            rdy.arm(pol);
648            self.start_conversion_async().await.map_err(Error::I2c)?;
649            rdy.wait_for_ready(pol).await.map_err(Error::Wait)?;
650            self.read_conversion_async().await.map_err(Error::I2c)
651        }
652        /// Fallback timed wait (documented: less precise than ALERT/RDY).
653        pub async fn read_adc_timed<D: AsyncDelayUs>(&mut self, delay: &mut D) -> Result<i16, I2C::Error> {
654            self.start_conversion_async().await?;
655            delay.delay_us(conversion_period_us(self.config.data_rate)).await;
656            self.read_conversion_async().await
657        }
658        /// Next sample of a RUNNING continuous stream: re-arm RDY, wait, read.
659        /// Re-arming every cycle is required: continuous RDY is a ~8µs pulse
660        /// that may fire between the previous read and this wait. No restart.
661        ///
662        /// # Precondition
663        /// Conversion-ready mode enabled (established by `continuous()` or
664        /// `enable_conversion_ready_pin_async()`).
665        pub async fn next_conversion<R: ConversionReady>(&mut self, rdy: &mut R) -> Result<i16, Error<I2C::Error, R::Error>> {
666            debug_assert_rdy(&self.config, "ADS1114::next_conversion");
667            let pol = ConversionReadyPolarity::from(self.config.comp_pol);
668            rdy.arm(pol);
669            rdy.wait_for_ready(pol).await.map_err(Error::Wait)?;
670            self.read_conversion_async().await.map_err(Error::I2c)
671        }
672        /// Race-safe continuous stream constructor (ADS1114 has a single input
673        /// path, so no MUX argument): arms `rdy` BEFORE starting the ADC so the
674        /// first ~8µs pulse cannot be missed. `stream.next()` re-arms every cycle.
675        ///
676        /// # Precondition
677        /// Conversion-ready mode must be enabled first (`enable_conversion_ready_pin_async()`).
678        pub async fn continuous<'a, 'r, R: ConversionReady>(&'a mut self, rdy: &'r mut R) -> Result<Continuous<'a, 'r, I2C, R, 4>, I2C::Error> {
679            debug_assert_rdy(&self.config, "ADS1114::continuous");
680            rdy.arm(ConversionReadyPolarity::from(self.config.comp_pol));
681            self.start_continuous_async_unchecked().await?;
682            Ok(Continuous { adc: self, rdy })
683        }
684    }
685
686    impl<I2C: AsyncI2c> ADS1115<I2C> {
687        /// Async RDY-register setup (same contract as the sync helper,
688        /// including the partial-write note: retry/reinitialize on I2C error).
689        pub async fn enable_conversion_ready_pin_async(&mut self) -> Result<(), I2C::Error> {
690            self.i2c.write(self.address as u8, &[REG_HI_THRESH, 0x80, 0x00]).await?;
691            self.i2c.write(self.address as u8, &[REG_LO_THRESH, 0x00, 0x00]).await?;
692            // Comparator upgrade (see sync helper): thresholds alone are inert.
693            self.config = self.config.as_conversion_ready();
694            Ok(())
695        }
696        pub async fn start_conversion_async(&mut self, mux: Mux) -> Result<(), I2C::Error> {
697            let b = build_config_bytes(&self.config, mux as u8, true, Mode::SingleShot);
698            write_config_async(&mut self.i2c, self.address as u8, b).await
699        }
700        pub async fn start_continuous_async_unchecked(&mut self, mux: Mux) -> Result<(), I2C::Error> {
701            // MODE=0 free-running; OS not re-asserted per sample.
702            // UNCHECKED: the caller MUST have armed RDY via `rdy.arm()` BEFORE
703            // calling this — the name is the warning. Prefer the race-safe
704            // `continuous()` constructor for event-driven use.
705            let b = build_config_bytes(&self.config, mux as u8, false, Mode::Continuous);
706            write_config_async(&mut self.i2c, self.address as u8, b).await
707        }
708        pub async fn read_conversion_async(&mut self) -> Result<i16, I2C::Error> {
709            read_conv_async(&mut self.i2c, self.address as u8).await
710        }
711        /// arm -> start -> await RDY edge -> read. `&mut self` throughout: no interleave hazard.
712        ///
713        /// # Precondition
714        /// Conversion-ready mode must be enabled first (`enable_conversion_ready_pin_async()`).
715        /// Otherwise ALERT/RDY stays inactive and this wait hangs forever (debug builds assert this).
716        pub async fn read_channel_event<R: ConversionReady>(&mut self, mux: Mux, rdy: &mut R) -> Result<i16, Error<I2C::Error, R::Error>> {
717            debug_assert_rdy(&self.config, "ADS1115::read_channel_event");
718            let pol = ConversionReadyPolarity::from(self.config.comp_pol);
719            rdy.arm(pol);
720            self.start_conversion_async(mux).await.map_err(Error::I2c)?;
721            rdy.wait_for_ready(pol).await.map_err(Error::Wait)?;
722            self.read_conversion_async().await.map_err(Error::I2c)
723        }
724        /// Timed fallback: start -> delay(dr) -> read. No config polling.
725        pub async fn read_channel_timed<D: AsyncDelayUs>(&mut self, mux: Mux, delay: &mut D) -> Result<i16, I2C::Error> {
726            self.start_conversion_async(mux).await?;
727            delay.delay_us(conversion_period_us(self.config.data_rate)).await;
728            self.read_conversion_async().await
729        }
730        // Idiomatic per-channel async wrappers (event-driven).
731        pub async fn read_adc_a0_async<R: ConversionReady>(&mut self, rdy: &mut R) -> Result<i16, Error<I2C::Error, R::Error>> { self.read_channel_event(Mux::A0, rdy).await }
732        pub async fn read_adc_a1_async<R: ConversionReady>(&mut self, rdy: &mut R) -> Result<i16, Error<I2C::Error, R::Error>> { self.read_channel_event(Mux::A1, rdy).await }
733        pub async fn read_adc_a2_async<R: ConversionReady>(&mut self, rdy: &mut R) -> Result<i16, Error<I2C::Error, R::Error>> { self.read_channel_event(Mux::A2, rdy).await }
734        pub async fn read_adc_a3_async<R: ConversionReady>(&mut self, rdy: &mut R) -> Result<i16, Error<I2C::Error, R::Error>> { self.read_channel_event(Mux::A3, rdy).await }
735        /// Sequential multi-channel event reads (single converter: strictly sequential).
736        pub async fn read_4adc_event<R: ConversionReady>(&mut self, rdy: &mut R) -> Result<[i16; 4], Error<I2C::Error, R::Error>> {
737            Ok([self.read_channel_event(Mux::A0, rdy).await?, self.read_channel_event(Mux::A1, rdy).await?,
738                self.read_channel_event(Mux::A2, rdy).await?, self.read_channel_event(Mux::A3, rdy).await?])
739        }
740        pub async fn read_4adc_timed<D: AsyncDelayUs>(&mut self, delay: &mut D) -> Result<[i16; 4], I2C::Error> {
741            Ok([self.read_channel_timed(Mux::A0, delay).await?, self.read_channel_timed(Mux::A1, delay).await?,
742                self.read_channel_timed(Mux::A2, delay).await?, self.read_channel_timed(Mux::A3, delay).await?])
743        }
744        // --- Differential pairs (one converter + MUX, sequential; NOT simultaneous) ---
745        async fn read_diff_event<R: ConversionReady>(&mut self, mux: Mux, rdy: &mut R) -> Result<(bool, u16), Error<I2C::Error, R::Error>> {
746            Ok(signed_to_polarity_magnitude(self.read_channel_event(mux, rdy).await?))
747        }
748        pub async fn read_a0_a1_async<R: ConversionReady>(&mut self, rdy: &mut R) -> Result<(bool, u16), Error<I2C::Error, R::Error>> { self.read_diff_event(Mux::A0N1, rdy).await }
749        pub async fn read_a0_a3_async<R: ConversionReady>(&mut self, rdy: &mut R) -> Result<(bool, u16), Error<I2C::Error, R::Error>> { self.read_diff_event(Mux::A0N3, rdy).await }
750        pub async fn read_a1_a3_async<R: ConversionReady>(&mut self, rdy: &mut R) -> Result<(bool, u16), Error<I2C::Error, R::Error>> { self.read_diff_event(Mux::A1N3, rdy).await }
751        pub async fn read_a2_a3_async<R: ConversionReady>(&mut self, rdy: &mut R) -> Result<(bool, u16), Error<I2C::Error, R::Error>> { self.read_diff_event(Mux::A2N3, rdy).await }
752        // --- Continuous: start once, then next_* waits RDY + reads (no restart) ---
753        // PULSE SAFETY: continuous RDY is ~8us pulse. The race-safe entry point
754        // is `continuous()`, which arms RDY BEFORE starting the ADC. The raw
755        // `start_continuous_async_unchecked()` below is deliberately
756        // scary-named: callers using it directly MUST call `rdy.arm(polarity)`
757        // themselves first, otherwise the first pulse can fire while
758        // unlistened. Requires conversion-ready mode (see `continuous()`);
759        // debug builds assert it.
760        pub async fn next_conversion<R: ConversionReady>(&mut self, rdy: &mut R) -> Result<i16, Error<I2C::Error, R::Error>> {
761            debug_assert_rdy(&self.config, "ADS1115::next_conversion");
762            let pol = ConversionReadyPolarity::from(self.config.comp_pol);
763            rdy.arm(pol);
764            rdy.wait_for_ready(pol).await.map_err(Error::Wait)?;
765            self.read_conversion_async().await.map_err(Error::I2c)
766        }
767        /// Race-safe stream constructor: arms `rdy` BEFORE starting the ADC so the
768        /// first ~8us pulse cannot be missed. Prefer this over manual
769        /// `start_continuous_async_unchecked()` + late `wait_for_ready()`.
770        ///
771        /// # Precondition
772        /// Conversion-ready mode must be enabled first (`enable_conversion_ready_pin_async()`).
773        pub async fn continuous<'a, 'r, R: ConversionReady>(&'a mut self, mux: Mux, rdy: &'r mut R) -> Result<Continuous<'a, 'r, I2C, R, 5>, I2C::Error> {
774            debug_assert_rdy(&self.config, "ADS1115::continuous");
775            rdy.arm(ConversionReadyPolarity::from(self.config.comp_pol));
776            self.start_continuous_async_unchecked(mux).await?;
777            Ok(Continuous { adc: self, rdy })
778        }
779    }
780
781    /// Borrowed continuous stream: ADC started once; `next()` re-arms RDY,
782    /// awaits the pulse, and reads. Holds `&mut` borrows so no other op can
783    /// interleave (no stale-data hazard). `no_std`, no alloc, no
784    /// futures::Stream dependency. Generic over the device model so ADS1114
785    /// and ADS1115 share the race-safe constructor/stream API.
786    pub struct Continuous<'a, 'r, I2C, RDY, const MODEL: u8> {
787        adc: &'a mut ADS111x<I2C, MODEL>,
788        rdy: &'r mut RDY,
789    }
790
791    impl<I2C: AsyncI2c, RDY: ConversionReady> Continuous<'_, '_, I2C, RDY, 5> {
792        /// Next raw sample of the running stream (re-arm → wait → read).
793        /// The MUX was fixed at `continuous()` time, so differential results
794        /// are the caller's one-line conversion, e.g.:
795        /// `signed_to_polarity_magnitude(stream.next().await?)`.
796        /// (Per-MUX `next_a*_a*()` shorthands were removed: they could not
797        /// select a MUX and misled readers of streams started on another pair.)
798        pub async fn next(&mut self) -> Result<i16, Error<I2C::Error, RDY::Error>> {
799            self.adc.next_conversion(self.rdy).await
800        }
801    }
802
803    impl<I2C: AsyncI2c, RDY: ConversionReady> Continuous<'_, '_, I2C, RDY, 4> {
804        /// Next ADS1114 continuous sample (re-arm → wait → read; ADS1114 has a
805        /// single input path, so no MUX/differential variants).
806        pub async fn next(&mut self) -> Result<i16, Error<I2C::Error, RDY::Error>> {
807            self.adc.next_conversion(self.rdy).await
808        }
809    }
810
811    // ---------- Embassy Timer fallback (feature "embassy", no RDY wiring needed) ----------
812    // start -> Timer::after(nominal period + margin) -> read. No CONFIG polling,
813    // no GPIO, timer returns no error so Result<T, I2C::Error> suffices.
814    #[cfg(feature = "embassy")]
815    async fn timer_wait_us(us: u32) {
816        embassy_time::Timer::after(embassy_time::Duration::from_micros(us as u64)).await
817    }
818
819    #[cfg(feature = "embassy")]
820    impl<I2C: AsyncI2c> ADS1113<I2C> {
821        /// Single-shot via Embassy Timer (ADS1113 has no ALERT/RDY).
822        pub async fn read_adc_timer(&mut self) -> Result<i16, I2C::Error> {
823            self.start_conversion_async().await?;
824            timer_wait_us(conversion_period_us(self.config.data_rate)).await;
825            self.read_conversion_async().await
826        }
827        /// Continuous: MODE=0 free-running (MODE=1 would be single-shot).
828        /// ADS1113 has no ALERT/RDY, so this timer path starts the ADC once,
829        pub async fn start_continuous_timer(&mut self) -> Result<(), I2C::Error> {
830            let b = build_config_bytes_ads1113_continuous(self.config.data_rate);
831            write_config_async(&mut self.i2c, self.address as u8, b).await
832        }
833        pub async fn next_timer(&mut self) -> Result<i16, I2C::Error> {
834            timer_wait_us(conversion_period_us(self.config.data_rate)).await;
835            self.read_conversion_async().await
836        }
837    }
838
839    #[cfg(feature = "embassy")]
840    impl<I2C: AsyncI2c> ADS1114<I2C> {
841        pub async fn read_adc_timer(&mut self) -> Result<i16, I2C::Error> {
842            self.start_conversion_async().await?;
843            timer_wait_us(conversion_period_us(self.config.data_rate)).await;
844            self.read_conversion_async().await
845        }
846        pub async fn continuous_timer(&mut self) -> Result<ContinuousTimer<'_, I2C, 4>, I2C::Error> {
847            let dr = self.config.data_rate;
848            self.start_continuous_async_unchecked().await?;
849            // First sample valid only after one full period; `next()` waits before each read.
850            Ok(ContinuousTimer { adc: self, period_us: conversion_period_us(dr) })
851        }
852    }
853
854    #[cfg(feature = "embassy")]
855    impl<I2C: AsyncI2c> ADS1115<I2C> {
856        /// Single-shot via Embassy Timer: no timer argument, no RDY needed.
857        pub async fn read_channel_timer(&mut self, mux: Mux) -> Result<i16, I2C::Error> {
858            self.start_conversion_async(mux).await?;
859            timer_wait_us(conversion_period_us(self.config.data_rate)).await;
860            self.read_conversion_async().await
861        }
862        pub async fn read_adc_a0_timer(&mut self) -> Result<i16, I2C::Error> { self.read_channel_timer(Mux::A0).await }
863        pub async fn read_adc_a1_timer(&mut self) -> Result<i16, I2C::Error> { self.read_channel_timer(Mux::A1).await }
864        pub async fn read_adc_a2_timer(&mut self) -> Result<i16, I2C::Error> { self.read_channel_timer(Mux::A2).await }
865        pub async fn read_adc_a3_timer(&mut self) -> Result<i16, I2C::Error> { self.read_channel_timer(Mux::A3).await }
866        async fn read_diff_timer(&mut self, mux: Mux) -> Result<(bool, u16), I2C::Error> {
867            Ok(signed_to_polarity_magnitude(self.read_channel_timer(mux).await?))
868        }
869        pub async fn read_a0_a1_timer(&mut self) -> Result<(bool, u16), I2C::Error> { self.read_diff_timer(Mux::A0N1).await }
870        pub async fn read_a0_a3_timer(&mut self) -> Result<(bool, u16), I2C::Error> { self.read_diff_timer(Mux::A0N3).await }
871        pub async fn read_a1_a3_timer(&mut self) -> Result<(bool, u16), I2C::Error> { self.read_diff_timer(Mux::A1N3).await }
872        pub async fn read_a2_a3_timer(&mut self) -> Result<(bool, u16), I2C::Error> { self.read_diff_timer(Mux::A2N3).await }
873        /// Timer stream: start once (MODE=0, no OS per sample), then
874        /// Timer::after(period) + read the LATEST conversion per sample — not
875        /// "the conversion from exactly one period ago". No double-delay:
876        /// constructor only starts; each `next()` waits ~one period, then reads.
877        pub async fn continuous_timer(&mut self, mux: Mux) -> Result<ContinuousTimer<'_, I2C, 5>, I2C::Error> {
878            let dr = self.config.data_rate;
879            self.start_continuous_async_unchecked(mux).await?;
880            Ok(ContinuousTimer { adc: self, period_us: conversion_period_us(dr) })
881        }
882    }
883
884    /// Timer-based continuous stream (no RDY race: timer needs no arming).
885    #[cfg(feature = "embassy")]
886    pub struct ContinuousTimer<'a, I2C, const MODEL: u8> {
887        adc: &'a mut ADS111x<I2C, MODEL>,
888        period_us: u32,
889    }
890
891    #[cfg(feature = "embassy")]
892    impl<I2C: AsyncI2c> ContinuousTimer<'_, I2C, 5> {
893        /// Next raw sample: wait ~one period, then read the LATEST conversion
894        /// (not "the conversion from exactly one period ago" — the ADC
895        /// free-runs and read timing shifts actual sample instants).
896        /// Differential decoding is the caller's
897        /// `signed_to_polarity_magnitude(...)` for the MUX selected
898        /// at `continuous_timer()` time.
899        pub async fn next(&mut self) -> Result<i16, I2C::Error> {
900            timer_wait_us(self.period_us).await;
901            self.adc.read_conversion_async().await
902        }
903    }
904
905    #[cfg(feature = "embassy")]
906    impl<I2C: AsyncI2c> ContinuousTimer<'_, I2C, 4> {
907        pub async fn next(&mut self) -> Result<i16, I2C::Error> {
908            timer_wait_us(self.period_us).await;
909            self.adc.read_conversion_async().await
910        }
911    }
912}
913
914/// Embassy integration (no core dependency on embassy crates):
915/// ```ignore
916/// use ads111x_rs::{ConversionReady, ConversionReadyPolarity, AsyncDelayUs};
917/// use core::future::Future;
918/// struct GpioRdy(embassy_stm32::gpio::Input<'static>);
919/// impl ConversionReady for GpioRdy {
920///     type Error = core::convert::Infallible;
921///     // Named future type: the future only *observes* the edge; synchronous
922///     // arming below is what guarantees no pulse fires while unlistened.
923///     // `arm()` MUST synchronously clear stale state, configure the edge, and
924///     // enable the detector — in that order — returning only once the
925///     // detector will observe the NEXT edge, e.g.:
926///     // self.0.clear_pending_event(); // consume leftover EXTI flag first
927///     // self.0.configure_edge(...);   // then select falling/rising
928///     // self.0.enable_interrupt();    // then enable; returns after HW write
929///     type WaitFuture<'a> = impl Future<Output = Result<(), Self::Error>> + 'a;
930///     fn arm(&mut self, pol: ConversionReadyPolarity) {
931///         // 1. Clear stale state FIRST: a leftover pending flag would make the
932///         //    subsequent wait return immediately (bogus early read).
933///         self.0.clear_pending_event();
934///         // 2. Configure the requested edge polarity.
935///         // 3. Enable/register the detector (synchronously, before return).
936///         match pol {
937///             ConversionReadyPolarity::ActiveLow => self.0.enable_falling_edge_interrupt(),
938///             ConversionReadyPolarity::ActiveHigh => self.0.enable_rising_edge_interrupt(),
939///         }
940///         // NOTE: a lazy `arm()` that defers to first poll violates the trait
941///         // contract and reintroduces the start-before-arm race. Exact method
942///         // names depend on the Embassy HAL (stm32/nRF/RPxxx GPIO/EXTI API);
943///         // the clear → configure → enable ORDER is what the contract requires.
944///     }
945///     fn wait_for_ready(&mut self, pol: ConversionReadyPolarity) -> Self::WaitFuture<'_> {
946///         async move {
947///             match pol {
948///                 ConversionReadyPolarity::ActiveLow => self.0.wait_for_falling_edge().await,
949///                 ConversionReadyPolarity::ActiveHigh => self.0.wait_for_rising_edge().await,
950///             }
951///             Ok(())
952///         }
953///     }
954/// }
955/// struct EmbTimer;
956/// impl AsyncDelayUs for EmbTimer {
957///     type DelayFuture<'a> = impl core::future::Future<Output = ()> + 'a;
958///     fn delay_us(&mut self, us: u32) -> Self::DelayFuture<'_> {
959///         async move { embassy_time::Timer::after_micros(us).await; }
960///     }
961/// }
962/// // App: wrap RDY wait in embassy_time::with_timeout to avoid hanging forever.
963/// ```
964/// Cancellation/timeout lives at the app layer (`with_timeout`); core stays timer-free.
965
966/// Electrical safety: PGA full-scale (e.g. `Gain::V6_144`) is a measurement range,
967/// NOT permission to exceed supply rails / absolute input limits in the datasheet.
968/// Always keep inputs within device absolute maximum ratings.
969
970/**
971## Config
972`gain`/`comp_*` fields apply only to ADS1114/ADS1115; ADS1113 ignores them (no PGA/comparator).
973`comp_que` must not be `DisableComparator` for ALERT/RDY conversion-ready mode.
974*/
975#[derive(Debug, Clone, Copy, Default)]
976pub struct Config {
977    pub gain: Gain,
978    pub data_rate: DataRate,
979    pub comp_mode: CompMode,
980    pub comp_pol: CompPol,
981    pub comp_lat: CompLat,
982    pub comp_que: CompQue,
983}
984
985impl Config {
986    pub fn with_gain(self, gain: Gain) -> Self { Self { gain, ..self } }
987    pub fn with_data_rate(self, data_rate: DataRate) -> Self { Self { data_rate, ..self } }
988    pub fn with_comp_mode(self, comp_mode: CompMode) -> Self { Self { comp_mode, ..self } }
989    pub fn with_comp_pol(self, comp_pol: CompPol) -> Self { Self { comp_pol, ..self } }
990    pub fn with_comp_lat(self, comp_lat: CompLat) -> Self { Self { comp_lat, ..self } }
991    pub fn with_comp_que(self, comp_que: CompQue) -> Self { Self { comp_que, ..self } }
992    /// Returns a `Config` with comparator settings suitable for RDY
993    /// (`COMP_QUE = OneConversion`, non-latching). This is a pure config
994    /// transformation: it does NOT program the ADS111x threshold registers
995    /// (`HI_THRESH` MSB=1 / `LO_THRESH` MSB=0 still required on hardware).
996    /// Call `enable_conversion_ready_pin*()` when configuring hardware — it
997    /// writes the registers AND applies this transformation. Relying on reset
998    /// threshold values alone is fragile if anything previously rewrote them.
999    ///
1000    /// Kept public deliberately for low-level configurability (e.g. composing
1001    /// a `Config` before the bus exists), but prefer
1002    /// `enable_conversion_ready_pin*()` whenever hardware is available: a
1003    /// config-only change with stale threshold registers leaves RDY inactive.
1004    pub fn as_conversion_ready(&self) -> Self {
1005        Self { comp_que: CompQue::OneConversion, comp_lat: CompLat::NonLatching, ..*self }
1006    }
1007    /// True when the comparator field enables the ALERT/RDY output
1008    /// (`COMP_QUE != DisableComparator`). Every RDY event API requires this;
1009    /// with the struct default the pin is inactive and event waits hang.
1010    /// `enable_conversion_ready_pin*()` establishes this automatically.
1011    pub fn is_conversion_ready(&self) -> bool {
1012        !matches!(self.comp_que, CompQue::DisableComparator)
1013    }
1014}
1015
1016/// Debug-only guard for the RDY precondition: panics in debug builds when an
1017/// event API is entered with a comparator-disabled config (which would hang).
1018#[inline]
1019fn debug_assert_rdy(config: &Config, what: &str) {
1020    debug_assert!(
1021        config.is_conversion_ready(),
1022        "{what}: COMP_QUE == DisableComparator; call enable_conversion_ready_pin*() or as_conversion_ready() first, else the RDY wait never completes"
1023    );
1024}
1025
1026/// PGA[11:9]: Only for ADS1114/5.
1027#[derive(Debug, Clone, Copy, Default)]
1028#[repr(u8)]
1029pub enum Gain {
1030    #[default]
1031    V6_144 = 0,
1032    V4_096 = 1 << 1,
1033    V2_048 = 2 << 1,
1034    V1_024 = 3 << 1,
1035    V0_512 = 4 << 1,
1036    V0_256 = 5 << 1,
1037}
1038
1039/// DR[7:5]: Data rate
1040#[derive(Debug, Clone, Copy, Default)]
1041#[repr(u8)]
1042pub enum DataRate {
1043    SPS8 = 0,
1044    SPS16 = 1 << 5,
1045    SPS32 = 2 << 5,
1046    SPS64 = 3 << 5,
1047    SPS128 = 4 << 5,
1048    SPS250 = 5 << 5,
1049    SPS475 = 6 << 5,
1050    #[default]
1051    SPS860 = 7 << 5,
1052}
1053
1054/// COMP_MODE [4]: Only for ADS1114/5
1055#[derive(Debug, Clone, Copy, Default)]
1056#[repr(u8)]
1057pub enum CompMode {
1058    #[default]
1059    Traditional = 0,
1060    Window = 1 << 4,
1061}
1062
1063/// COMP_POL [3]: Only for ADS1114/5
1064#[derive(Debug, Clone, Copy, Default)]
1065#[repr(u8)]
1066pub enum CompPol {
1067    #[default]
1068    ActiveLow = 0,
1069    ActiveHigh = 1 << 3,
1070}
1071
1072/// COMP_LAT [2]: Only for ADS1114/5
1073#[derive(Debug, Clone, Copy, Default)]
1074#[repr(u8)]
1075pub enum CompLat {
1076    #[default]
1077    NonLatching = 0,
1078    Latching = 1 << 2,
1079}
1080
1081/// COMP_QUE [1:0]: Only for ADS1114/5
1082#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
1083#[repr(u8)]
1084pub enum CompQue {
1085    OneConversion = 0,
1086    TwoConversions = 1,
1087    FourConversions = 2,
1088    #[default]
1089    DisableComparator = 3,
1090}
1091
1092#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
1093#[repr(u8)]
1094pub enum Address {
1095    #[default]
1096    Ground = 0b1001000,
1097    VDD = 0b1001001,
1098    SDA = 0b1001010,
1099    SCL = 0b1001011,
1100}
1101
1102// ============================================================================
1103// Multi-device support: several ADS1115s on ONE I2C bus (bus-owning coordinator).
1104//
1105// ```text
1106//              ┌── ADS1115 @ 0x48 (Ground)
1107//              ├── ADS1115 @ 0x49 (VDD)
1108//  I2C BUS ────┼── ADS1115 @ 0x4A (SDA)
1109//              └── ADS1115 @ 0x4B (SCL)
1110// ```
1111// One bus + N addresses + per-device state. Max 4 directly-addressed devices
1112// per bus (ADDR pin); beyond that use an external I2C mux / second bus — the
1113// driver stays mux-agnostic (any `embedded-hal` I2C impl works).
1114//
1115// SEMANTICS (read carefully):
1116// - "Channels on one ADS1115" = multiplexed inputs on ONE converter (sequential).
1117// - "Multiple ADS1115s" = independent converters (parallel/overlapping).
1118// - 4 ADS1115 x 4 single-ended inputs = 16 logical channels (TI reference
1119//   design), but NOT 16 simultaneous samples: each ADS1115 still multiplexes.
1120// - Conversions started via sequential I2C writes give OVERLAPPING windows,
1121//   not phase-locked sampling. No hardware-sync claim is made.
1122// - Continuous mode has ONE conversion register per device (no FIFO):
1123//   `next_*` returns the LATEST sample; late readers lose intermediates.
1124// ============================================================================
1125
1126/// Per-device configuration within a multi-ADC array.
1127#[derive(Debug, Clone, Copy)]
1128pub struct DeviceConfig {
1129    pub address: Address,
1130    pub config: Config,
1131    pub mux: Mux,
1132}
1133
1134impl DeviceConfig {
1135    pub fn new(address: Address, config: Config, mux: Mux) -> Self {
1136        Self { address, config, mux }
1137    }
1138}
1139
1140/// Single-device result retaining device identity (no alloc).
1141#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1142pub struct Sample {
1143    pub index: usize,
1144    pub address: Address,
1145    pub value: i16,
1146}
1147
1148/// Differential single-device result retaining identity.
1149#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1150pub struct DiffSample {
1151    pub index: usize,
1152    pub address: Address,
1153    pub negative: bool,
1154    pub magnitude: u16,
1155}
1156
1157/// Multi-device error: rejects duplicate addresses at construction; every
1158/// I2C/wait failure carries the device index + address.
1159#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1160pub enum MultiError<I2cError, WaitError = ()> {
1161    DuplicateAddress { first: usize, second: usize, address: Address },
1162    InvalidIndex(usize),
1163    I2c { index: usize, address: Address, error: I2cError },
1164    Wait { index: usize, address: Address, error: WaitError },
1165}
1166
1167/// Bus-owning coordinator for N ADS1115s sharing one I2C bus.
1168/// Owns the bus: exactly one transaction active at a time (Embassy-safe).
1169pub struct MultiADS1115<I2C, const N: usize> {
1170    i2c: I2C,
1171    devices: [DeviceConfig; N],
1172}
1173
1174/// Four-device convenience alias (max directly-addressed ADS111x per bus).
1175pub type ADS1115Array<I2C> = MultiADS1115<I2C, 4>;
1176
1177impl<I2C: embedded_hal::i2c::ErrorType, const N: usize> MultiADS1115<I2C, N> {
1178    /// Returns Err on duplicate addresses (use an external I2C mux if you must
1179    /// repeat an address behind isolated segments).
1180    pub fn new(i2c: I2C, devices: [DeviceConfig; N]) -> Result<Self, MultiError<I2cErrorOf<I2C>, ()>> {
1181        let _ = &i2c;
1182        let me = Self { i2c, devices };
1183        me.check_unique()?;
1184        Ok(me)
1185    }
1186
1187    fn check_unique(&self) -> Result<(), MultiError<I2cErrorOf<I2C>, ()>> {
1188        let mut i = 0;
1189        while i < N {
1190            let mut j = i + 1;
1191            while j < N {
1192                if self.devices[i].address as u8 == self.devices[j].address as u8 {
1193                    return Err(MultiError::DuplicateAddress {
1194                        first: i, second: j, address: self.devices[i].address,
1195                    });
1196                }
1197                j += 1;
1198            }
1199            i += 1;
1200        }
1201        Ok(())
1202    }
1203
1204    pub fn device(&self, index: usize) -> Option<&DeviceConfig> { self.devices.get(index) }
1205    pub fn device_mut(&mut self, index: usize) -> Option<&mut DeviceConfig> { self.devices.get_mut(index) }
1206    pub fn release(self) -> I2C { self.i2c }
1207}
1208
1209/// Helper: I2C error type of a bus without requiring a full I2c bound at construction.
1210pub trait BusError {
1211    type Error;
1212}
1213impl<T: embedded_hal::i2c::ErrorType> BusError for T {
1214    type Error = T::Error;
1215}
1216type I2cErrorOf<B> = <B as BusError>::Error;
1217
1218fn multi_write_config<I2C: I2c>(i2c: &mut I2C, addr: u8, b: [u8; 2]) -> Result<(), I2C::Error> {
1219    i2c.write(addr, &[REG_CONFIG, b[0], b[1]])
1220}
1221fn multi_read_conv<I2C: I2c>(i2c: &mut I2C, addr: u8) -> Result<i16, I2C::Error> {
1222    let mut d = [0u8; 2];
1223    i2c.write_read(addr, &[REG_CONVERSION], &mut d)?;
1224    Ok(i16::from_be_bytes(d))
1225}
1226fn multi_wait<I2C: I2c>(i2c: &mut I2C, addr: u8) -> Result<(), I2C::Error> {
1227    let mut buf = [0u8; 2];
1228    loop {
1229        i2c.write_read(addr, &[REG_CONFIG], &mut buf)?;
1230        if conversion_complete(buf[0]) { break; }
1231    }
1232    Ok(())
1233}
1234
1235impl<I2C: I2c, const N: usize> MultiADS1115<I2C, N> {
1236    fn start_idx(&mut self, index: usize, mux: Mux) -> Result<(), MultiError<I2C::Error, ()>> {
1237        let dev = *self.devices.get(index).ok_or(MultiError::InvalidIndex(index))?;
1238        let (addr, cfg) = (dev.address, dev.config);
1239        let b = build_config_bytes(&cfg, mux as u8, true, Mode::SingleShot);
1240        multi_write_config(&mut self.i2c, addr as u8, b)
1241            .map_err(|e| MultiError::I2c { index, address: addr, error: e })
1242    }
1243
1244    /// Start one device (single-shot, OS=1). Prefer `start_all` for overlap.
1245    pub fn start_conversion(&mut self, index: usize, mux: Mux) -> Result<(), MultiError<I2C::Error, ()>> {
1246        self.start_idx(index, mux)
1247    }
1248
1249    /// Start ALL devices first (sequential I2C writes, overlapping conversions).
1250    ///
1251    /// # Failure semantics
1252    /// I2C writes are issued device by device, so an error partway through
1253    /// leaves earlier devices already running while later ones never started.
1254    /// The driver does NOT roll back: stopping the started devices would need
1255    /// further I2C transactions that could themselves fail, and silently
1256    /// reconfiguring hardware behind the caller's back is worse than reporting
1257    /// the error with its device index/address (`MultiError::I2c`). On error,
1258    /// re-establish a known state explicitly (e.g. re-issue the start for all
1259    /// devices, or reset/reconfigure the affected ones) before capturing.
1260    pub fn start_all(&mut self, muxes: [Mux; N]) -> Result<(), MultiError<I2C::Error, ()>> {
1261        let mut i = 0;
1262        while i < N {
1263            self.start_idx(i, muxes[i])?;
1264            i += 1;
1265        }
1266        Ok(())
1267    }
1268
1269    fn read_idx(&mut self, index: usize) -> Result<Sample, MultiError<I2C::Error, ()>> {
1270        let addr = self.devices.get(index).map(|d| d.address).ok_or(MultiError::InvalidIndex(index))?;
1271        multi_wait(&mut self.i2c, addr as u8)
1272            .map_err(|e| MultiError::I2c { index, address: addr, error: e })?;
1273        let v = multi_read_conv(&mut self.i2c, addr as u8)
1274            .map_err(|e| MultiError::I2c { index, address: addr, error: e })?;
1275        Ok(Sample { index, address: addr, value: v })
1276    }
1277
1278    /// Parallel capture with per-device MUX: START all, then WAIT+READ all.
1279    /// Returns samples in device order (index/address identity preserved).
1280    pub fn capture_parallel(&mut self, muxes: [Mux; N]) -> Result<[Sample; N], MultiError<I2C::Error, ()>>
1281    where [Sample; N]: Default {
1282        self.start_all(muxes)?;
1283        let mut out: [Sample; N] = Default::default();
1284        let mut i = 0;
1285        while i < N {
1286            out.as_mut()[i] = self.read_idx(i)?;
1287            i += 1;
1288        }
1289        Ok(out)
1290    }
1291
1292    /// Capture using each device's stored `mux`.
1293    pub fn capture_all(&mut self) -> Result<[Sample; N], MultiError<I2C::Error, ()>>
1294    where [Sample; N]: Default {
1295        let mut muxes: [Mux; N] = [Mux::A0; N];
1296        let mut i = 0;
1297        while i < N {
1298            muxes[i] = self.devices[i].mux;
1299            i += 1;
1300        }
1301        self.capture_parallel(muxes)
1302    }
1303
1304    /// Parallel differential capture -> [(negative, magnitude); N].
1305    pub fn capture_parallel_diff(&mut self, muxes: [Mux; N]) -> Result<[DiffSample; N], MultiError<I2C::Error, ()>>
1306    where [DiffSample; N]: Default {
1307        self.start_all(muxes)?;
1308        let mut out: [DiffSample; N] = Default::default();
1309        let mut i = 0;
1310        while i < N {
1311            let s = self.read_idx(i)?;
1312            let (negative, magnitude) = signed_to_polarity_magnitude(s.value);
1313            out.as_mut()[i] = DiffSample { index: i, address: s.address, negative, magnitude };
1314            i += 1;
1315        }
1316        Ok(out)
1317    }
1318
1319    /// All devices enter continuous mode (MODE=0, OS=0), once each.
1320    ///
1321    /// # Failure semantics
1322    /// Same partial-start rule as `start_all`: an I2C error partway through
1323    /// leaves earlier devices converting while later ones never started, with
1324    /// no rollback. Re-establish a known state before streaming.
1325    pub fn start_continuous_all(&mut self, muxes: [Mux; N]) -> Result<(), MultiError<I2C::Error, ()>> {
1326        let mut i = 0;
1327        while i < N {
1328            let (addr, cfg) = (self.devices[i].address, self.devices[i].config);
1329            let b = build_config_bytes(&cfg, muxes[i] as u8, false, Mode::Continuous);
1330            multi_write_config(&mut self.i2c, addr as u8, b)
1331                .map_err(|e| MultiError::I2c { index: i, address: addr, error: e })?;
1332            i += 1;
1333        }
1334        Ok(())
1335    }
1336
1337    // NOTE: there is deliberately NO blocking multi-RDY `next_all_blocking()`.
1338    // Sequential blocking waits cannot observe N independent ~8µs pulses: while
1339    // blocked on ADC0, ADC1..N can fire unseen. Use the concurrent async
1340    // `next_all_event()` or the timer-based `next_all_timed()` instead. (A
1341    // blocking multi API would additionally require level-latched ready flags
1342    // that stay asserted until serviced — hardware the driver cannot assume.)
1343    /// Latest sample per device via a caller-supplied delay: waits ~one
1344    /// (max) conversion period once, then reads all. No restart, no CONFIG
1345    /// polling, no RDY wiring needed. Timing is a conservative allowance, not
1346    /// synchronized timestamps (see timer docs).
1347    pub fn next_all_timed(
1348        &mut self,
1349        mut delay_us: impl FnMut(u32),
1350    ) -> Result<[Sample; N], MultiError<I2C::Error, ()>>
1351    where [Sample; N]: Default {
1352        let mut period = 0;
1353        let mut i = 0;
1354        while i < N {
1355            let p = conversion_period_us(self.devices[i].config.data_rate);
1356            if p > period {
1357                period = p;
1358            }
1359            i += 1;
1360        }
1361        delay_us(period);
1362        let mut out: [Sample; N] = Default::default();
1363        let mut i = 0;
1364        while i < N {
1365            let dev = self.devices[i];
1366            let v = multi_read_conv(&mut self.i2c, dev.address as u8)
1367                .map_err(|e| MultiError::I2c { index: i, address: dev.address, error: e })?;
1368            out.as_mut()[i] = Sample { index: i, address: dev.address, value: v };
1369            i += 1;
1370        }
1371        Ok(out)
1372    }
1373
1374    // (no dummy-error helper: out-of-range indices use MultiError::InvalidIndex)
1375}
1376
1377impl Default for Sample {
1378    fn default() -> Self { Self { index: 0, address: Address::Ground, value: 0 } }
1379}
1380impl Default for DiffSample {
1381    fn default() -> Self { Self { index: 0, address: Address::Ground, negative: false, magnitude: 0 } }
1382}
1383
1384/// Bus-independent multi-device helpers: arming and concurrent waiting touch
1385/// only device configs and RDY sources, never the I2C bus, so they carry no
1386/// `AsyncI2c` bound and stay usable with any bus type.
1387#[cfg(feature = "async")]
1388impl<I2C: embedded_hal::i2c::ErrorType, const N: usize> MultiADS1115<I2C, N> {
1389    fn polarities(&self) -> [ConversionReadyPolarity; N] {
1390        core::array::from_fn(|i| {
1391            ConversionReadyPolarity::from(self.devices.get(i).map(|d| d.config.comp_pol).unwrap_or(CompPol::ActiveLow))
1392        })
1393    }
1394    /// Synchronously arm every RDY detector (per-device polarity from its
1395    /// config). Call this BEFORE starting any conversion: it is the only
1396    /// way to guarantee no ~8µs conversion-ready pulse fires while no
1397    /// detector is listening.
1398    ///
1399    /// # Precondition
1400    /// Every device must have conversion-ready mode enabled (threshold
1401    /// registers + `COMP_QUE != DisableComparator`); debug builds assert
1402    /// this per device, since an inactive RDY output would hang the wait.
1403    pub fn arm_all_ready<R: ConversionReady>(&self, rdy: &mut [R; N]) {
1404        let mut i = 0;
1405        while i < N {
1406            if let Some(dev) = self.devices.get(i) {
1407                debug_assert_rdy(&dev.config, "MultiADS1115::arm_all_ready");
1408            }
1409            let pol = ConversionReadyPolarity::from(
1410                self.devices.get(i).map(|d| d.config.comp_pol).unwrap_or(CompPol::ActiveLow),
1411            );
1412            rdy[i].arm(pol);
1413            i += 1;
1414        }
1415    }
1416    /// Wait for ALL devices concurrently (single join future; observes edges
1417    /// on detectors that must already have been armed via `arm_all_ready`
1418    /// before the conversions were started). Wait failures carry the failing
1419    /// device's index AND address.
1420    ///
1421    /// # Precondition
1422    /// Same conversion-ready precondition as `capture_parallel_event`
1423    /// (asserted per device in `arm_all_ready`; call it before starting).
1424    pub async fn wait_all_ready_async<R: ConversionReady>(
1425        &self,
1426        rdy: &mut [R; N],
1427    ) -> Result<(), MultiError<I2C::Error, R::Error>> {
1428        let mut i = 0;
1429        while i < N {
1430            if let Some(dev) = self.devices.get(i) {
1431                debug_assert_rdy(&dev.config, "MultiADS1115::wait_all_ready_async");
1432            }
1433            i += 1;
1434        }
1435        let pol = self.polarities();
1436        wait_all_ready(rdy, pol).await.map_err(|e| {
1437            let address = self
1438                .devices
1439                .get(e.index)
1440                .map(|d| d.address)
1441                .unwrap_or(Address::Ground);
1442            MultiError::Wait { index: e.index, address, error: e.error }
1443        })
1444    }
1445}
1446
1447#[cfg(feature = "async")]
1448mod multi_async {
1449    use super::*;
1450    use embedded_hal_async::i2c::I2c as AsyncI2c;
1451    async fn mstart<I2C: AsyncI2c>(i2c: &mut I2C, addr: u8, cfg: &Config, mux: Mux) -> Result<(), I2C::Error> {
1452        let b = build_config_bytes(cfg, mux as u8, true, Mode::SingleShot);
1453        i2c.write(addr, &[REG_CONFIG, b[0], b[1]]).await
1454    }
1455    async fn mread<I2C: AsyncI2c>(i2c: &mut I2C, addr: u8) -> Result<i16, I2C::Error> {
1456        let mut d = [0u8; 2];
1457        i2c.write_read(addr, &[REG_CONVERSION], &mut d).await?;
1458        Ok(i16::from_be_bytes(d))
1459    }
1460
1461    /// Lift a wait-free multi error (pure reads) into a caller expecting a wait
1462    /// error type. The `Wait` arm is unreachable by construction.
1463    fn lift_no_wait<I2cError, WaitError>(
1464        e: MultiError<I2cError, ()>,
1465    ) -> MultiError<I2cError, WaitError> {
1466        match e {
1467            MultiError::DuplicateAddress { first, second, address } => {
1468                MultiError::DuplicateAddress { first, second, address }
1469            }
1470            MultiError::InvalidIndex(i) => MultiError::InvalidIndex(i),
1471            MultiError::I2c { index, address, error } => MultiError::I2c { index, address, error },
1472            MultiError::Wait { .. } => unreachable!("pure read path never waits"),
1473        }
1474    }
1475
1476    impl<I2C: AsyncI2c, const N: usize> MultiADS1115<I2C, N> {
1477        async fn start_idx_a(&mut self, index: usize, mux: Mux) -> Result<(), MultiError<I2C::Error, ()>> {
1478            let dev = *self.devices.get(index).ok_or(MultiError::InvalidIndex(index))?;
1479            mstart(&mut self.i2c, dev.address as u8, &dev.config, mux).await
1480                .map_err(|e| MultiError::I2c { index, address: dev.address, error: e })
1481        }
1482        /// START all (overlapping conversion windows; I2C writes serialized,
1483        /// conversions concurrent). Read with `read_started_all_event/_timer`.
1484        ///
1485        /// # Failure semantics
1486        /// Same partial-start rule as sync `start_all`: an I2C error partway
1487        /// through leaves earlier devices running with no rollback; the error
1488        /// carries the failing device index/address for recovery.
1489        pub async fn start_all_async(&mut self, muxes: [Mux; N]) -> Result<(), MultiError<I2C::Error, ()>> {
1490            let mut i = 0;
1491            while i < N {
1492                self.start_idx_a(i, muxes[i]).await?;
1493                i += 1;
1494            }
1495            Ok(())
1496        }
1497        async fn read_started_idx(
1498            &mut self,
1499            index: usize,
1500        ) -> Result<Sample, MultiError<I2C::Error, ()>> {
1501            let dev = *self.devices.get(index).ok_or(MultiError::InvalidIndex(index))?;
1502            let v = mread(&mut self.i2c, dev.address as u8).await
1503                .map_err(|e| MultiError::I2c { index, address: dev.address, error: e })?;
1504            Ok(Sample { index, address: dev.address, value: v })
1505        }
1506        /// Read conversion registers of already-started devices (no wait, no
1507        /// start, no RDY involved — hence no `ConversionReady` generic: callers
1508        /// need no fake RDY type just to read a completed conversion).
1509        pub async fn read_all_async(
1510            &mut self,
1511        ) -> Result<[Sample; N], MultiError<I2C::Error, ()>>
1512        where [Sample; N]: Default {
1513            let mut out: [Sample; N] = Default::default();
1514            let mut i = 0;
1515            while i < N {
1516                out.as_mut()[i] = self.read_started_idx(i).await?;
1517                i += 1;
1518            }
1519            Ok(out)
1520        }
1521        /// Parallel RDY capture: ARM all -> START all -> wait ALL concurrently
1522        /// -> READ all. Arming precedes every START, so no conversion-ready
1523        /// pulse can fire while its detector is unarmed. Each ADC needs its
1524        /// OWN RDY pin (`rdy[i]` <-> device i).
1525        ///
1526        /// # Precondition
1527        /// Every device must have conversion-ready mode enabled (threshold
1528        /// registers + `COMP_QUE != DisableComparator`, e.g. via
1529        /// `enable_conversion_ready_pin_async()` applied to each device's
1530        /// config). Otherwise RDY stays inactive and the wait hangs forever
1531        /// (debug builds assert this in `arm_all_ready`).
1532        ///
1533        /// # Failure semantics
1534        /// The START phase follows the partial-start rule (`start_all` docs).
1535        pub async fn capture_parallel_event<R: ConversionReady>(
1536            &mut self, muxes: [Mux; N], rdy: &mut [R; N],
1537        ) -> Result<[Sample; N], MultiError<I2C::Error, R::Error>>
1538        where [Sample; N]: Default {
1539            self.arm_all_ready(rdy);
1540            self.start_all_async(muxes).await.map_err(|e| match e {
1541                MultiError::InvalidIndex(i) => MultiError::InvalidIndex(i),
1542                MultiError::DuplicateAddress { first, second, address } => MultiError::DuplicateAddress { first, second, address },
1543                MultiError::I2c { index, address, error } => MultiError::I2c { index, address, error },
1544                MultiError::Wait { .. } => unreachable!(),
1545            })?;
1546            self.wait_all_ready_async(rdy).await?;
1547            self.read_all_async().await.map_err(lift_no_wait)
1548        }
1549        /// Parallel differential RDY capture (same start/wait/read pipeline).
1550        ///
1551        /// # Precondition
1552        /// Same conversion-ready precondition as `capture_parallel_event`.
1553        pub async fn capture_parallel_diff_event<R: ConversionReady>(
1554            &mut self, muxes: [Mux; N], rdy: &mut [R; N],
1555        ) -> Result<[DiffSample; N], MultiError<I2C::Error, R::Error>>
1556        where [DiffSample; N]: Default, [Sample; N]: Default {
1557            let samples = self.capture_parallel_event(muxes, rdy).await?;
1558            let mut out: [DiffSample; N] = Default::default();
1559            let mut i = 0;
1560            while i < N {
1561                let (negative, magnitude) = signed_to_polarity_magnitude(samples[i].value);
1562                out.as_mut()[i] = DiffSample { index: i, address: samples[i].address, negative, magnitude };
1563                i += 1;
1564            }
1565            Ok(out)
1566        }
1567        /// Multi-device continuous: MODE=0/OS=0 on every ADC, once each.
1568        /// UNCHECKED: call `arm_all_ready(rdy)` BEFORE this (the name is the
1569        /// warning) so no first pulse fires while unlistened
1570        /// (`next_all_event` re-arms every cycle, but arming before the
1571        /// initial START is what protects the first period).
1572        ///
1573        /// # Failure semantics
1574        /// Same partial-start rule as `start_all`: no rollback on I2C error.
1575        /// Prefer the combined `continuous_all_event()` constructor, which
1576        /// documents the full arm+start contract in one place.
1577        pub async fn start_continuous_all_async_unchecked(&mut self, muxes: [Mux; N]) -> Result<(), MultiError<I2C::Error, ()>> {
1578            let mut i = 0;
1579            while i < N {
1580                let dev = *self.devices.get(i).ok_or(MultiError::InvalidIndex(i))?;
1581                let b = build_config_bytes(&dev.config, muxes[i] as u8, false, Mode::Continuous);
1582                self.i2c.write(dev.address as u8, &[REG_CONFIG, b[0], b[1]]).await
1583                    .map_err(|e| MultiError::I2c { index: i, address: dev.address, error: e })?;
1584                i += 1;
1585            }
1586            Ok(())
1587        }
1588        /// Race-safe multi-device continuous session: arms ALL RDY detectors
1589        /// BEFORE starting every ADC (MODE=0/OS=0, once each). This is the
1590        /// event-driven counterpart of the single-ADC `continuous()`
1591        /// constructor — prefer it over manual `arm_all_ready()` +
1592        /// `start_continuous_all_async_unchecked()`. Each `next_all_event()` re-arms
1593        /// every cycle. Debug-asserts the RDY precondition per device.
1594        ///
1595        /// # Failure semantics
1596        /// The START phase follows the partial-start rule (`start_all` docs):
1597        /// an I2C error partway through leaves earlier devices running with no
1598        /// rollback; arming itself cannot fail.
1599        ///
1600        /// # Precondition
1601        /// Same conversion-ready precondition as `capture_parallel_event`.
1602        pub async fn continuous_all_event<R: ConversionReady>(
1603            &mut self,
1604            muxes: [Mux; N],
1605            rdy: &mut [R; N],
1606        ) -> Result<(), MultiError<I2C::Error, ()>> {
1607            self.arm_all_ready(rdy);
1608            self.start_continuous_all_async_unchecked(muxes).await
1609        }
1610        /// Latest sample per device: re-arm all, then concurrent wait-all and
1611        /// read all. No restart, no FIFO — late readers get the latest
1612        /// conversion only. Re-arming each cycle means a pulse in flight
1613        /// between cycles resolves to the NEXT period's pulse (a sample may be
1614        /// skipped, but the wait cannot hang on an already-consumed edge).
1615        ///
1616        /// # Precondition
1617        /// Same conversion-ready precondition as `capture_parallel_event`
1618        /// (asserted per device on every re-arm).
1619        pub async fn next_all_event<R: ConversionReady>(
1620            &mut self, rdy: &mut [R; N],
1621        ) -> Result<[Sample; N], MultiError<I2C::Error, R::Error>>
1622        where [Sample; N]: Default {
1623            self.arm_all_ready(rdy);
1624            self.wait_all_ready_async(rdy).await?;
1625            self.read_all_async().await.map_err(lift_no_wait)
1626        }
1627    }
1628
1629    // ---- Embassy Timer multi-device capture (feature "embassy") ----
1630    #[cfg(feature = "embassy")]
1631    impl<I2C: AsyncI2c, const N: usize> MultiADS1115<I2C, N> {
1632        fn max_period_us(&self) -> u32 {
1633            let mut m = 0;
1634            let mut i = 0;
1635            while i < N {
1636                let p = conversion_period_us(self.devices[i].config.data_rate);
1637                if p > m { m = p; }
1638                i += 1;
1639            }
1640            m
1641        }
1642        /// Parallel timer capture: START all, ONE shared `Timer::after(max period)`,
1643        /// then READ all. Conversions overlap; CONFIG never rewritten per sample.
1644        /// Allows every device a conservative conversion window; it does
1645        /// NOT guarantee synchronized sample timestamps (faster ADCs may have
1646        /// completed several conversions — reads return the LATEST sample).
1647        pub async fn capture_parallel_timer(&mut self, muxes: [Mux; N]) -> Result<[Sample; N], MultiError<I2C::Error, ()>>
1648        where [Sample; N]: Default {
1649            self.start_all_async(muxes).await?;
1650            let period = self.max_period_us();
1651            embassy_time::Timer::after(embassy_time::Duration::from_micros(period as u64)).await;
1652            let mut out: [Sample; N] = Default::default();
1653            let mut i = 0;
1654            while i < N {
1655                let dev = self.devices[i];
1656                let v = mread(&mut self.i2c, dev.address as u8).await
1657                    .map_err(|e| MultiError::I2c { index: i, address: dev.address, error: e })?;
1658                out.as_mut()[i] = Sample { index: i, address: dev.address, value: v };
1659                i += 1;
1660            }
1661            Ok(out)
1662        }
1663        pub async fn capture_all_timer(&mut self) -> Result<[Sample; N], MultiError<I2C::Error, ()>>
1664        where [Sample; N]: Default {
1665            let mut muxes: [Mux; N] = [Mux::A0; N];
1666            let mut i = 0;
1667            while i < N {
1668                muxes[i] = self.devices[i].mux;
1669                i += 1;
1670            }
1671            self.capture_parallel_timer(muxes).await
1672        }
1673        pub async fn capture_parallel_diff_timer(&mut self, muxes: [Mux; N]) -> Result<[DiffSample; N], MultiError<I2C::Error, ()>>
1674        where [DiffSample; N]: Default {
1675            self.start_all_async(muxes).await?;
1676            let period = self.max_period_us();
1677            embassy_time::Timer::after(embassy_time::Duration::from_micros(period as u64)).await;
1678            let mut out: [DiffSample; N] = Default::default();
1679            let mut i = 0;
1680            while i < N {
1681                let dev = self.devices[i];
1682                let v = mread(&mut self.i2c, dev.address as u8).await
1683                    .map_err(|e| MultiError::I2c { index: i, address: dev.address, error: e })?;
1684                let (negative, magnitude) = signed_to_polarity_magnitude(v);
1685                out.as_mut()[i] = DiffSample { index: i, address: dev.address, negative, magnitude };
1686                i += 1;
1687            }
1688            Ok(out)
1689        }
1690        /// Timer continuous: start all once, then per `next_all_timer`:
1691        /// Timer::after(max period) + read all (no restart).
1692        pub async fn start_continuous_all_timer(&mut self, muxes: [Mux; N]) -> Result<(), MultiError<I2C::Error, ()>> {
1693            self.start_continuous_all_async_unchecked(muxes).await
1694        }
1695        pub async fn next_all_timer(&mut self) -> Result<[Sample; N], MultiError<I2C::Error, ()>>
1696        where [Sample; N]: Default {
1697            let period = self.max_period_us();
1698            embassy_time::Timer::after(embassy_time::Duration::from_micros(period as u64)).await;
1699            let mut out: [Sample; N] = Default::default();
1700            let mut i = 0;
1701            while i < N {
1702                let dev = self.devices[i];
1703                let v = mread(&mut self.i2c, dev.address as u8).await
1704                    .map_err(|e| MultiError::I2c { index: i, address: dev.address, error: e })?;
1705                out.as_mut()[i] = Sample { index: i, address: dev.address, value: v };
1706                i += 1;
1707            }
1708            Ok(out)
1709        }
1710    }
1711}
1712
1713#[cfg(test)]
1714mod tests {
1715    extern crate std;
1716    use super::*;
1717
1718    #[test]
1719    fn config_bytes_mux_gain_dr() {
1720        let cfg = Config { gain: Gain::V2_048, data_rate: DataRate::SPS128, ..Default::default() };
1721        // MUX A0=100, gain V2_048=2<<1=4, OS=1, MODE single=1
1722        let b = build_config_bytes(&cfg, Mux::A0 as u8, true, Mode::SingleShot);
1723        assert_eq!(b[0], 0x80 | (0b100 << 4) | 0x04 | 0x01);
1724        assert_eq!(b[1], (4 << 5) | 3); // DR + COMP_QUE disabled default
1725        assert!(conversion_complete(0x80) && !conversion_complete(0x00));
1726    }
1727
1728    #[test]
1729    fn all_mux_bits() {
1730        for (m, bits) in [(Mux::A0N1,0),(Mux::A0N3,1),(Mux::A1N3,2),(Mux::A2N3,3),(Mux::A0,4),(Mux::A1,5),(Mux::A2,6),(Mux::A3,7)] {
1731            let b = build_config_bytes(&Config::default(), m as u8, true, Mode::SingleShot);
1732            assert_eq!((b[0] >> 4) & 7, bits);
1733        }
1734    }
1735
1736    #[test]
1737    fn ads1113_ignores_pga() {
1738        let b = build_config_bytes_ads1113(DataRate::SPS860, true);
1739        assert_eq!(b[0] & 0x0E, 0); // no PGA bits
1740        assert_eq!(b[1] & 3, 3); // comparator disabled
1741    }
1742
1743    #[test]
1744    fn continuous_clears_mode_bit() {
1745        let b = build_config_bytes(&Config::default(), Mux::A0 as u8, false, Mode::Continuous);
1746        assert_eq!(b[0] & 1, 0);
1747        assert_eq!(b[0] & 0x80, 0, "continuous must not set OS=1");
1748        let s = build_config_bytes(&Config::default(), Mux::A0 as u8, true, Mode::SingleShot);
1749        assert_eq!(s[0] & 1, 1);
1750        assert_ne!(s[0] & 0x80, 0);
1751        // ADS1113 continuous: MODE=0 (bit0=0), OS=0.
1752        #[cfg(feature = "embassy")]
1753        {
1754            let c = build_config_bytes_ads1113_continuous(DataRate::SPS128);
1755            assert_eq!(c, [0, DataRate::SPS128 as u8 | 0x03]);
1756        }
1757    }
1758
1759    #[cfg(feature = "embassy")]
1760    #[test]
1761    fn embassy_timer_api_no_poll() {
1762        // Timer paths: single CONFIG write per start, Timer::after for sync,
1763        // never a CONFIG read loop. `next()` = wait + read (no restart).
1764        let src = include_str!("lib.rs");
1765        for name in ["fn read_channel_timer", "fn continuous_timer", "fn read_a0_a1_timer",
1766            "fn read_adc_a0_timer", "Timer::after", "ContinuousTimer"] {
1767            assert!(src.contains(name), "missing {name}");
1768        }
1769        let m = src.find("mod async_api").expect("async_api");
1770        let end = src[m..].find("/// Embassy integration").map(|e| m + e).unwrap_or(src.len());
1771        assert!(!src[m..end].contains("read_asyncadc"));
1772    }
1773
1774    #[test]
1775    fn rdy_threshold_values() {
1776        // Conversion-ready requires Hi=0x8000, Lo=0x0000 and COMP_QUE != 11b.
1777        let cfg = Config::default().as_conversion_ready();
1778        assert_ne!(cfg.comp_que as u8, 3);
1779        assert_eq!([0x80u8, 0x00u8], [0x80, 0x00]); // Hi_thresh bytes
1780        assert_eq!([0x00u8, 0x00u8], [0x00, 0x00]); // Lo_thresh bytes
1781    }
1782
1783    #[test]
1784    fn polarity_matches_comp_pol() {
1785        assert_eq!(ConversionReadyPolarity::from(CompPol::ActiveLow), ConversionReadyPolarity::ActiveLow);
1786        assert_eq!(ConversionReadyPolarity::from(CompPol::ActiveHigh), ConversionReadyPolarity::ActiveHigh);
1787        assert!(conversion_period_us(DataRate::SPS8) > conversion_period_us(DataRate::SPS860));
1788    }
1789
1790    #[test]
1791    fn polarity_magnitude() {
1792        assert_eq!(signed_to_polarity_magnitude(0), (false, 0));
1793        assert_eq!(signed_to_polarity_magnitude(123), (false, 123));
1794        assert_eq!(signed_to_polarity_magnitude(-123), (true, 123));
1795        assert_eq!(signed_to_polarity_magnitude(i16::MAX), (false, 32767));
1796        assert_eq!(signed_to_polarity_magnitude(i16::MIN), (true, 32768));
1797    }
1798
1799    #[test]
1800    fn diff_mux_mappings() {
1801        assert_eq!(Mux::A0N1 as u8, 0b000);
1802        assert_eq!(Mux::A0N3 as u8, 0b001);
1803        assert_eq!(Mux::A1N3 as u8, 0b010);
1804        assert_eq!(Mux::A2N3 as u8, 0b011);
1805        for (m, bits) in [(Mux::A0N1, 0), (Mux::A0N3, 1), (Mux::A1N3, 2), (Mux::A2N3, 3)] {
1806            let b = build_config_bytes(&Config::default(), m as u8, true, Mode::SingleShot);
1807            assert_eq!((b[0] >> 4) & 7, bits);
1808        }
1809        // as_conversion_ready forces COMP_QUE != 11b, non-latching.
1810        let c = Config::default().as_conversion_ready();
1811        assert_ne!(c.comp_que as u8, 3);
1812        assert_eq!(c.comp_lat as u8, 0);
1813    }
1814
1815    #[cfg(feature = "async")]
1816    #[test]
1817    fn async_recommended_api_has_no_poll_loop() {        // Regression: async module must contain no `loop` polling at all
1818        // (legacy polling removed); expected flow is write CONFIG -> await RDY -> read CONVERSION.
1819        let src = include_str!("lib.rs");
1820        let m = src.find("mod async_api").expect("async_api");
1821        let end = src[m..].find("mod tests").map(|e| m + e).unwrap_or(src.len());
1822        let body = &src[m..end];
1823        // Async modules must contain no `loop {` CONFIG polling. (Sync
1824        // `multi_wait`/`wait_complete_sync` legitimately poll — sync has no
1825        // event source — so check per async fn instead of the whole slice.)
1826        for name in ["fn read_channel_event", "fn read_channel_timed", "fn next_conversion", "fn continuous",
1827            "fn start_all_async", "fn capture_parallel_event", "fn capture_parallel_timer", "fn next_all_timer",
1828            "fn wait_all_ready_async", "fn read_all_async", "fn next_all_event"] {
1829            let i = body.find(name).unwrap_or_else(|| panic!("missing {name}"));
1830            let fn_body = &body[i..body[i..].find("/// ").map(|e| i + e).unwrap_or(body.len())];
1831            let fn_body = &fn_body[..fn_body.find("\n    }\n").unwrap_or(fn_body.len())];
1832            assert!(!fn_body.contains("loop {"), "{name} must not poll");
1833        }
1834        assert!(!body.contains("read_asyncadc") && !body.contains("read_async_adc") && !body.contains("read_async_4adc"));
1835        // Structural guard for the critical race fix: parallel capture must go
1836        // through the concurrent join, never sequential per-device awaits.
1837        // (JoinAllReady lives at crate root, ahead of `mod async_api`.)
1838        for must in ["wait_all_ready(", "JoinAllReady", "read_all_async"] {
1839            assert!(src.contains(must), "missing concurrent primitive {must}");
1840        }
1841    }
1842
1843    // ---- concurrent RDY test: pulse dies unless EVERY detector is armed upfront ----
1844    #[cfg(feature = "async")]
1845    struct PulseRdy<'c> {
1846        clock: &'c std::cell::Cell<u32>,
1847        log: &'c std::cell::RefCell<std::vec::Vec<(u8, usize)>>,
1848        id: usize,
1849        armed_at: std::cell::Cell<Option<u32>>,
1850        arms: std::cell::Cell<u32>,
1851        polls: std::cell::Cell<u32>,
1852        fire_after: u32,
1853        deadline: u32,
1854    }
1855    #[cfg(feature = "async")]
1856    struct PulseFut<'a, 'c> {
1857        rdy: &'a PulseRdy<'c>,
1858    }
1859    #[cfg(feature = "async")]
1860    impl<'c> ConversionReady for PulseRdy<'c> {
1861        type Error = core::convert::Infallible;
1862        type WaitFuture<'a> = PulseFut<'a, 'c> where Self: 'a;
1863        fn arm(&mut self, _pol: ConversionReadyPolarity) {
1864            // Synchronous hardware arming: records the clock instant from which
1865            // the (short) pulse is observable. Tag 0 = arm event in order log.
1866            // Every call re-arms (mirrors re-enabling the edge detector each
1867            // continuous cycle); the pulse is observable iff the LATEST arm
1868            // precedes the conversion completion (deadline).
1869            self.armed_at.set(Some(self.clock.get()));
1870            self.arms.set(self.arms.get() + 1);
1871            self.log.borrow_mut().push((0, self.id));
1872        }
1873        fn wait_for_ready(&mut self, _pol: ConversionReadyPolarity) -> Self::WaitFuture<'_> {
1874            // Tag 2 = wait intent (future creation) in order log.
1875            self.log.borrow_mut().push((2, self.id));
1876            PulseFut { rdy: self }
1877        }
1878    }
1879    #[cfg(feature = "async")]
1880    impl core::future::Future for PulseFut<'_, '_> {
1881        type Output = Result<(), core::convert::Infallible>;
1882        fn poll(
1883            self: core::pin::Pin<&mut Self>,
1884            cx: &mut core::task::Context<'_>,
1885        ) -> core::task::Poll<Self::Output> {
1886            let r = self.rdy;
1887            // Unarmed detector observes nothing (mirrors hardware: no interrupt
1888            // registered => pulse passes unseen), even once polled.
1889            let armed = match r.armed_at.get() {
1890                Some(t) => t,
1891                None => return core::task::Poll::Pending,
1892            };
1893            // Pulse already gone if the detector was armed too late.
1894            if armed > r.deadline {
1895                return core::task::Poll::Pending; // never fires: missed ~8µs pulse
1896            }
1897            let p = r.polls.get() + 1;
1898            r.polls.set(p);
1899            if p >= r.fire_after {
1900                core::task::Poll::Ready(Ok(()))
1901            } else {
1902                cx.waker().wake_by_ref();
1903                core::task::Poll::Pending
1904            }
1905        }
1906    }
1907
1908    #[cfg(feature = "async")]
1909    #[test]
1910    fn wait_all_ready_arms_every_detector_upfront() {
1911        use std::sync::Arc;
1912        struct Noop;
1913        impl std::task::Wake for Noop {
1914            fn wake(self: Arc<Self>) {}
1915        }
1916        let waker = std::task::Waker::from(Arc::new(Noop));
1917        let clock = std::cell::Cell::new(0u32);
1918        let log = std::cell::RefCell::new(std::vec::Vec::new());
1919        // Pulse observable ONLY if armed at clock 0 with 2 self-polls to fire:
1920        // a sequential wait(RDY0).await; wait(RDY1).await; ... implementation
1921        // would arm detectors 1..3 late and hang forever here. Per the trait
1922        // contract, arming is the driver's explicit synchronous step BEFORE
1923        // polling: mirror `capture_parallel_event` (arm all, then join).
1924        let mut rdy: [PulseRdy; 4] = core::array::from_fn(|id| PulseRdy {
1925            clock: &clock,
1926            log: &log,
1927            id,
1928            armed_at: std::cell::Cell::new(None),
1929            arms: std::cell::Cell::new(0),
1930            polls: std::cell::Cell::new(0),
1931            fire_after: 2,
1932            deadline: 0,
1933        });
1934        for r in rdy.iter_mut() {
1935            r.arm(ConversionReadyPolarity::ActiveLow);
1936        }
1937        assert_eq!(log.borrow().len(), 4, "all detectors armed before first poll");
1938        let mut join = wait_all_ready(&mut rdy, [ConversionReadyPolarity::ActiveLow; 4]);
1939        let mut join = unsafe { core::pin::Pin::new_unchecked(&mut join) };
1940        let mut cx = core::task::Context::from_waker(&waker);
1941        let mut rounds = 0;
1942        let done = loop {
1943            clock.set(rounds);
1944            match join.as_mut().poll(&mut cx) {
1945                core::task::Poll::Ready(r) => break r,
1946                core::task::Poll::Pending => {}
1947            }
1948            rounds += 1;
1949            assert!(rounds < 100, "RDY pulse missed: detectors not observed concurrently");
1950        };
1951        assert!(done.is_ok());
1952        for r in rdy.iter() {
1953            assert_eq!(r.armed_at.get(), Some(0), "every detector armed before START");
1954        }
1955    }
1956
1957    #[cfg(feature = "async")]
1958    #[test]
1959    fn unarmed_join_observes_nothing() {
1960        // Contract enforcement: without the explicit synchronous arm step, the
1961        // join must NOT complete — this is what makes arm-before-start load
1962        // bearing rather than advisory.
1963        use std::sync::Arc;
1964        struct Noop;
1965        impl std::task::Wake for Noop {
1966            fn wake(self: Arc<Self>) {}
1967        }
1968        let waker = std::task::Waker::from(Arc::new(Noop));
1969        let clock = std::cell::Cell::new(0u32);
1970        let log = std::cell::RefCell::new(std::vec::Vec::new());
1971        let mut rdy: [PulseRdy; 2] = core::array::from_fn(|id| PulseRdy {
1972            clock: &clock,
1973            log: &log,
1974            id,
1975            armed_at: std::cell::Cell::new(None),
1976            arms: std::cell::Cell::new(0),
1977            polls: std::cell::Cell::new(0),
1978            fire_after: 1,
1979            deadline: u32::MAX,
1980        });
1981        let mut join = wait_all_ready(&mut rdy, [ConversionReadyPolarity::ActiveLow; 2]);
1982        let mut join = unsafe { core::pin::Pin::new_unchecked(&mut join) };
1983        let mut cx = core::task::Context::from_waker(&waker);
1984        let mut ready = false;
1985        for _ in 0..20 {
1986            if matches!(join.as_mut().poll(&mut cx), core::task::Poll::Ready(_)) {
1987                ready = true;
1988                break;
1989            }
1990        }
1991        assert!(!ready, "un-armed detectors must observe nothing");
1992    }
1993
1994    // ---- wait-error attribution: failing device index + address, not index 0 ----
1995    #[cfg(feature = "async")]
1996    struct FailRdy {
1997        fail: bool,
1998        armed: std::cell::Cell<bool>,
1999    }
2000    #[cfg(feature = "async")]
2001    struct FailFut<'a> {
2002        rdy: &'a FailRdy,
2003    }
2004    #[cfg(feature = "async")]
2005    impl ConversionReady for FailRdy {
2006        type Error = MockErr;
2007        type WaitFuture<'a> = FailFut<'a> where Self: 'a;
2008        fn arm(&mut self, _pol: ConversionReadyPolarity) {
2009            self.armed.set(true);
2010        }
2011        fn wait_for_ready(&mut self, _pol: ConversionReadyPolarity) -> Self::WaitFuture<'_> {
2012            FailFut { rdy: self }
2013        }
2014    }
2015    #[cfg(feature = "async")]
2016    impl core::future::Future for FailFut<'_> {
2017        type Output = Result<(), MockErr>;
2018        fn poll(
2019            self: core::pin::Pin<&mut Self>,
2020            cx: &mut core::task::Context<'_>,
2021        ) -> core::task::Poll<Self::Output> {
2022            if self.rdy.fail {
2023                return core::task::Poll::Ready(Err(MockErr));
2024            }
2025            if self.rdy.armed.get() {
2026                core::task::Poll::Ready(Ok(()))
2027            } else {
2028                cx.waker().wake_by_ref();
2029                core::task::Poll::Pending
2030            }
2031        }
2032    }
2033
2034    #[cfg(feature = "async")]
2035    #[test]
2036    fn wait_failure_attributes_correct_device() {
2037        // Device 2 (address SDA) fails: join reports index 2, and the multi
2038        // wrapper maps it to index 2 + address SDA (previously hardcoded 0).
2039        let mut rdy = [
2040            FailRdy { fail: false, armed: std::cell::Cell::new(false) },
2041            FailRdy { fail: false, armed: std::cell::Cell::new(false) },
2042            FailRdy { fail: true, armed: std::cell::Cell::new(false) },
2043            FailRdy { fail: false, armed: std::cell::Cell::new(false) },
2044        ];
2045        for r in rdy.iter_mut() {
2046            r.arm(ConversionReadyPolarity::ActiveLow);
2047        }
2048        let err = block_on(wait_all_ready(&mut rdy, [ConversionReadyPolarity::ActiveLow; 4]));
2049        match err {
2050            Err(WaitFailed { index: 2, .. }) => {}
2051            other => panic!("expected failure at index 2, got {other:?}"),
2052        }
2053
2054        let cfg = Config::default().as_conversion_ready();
2055        let multi = MultiADS1115::new(
2056            MockI2c::new(),
2057            [
2058                DeviceConfig::new(Address::Ground, cfg, Mux::A0),
2059                DeviceConfig::new(Address::VDD, cfg, Mux::A1),
2060                DeviceConfig::new(Address::SDA, cfg, Mux::A2),
2061                DeviceConfig::new(Address::SCL, cfg, Mux::A3),
2062            ],
2063        )
2064        .unwrap();
2065        let mut rdy = [
2066            FailRdy { fail: false, armed: std::cell::Cell::new(false) },
2067            FailRdy { fail: false, armed: std::cell::Cell::new(false) },
2068            FailRdy { fail: true, armed: std::cell::Cell::new(false) },
2069            FailRdy { fail: false, armed: std::cell::Cell::new(false) },
2070        ];
2071        let err = block_on(multi.wait_all_ready_async(&mut rdy));
2072        match err {
2073            Err(MultiError::Wait { index: 2, address: Address::SDA, .. }) => {}
2074            other => panic!("expected Wait{{index 2, SDA}}, got {other:?}"),
2075        }
2076    }
2077
2078    // ---- end-to-end: capture_parallel_event arms ALL before ANY start ----
2079    #[cfg(feature = "async")]
2080    struct AsyncMockI2c<'c> {
2081        log: &'c std::cell::RefCell<std::vec::Vec<(u8, usize)>>,
2082        canned: [(u8, i16); 2],
2083    }
2084    #[cfg(feature = "async")]
2085    impl embedded_hal::i2c::ErrorType for AsyncMockI2c<'_> {
2086        type Error = MockErr;
2087    }
2088    #[cfg(feature = "async")]
2089    impl embedded_hal_async::i2c::I2c for AsyncMockI2c<'_> {
2090        async fn read(&mut self, _a: u8, _b: &mut [u8]) -> Result<(), Self::Error> { Ok(()) }
2091        async fn write(&mut self, addr: u8, bytes: &[u8]) -> Result<(), Self::Error> {
2092            if !bytes.is_empty() && bytes[0] == REG_CONFIG {
2093                // Tag 1 = CONFIG START write; records detector-arm coverage implicitly
2094                // via shared order log (tag 0 = arm from PulseRdy).
2095                self.log.borrow_mut().push((1, (addr - 0x48) as usize));
2096            }
2097            Ok(())
2098        }
2099        async fn write_read(&mut self, addr: u8, write: &[u8], read: &mut [u8]) -> Result<(), Self::Error> {
2100            if write == [REG_CONVERSION] {
2101                let v = self.canned.iter().find(|(a, _)| *a == addr).map(|(_, v)| *v).unwrap_or(0);
2102                read.copy_from_slice(&v.to_be_bytes()[..read.len().min(2)]);
2103            }
2104            Ok(())
2105        }
2106        async fn transaction(
2107            &mut self,
2108            _a: u8,
2109            _o: &mut [embedded_hal::i2c::Operation<'_>],
2110        ) -> Result<(), Self::Error> {
2111            Ok(())
2112        }
2113    }
2114
2115    #[cfg(feature = "async")]
2116    fn block_on<F: core::future::Future>(mut f: F) -> F::Output {
2117        use std::sync::Arc;
2118        struct Noop;
2119        impl std::task::Wake for Noop {
2120            fn wake(self: Arc<Self>) {}
2121        }
2122        let waker = std::task::Waker::from(Arc::new(Noop));
2123        let mut cx = core::task::Context::from_waker(&waker);
2124        // SAFETY: future never moved after pinning; driven to completion here.
2125        let mut f = unsafe { core::pin::Pin::new_unchecked(&mut f) };
2126        let mut rounds = 0u32;
2127        loop {
2128            match f.as_mut().poll(&mut cx) {
2129                core::task::Poll::Ready(v) => return v,
2130                core::task::Poll::Pending => {}
2131            }
2132            rounds += 1;
2133            assert!(rounds < 1000, "async test future stalled");
2134        }
2135    }
2136
2137    #[cfg(feature = "async")]
2138    #[test]
2139    fn capture_parallel_event_arms_before_any_start() {
2140        let clock = std::cell::Cell::new(0u32);
2141        let log = std::cell::RefCell::new(std::vec::Vec::new());
2142        let i2c = AsyncMockI2c { log: &log, canned: [(0x48, 1000), (0x49, -2000)] };
2143        let cfg = Config::default().with_data_rate(DataRate::SPS860).as_conversion_ready();
2144        let mut multi = MultiADS1115::new(
2145            i2c,
2146            [DeviceConfig::new(Address::Ground, cfg, Mux::A0), DeviceConfig::new(Address::VDD, cfg, Mux::A1)],
2147        )
2148        .unwrap();
2149        let mut rdy: [PulseRdy; 2] = core::array::from_fn(|id| PulseRdy {
2150            clock: &clock,
2151            log: &log,
2152            id,
2153            armed_at: std::cell::Cell::new(None),
2154            arms: std::cell::Cell::new(0),
2155            polls: std::cell::Cell::new(0),
2156            fire_after: 1,
2157            deadline: u32::MAX,
2158        });
2159        let samples = block_on(multi.capture_parallel_event([Mux::A0, Mux::A1], &mut rdy)).unwrap();
2160        assert_eq!([samples[0].value, samples[1].value], [1000, -2000]);
2161        assert_eq!([samples[0].address, samples[1].address], [Address::Ground, Address::VDD]);
2162        // Order proof: every arm (tag 0) precedes every CONFIG START (tag 1).
2163        let log = log.borrow();
2164        let first_start = log.iter().position(|(t, _)| *t == 1).expect("starts logged");
2165        assert!(log.iter().take(first_start).all(|(t, _)| *t == 0));
2166        assert_eq!(log.iter().filter(|(t, _)| *t == 0).count(), 2, "both detectors armed");
2167        assert_eq!(log.iter().filter(|(t, _)| *t == 1).count(), 2, "both ADCs started");
2168    }
2169
2170    #[cfg(feature = "async")]
2171    #[test]
2172    fn continuous_all_event_arms_before_any_start() {
2173        // The combined continuous constructor must arm ALL detectors before
2174        // the first CONFIG START (MODE=0/OS=0), then next_all_event() must
2175        // re-arm every cycle before waiting.
2176        let clock = std::cell::Cell::new(0u32);
2177        let log = std::cell::RefCell::new(std::vec::Vec::new());
2178        let i2c = AsyncMockI2c { log: &log, canned: [(0x48, 1000), (0x49, -2000)] };
2179        let cfg = Config::default().with_data_rate(DataRate::SPS860).as_conversion_ready();
2180        let mut multi = MultiADS1115::new(
2181            i2c,
2182            [DeviceConfig::new(Address::Ground, cfg, Mux::A0), DeviceConfig::new(Address::VDD, cfg, Mux::A1)],
2183        )
2184        .unwrap();
2185        let mut rdy: [PulseRdy; 2] = core::array::from_fn(|id| PulseRdy {
2186            clock: &clock,
2187            log: &log,
2188            id,
2189            armed_at: std::cell::Cell::new(None),
2190            arms: std::cell::Cell::new(0),
2191            polls: std::cell::Cell::new(0),
2192            fire_after: 1,
2193            deadline: u32::MAX,
2194        });
2195        block_on(multi.continuous_all_event([Mux::A0, Mux::A1], &mut rdy)).unwrap();
2196        // Order proof: every arm (tag 0) precedes every CONFIG START (tag 1).
2197        // MODE=0/OS=0 continuous starts: exactly 2 writes, both non-OS, MODE clear.
2198        {
2199            let log = log.borrow();
2200            let first_start = log.iter().position(|(t, _)| *t == 1).expect("starts logged");
2201            assert!(log.iter().take(first_start).all(|(t, _)| *t == 0));
2202            assert_eq!(log.iter().filter(|(t, _)| *t == 0).count(), 2, "both detectors armed");
2203            assert_eq!(log.iter().filter(|(t, _)| *t == 1).count(), 2, "both ADCs started once");
2204        }
2205        // One full cycle: re-arm all (tag 0), wait intents (tag 2), then reads.
2206        let samples = block_on(multi.next_all_event(&mut rdy)).unwrap();
2207        assert_eq!([samples[0].value, samples[1].value], [1000, -2000]);
2208        let log = log.borrow();
2209        let tags: std::vec::Vec<u8> = log.iter().map(|(t, _)| *t).collect();
2210        assert_eq!(&tags[..6], &[0, 0, 1, 1, 0, 0], "arm,arm,start,start then re-arm,re-arm");
2211        assert_eq!(tags.iter().filter(|t| **t == 2).count(), 2, "both waits observed");
2212    }
2213
2214    #[cfg(feature = "async")]
2215    #[test]
2216    fn single_shot_event_arms_before_start() {
2217        // The single-ADC race from review: read_channel_event must arm RDY
2218        // BEFORE writing CONFIG START, not after.
2219        let clock = std::cell::Cell::new(0u32);
2220        let log = std::cell::RefCell::new(std::vec::Vec::new());
2221        let i2c = AsyncMockI2c { log: &log, canned: [(0x48, 555), (0x49, 0)] };
2222        let mut adc = ADS1115::new(Address::Ground, i2c, Config::default().with_data_rate(DataRate::SPS860).as_conversion_ready());
2223        let mut rdy = PulseRdy {
2224            clock: &clock,
2225            log: &log,
2226            id: 0,
2227            armed_at: std::cell::Cell::new(None),
2228            arms: std::cell::Cell::new(0),
2229            polls: std::cell::Cell::new(0),
2230            fire_after: 1,
2231            deadline: u32::MAX,
2232        };
2233        let v = block_on(adc.read_channel_event(Mux::A0, &mut rdy)).unwrap();
2234        assert_eq!(v, 555);
2235        let log = log.borrow();
2236        // arm(0) -> CONFIG START(1) -> wait intent(2): arm precedes START.
2237        assert_eq!(log.as_slice(), &[(0, 0), (1, 0), (2, 0)], "arm must precede CONFIG START");
2238    }
2239
2240    #[cfg(feature = "async")]
2241    #[test]
2242    fn continuous_next_rearms_every_cycle() {
2243        // Regression for the single-ADC continuous re-arm race: each
2244        // `next()` must be arm -> wait -> read, so a ~8µs pulse firing between
2245        // cycles is observed rather than missed. Proves the exact event order
2246        // across stream construction + two samples, for ADS1115 and ADS1114.
2247        let clock = std::cell::Cell::new(0u32);
2248        let log = std::cell::RefCell::new(std::vec::Vec::new());
2249        // ADS1115 stream: arm, START, then per-sample arm -> wait -> read.
2250        let i2c = AsyncMockI2c { log: &log, canned: [(0x48, 111), (0x49, 0)] };
2251        let mut adc = ADS1115::new(Address::Ground, i2c, Config::default().as_conversion_ready());
2252        let mut rdy = PulseRdy {
2253            clock: &clock,
2254            log: &log,
2255            id: 0,
2256            armed_at: std::cell::Cell::new(None),
2257            arms: std::cell::Cell::new(0),
2258            polls: std::cell::Cell::new(0),
2259            fire_after: 1,
2260            deadline: u32::MAX,
2261        };
2262        let mut stream = block_on(adc.continuous(Mux::A0, &mut rdy)).unwrap();
2263        assert_eq!(block_on(stream.next()).unwrap(), 111);
2264        assert_eq!(block_on(stream.next()).unwrap(), 111);
2265        drop(stream);
2266        assert_eq!(
2267            log.borrow().as_slice(),
2268            &[(0, 0), (1, 0), (0, 0), (2, 0), (0, 0), (2, 0)],
2269            "continuous() arms before START; every next() re-arms before waiting"
2270        );
2271        assert_eq!(rdy.arms.get(), 3, "constructor + one re-arm per next()");
2272        // ADS1114 stream constructor shares the same guarantee (no MUX arg).
2273        log.borrow_mut().clear();
2274        let i2c = AsyncMockI2c { log: &log, canned: [(0x48, 222), (0x49, 0)] };
2275        let mut adc = ADS1114::new(i2c, Config::default().as_conversion_ready());
2276        let mut rdy = PulseRdy {
2277            clock: &clock,
2278            log: &log,
2279            id: 0,
2280            armed_at: std::cell::Cell::new(None),
2281            arms: std::cell::Cell::new(0),
2282            polls: std::cell::Cell::new(0),
2283            fire_after: 1,
2284            deadline: u32::MAX,
2285        };
2286        let mut stream = block_on(adc.continuous(&mut rdy)).unwrap();
2287        assert_eq!(block_on(stream.next()).unwrap(), 222);
2288        drop(stream);
2289        assert_eq!(
2290            log.borrow().as_slice(),
2291            &[(0, 0), (1, 0), (0, 0), (2, 0)],
2292            "ADS1114 continuous() arms before START; next() re-arms"
2293        );
2294        assert_eq!(rdy.arms.get(), 2);
2295    }
2296
2297    #[test]
2298    fn enable_rdy_upgrades_comparator_config() {
2299        // Option A: enabling RDY must leave the device actually usable, i.e.
2300        // COMP_QUE != DisableComparator despite the struct default.
2301        let mut adc = ADS1115::new(Address::Ground, MockI2c::new(), Config::default());
2302        assert_eq!(adc.config.comp_que as u8, CompQue::DisableComparator as u8);
2303        adc.enable_conversion_ready_pin().unwrap();
2304        assert_ne!(adc.config.comp_que as u8, CompQue::DisableComparator as u8);
2305        assert_eq!(adc.config.comp_lat as u8, CompLat::NonLatching as u8);
2306        // Two threshold writes: HI=0x8000, LO=0x0000.
2307        let thresh: std::vec::Vec<(u8, std::vec::Vec<u8>)> =
2308            adc.i2c.writes.iter().cloned().collect();
2309        assert_eq!(thresh.len(), 2);
2310        assert_eq!(thresh[0].1.as_slice(), &[REG_HI_THRESH, 0x80, 0x00]);
2311        assert_eq!(thresh[1].1.as_slice(), &[REG_LO_THRESH, 0x00, 0x00]);
2312    }
2313
2314    #[test]
2315    fn rdy_precondition_predicate_and_guard() {
2316        // Default config leaves RDY inactive; the predicate reports it and the
2317        // event entry points refuse it in debug builds instead of hanging.
2318        assert!(!Config::default().is_conversion_ready());
2319        assert!(Config::default().as_conversion_ready().is_conversion_ready());
2320    }
2321
2322    #[test]
2323    #[should_panic(expected = "COMP_QUE == DisableComparator")]
2324    fn event_api_rejects_disabled_comparator_in_debug() {
2325        // Entering an RDY event API without conversion-ready mode must fail
2326        // loudly in debug builds rather than hang on an inactive pin.
2327        let mut adc = ADS1115::new(Address::Ground, MockI2c::new(), Config::default());
2328        adc.start_continuous(Mux::A0).unwrap();
2329        let mut rdy = MockBlockingRdy { waited: std::cell::Cell::new(0), armed: std::cell::Cell::new(0) };
2330        let _ = adc.next_conversion_blocking(&mut rdy);
2331    }
2332
2333    #[test]
2334    fn timer_margin_is_proportional_not_blanket() {
2335        // ~12.5% + 100µs: full-rate friendly, still strictly above nominal.
2336        // 860 SPS: nominal 1162µs -> ~1407µs (≈710 reads/s, was ≈462 with +1ms).
2337        assert_eq!(conversion_period_us(DataRate::SPS860), 1162 + 145 + 100);
2338        assert_eq!(conversion_period_us(DataRate::SPS8), 125_000 + 15_625 + 100);
2339        for dr in [DataRate::SPS8, DataRate::SPS128, DataRate::SPS860] {
2340            let nominal = 1_000_000
2341                / match dr {
2342                    DataRate::SPS8 => 8,
2343                    DataRate::SPS128 => 128,
2344                    DataRate::SPS860 => 860,
2345                    _ => unreachable!(),
2346                };
2347            assert!(conversion_period_us(dr) > nominal);
2348        }
2349    }
2350
2351    // ---- multi-device mock tests (host-side std allowed in tests) ----
2352    #[derive(Debug, Clone, Copy, PartialEq, Eq)]
2353    struct MockErr;
2354    impl embedded_hal::i2c::Error for MockErr {
2355        fn kind(&self) -> embedded_hal::i2c::ErrorKind { embedded_hal::i2c::ErrorKind::Other }
2356    }
2357
2358    struct MockI2c {
2359        /// (addr, bytes) for every write, in order.
2360        writes: std::vec::Vec<(u8, std::vec::Vec<u8>)>,
2361        /// canned conversion result per address.
2362        canned: [(u8, i16); 4],
2363        config_reads: usize,
2364        /// Fail the nth `write` call (0-based) with `MockErr`; `None` disables.
2365        fail_write: Option<usize>,
2366        write_count: usize,
2367    }
2368    impl MockI2c {
2369        fn new() -> Self {
2370            Self {
2371                writes: std::vec::Vec::new(),
2372                canned: [(0x48, 100), (0x49, -200), (0x4A, 300), (0x4B, i16::MIN)],
2373                config_reads: 0,
2374                fail_write: None,
2375                write_count: 0,
2376            }
2377        }
2378        fn canned(&self, addr: u8) -> i16 {
2379            self.canned.iter().find(|(a, _)| *a == addr).map(|(_, v)| *v).unwrap_or(0)
2380        }
2381        fn config_writes(&self) -> usize {
2382            self.writes.iter().filter(|(_, b)| !b.is_empty() && b[0] == REG_CONFIG).count()
2383        }
2384    }
2385    impl embedded_hal::i2c::ErrorType for MockI2c {
2386        type Error = MockErr;
2387    }
2388    impl embedded_hal::i2c::I2c for MockI2c {
2389        fn read(&mut self, _a: u8, _b: &mut [u8]) -> Result<(), Self::Error> { Ok(()) }
2390        fn write(&mut self, addr: u8, bytes: &[u8]) -> Result<(), Self::Error> {
2391            if self.fail_write == Some(self.write_count) {
2392                self.write_count += 1;
2393                return Err(MockErr);
2394            }
2395            self.write_count += 1;
2396            self.writes.push((addr, bytes.to_vec()));
2397            Ok(())
2398        }
2399        fn write_read(&mut self, addr: u8, write: &[u8], read: &mut [u8]) -> Result<(), Self::Error> {
2400            match write {
2401                [REG_CONVERSION] => {
2402                    read.copy_from_slice(&self.canned(addr).to_be_bytes()[..read.len().min(2)]);
2403                    Ok(())
2404                }
2405                [REG_CONFIG] => {
2406                    self.config_reads += 1;
2407                    // OS=1 (complete) + preserve nothing else; tests only need OS bit.
2408                    read[0] = 0x80;
2409                    if read.len() > 1 { read[1] = 0; }
2410                    Ok(())
2411                }
2412                _ => Ok(()),
2413            }
2414        }
2415        fn transaction(&mut self, _a: u8, _o: &mut [embedded_hal::i2c::Operation<'_>]) -> Result<(), Self::Error> { Ok(()) }
2416    }
2417
2418    fn dev(address: Address, gain: Gain, dr: DataRate, mux: Mux) -> DeviceConfig {
2419        DeviceConfig::new(address, Config::default().with_gain(gain).with_data_rate(dr), mux)
2420    }
2421
2422    #[test]
2423    fn multi_partial_start_leaves_earlier_devices_running() {
2424        // Failure semantics proof: if the 3rd device's START write fails,
2425        // devices 0-1 remain started (their CONFIG writes are on the bus),
2426        // device 2+ never started, no rollback, error names device 2/SDA.
2427        let mut bus = MockI2c::new();
2428        bus.fail_write = Some(2);
2429        let mut m = MultiADS1115::new(
2430            bus,
2431            [
2432                dev(Address::Ground, Gain::V2_048, DataRate::SPS128, Mux::A0),
2433                dev(Address::VDD, Gain::V2_048, DataRate::SPS128, Mux::A1),
2434                dev(Address::SDA, Gain::V2_048, DataRate::SPS128, Mux::A2),
2435            ],
2436        )
2437        .unwrap();
2438        let err = m.start_all([Mux::A0, Mux::A1, Mux::A2]).unwrap_err();
2439        match err {
2440            MultiError::I2c { index: 2, address: Address::SDA, .. } => {}
2441            other => panic!("expected I2c error at device 2/SDA, got {other:?}"),
2442        }
2443        // Devices 0-1 started (CONFIG on bus), device 2 absent: partial start.
2444        let started: std::vec::Vec<u8> = m
2445            .i2c
2446            .writes
2447            .iter()
2448            .filter(|(_, b)| !b.is_empty() && b[0] == REG_CONFIG)
2449            .map(|(a, _)| *a)
2450            .collect();
2451        assert_eq!(started.as_slice(), &[0x48, 0x49]);
2452    }
2453
2454    #[test]
2455    fn multi_rejects_duplicate_addresses() {        let r = MultiADS1115::new(MockI2c::new(), [
2456            dev(Address::Ground, Gain::V6_144, DataRate::SPS128, Mux::A0),
2457            dev(Address::Ground, Gain::V2_048, DataRate::SPS860, Mux::A1),
2458        ]);
2459        assert!(matches!(r, Err(MultiError::DuplicateAddress { first: 0, second: 1, .. })));
2460    }
2461
2462    #[test]
2463    fn multi_parallel_start_before_read_and_ordering() {
2464        let mut m = MultiADS1115::new(MockI2c::new(), [
2465            dev(Address::Ground, Gain::V6_144, DataRate::SPS128, Mux::A0),
2466            dev(Address::VDD, Gain::V2_048, DataRate::SPS860, Mux::A1),
2467            dev(Address::SDA, Gain::V1_024, DataRate::SPS250, Mux::A2),
2468            dev(Address::SCL, Gain::V0_512, DataRate::SPS64, Mux::A3),
2469        ]).unwrap();
2470        let s = m.capture_parallel([Mux::A0, Mux::A1, Mux::A2, Mux::A3]).unwrap();
2471        // Identity + ordering preserved.
2472        assert_eq!((s[0].address, s[0].value), (Address::Ground, 100));
2473        assert_eq!((s[1].address, s[1].value), (Address::VDD, -200));
2474        assert_eq!((s[2].address, s[2].value), (Address::SDA, 300));
2475        assert_eq!((s[3].address, s[3].value), (Address::SCL, i16::MIN));
2476        // All 4 STARTs (CONFIG writes) precede any CONVERSION read: CONFIG reads
2477        // happen only in read phase; count CONFIG writes == 4 exactly.
2478        assert_eq!(m.i2c.config_writes(), 4);
2479        // Independent configs: distinct gain bits per device in CONFIG writes.
2480        let cfgs: std::vec::Vec<[u8; 2]> = m.i2c.writes.iter()
2481            .filter(|(_, b)| b[0] == REG_CONFIG)
2482            .map(|(_, b)| [b[1], b[2]]).collect();
2483        assert_eq!(cfgs.len(), 4);
2484        assert!(cfgs[0][0] & 0x0E != cfgs[1][0] & 0x0E);
2485        // MUX bits follow requested order A0..A3.
2486        for (i, c) in cfgs.iter().enumerate() {
2487            assert_eq!((c[0] >> 4) & 7, (0b100 + i as u8) & 7);
2488        }
2489    }
2490
2491    #[test]
2492    fn multi_diff_and_invalid() {
2493        let mut m = MultiADS1115::new(MockI2c::new(), [
2494            dev(Address::Ground, Gain::V2_048, DataRate::SPS128, Mux::A0N1),
2495            dev(Address::VDD, Gain::V2_048, DataRate::SPS128, Mux::A0N3),
2496        ]).unwrap();
2497        let d = m.capture_parallel_diff([Mux::A0N1, Mux::A0N3]).unwrap();
2498        assert_eq!((d[0].negative, d[0].magnitude), (false, 100));
2499        assert_eq!((d[1].negative, d[1].magnitude), (true, 200));
2500        assert!(matches!(m.start_conversion(9, Mux::A0), Err(MultiError::InvalidIndex(9))));
2501        // Raw-bits helper rejects out-of-range instead of masking.
2502        let mut single = ADS1115::new(Address::Ground, MockI2c::new(), Config::default());
2503        assert!(matches!(single.read_channel_raw_bits(8), Err(MuxOrI2c::Mux(MuxError(8)))));
2504        assert!(matches!(single.read_channel_raw_bits(255), Err(MuxOrI2c::Mux(_))));
2505        assert!(single.read_channel_raw_bits(4).is_ok());
2506    }
2507
2508    #[test]
2509    fn multi_continuous_writes_once() {
2510        let mut m = MultiADS1115::new(MockI2c::new(), [
2511            dev(Address::Ground, Gain::V2_048, DataRate::SPS128, Mux::A0),
2512            dev(Address::VDD, Gain::V2_048, DataRate::SPS128, Mux::A1),
2513        ]).unwrap();
2514        m.start_continuous_all([Mux::A0, Mux::A1]).unwrap();
2515        // MODE=0, OS=0 on both.
2516        for (_, b) in m.i2c.writes.iter().filter(|(_, b)| b[0] == REG_CONFIG) {
2517            assert_eq!(b[1] & 1, 0);
2518            assert_eq!(b[1] & 0x80, 0);
2519        }
2520        let before = m.i2c.config_writes();
2521        // Timed continuous read: delay called once with the max period, CONFIG
2522        // never rewritten, and — critically — no OS-bit polling (OS reads 0 in
2523        // continuous mode, so polling would hang forever).
2524        let mut delays = std::vec::Vec::new();
2525        let s = m.next_all_timed(|us| delays.push(us)).unwrap();
2526        assert_eq!(m.i2c.config_writes(), before, "next_all must not rewrite CONFIG");
2527        assert_eq!(delays.len(), 1, "single shared delay, not per-device");
2528        assert_eq!(delays[0], conversion_period_us(DataRate::SPS128));
2529        assert_eq!(m.i2c.config_reads, 0, "no CONFIG polling in continuous mode");
2530        assert_eq!(s[0].value, 100);
2531        assert_eq!(s[1].value, -200);
2532    }
2533
2534    struct MockBlockingRdy {
2535        waited: std::cell::Cell<u32>,
2536        armed: std::cell::Cell<u32>,
2537    }
2538    impl BlockingReady for MockBlockingRdy {
2539        type Error = MockErr;
2540        fn arm(&mut self, _pol: ConversionReadyPolarity) {
2541            self.armed.set(self.armed.get() + 1);
2542        }
2543        fn wait_for_ready(&mut self, _pol: ConversionReadyPolarity) -> Result<(), Self::Error> {
2544            // Mirrors latched hardware: the wait observes the edge only if the
2545            // detector was armed first.
2546            assert!(self.armed.get() > 0, "wait without arm");
2547            self.waited.set(self.waited.get() + 1);
2548            Ok(())
2549        }
2550    }
2551
2552    #[test]
2553    fn sync_continuous_uses_rdy_or_delay_not_os_poll() {
2554        let mut adc = ADS1115::new(Address::Ground, MockI2c::new(), Config::default());
2555        // RDY precondition: enable conversion-ready mode first (also covers the
2556        // debug_assert_rdy guard — default config would now panic by design).
2557        adc.enable_conversion_ready_pin().unwrap();
2558        assert!(adc.config.is_conversion_ready());
2559        adc.start_continuous(Mux::A0).unwrap();
2560        // Blocking variant: waits on RDY, reads conversion, never polls CONFIG.
2561        let mut rdy = MockBlockingRdy { waited: std::cell::Cell::new(0), armed: std::cell::Cell::new(0) };
2562        let v = adc.next_conversion_blocking(&mut rdy).unwrap();
2563        assert_eq!(v, 100);
2564        assert_eq!(rdy.waited.get(), 1);
2565        assert_eq!(rdy.armed.get(), 1, "single-ADC blocking path re-arms every sample");
2566        assert_eq!(adc.i2c.config_reads, 0, "OS polling invalid in continuous mode");
2567        // Timed variant: delay called once with the data-rate period.
2568        let mut delays = std::vec::Vec::new();
2569        let v = adc.next_conversion_timed(|us| delays.push(us)).unwrap();
2570        assert_eq!(v, 100);
2571        assert_eq!(delays.as_slice(), &[conversion_period_us(DataRate::SPS860)]);
2572        assert_eq!(adc.i2c.config_reads, 0, "OS polling invalid in continuous mode");
2573        // Static guard: sync continuous bodies must not reference the OS poll.
2574        // (There is deliberately no multi-device blocking RDY API: sequential
2575        // blocking waits cannot observe N independent ~8µs pulses. The safe
2576        // multi paths are next_all_event() and next_all_timed().)
2577        let src = include_str!("lib.rs");
2578        for name in ["fn next_conversion_blocking", "fn next_conversion_timed", "fn next_all_timed"] {
2579            let i = src.find(name).expect(name);
2580            let body = &src[i..src[i..].find("\n    }\n").map(|e| i + e).unwrap_or(src.len())];
2581            assert!(!body.contains("wait_complete_sync") && !body.contains("multi_wait"),
2582                "{name} must not OS-poll in continuous mode");
2583        }
2584        // The needle is assembled at runtime so this very assertion's source
2585        // text cannot self-match the forbidden definition.
2586        let needle = std::string::String::from("pub fn next") + "_all_blocking";
2587        assert!(!src.contains(&needle[..]),
2588            "sequential blocking multi-RDY waits were removed as pulse-unsafe");
2589    }
2590}