#![no_std]
#[cfg(feature = "async")]
#[allow(unused_imports)]
use embedded_hal_async::i2c::I2c as AsyncI2c;
use embedded_hal::i2c::I2c;
const REG_CONVERSION: u8 = 0x00;
const REG_CONFIG: u8 = 0x01;
const REG_LO_THRESH: u8 = 0x02;
const REG_HI_THRESH: u8 = 0x03;
const OS_MASK: u8 = 0x80;
#[inline]
pub fn conversion_complete(cfg_hi: u8) -> bool {
cfg_hi & OS_MASK != 0
}
pub struct ADS111x<I2C, const MODEL: u8> {
address: Address,
i2c: I2C,
pub config: Config,
}
pub type ADS1115<I2C> = ADS111x<I2C, 5>;
pub type ADS1114<I2C> = ADS111x<I2C, 4>;
pub type ADS1113<I2C> = ADS111x<I2C, 3>;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Error<I2cError, WaitError = ()> {
I2c(I2cError),
Wait(WaitError),
}
impl<I2cError, WaitError> From<I2cError> for Error<I2cError, WaitError> {
fn from(e: I2cError) -> Self {
Self::I2c(e)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(u8)]
pub enum Mux {
A0N1 = 0b000,
A0N3 = 0b001,
A1N3 = 0b010,
A2N3 = 0b011,
A0 = 0b100,
A1 = 0b101,
A2 = 0b110,
A3 = 0b111,
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub enum Mode {
#[default]
SingleShot,
Continuous,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ConversionReadyPolarity {
ActiveLow,
ActiveHigh,
}
impl From<CompPol> for ConversionReadyPolarity {
fn from(p: CompPol) -> Self {
match p {
CompPol::ActiveLow => Self::ActiveLow,
CompPol::ActiveHigh => Self::ActiveHigh,
}
}
}
pub trait ConversionReady {
type Error;
type WaitFuture<'a>: core::future::Future<Output = Result<(), Self::Error>> + 'a
where
Self: 'a;
fn arm(&mut self, polarity: ConversionReadyPolarity);
fn wait_for_ready(&mut self, polarity: ConversionReadyPolarity) -> Self::WaitFuture<'_>;
}
pub trait BlockingReady {
type Error;
fn arm(&mut self, polarity: ConversionReadyPolarity);
fn wait_for_ready(&mut self, polarity: ConversionReadyPolarity) -> Result<(), Self::Error>;
}
#[cfg(feature = "async")]
pub fn wait_all_ready<R: ConversionReady, const N: usize>(
rdy: &mut [R; N],
pol: [ConversionReadyPolarity; N],
) -> JoinAllReady<'_, R, N> { let ptr = rdy.as_mut_ptr();
let futs = core::array::from_fn(|i| unsafe { (&mut *ptr.add(i)).wait_for_ready(pol[i]) });
JoinAllReady { futs, done: [false; N], remaining: N }
}
#[cfg(feature = "async")]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct WaitFailed<E> {
pub index: usize,
pub error: E,
}
#[cfg(feature = "async")]
pub struct JoinAllReady<'r, R: ConversionReady + 'r, const N: usize> {
futs: [R::WaitFuture<'r>; N],
done: [bool; N],
remaining: usize,
}
#[cfg(feature = "async")]
impl<R: ConversionReady, const N: usize> core::future::Future for JoinAllReady<'_, R, N> { type Output = Result<(), WaitFailed<R::Error>>;
fn poll(
self: core::pin::Pin<&mut Self>,
cx: &mut core::task::Context<'_>,
) -> core::task::Poll<Self::Output> {
let this = unsafe { self.get_unchecked_mut() };
let mut i = 0;
while i < N {
if !this.done[i] {
let f = unsafe { core::pin::Pin::new_unchecked(&mut this.futs[i]) };
match f.poll(cx) {
core::task::Poll::Ready(Ok(())) => {
this.done[i] = true;
this.remaining -= 1;
}
core::task::Poll::Ready(Err(error)) => {
return core::task::Poll::Ready(Err(WaitFailed { index: i, error }))
}
core::task::Poll::Pending => {}
}
}
i += 1;
}
if this.remaining == 0 {
core::task::Poll::Ready(Ok(()))
} else {
core::task::Poll::Pending
}
}
}
pub trait AsyncDelayUs {
type DelayFuture<'a>: core::future::Future<Output = ()> + 'a
where
Self: 'a;
fn delay_us(&mut self, us: u32) -> Self::DelayFuture<'_>;
}
fn build_config_bytes(cfg: &Config, mux_bits: u8, os_start: bool, mode: Mode) -> [u8; 2] {
debug_assert!(!(mode == Mode::Continuous && os_start), "continuous must not set OS=1");
let os = if os_start { 1u8 << 7 } else { 0 };
let mode_bit = match mode {
Mode::SingleShot => 1u8,
Mode::Continuous => 0u8,
};
let hi = os | ((mux_bits & 0x07) << 4) | (cfg.gain as u8) | mode_bit;
let lo = (cfg.data_rate as u8)
| (cfg.comp_mode as u8)
| (cfg.comp_pol as u8)
| (cfg.comp_lat as u8)
| (cfg.comp_que as u8);
[hi, lo]
}
fn build_config_bytes_ads1113(data_rate: DataRate, os_start: bool) -> [u8; 2] {
let os = if os_start { 1u8 << 7 } else { 0 };
let hi = os | 1u8;
let lo = data_rate as u8 | 0x03; [hi, lo]
}
#[cfg(feature = "embassy")]
fn build_config_bytes_ads1113_continuous(data_rate: DataRate) -> [u8; 2] {
[0u8, data_rate as u8 | 0x03]
}
fn write_config<I2C: I2c>(i2c: &mut I2C, addr: u8, bytes: [u8; 2]) -> Result<(), I2C::Error> {
i2c.write(addr, &[REG_CONFIG, bytes[0], bytes[1]])
}
fn read_conversion<I2C: I2c>(i2c: &mut I2C, addr: u8) -> Result<i16, I2C::Error> {
let mut data = [0u8; 2];
i2c.write_read(addr, &[REG_CONVERSION], &mut data)?;
Ok(i16::from_be_bytes(data))
}
pub fn conversion_period_us(dr: DataRate) -> u32 {
let sps = match dr {
DataRate::SPS8 => 8,
DataRate::SPS16 => 16,
DataRate::SPS32 => 32,
DataRate::SPS64 => 64,
DataRate::SPS128 => 128,
DataRate::SPS250 => 250,
DataRate::SPS475 => 475,
DataRate::SPS860 => 860,
};
let nominal = 1_000_000 / sps as u32;
nominal + nominal / 8 + 100
}
impl<I2C> ADS1113<I2C> {
pub fn new(i2c: I2C, config: Config) -> Self {
Self { address: Address::Ground, i2c, config }
}
pub fn release(self) -> I2C { self.i2c }
}
impl<I2C> ADS1114<I2C> {
pub fn new(i2c: I2C, config: Config) -> Self {
Self { address: Address::Ground, i2c, config }
}
pub fn release(self) -> I2C { self.i2c }
}
impl<I2C> ADS1115<I2C> {
pub fn new(address: Address, i2c: I2C, config: Config) -> Self {
Self { address, i2c, config }
}
pub fn release(self) -> I2C { self.i2c }
}
fn wait_complete_sync<I2C: I2c>(i2c: &mut I2C, addr: u8) -> Result<(), I2C::Error> {
let mut buf = [0u8; 2];
loop {
i2c.write_read(addr, &[REG_CONFIG], &mut buf)?;
if conversion_complete(buf[0]) { break; }
}
Ok(())
}
impl<I2C: I2c> ADS1113<I2C> {
fn read_channel_raw(&mut self) -> Result<i16, I2C::Error> {
let b = build_config_bytes_ads1113(self.config.data_rate, true);
write_config(&mut self.i2c, self.address as u8, b)?;
wait_complete_sync(&mut self.i2c, self.address as u8)?;
read_conversion(&mut self.i2c, self.address as u8)
}
pub fn read_adc(&mut self) -> Result<i16, I2C::Error> { self.read_channel_raw() }
pub fn read_adc_timed(&mut self, mut delay_us: impl FnMut(u32)) -> Result<i16, I2C::Error> {
let b = build_config_bytes_ads1113(self.config.data_rate, true);
write_config(&mut self.i2c, self.address as u8, b)?;
delay_us(conversion_period_us(self.config.data_rate));
read_conversion(&mut self.i2c, self.address as u8)
}
}
impl<I2C: I2c> ADS1114<I2C> {
pub fn enable_conversion_ready_pin(&mut self) -> Result<(), I2C::Error> {
enable_rdy_registers(&mut self.i2c, self.address as u8)?;
self.config = self.config.as_conversion_ready();
Ok(())
}
pub fn start_conversion(&mut self) -> Result<(), I2C::Error> {
let b = build_config_bytes(&self.config, 0b100, true, Mode::SingleShot);
write_config(&mut self.i2c, self.address as u8, b)
}
pub fn read_conversion(&mut self) -> Result<i16, I2C::Error> {
read_conversion(&mut self.i2c, self.address as u8)
}
pub fn start_continuous(&mut self) -> Result<(), I2C::Error> {
let b = build_config_bytes(&self.config, 0b100, false, Mode::Continuous);
write_config(&mut self.i2c, self.address as u8, b)
}
pub fn next_conversion_blocking<R: BlockingReady>(
&mut self,
rdy: &mut R,
) -> Result<i16, Error<I2C::Error, R::Error>> {
debug_assert_rdy(&self.config, "ADS1114::next_conversion_blocking");
let pol = ConversionReadyPolarity::from(self.config.comp_pol);
rdy.arm(pol);
rdy.wait_for_ready(pol).map_err(Error::Wait)?;
self.read_conversion().map_err(Error::I2c)
}
pub fn next_conversion_timed(
&mut self,
mut delay_us: impl FnMut(u32),
) -> Result<i16, I2C::Error> {
delay_us(conversion_period_us(self.config.data_rate));
self.read_conversion()
}
fn read_channel_raw(&mut self) -> Result<i16, I2C::Error> {
self.start_conversion()?;
wait_complete_sync(&mut self.i2c, self.address as u8)?;
self.read_conversion()
}
pub fn read_adc(&mut self) -> Result<i16, I2C::Error> { self.read_channel_raw() }
}
#[inline]
pub fn signed_to_polarity_magnitude(value: i16) -> (bool, u16) {
if value < 0 { (true, value.unsigned_abs()) } else { (false, value as u16) }
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub struct Channel0(pub u16);
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub struct Channel1(pub u16);
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub struct Channel2(pub u16);
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub struct Channel3(pub u16);
fn enable_rdy_registers<I2C: I2c>(i2c: &mut I2C, addr: u8) -> Result<(), I2C::Error> {
i2c.write(addr, &[REG_HI_THRESH, 0x80, 0x00])?;
i2c.write(addr, &[REG_LO_THRESH, 0x00, 0x00])?;
Ok(())
}
impl<I2C: I2c> ADS1115<I2C> {
pub fn enable_conversion_ready_pin(&mut self) -> Result<(), I2C::Error> {
enable_rdy_registers(&mut self.i2c, self.address as u8)?;
self.config = self.config.as_conversion_ready();
Ok(())
}
pub fn start_conversion(&mut self, mux: Mux) -> Result<(), I2C::Error> {
let b = build_config_bytes(&self.config, mux as u8, true, Mode::SingleShot);
write_config(&mut self.i2c, self.address as u8, b)
}
pub fn read_conversion(&mut self) -> Result<i16, I2C::Error> {
read_conversion(&mut self.i2c, self.address as u8)
}
pub fn start_continuous(&mut self, mux: Mux) -> Result<(), I2C::Error> {
let b = build_config_bytes(&self.config, mux as u8, false, Mode::Continuous);
write_config(&mut self.i2c, self.address as u8, b)
}
pub fn next_conversion_blocking<R: BlockingReady>(
&mut self,
rdy: &mut R,
) -> Result<i16, Error<I2C::Error, R::Error>> {
debug_assert_rdy(&self.config, "ADS1115::next_conversion_blocking");
let pol = ConversionReadyPolarity::from(self.config.comp_pol);
rdy.arm(pol);
rdy.wait_for_ready(pol).map_err(Error::Wait)?;
self.read_conversion().map_err(Error::I2c)
}
pub fn next_conversion_timed(
&mut self,
mut delay_us: impl FnMut(u32),
) -> Result<i16, I2C::Error> {
delay_us(conversion_period_us(self.config.data_rate));
self.read_conversion()
}
fn read_channel_raw(&mut self, mux: Mux) -> Result<i16, I2C::Error> {
self.start_conversion(mux)?;
wait_complete_sync(&mut self.i2c, self.address as u8)?;
self.read_conversion()
}
pub fn read_channel(&mut self, mux: Mux) -> Result<i16, I2C::Error> { self.read_channel_raw(mux) }
pub fn read_channel_raw_bits(&mut self, mux: u8) -> Result<i16, MuxOrI2c<I2C::Error>> {
Ok(self.read_channel_raw(match_mux(mux).map_err(|_| MuxOrI2c::Mux(MuxError(mux)))?).map_err(MuxOrI2c::I2c)?)
}
pub fn read_adc_a0(&mut self) -> Result<i16, I2C::Error> { self.read_channel_raw(Mux::A0) }
pub fn read_adc_a1(&mut self) -> Result<i16, I2C::Error> { self.read_channel_raw(Mux::A1) }
pub fn read_adc_a2(&mut self) -> Result<i16, I2C::Error> { self.read_channel_raw(Mux::A2) }
pub fn read_adc_a3(&mut self) -> Result<i16, I2C::Error> { self.read_channel_raw(Mux::A3) }
pub fn read_adc_a0n1(&mut self) -> Result<i16, I2C::Error> { self.read_channel_raw(Mux::A0N1) }
pub fn read_adc_a0n3(&mut self) -> Result<i16, I2C::Error> { self.read_channel_raw(Mux::A0N3) }
pub fn read_adc_a1n3(&mut self) -> Result<i16, I2C::Error> { self.read_channel_raw(Mux::A1N3) }
pub fn read_adc_a2n3(&mut self) -> Result<i16, I2C::Error> { self.read_channel_raw(Mux::A2N3) }
pub fn read_4adc(&mut self) -> Result<[i16; 4], I2C::Error> {
Ok([self.read_adc_a0()?, self.read_adc_a1()?, self.read_adc_a2()?, self.read_adc_a3()?])
}
pub fn read_channels(&mut self) -> Result<(Channel0, Channel1, Channel2, Channel3), I2C::Error> {
Ok((Channel0(self.read_adc_a0()? as u16), Channel1(self.read_adc_a1()? as u16),
Channel2(self.read_adc_a2()? as u16), Channel3(self.read_adc_a3()? as u16)))
}
pub fn read_a0_a1(&mut self) -> Result<(bool, u16), I2C::Error> {
Ok(signed_to_polarity_magnitude(self.read_channel_raw(Mux::A0N1)?))
}
pub fn read_a0_a3(&mut self) -> Result<(bool, u16), I2C::Error> {
Ok(signed_to_polarity_magnitude(self.read_channel_raw(Mux::A0N3)?))
}
pub fn read_a1_a3(&mut self) -> Result<(bool, u16), I2C::Error> {
Ok(signed_to_polarity_magnitude(self.read_channel_raw(Mux::A1N3)?))
}
pub fn read_a2_a3(&mut self) -> Result<(bool, u16), I2C::Error> {
Ok(signed_to_polarity_magnitude(self.read_channel_raw(Mux::A2N3)?))
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct MuxError(pub u8);
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum MuxOrI2c<E> {
Mux(MuxError),
I2c(E),
}
fn match_mux(bits: u8) -> Result<Mux, MuxError> {
match bits {
0 => Ok(Mux::A0N1), 1 => Ok(Mux::A0N3), 2 => Ok(Mux::A1N3), 3 => Ok(Mux::A2N3),
4 => Ok(Mux::A0), 5 => Ok(Mux::A1), 6 => Ok(Mux::A2), 7 => Ok(Mux::A3),
b => Err(MuxError(b)),
}
}
#[cfg(feature = "async")]
mod async_api {
use super::*;
use embedded_hal_async::i2c::I2c as AsyncI2c;
async fn write_config_async<I2C: AsyncI2c>(i2c: &mut I2C, addr: u8, b: [u8; 2]) -> Result<(), I2C::Error> {
i2c.write(addr, &[REG_CONFIG, b[0], b[1]]).await
}
async fn read_conv_async<I2C: AsyncI2c>(i2c: &mut I2C, addr: u8) -> Result<i16, I2C::Error> {
let mut d = [0u8; 2];
i2c.write_read(addr, &[REG_CONVERSION], &mut d).await?;
Ok(i16::from_be_bytes(d))
}
impl<I2C: AsyncI2c> ADS1113<I2C> {
pub async fn read_adc_async<D: AsyncDelayUs>(&mut self, delay: &mut D) -> Result<i16, I2C::Error> {
self.start_conversion_async().await?;
delay.delay_us(conversion_period_us(self.config.data_rate)).await;
self.read_conversion_async().await
}
pub async fn start_conversion_async(&mut self) -> Result<(), I2C::Error> {
let b = build_config_bytes_ads1113(self.config.data_rate, true);
write_config_async(&mut self.i2c, self.address as u8, b).await
}
pub async fn read_conversion_async(&mut self) -> Result<i16, I2C::Error> {
read_conv_async(&mut self.i2c, self.address as u8).await
}
}
impl<I2C: AsyncI2c> ADS1114<I2C> {
pub async fn enable_conversion_ready_pin_async(&mut self) -> Result<(), I2C::Error> {
self.i2c.write(self.address as u8, &[REG_HI_THRESH, 0x80, 0x00]).await?;
self.i2c.write(self.address as u8, &[REG_LO_THRESH, 0x00, 0x00]).await?;
self.config = self.config.as_conversion_ready();
Ok(())
}
pub async fn start_conversion_async(&mut self) -> Result<(), I2C::Error> {
let b = build_config_bytes(&self.config, 0b100, true, Mode::SingleShot);
write_config_async(&mut self.i2c, self.address as u8, b).await
}
pub async fn start_continuous_async_unchecked(&mut self) -> Result<(), I2C::Error> {
let b = build_config_bytes(&self.config, 0b100, false, Mode::Continuous);
write_config_async(&mut self.i2c, self.address as u8, b).await
}
pub async fn read_conversion_async(&mut self) -> Result<i16, I2C::Error> {
read_conv_async(&mut self.i2c, self.address as u8).await
}
pub async fn read_adc_event<R: ConversionReady>(&mut self, rdy: &mut R) -> Result<i16, Error<I2C::Error, R::Error>> {
debug_assert_rdy(&self.config, "ADS1114::read_adc_event");
let pol = ConversionReadyPolarity::from(self.config.comp_pol);
rdy.arm(pol);
self.start_conversion_async().await.map_err(Error::I2c)?;
rdy.wait_for_ready(pol).await.map_err(Error::Wait)?;
self.read_conversion_async().await.map_err(Error::I2c)
}
pub async fn read_adc_timed<D: AsyncDelayUs>(&mut self, delay: &mut D) -> Result<i16, I2C::Error> {
self.start_conversion_async().await?;
delay.delay_us(conversion_period_us(self.config.data_rate)).await;
self.read_conversion_async().await
}
pub async fn next_conversion<R: ConversionReady>(&mut self, rdy: &mut R) -> Result<i16, Error<I2C::Error, R::Error>> {
debug_assert_rdy(&self.config, "ADS1114::next_conversion");
let pol = ConversionReadyPolarity::from(self.config.comp_pol);
rdy.arm(pol);
rdy.wait_for_ready(pol).await.map_err(Error::Wait)?;
self.read_conversion_async().await.map_err(Error::I2c)
}
pub async fn continuous<'a, 'r, R: ConversionReady>(&'a mut self, rdy: &'r mut R) -> Result<Continuous<'a, 'r, I2C, R, 4>, I2C::Error> {
debug_assert_rdy(&self.config, "ADS1114::continuous");
rdy.arm(ConversionReadyPolarity::from(self.config.comp_pol));
self.start_continuous_async_unchecked().await?;
Ok(Continuous { adc: self, rdy })
}
}
impl<I2C: AsyncI2c> ADS1115<I2C> {
pub async fn enable_conversion_ready_pin_async(&mut self) -> Result<(), I2C::Error> {
self.i2c.write(self.address as u8, &[REG_HI_THRESH, 0x80, 0x00]).await?;
self.i2c.write(self.address as u8, &[REG_LO_THRESH, 0x00, 0x00]).await?;
self.config = self.config.as_conversion_ready();
Ok(())
}
pub async fn start_conversion_async(&mut self, mux: Mux) -> Result<(), I2C::Error> {
let b = build_config_bytes(&self.config, mux as u8, true, Mode::SingleShot);
write_config_async(&mut self.i2c, self.address as u8, b).await
}
pub async fn start_continuous_async_unchecked(&mut self, mux: Mux) -> Result<(), I2C::Error> {
let b = build_config_bytes(&self.config, mux as u8, false, Mode::Continuous);
write_config_async(&mut self.i2c, self.address as u8, b).await
}
pub async fn read_conversion_async(&mut self) -> Result<i16, I2C::Error> {
read_conv_async(&mut self.i2c, self.address as u8).await
}
pub async fn read_channel_event<R: ConversionReady>(&mut self, mux: Mux, rdy: &mut R) -> Result<i16, Error<I2C::Error, R::Error>> {
debug_assert_rdy(&self.config, "ADS1115::read_channel_event");
let pol = ConversionReadyPolarity::from(self.config.comp_pol);
rdy.arm(pol);
self.start_conversion_async(mux).await.map_err(Error::I2c)?;
rdy.wait_for_ready(pol).await.map_err(Error::Wait)?;
self.read_conversion_async().await.map_err(Error::I2c)
}
pub async fn read_channel_timed<D: AsyncDelayUs>(&mut self, mux: Mux, delay: &mut D) -> Result<i16, I2C::Error> {
self.start_conversion_async(mux).await?;
delay.delay_us(conversion_period_us(self.config.data_rate)).await;
self.read_conversion_async().await
}
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 }
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 }
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 }
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 }
pub async fn read_4adc_event<R: ConversionReady>(&mut self, rdy: &mut R) -> Result<[i16; 4], Error<I2C::Error, R::Error>> {
Ok([self.read_channel_event(Mux::A0, rdy).await?, self.read_channel_event(Mux::A1, rdy).await?,
self.read_channel_event(Mux::A2, rdy).await?, self.read_channel_event(Mux::A3, rdy).await?])
}
pub async fn read_4adc_timed<D: AsyncDelayUs>(&mut self, delay: &mut D) -> Result<[i16; 4], I2C::Error> {
Ok([self.read_channel_timed(Mux::A0, delay).await?, self.read_channel_timed(Mux::A1, delay).await?,
self.read_channel_timed(Mux::A2, delay).await?, self.read_channel_timed(Mux::A3, delay).await?])
}
async fn read_diff_event<R: ConversionReady>(&mut self, mux: Mux, rdy: &mut R) -> Result<(bool, u16), Error<I2C::Error, R::Error>> {
Ok(signed_to_polarity_magnitude(self.read_channel_event(mux, rdy).await?))
}
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 }
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 }
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 }
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 }
pub async fn next_conversion<R: ConversionReady>(&mut self, rdy: &mut R) -> Result<i16, Error<I2C::Error, R::Error>> {
debug_assert_rdy(&self.config, "ADS1115::next_conversion");
let pol = ConversionReadyPolarity::from(self.config.comp_pol);
rdy.arm(pol);
rdy.wait_for_ready(pol).await.map_err(Error::Wait)?;
self.read_conversion_async().await.map_err(Error::I2c)
}
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> {
debug_assert_rdy(&self.config, "ADS1115::continuous");
rdy.arm(ConversionReadyPolarity::from(self.config.comp_pol));
self.start_continuous_async_unchecked(mux).await?;
Ok(Continuous { adc: self, rdy })
}
}
pub struct Continuous<'a, 'r, I2C, RDY, const MODEL: u8> {
adc: &'a mut ADS111x<I2C, MODEL>,
rdy: &'r mut RDY,
}
impl<I2C: AsyncI2c, RDY: ConversionReady> Continuous<'_, '_, I2C, RDY, 5> {
pub async fn next(&mut self) -> Result<i16, Error<I2C::Error, RDY::Error>> {
self.adc.next_conversion(self.rdy).await
}
}
impl<I2C: AsyncI2c, RDY: ConversionReady> Continuous<'_, '_, I2C, RDY, 4> {
pub async fn next(&mut self) -> Result<i16, Error<I2C::Error, RDY::Error>> {
self.adc.next_conversion(self.rdy).await
}
}
#[cfg(feature = "embassy")]
async fn timer_wait_us(us: u32) {
embassy_time::Timer::after(embassy_time::Duration::from_micros(us as u64)).await
}
#[cfg(feature = "embassy")]
impl<I2C: AsyncI2c> ADS1113<I2C> {
pub async fn read_adc_timer(&mut self) -> Result<i16, I2C::Error> {
self.start_conversion_async().await?;
timer_wait_us(conversion_period_us(self.config.data_rate)).await;
self.read_conversion_async().await
}
pub async fn start_continuous_timer(&mut self) -> Result<(), I2C::Error> {
let b = build_config_bytes_ads1113_continuous(self.config.data_rate);
write_config_async(&mut self.i2c, self.address as u8, b).await
}
pub async fn next_timer(&mut self) -> Result<i16, I2C::Error> {
timer_wait_us(conversion_period_us(self.config.data_rate)).await;
self.read_conversion_async().await
}
}
#[cfg(feature = "embassy")]
impl<I2C: AsyncI2c> ADS1114<I2C> {
pub async fn read_adc_timer(&mut self) -> Result<i16, I2C::Error> {
self.start_conversion_async().await?;
timer_wait_us(conversion_period_us(self.config.data_rate)).await;
self.read_conversion_async().await
}
pub async fn continuous_timer(&mut self) -> Result<ContinuousTimer<'_, I2C, 4>, I2C::Error> {
let dr = self.config.data_rate;
self.start_continuous_async_unchecked().await?;
Ok(ContinuousTimer { adc: self, period_us: conversion_period_us(dr) })
}
}
#[cfg(feature = "embassy")]
impl<I2C: AsyncI2c> ADS1115<I2C> {
pub async fn read_channel_timer(&mut self, mux: Mux) -> Result<i16, I2C::Error> {
self.start_conversion_async(mux).await?;
timer_wait_us(conversion_period_us(self.config.data_rate)).await;
self.read_conversion_async().await
}
pub async fn read_adc_a0_timer(&mut self) -> Result<i16, I2C::Error> { self.read_channel_timer(Mux::A0).await }
pub async fn read_adc_a1_timer(&mut self) -> Result<i16, I2C::Error> { self.read_channel_timer(Mux::A1).await }
pub async fn read_adc_a2_timer(&mut self) -> Result<i16, I2C::Error> { self.read_channel_timer(Mux::A2).await }
pub async fn read_adc_a3_timer(&mut self) -> Result<i16, I2C::Error> { self.read_channel_timer(Mux::A3).await }
async fn read_diff_timer(&mut self, mux: Mux) -> Result<(bool, u16), I2C::Error> {
Ok(signed_to_polarity_magnitude(self.read_channel_timer(mux).await?))
}
pub async fn read_a0_a1_timer(&mut self) -> Result<(bool, u16), I2C::Error> { self.read_diff_timer(Mux::A0N1).await }
pub async fn read_a0_a3_timer(&mut self) -> Result<(bool, u16), I2C::Error> { self.read_diff_timer(Mux::A0N3).await }
pub async fn read_a1_a3_timer(&mut self) -> Result<(bool, u16), I2C::Error> { self.read_diff_timer(Mux::A1N3).await }
pub async fn read_a2_a3_timer(&mut self) -> Result<(bool, u16), I2C::Error> { self.read_diff_timer(Mux::A2N3).await }
pub async fn continuous_timer(&mut self, mux: Mux) -> Result<ContinuousTimer<'_, I2C, 5>, I2C::Error> {
let dr = self.config.data_rate;
self.start_continuous_async_unchecked(mux).await?;
Ok(ContinuousTimer { adc: self, period_us: conversion_period_us(dr) })
}
}
#[cfg(feature = "embassy")]
pub struct ContinuousTimer<'a, I2C, const MODEL: u8> {
adc: &'a mut ADS111x<I2C, MODEL>,
period_us: u32,
}
#[cfg(feature = "embassy")]
impl<I2C: AsyncI2c> ContinuousTimer<'_, I2C, 5> {
pub async fn next(&mut self) -> Result<i16, I2C::Error> {
timer_wait_us(self.period_us).await;
self.adc.read_conversion_async().await
}
}
#[cfg(feature = "embassy")]
impl<I2C: AsyncI2c> ContinuousTimer<'_, I2C, 4> {
pub async fn next(&mut self) -> Result<i16, I2C::Error> {
timer_wait_us(self.period_us).await;
self.adc.read_conversion_async().await
}
}
}
#[derive(Debug, Clone, Copy, Default)]
pub struct Config {
pub gain: Gain,
pub data_rate: DataRate,
pub comp_mode: CompMode,
pub comp_pol: CompPol,
pub comp_lat: CompLat,
pub comp_que: CompQue,
}
impl Config {
pub fn with_gain(self, gain: Gain) -> Self { Self { gain, ..self } }
pub fn with_data_rate(self, data_rate: DataRate) -> Self { Self { data_rate, ..self } }
pub fn with_comp_mode(self, comp_mode: CompMode) -> Self { Self { comp_mode, ..self } }
pub fn with_comp_pol(self, comp_pol: CompPol) -> Self { Self { comp_pol, ..self } }
pub fn with_comp_lat(self, comp_lat: CompLat) -> Self { Self { comp_lat, ..self } }
pub fn with_comp_que(self, comp_que: CompQue) -> Self { Self { comp_que, ..self } }
pub fn as_conversion_ready(&self) -> Self {
Self { comp_que: CompQue::OneConversion, comp_lat: CompLat::NonLatching, ..*self }
}
pub fn is_conversion_ready(&self) -> bool {
!matches!(self.comp_que, CompQue::DisableComparator)
}
}
#[inline]
fn debug_assert_rdy(config: &Config, what: &str) {
debug_assert!(
config.is_conversion_ready(),
"{what}: COMP_QUE == DisableComparator; call enable_conversion_ready_pin*() or as_conversion_ready() first, else the RDY wait never completes"
);
}
#[derive(Debug, Clone, Copy, Default)]
#[repr(u8)]
pub enum Gain {
#[default]
V6_144 = 0,
V4_096 = 1 << 1,
V2_048 = 2 << 1,
V1_024 = 3 << 1,
V0_512 = 4 << 1,
V0_256 = 5 << 1,
}
#[derive(Debug, Clone, Copy, Default)]
#[repr(u8)]
pub enum DataRate {
SPS8 = 0,
SPS16 = 1 << 5,
SPS32 = 2 << 5,
SPS64 = 3 << 5,
SPS128 = 4 << 5,
SPS250 = 5 << 5,
SPS475 = 6 << 5,
#[default]
SPS860 = 7 << 5,
}
#[derive(Debug, Clone, Copy, Default)]
#[repr(u8)]
pub enum CompMode {
#[default]
Traditional = 0,
Window = 1 << 4,
}
#[derive(Debug, Clone, Copy, Default)]
#[repr(u8)]
pub enum CompPol {
#[default]
ActiveLow = 0,
ActiveHigh = 1 << 3,
}
#[derive(Debug, Clone, Copy, Default)]
#[repr(u8)]
pub enum CompLat {
#[default]
NonLatching = 0,
Latching = 1 << 2,
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
#[repr(u8)]
pub enum CompQue {
OneConversion = 0,
TwoConversions = 1,
FourConversions = 2,
#[default]
DisableComparator = 3,
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
#[repr(u8)]
pub enum Address {
#[default]
Ground = 0b1001000,
VDD = 0b1001001,
SDA = 0b1001010,
SCL = 0b1001011,
}
#[derive(Debug, Clone, Copy)]
pub struct DeviceConfig {
pub address: Address,
pub config: Config,
pub mux: Mux,
}
impl DeviceConfig {
pub fn new(address: Address, config: Config, mux: Mux) -> Self {
Self { address, config, mux }
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Sample {
pub index: usize,
pub address: Address,
pub value: i16,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct DiffSample {
pub index: usize,
pub address: Address,
pub negative: bool,
pub magnitude: u16,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum MultiError<I2cError, WaitError = ()> {
DuplicateAddress { first: usize, second: usize, address: Address },
InvalidIndex(usize),
I2c { index: usize, address: Address, error: I2cError },
Wait { index: usize, address: Address, error: WaitError },
}
pub struct MultiADS1115<I2C, const N: usize> {
i2c: I2C,
devices: [DeviceConfig; N],
}
pub type ADS1115Array<I2C> = MultiADS1115<I2C, 4>;
impl<I2C: embedded_hal::i2c::ErrorType, const N: usize> MultiADS1115<I2C, N> {
pub fn new(i2c: I2C, devices: [DeviceConfig; N]) -> Result<Self, MultiError<I2cErrorOf<I2C>, ()>> {
let _ = &i2c;
let me = Self { i2c, devices };
me.check_unique()?;
Ok(me)
}
fn check_unique(&self) -> Result<(), MultiError<I2cErrorOf<I2C>, ()>> {
let mut i = 0;
while i < N {
let mut j = i + 1;
while j < N {
if self.devices[i].address as u8 == self.devices[j].address as u8 {
return Err(MultiError::DuplicateAddress {
first: i, second: j, address: self.devices[i].address,
});
}
j += 1;
}
i += 1;
}
Ok(())
}
pub fn device(&self, index: usize) -> Option<&DeviceConfig> { self.devices.get(index) }
pub fn device_mut(&mut self, index: usize) -> Option<&mut DeviceConfig> { self.devices.get_mut(index) }
pub fn release(self) -> I2C { self.i2c }
}
pub trait BusError {
type Error;
}
impl<T: embedded_hal::i2c::ErrorType> BusError for T {
type Error = T::Error;
}
type I2cErrorOf<B> = <B as BusError>::Error;
fn multi_write_config<I2C: I2c>(i2c: &mut I2C, addr: u8, b: [u8; 2]) -> Result<(), I2C::Error> {
i2c.write(addr, &[REG_CONFIG, b[0], b[1]])
}
fn multi_read_conv<I2C: I2c>(i2c: &mut I2C, addr: u8) -> Result<i16, I2C::Error> {
let mut d = [0u8; 2];
i2c.write_read(addr, &[REG_CONVERSION], &mut d)?;
Ok(i16::from_be_bytes(d))
}
fn multi_wait<I2C: I2c>(i2c: &mut I2C, addr: u8) -> Result<(), I2C::Error> {
let mut buf = [0u8; 2];
loop {
i2c.write_read(addr, &[REG_CONFIG], &mut buf)?;
if conversion_complete(buf[0]) { break; }
}
Ok(())
}
impl<I2C: I2c, const N: usize> MultiADS1115<I2C, N> {
fn start_idx(&mut self, index: usize, mux: Mux) -> Result<(), MultiError<I2C::Error, ()>> {
let dev = *self.devices.get(index).ok_or(MultiError::InvalidIndex(index))?;
let (addr, cfg) = (dev.address, dev.config);
let b = build_config_bytes(&cfg, mux as u8, true, Mode::SingleShot);
multi_write_config(&mut self.i2c, addr as u8, b)
.map_err(|e| MultiError::I2c { index, address: addr, error: e })
}
pub fn start_conversion(&mut self, index: usize, mux: Mux) -> Result<(), MultiError<I2C::Error, ()>> {
self.start_idx(index, mux)
}
pub fn start_all(&mut self, muxes: [Mux; N]) -> Result<(), MultiError<I2C::Error, ()>> {
let mut i = 0;
while i < N {
self.start_idx(i, muxes[i])?;
i += 1;
}
Ok(())
}
fn read_idx(&mut self, index: usize) -> Result<Sample, MultiError<I2C::Error, ()>> {
let addr = self.devices.get(index).map(|d| d.address).ok_or(MultiError::InvalidIndex(index))?;
multi_wait(&mut self.i2c, addr as u8)
.map_err(|e| MultiError::I2c { index, address: addr, error: e })?;
let v = multi_read_conv(&mut self.i2c, addr as u8)
.map_err(|e| MultiError::I2c { index, address: addr, error: e })?;
Ok(Sample { index, address: addr, value: v })
}
pub fn capture_parallel(&mut self, muxes: [Mux; N]) -> Result<[Sample; N], MultiError<I2C::Error, ()>>
where [Sample; N]: Default {
self.start_all(muxes)?;
let mut out: [Sample; N] = Default::default();
let mut i = 0;
while i < N {
out.as_mut()[i] = self.read_idx(i)?;
i += 1;
}
Ok(out)
}
pub fn capture_all(&mut self) -> Result<[Sample; N], MultiError<I2C::Error, ()>>
where [Sample; N]: Default {
let mut muxes: [Mux; N] = [Mux::A0; N];
let mut i = 0;
while i < N {
muxes[i] = self.devices[i].mux;
i += 1;
}
self.capture_parallel(muxes)
}
pub fn capture_parallel_diff(&mut self, muxes: [Mux; N]) -> Result<[DiffSample; N], MultiError<I2C::Error, ()>>
where [DiffSample; N]: Default {
self.start_all(muxes)?;
let mut out: [DiffSample; N] = Default::default();
let mut i = 0;
while i < N {
let s = self.read_idx(i)?;
let (negative, magnitude) = signed_to_polarity_magnitude(s.value);
out.as_mut()[i] = DiffSample { index: i, address: s.address, negative, magnitude };
i += 1;
}
Ok(out)
}
pub fn start_continuous_all(&mut self, muxes: [Mux; N]) -> Result<(), MultiError<I2C::Error, ()>> {
let mut i = 0;
while i < N {
let (addr, cfg) = (self.devices[i].address, self.devices[i].config);
let b = build_config_bytes(&cfg, muxes[i] as u8, false, Mode::Continuous);
multi_write_config(&mut self.i2c, addr as u8, b)
.map_err(|e| MultiError::I2c { index: i, address: addr, error: e })?;
i += 1;
}
Ok(())
}
pub fn next_all_timed(
&mut self,
mut delay_us: impl FnMut(u32),
) -> Result<[Sample; N], MultiError<I2C::Error, ()>>
where [Sample; N]: Default {
let mut period = 0;
let mut i = 0;
while i < N {
let p = conversion_period_us(self.devices[i].config.data_rate);
if p > period {
period = p;
}
i += 1;
}
delay_us(period);
let mut out: [Sample; N] = Default::default();
let mut i = 0;
while i < N {
let dev = self.devices[i];
let v = multi_read_conv(&mut self.i2c, dev.address as u8)
.map_err(|e| MultiError::I2c { index: i, address: dev.address, error: e })?;
out.as_mut()[i] = Sample { index: i, address: dev.address, value: v };
i += 1;
}
Ok(out)
}
}
impl Default for Sample {
fn default() -> Self { Self { index: 0, address: Address::Ground, value: 0 } }
}
impl Default for DiffSample {
fn default() -> Self { Self { index: 0, address: Address::Ground, negative: false, magnitude: 0 } }
}
#[cfg(feature = "async")]
impl<I2C: embedded_hal::i2c::ErrorType, const N: usize> MultiADS1115<I2C, N> {
fn polarities(&self) -> [ConversionReadyPolarity; N] {
core::array::from_fn(|i| {
ConversionReadyPolarity::from(self.devices.get(i).map(|d| d.config.comp_pol).unwrap_or(CompPol::ActiveLow))
})
}
pub fn arm_all_ready<R: ConversionReady>(&self, rdy: &mut [R; N]) {
let mut i = 0;
while i < N {
if let Some(dev) = self.devices.get(i) {
debug_assert_rdy(&dev.config, "MultiADS1115::arm_all_ready");
}
let pol = ConversionReadyPolarity::from(
self.devices.get(i).map(|d| d.config.comp_pol).unwrap_or(CompPol::ActiveLow),
);
rdy[i].arm(pol);
i += 1;
}
}
pub async fn wait_all_ready_async<R: ConversionReady>(
&self,
rdy: &mut [R; N],
) -> Result<(), MultiError<I2C::Error, R::Error>> {
let mut i = 0;
while i < N {
if let Some(dev) = self.devices.get(i) {
debug_assert_rdy(&dev.config, "MultiADS1115::wait_all_ready_async");
}
i += 1;
}
let pol = self.polarities();
wait_all_ready(rdy, pol).await.map_err(|e| {
let address = self
.devices
.get(e.index)
.map(|d| d.address)
.unwrap_or(Address::Ground);
MultiError::Wait { index: e.index, address, error: e.error }
})
}
}
#[cfg(feature = "async")]
mod multi_async {
use super::*;
use embedded_hal_async::i2c::I2c as AsyncI2c;
async fn mstart<I2C: AsyncI2c>(i2c: &mut I2C, addr: u8, cfg: &Config, mux: Mux) -> Result<(), I2C::Error> {
let b = build_config_bytes(cfg, mux as u8, true, Mode::SingleShot);
i2c.write(addr, &[REG_CONFIG, b[0], b[1]]).await
}
async fn mread<I2C: AsyncI2c>(i2c: &mut I2C, addr: u8) -> Result<i16, I2C::Error> {
let mut d = [0u8; 2];
i2c.write_read(addr, &[REG_CONVERSION], &mut d).await?;
Ok(i16::from_be_bytes(d))
}
fn lift_no_wait<I2cError, WaitError>(
e: MultiError<I2cError, ()>,
) -> MultiError<I2cError, WaitError> {
match e {
MultiError::DuplicateAddress { first, second, address } => {
MultiError::DuplicateAddress { first, second, address }
}
MultiError::InvalidIndex(i) => MultiError::InvalidIndex(i),
MultiError::I2c { index, address, error } => MultiError::I2c { index, address, error },
MultiError::Wait { .. } => unreachable!("pure read path never waits"),
}
}
impl<I2C: AsyncI2c, const N: usize> MultiADS1115<I2C, N> {
async fn start_idx_a(&mut self, index: usize, mux: Mux) -> Result<(), MultiError<I2C::Error, ()>> {
let dev = *self.devices.get(index).ok_or(MultiError::InvalidIndex(index))?;
mstart(&mut self.i2c, dev.address as u8, &dev.config, mux).await
.map_err(|e| MultiError::I2c { index, address: dev.address, error: e })
}
pub async fn start_all_async(&mut self, muxes: [Mux; N]) -> Result<(), MultiError<I2C::Error, ()>> {
let mut i = 0;
while i < N {
self.start_idx_a(i, muxes[i]).await?;
i += 1;
}
Ok(())
}
async fn read_started_idx(
&mut self,
index: usize,
) -> Result<Sample, MultiError<I2C::Error, ()>> {
let dev = *self.devices.get(index).ok_or(MultiError::InvalidIndex(index))?;
let v = mread(&mut self.i2c, dev.address as u8).await
.map_err(|e| MultiError::I2c { index, address: dev.address, error: e })?;
Ok(Sample { index, address: dev.address, value: v })
}
pub async fn read_all_async(
&mut self,
) -> Result<[Sample; N], MultiError<I2C::Error, ()>>
where [Sample; N]: Default {
let mut out: [Sample; N] = Default::default();
let mut i = 0;
while i < N {
out.as_mut()[i] = self.read_started_idx(i).await?;
i += 1;
}
Ok(out)
}
pub async fn capture_parallel_event<R: ConversionReady>(
&mut self, muxes: [Mux; N], rdy: &mut [R; N],
) -> Result<[Sample; N], MultiError<I2C::Error, R::Error>>
where [Sample; N]: Default {
self.arm_all_ready(rdy);
self.start_all_async(muxes).await.map_err(|e| match e {
MultiError::InvalidIndex(i) => MultiError::InvalidIndex(i),
MultiError::DuplicateAddress { first, second, address } => MultiError::DuplicateAddress { first, second, address },
MultiError::I2c { index, address, error } => MultiError::I2c { index, address, error },
MultiError::Wait { .. } => unreachable!(),
})?;
self.wait_all_ready_async(rdy).await?;
self.read_all_async().await.map_err(lift_no_wait)
}
pub async fn capture_parallel_diff_event<R: ConversionReady>(
&mut self, muxes: [Mux; N], rdy: &mut [R; N],
) -> Result<[DiffSample; N], MultiError<I2C::Error, R::Error>>
where [DiffSample; N]: Default, [Sample; N]: Default {
let samples = self.capture_parallel_event(muxes, rdy).await?;
let mut out: [DiffSample; N] = Default::default();
let mut i = 0;
while i < N {
let (negative, magnitude) = signed_to_polarity_magnitude(samples[i].value);
out.as_mut()[i] = DiffSample { index: i, address: samples[i].address, negative, magnitude };
i += 1;
}
Ok(out)
}
pub async fn start_continuous_all_async_unchecked(&mut self, muxes: [Mux; N]) -> Result<(), MultiError<I2C::Error, ()>> {
let mut i = 0;
while i < N {
let dev = *self.devices.get(i).ok_or(MultiError::InvalidIndex(i))?;
let b = build_config_bytes(&dev.config, muxes[i] as u8, false, Mode::Continuous);
self.i2c.write(dev.address as u8, &[REG_CONFIG, b[0], b[1]]).await
.map_err(|e| MultiError::I2c { index: i, address: dev.address, error: e })?;
i += 1;
}
Ok(())
}
pub async fn continuous_all_event<R: ConversionReady>(
&mut self,
muxes: [Mux; N],
rdy: &mut [R; N],
) -> Result<(), MultiError<I2C::Error, ()>> {
self.arm_all_ready(rdy);
self.start_continuous_all_async_unchecked(muxes).await
}
pub async fn next_all_event<R: ConversionReady>(
&mut self, rdy: &mut [R; N],
) -> Result<[Sample; N], MultiError<I2C::Error, R::Error>>
where [Sample; N]: Default {
self.arm_all_ready(rdy);
self.wait_all_ready_async(rdy).await?;
self.read_all_async().await.map_err(lift_no_wait)
}
}
#[cfg(feature = "embassy")]
impl<I2C: AsyncI2c, const N: usize> MultiADS1115<I2C, N> {
fn max_period_us(&self) -> u32 {
let mut m = 0;
let mut i = 0;
while i < N {
let p = conversion_period_us(self.devices[i].config.data_rate);
if p > m { m = p; }
i += 1;
}
m
}
pub async fn capture_parallel_timer(&mut self, muxes: [Mux; N]) -> Result<[Sample; N], MultiError<I2C::Error, ()>>
where [Sample; N]: Default {
self.start_all_async(muxes).await?;
let period = self.max_period_us();
embassy_time::Timer::after(embassy_time::Duration::from_micros(period as u64)).await;
let mut out: [Sample; N] = Default::default();
let mut i = 0;
while i < N {
let dev = self.devices[i];
let v = mread(&mut self.i2c, dev.address as u8).await
.map_err(|e| MultiError::I2c { index: i, address: dev.address, error: e })?;
out.as_mut()[i] = Sample { index: i, address: dev.address, value: v };
i += 1;
}
Ok(out)
}
pub async fn capture_all_timer(&mut self) -> Result<[Sample; N], MultiError<I2C::Error, ()>>
where [Sample; N]: Default {
let mut muxes: [Mux; N] = [Mux::A0; N];
let mut i = 0;
while i < N {
muxes[i] = self.devices[i].mux;
i += 1;
}
self.capture_parallel_timer(muxes).await
}
pub async fn capture_parallel_diff_timer(&mut self, muxes: [Mux; N]) -> Result<[DiffSample; N], MultiError<I2C::Error, ()>>
where [DiffSample; N]: Default {
self.start_all_async(muxes).await?;
let period = self.max_period_us();
embassy_time::Timer::after(embassy_time::Duration::from_micros(period as u64)).await;
let mut out: [DiffSample; N] = Default::default();
let mut i = 0;
while i < N {
let dev = self.devices[i];
let v = mread(&mut self.i2c, dev.address as u8).await
.map_err(|e| MultiError::I2c { index: i, address: dev.address, error: e })?;
let (negative, magnitude) = signed_to_polarity_magnitude(v);
out.as_mut()[i] = DiffSample { index: i, address: dev.address, negative, magnitude };
i += 1;
}
Ok(out)
}
pub async fn start_continuous_all_timer(&mut self, muxes: [Mux; N]) -> Result<(), MultiError<I2C::Error, ()>> {
self.start_continuous_all_async_unchecked(muxes).await
}
pub async fn next_all_timer(&mut self) -> Result<[Sample; N], MultiError<I2C::Error, ()>>
where [Sample; N]: Default {
let period = self.max_period_us();
embassy_time::Timer::after(embassy_time::Duration::from_micros(period as u64)).await;
let mut out: [Sample; N] = Default::default();
let mut i = 0;
while i < N {
let dev = self.devices[i];
let v = mread(&mut self.i2c, dev.address as u8).await
.map_err(|e| MultiError::I2c { index: i, address: dev.address, error: e })?;
out.as_mut()[i] = Sample { index: i, address: dev.address, value: v };
i += 1;
}
Ok(out)
}
}
}
#[cfg(test)]
mod tests {
extern crate std;
use super::*;
#[test]
fn config_bytes_mux_gain_dr() {
let cfg = Config { gain: Gain::V2_048, data_rate: DataRate::SPS128, ..Default::default() };
let b = build_config_bytes(&cfg, Mux::A0 as u8, true, Mode::SingleShot);
assert_eq!(b[0], 0x80 | (0b100 << 4) | 0x04 | 0x01);
assert_eq!(b[1], (4 << 5) | 3); assert!(conversion_complete(0x80) && !conversion_complete(0x00));
}
#[test]
fn all_mux_bits() {
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)] {
let b = build_config_bytes(&Config::default(), m as u8, true, Mode::SingleShot);
assert_eq!((b[0] >> 4) & 7, bits);
}
}
#[test]
fn ads1113_ignores_pga() {
let b = build_config_bytes_ads1113(DataRate::SPS860, true);
assert_eq!(b[0] & 0x0E, 0); assert_eq!(b[1] & 3, 3); }
#[test]
fn continuous_clears_mode_bit() {
let b = build_config_bytes(&Config::default(), Mux::A0 as u8, false, Mode::Continuous);
assert_eq!(b[0] & 1, 0);
assert_eq!(b[0] & 0x80, 0, "continuous must not set OS=1");
let s = build_config_bytes(&Config::default(), Mux::A0 as u8, true, Mode::SingleShot);
assert_eq!(s[0] & 1, 1);
assert_ne!(s[0] & 0x80, 0);
#[cfg(feature = "embassy")]
{
let c = build_config_bytes_ads1113_continuous(DataRate::SPS128);
assert_eq!(c, [0, DataRate::SPS128 as u8 | 0x03]);
}
}
#[cfg(feature = "embassy")]
#[test]
fn embassy_timer_api_no_poll() {
let src = include_str!("lib.rs");
for name in ["fn read_channel_timer", "fn continuous_timer", "fn read_a0_a1_timer",
"fn read_adc_a0_timer", "Timer::after", "ContinuousTimer"] {
assert!(src.contains(name), "missing {name}");
}
let m = src.find("mod async_api").expect("async_api");
let end = src[m..].find("/// Embassy integration").map(|e| m + e).unwrap_or(src.len());
assert!(!src[m..end].contains("read_asyncadc"));
}
#[test]
fn rdy_threshold_values() {
let cfg = Config::default().as_conversion_ready();
assert_ne!(cfg.comp_que as u8, 3);
assert_eq!([0x80u8, 0x00u8], [0x80, 0x00]); assert_eq!([0x00u8, 0x00u8], [0x00, 0x00]); }
#[test]
fn polarity_matches_comp_pol() {
assert_eq!(ConversionReadyPolarity::from(CompPol::ActiveLow), ConversionReadyPolarity::ActiveLow);
assert_eq!(ConversionReadyPolarity::from(CompPol::ActiveHigh), ConversionReadyPolarity::ActiveHigh);
assert!(conversion_period_us(DataRate::SPS8) > conversion_period_us(DataRate::SPS860));
}
#[test]
fn polarity_magnitude() {
assert_eq!(signed_to_polarity_magnitude(0), (false, 0));
assert_eq!(signed_to_polarity_magnitude(123), (false, 123));
assert_eq!(signed_to_polarity_magnitude(-123), (true, 123));
assert_eq!(signed_to_polarity_magnitude(i16::MAX), (false, 32767));
assert_eq!(signed_to_polarity_magnitude(i16::MIN), (true, 32768));
}
#[test]
fn diff_mux_mappings() {
assert_eq!(Mux::A0N1 as u8, 0b000);
assert_eq!(Mux::A0N3 as u8, 0b001);
assert_eq!(Mux::A1N3 as u8, 0b010);
assert_eq!(Mux::A2N3 as u8, 0b011);
for (m, bits) in [(Mux::A0N1, 0), (Mux::A0N3, 1), (Mux::A1N3, 2), (Mux::A2N3, 3)] {
let b = build_config_bytes(&Config::default(), m as u8, true, Mode::SingleShot);
assert_eq!((b[0] >> 4) & 7, bits);
}
let c = Config::default().as_conversion_ready();
assert_ne!(c.comp_que as u8, 3);
assert_eq!(c.comp_lat as u8, 0);
}
#[cfg(feature = "async")]
#[test]
fn async_recommended_api_has_no_poll_loop() { let src = include_str!("lib.rs");
let m = src.find("mod async_api").expect("async_api");
let end = src[m..].find("mod tests").map(|e| m + e).unwrap_or(src.len());
let body = &src[m..end];
for name in ["fn read_channel_event", "fn read_channel_timed", "fn next_conversion", "fn continuous",
"fn start_all_async", "fn capture_parallel_event", "fn capture_parallel_timer", "fn next_all_timer",
"fn wait_all_ready_async", "fn read_all_async", "fn next_all_event"] {
let i = body.find(name).unwrap_or_else(|| panic!("missing {name}"));
let fn_body = &body[i..body[i..].find("/// ").map(|e| i + e).unwrap_or(body.len())];
let fn_body = &fn_body[..fn_body.find("\n }\n").unwrap_or(fn_body.len())];
assert!(!fn_body.contains("loop {"), "{name} must not poll");
}
assert!(!body.contains("read_asyncadc") && !body.contains("read_async_adc") && !body.contains("read_async_4adc"));
for must in ["wait_all_ready(", "JoinAllReady", "read_all_async"] {
assert!(src.contains(must), "missing concurrent primitive {must}");
}
}
#[cfg(feature = "async")]
struct PulseRdy<'c> {
clock: &'c std::cell::Cell<u32>,
log: &'c std::cell::RefCell<std::vec::Vec<(u8, usize)>>,
id: usize,
armed_at: std::cell::Cell<Option<u32>>,
arms: std::cell::Cell<u32>,
polls: std::cell::Cell<u32>,
fire_after: u32,
deadline: u32,
}
#[cfg(feature = "async")]
struct PulseFut<'a, 'c> {
rdy: &'a PulseRdy<'c>,
}
#[cfg(feature = "async")]
impl<'c> ConversionReady for PulseRdy<'c> {
type Error = core::convert::Infallible;
type WaitFuture<'a> = PulseFut<'a, 'c> where Self: 'a;
fn arm(&mut self, _pol: ConversionReadyPolarity) {
self.armed_at.set(Some(self.clock.get()));
self.arms.set(self.arms.get() + 1);
self.log.borrow_mut().push((0, self.id));
}
fn wait_for_ready(&mut self, _pol: ConversionReadyPolarity) -> Self::WaitFuture<'_> {
self.log.borrow_mut().push((2, self.id));
PulseFut { rdy: self }
}
}
#[cfg(feature = "async")]
impl core::future::Future for PulseFut<'_, '_> {
type Output = Result<(), core::convert::Infallible>;
fn poll(
self: core::pin::Pin<&mut Self>,
cx: &mut core::task::Context<'_>,
) -> core::task::Poll<Self::Output> {
let r = self.rdy;
let armed = match r.armed_at.get() {
Some(t) => t,
None => return core::task::Poll::Pending,
};
if armed > r.deadline {
return core::task::Poll::Pending; }
let p = r.polls.get() + 1;
r.polls.set(p);
if p >= r.fire_after {
core::task::Poll::Ready(Ok(()))
} else {
cx.waker().wake_by_ref();
core::task::Poll::Pending
}
}
}
#[cfg(feature = "async")]
#[test]
fn wait_all_ready_arms_every_detector_upfront() {
use std::sync::Arc;
struct Noop;
impl std::task::Wake for Noop {
fn wake(self: Arc<Self>) {}
}
let waker = std::task::Waker::from(Arc::new(Noop));
let clock = std::cell::Cell::new(0u32);
let log = std::cell::RefCell::new(std::vec::Vec::new());
let mut rdy: [PulseRdy; 4] = core::array::from_fn(|id| PulseRdy {
clock: &clock,
log: &log,
id,
armed_at: std::cell::Cell::new(None),
arms: std::cell::Cell::new(0),
polls: std::cell::Cell::new(0),
fire_after: 2,
deadline: 0,
});
for r in rdy.iter_mut() {
r.arm(ConversionReadyPolarity::ActiveLow);
}
assert_eq!(log.borrow().len(), 4, "all detectors armed before first poll");
let mut join = wait_all_ready(&mut rdy, [ConversionReadyPolarity::ActiveLow; 4]);
let mut join = unsafe { core::pin::Pin::new_unchecked(&mut join) };
let mut cx = core::task::Context::from_waker(&waker);
let mut rounds = 0;
let done = loop {
clock.set(rounds);
match join.as_mut().poll(&mut cx) {
core::task::Poll::Ready(r) => break r,
core::task::Poll::Pending => {}
}
rounds += 1;
assert!(rounds < 100, "RDY pulse missed: detectors not observed concurrently");
};
assert!(done.is_ok());
for r in rdy.iter() {
assert_eq!(r.armed_at.get(), Some(0), "every detector armed before START");
}
}
#[cfg(feature = "async")]
#[test]
fn unarmed_join_observes_nothing() {
use std::sync::Arc;
struct Noop;
impl std::task::Wake for Noop {
fn wake(self: Arc<Self>) {}
}
let waker = std::task::Waker::from(Arc::new(Noop));
let clock = std::cell::Cell::new(0u32);
let log = std::cell::RefCell::new(std::vec::Vec::new());
let mut rdy: [PulseRdy; 2] = core::array::from_fn(|id| PulseRdy {
clock: &clock,
log: &log,
id,
armed_at: std::cell::Cell::new(None),
arms: std::cell::Cell::new(0),
polls: std::cell::Cell::new(0),
fire_after: 1,
deadline: u32::MAX,
});
let mut join = wait_all_ready(&mut rdy, [ConversionReadyPolarity::ActiveLow; 2]);
let mut join = unsafe { core::pin::Pin::new_unchecked(&mut join) };
let mut cx = core::task::Context::from_waker(&waker);
let mut ready = false;
for _ in 0..20 {
if matches!(join.as_mut().poll(&mut cx), core::task::Poll::Ready(_)) {
ready = true;
break;
}
}
assert!(!ready, "un-armed detectors must observe nothing");
}
#[cfg(feature = "async")]
struct FailRdy {
fail: bool,
armed: std::cell::Cell<bool>,
}
#[cfg(feature = "async")]
struct FailFut<'a> {
rdy: &'a FailRdy,
}
#[cfg(feature = "async")]
impl ConversionReady for FailRdy {
type Error = MockErr;
type WaitFuture<'a> = FailFut<'a> where Self: 'a;
fn arm(&mut self, _pol: ConversionReadyPolarity) {
self.armed.set(true);
}
fn wait_for_ready(&mut self, _pol: ConversionReadyPolarity) -> Self::WaitFuture<'_> {
FailFut { rdy: self }
}
}
#[cfg(feature = "async")]
impl core::future::Future for FailFut<'_> {
type Output = Result<(), MockErr>;
fn poll(
self: core::pin::Pin<&mut Self>,
cx: &mut core::task::Context<'_>,
) -> core::task::Poll<Self::Output> {
if self.rdy.fail {
return core::task::Poll::Ready(Err(MockErr));
}
if self.rdy.armed.get() {
core::task::Poll::Ready(Ok(()))
} else {
cx.waker().wake_by_ref();
core::task::Poll::Pending
}
}
}
#[cfg(feature = "async")]
#[test]
fn wait_failure_attributes_correct_device() {
let mut rdy = [
FailRdy { fail: false, armed: std::cell::Cell::new(false) },
FailRdy { fail: false, armed: std::cell::Cell::new(false) },
FailRdy { fail: true, armed: std::cell::Cell::new(false) },
FailRdy { fail: false, armed: std::cell::Cell::new(false) },
];
for r in rdy.iter_mut() {
r.arm(ConversionReadyPolarity::ActiveLow);
}
let err = block_on(wait_all_ready(&mut rdy, [ConversionReadyPolarity::ActiveLow; 4]));
match err {
Err(WaitFailed { index: 2, .. }) => {}
other => panic!("expected failure at index 2, got {other:?}"),
}
let cfg = Config::default().as_conversion_ready();
let multi = MultiADS1115::new(
MockI2c::new(),
[
DeviceConfig::new(Address::Ground, cfg, Mux::A0),
DeviceConfig::new(Address::VDD, cfg, Mux::A1),
DeviceConfig::new(Address::SDA, cfg, Mux::A2),
DeviceConfig::new(Address::SCL, cfg, Mux::A3),
],
)
.unwrap();
let mut rdy = [
FailRdy { fail: false, armed: std::cell::Cell::new(false) },
FailRdy { fail: false, armed: std::cell::Cell::new(false) },
FailRdy { fail: true, armed: std::cell::Cell::new(false) },
FailRdy { fail: false, armed: std::cell::Cell::new(false) },
];
let err = block_on(multi.wait_all_ready_async(&mut rdy));
match err {
Err(MultiError::Wait { index: 2, address: Address::SDA, .. }) => {}
other => panic!("expected Wait{{index 2, SDA}}, got {other:?}"),
}
}
#[cfg(feature = "async")]
struct AsyncMockI2c<'c> {
log: &'c std::cell::RefCell<std::vec::Vec<(u8, usize)>>,
canned: [(u8, i16); 2],
}
#[cfg(feature = "async")]
impl embedded_hal::i2c::ErrorType for AsyncMockI2c<'_> {
type Error = MockErr;
}
#[cfg(feature = "async")]
impl embedded_hal_async::i2c::I2c for AsyncMockI2c<'_> {
async fn read(&mut self, _a: u8, _b: &mut [u8]) -> Result<(), Self::Error> { Ok(()) }
async fn write(&mut self, addr: u8, bytes: &[u8]) -> Result<(), Self::Error> {
if !bytes.is_empty() && bytes[0] == REG_CONFIG {
self.log.borrow_mut().push((1, (addr - 0x48) as usize));
}
Ok(())
}
async fn write_read(&mut self, addr: u8, write: &[u8], read: &mut [u8]) -> Result<(), Self::Error> {
if write == [REG_CONVERSION] {
let v = self.canned.iter().find(|(a, _)| *a == addr).map(|(_, v)| *v).unwrap_or(0);
read.copy_from_slice(&v.to_be_bytes()[..read.len().min(2)]);
}
Ok(())
}
async fn transaction(
&mut self,
_a: u8,
_o: &mut [embedded_hal::i2c::Operation<'_>],
) -> Result<(), Self::Error> {
Ok(())
}
}
#[cfg(feature = "async")]
fn block_on<F: core::future::Future>(mut f: F) -> F::Output {
use std::sync::Arc;
struct Noop;
impl std::task::Wake for Noop {
fn wake(self: Arc<Self>) {}
}
let waker = std::task::Waker::from(Arc::new(Noop));
let mut cx = core::task::Context::from_waker(&waker);
let mut f = unsafe { core::pin::Pin::new_unchecked(&mut f) };
let mut rounds = 0u32;
loop {
match f.as_mut().poll(&mut cx) {
core::task::Poll::Ready(v) => return v,
core::task::Poll::Pending => {}
}
rounds += 1;
assert!(rounds < 1000, "async test future stalled");
}
}
#[cfg(feature = "async")]
#[test]
fn capture_parallel_event_arms_before_any_start() {
let clock = std::cell::Cell::new(0u32);
let log = std::cell::RefCell::new(std::vec::Vec::new());
let i2c = AsyncMockI2c { log: &log, canned: [(0x48, 1000), (0x49, -2000)] };
let cfg = Config::default().with_data_rate(DataRate::SPS860).as_conversion_ready();
let mut multi = MultiADS1115::new(
i2c,
[DeviceConfig::new(Address::Ground, cfg, Mux::A0), DeviceConfig::new(Address::VDD, cfg, Mux::A1)],
)
.unwrap();
let mut rdy: [PulseRdy; 2] = core::array::from_fn(|id| PulseRdy {
clock: &clock,
log: &log,
id,
armed_at: std::cell::Cell::new(None),
arms: std::cell::Cell::new(0),
polls: std::cell::Cell::new(0),
fire_after: 1,
deadline: u32::MAX,
});
let samples = block_on(multi.capture_parallel_event([Mux::A0, Mux::A1], &mut rdy)).unwrap();
assert_eq!([samples[0].value, samples[1].value], [1000, -2000]);
assert_eq!([samples[0].address, samples[1].address], [Address::Ground, Address::VDD]);
let log = log.borrow();
let first_start = log.iter().position(|(t, _)| *t == 1).expect("starts logged");
assert!(log.iter().take(first_start).all(|(t, _)| *t == 0));
assert_eq!(log.iter().filter(|(t, _)| *t == 0).count(), 2, "both detectors armed");
assert_eq!(log.iter().filter(|(t, _)| *t == 1).count(), 2, "both ADCs started");
}
#[cfg(feature = "async")]
#[test]
fn continuous_all_event_arms_before_any_start() {
let clock = std::cell::Cell::new(0u32);
let log = std::cell::RefCell::new(std::vec::Vec::new());
let i2c = AsyncMockI2c { log: &log, canned: [(0x48, 1000), (0x49, -2000)] };
let cfg = Config::default().with_data_rate(DataRate::SPS860).as_conversion_ready();
let mut multi = MultiADS1115::new(
i2c,
[DeviceConfig::new(Address::Ground, cfg, Mux::A0), DeviceConfig::new(Address::VDD, cfg, Mux::A1)],
)
.unwrap();
let mut rdy: [PulseRdy; 2] = core::array::from_fn(|id| PulseRdy {
clock: &clock,
log: &log,
id,
armed_at: std::cell::Cell::new(None),
arms: std::cell::Cell::new(0),
polls: std::cell::Cell::new(0),
fire_after: 1,
deadline: u32::MAX,
});
block_on(multi.continuous_all_event([Mux::A0, Mux::A1], &mut rdy)).unwrap();
{
let log = log.borrow();
let first_start = log.iter().position(|(t, _)| *t == 1).expect("starts logged");
assert!(log.iter().take(first_start).all(|(t, _)| *t == 0));
assert_eq!(log.iter().filter(|(t, _)| *t == 0).count(), 2, "both detectors armed");
assert_eq!(log.iter().filter(|(t, _)| *t == 1).count(), 2, "both ADCs started once");
}
let samples = block_on(multi.next_all_event(&mut rdy)).unwrap();
assert_eq!([samples[0].value, samples[1].value], [1000, -2000]);
let log = log.borrow();
let tags: std::vec::Vec<u8> = log.iter().map(|(t, _)| *t).collect();
assert_eq!(&tags[..6], &[0, 0, 1, 1, 0, 0], "arm,arm,start,start then re-arm,re-arm");
assert_eq!(tags.iter().filter(|t| **t == 2).count(), 2, "both waits observed");
}
#[cfg(feature = "async")]
#[test]
fn single_shot_event_arms_before_start() {
let clock = std::cell::Cell::new(0u32);
let log = std::cell::RefCell::new(std::vec::Vec::new());
let i2c = AsyncMockI2c { log: &log, canned: [(0x48, 555), (0x49, 0)] };
let mut adc = ADS1115::new(Address::Ground, i2c, Config::default().with_data_rate(DataRate::SPS860).as_conversion_ready());
let mut rdy = PulseRdy {
clock: &clock,
log: &log,
id: 0,
armed_at: std::cell::Cell::new(None),
arms: std::cell::Cell::new(0),
polls: std::cell::Cell::new(0),
fire_after: 1,
deadline: u32::MAX,
};
let v = block_on(adc.read_channel_event(Mux::A0, &mut rdy)).unwrap();
assert_eq!(v, 555);
let log = log.borrow();
assert_eq!(log.as_slice(), &[(0, 0), (1, 0), (2, 0)], "arm must precede CONFIG START");
}
#[cfg(feature = "async")]
#[test]
fn continuous_next_rearms_every_cycle() {
let clock = std::cell::Cell::new(0u32);
let log = std::cell::RefCell::new(std::vec::Vec::new());
let i2c = AsyncMockI2c { log: &log, canned: [(0x48, 111), (0x49, 0)] };
let mut adc = ADS1115::new(Address::Ground, i2c, Config::default().as_conversion_ready());
let mut rdy = PulseRdy {
clock: &clock,
log: &log,
id: 0,
armed_at: std::cell::Cell::new(None),
arms: std::cell::Cell::new(0),
polls: std::cell::Cell::new(0),
fire_after: 1,
deadline: u32::MAX,
};
let mut stream = block_on(adc.continuous(Mux::A0, &mut rdy)).unwrap();
assert_eq!(block_on(stream.next()).unwrap(), 111);
assert_eq!(block_on(stream.next()).unwrap(), 111);
drop(stream);
assert_eq!(
log.borrow().as_slice(),
&[(0, 0), (1, 0), (0, 0), (2, 0), (0, 0), (2, 0)],
"continuous() arms before START; every next() re-arms before waiting"
);
assert_eq!(rdy.arms.get(), 3, "constructor + one re-arm per next()");
log.borrow_mut().clear();
let i2c = AsyncMockI2c { log: &log, canned: [(0x48, 222), (0x49, 0)] };
let mut adc = ADS1114::new(i2c, Config::default().as_conversion_ready());
let mut rdy = PulseRdy {
clock: &clock,
log: &log,
id: 0,
armed_at: std::cell::Cell::new(None),
arms: std::cell::Cell::new(0),
polls: std::cell::Cell::new(0),
fire_after: 1,
deadline: u32::MAX,
};
let mut stream = block_on(adc.continuous(&mut rdy)).unwrap();
assert_eq!(block_on(stream.next()).unwrap(), 222);
drop(stream);
assert_eq!(
log.borrow().as_slice(),
&[(0, 0), (1, 0), (0, 0), (2, 0)],
"ADS1114 continuous() arms before START; next() re-arms"
);
assert_eq!(rdy.arms.get(), 2);
}
#[test]
fn enable_rdy_upgrades_comparator_config() {
let mut adc = ADS1115::new(Address::Ground, MockI2c::new(), Config::default());
assert_eq!(adc.config.comp_que as u8, CompQue::DisableComparator as u8);
adc.enable_conversion_ready_pin().unwrap();
assert_ne!(adc.config.comp_que as u8, CompQue::DisableComparator as u8);
assert_eq!(adc.config.comp_lat as u8, CompLat::NonLatching as u8);
let thresh: std::vec::Vec<(u8, std::vec::Vec<u8>)> =
adc.i2c.writes.iter().cloned().collect();
assert_eq!(thresh.len(), 2);
assert_eq!(thresh[0].1.as_slice(), &[REG_HI_THRESH, 0x80, 0x00]);
assert_eq!(thresh[1].1.as_slice(), &[REG_LO_THRESH, 0x00, 0x00]);
}
#[test]
fn rdy_precondition_predicate_and_guard() {
assert!(!Config::default().is_conversion_ready());
assert!(Config::default().as_conversion_ready().is_conversion_ready());
}
#[test]
#[should_panic(expected = "COMP_QUE == DisableComparator")]
fn event_api_rejects_disabled_comparator_in_debug() {
let mut adc = ADS1115::new(Address::Ground, MockI2c::new(), Config::default());
adc.start_continuous(Mux::A0).unwrap();
let mut rdy = MockBlockingRdy { waited: std::cell::Cell::new(0), armed: std::cell::Cell::new(0) };
let _ = adc.next_conversion_blocking(&mut rdy);
}
#[test]
fn timer_margin_is_proportional_not_blanket() {
assert_eq!(conversion_period_us(DataRate::SPS860), 1162 + 145 + 100);
assert_eq!(conversion_period_us(DataRate::SPS8), 125_000 + 15_625 + 100);
for dr in [DataRate::SPS8, DataRate::SPS128, DataRate::SPS860] {
let nominal = 1_000_000
/ match dr {
DataRate::SPS8 => 8,
DataRate::SPS128 => 128,
DataRate::SPS860 => 860,
_ => unreachable!(),
};
assert!(conversion_period_us(dr) > nominal);
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
struct MockErr;
impl embedded_hal::i2c::Error for MockErr {
fn kind(&self) -> embedded_hal::i2c::ErrorKind { embedded_hal::i2c::ErrorKind::Other }
}
struct MockI2c {
writes: std::vec::Vec<(u8, std::vec::Vec<u8>)>,
canned: [(u8, i16); 4],
config_reads: usize,
fail_write: Option<usize>,
write_count: usize,
}
impl MockI2c {
fn new() -> Self {
Self {
writes: std::vec::Vec::new(),
canned: [(0x48, 100), (0x49, -200), (0x4A, 300), (0x4B, i16::MIN)],
config_reads: 0,
fail_write: None,
write_count: 0,
}
}
fn canned(&self, addr: u8) -> i16 {
self.canned.iter().find(|(a, _)| *a == addr).map(|(_, v)| *v).unwrap_or(0)
}
fn config_writes(&self) -> usize {
self.writes.iter().filter(|(_, b)| !b.is_empty() && b[0] == REG_CONFIG).count()
}
}
impl embedded_hal::i2c::ErrorType for MockI2c {
type Error = MockErr;
}
impl embedded_hal::i2c::I2c for MockI2c {
fn read(&mut self, _a: u8, _b: &mut [u8]) -> Result<(), Self::Error> { Ok(()) }
fn write(&mut self, addr: u8, bytes: &[u8]) -> Result<(), Self::Error> {
if self.fail_write == Some(self.write_count) {
self.write_count += 1;
return Err(MockErr);
}
self.write_count += 1;
self.writes.push((addr, bytes.to_vec()));
Ok(())
}
fn write_read(&mut self, addr: u8, write: &[u8], read: &mut [u8]) -> Result<(), Self::Error> {
match write {
[REG_CONVERSION] => {
read.copy_from_slice(&self.canned(addr).to_be_bytes()[..read.len().min(2)]);
Ok(())
}
[REG_CONFIG] => {
self.config_reads += 1;
read[0] = 0x80;
if read.len() > 1 { read[1] = 0; }
Ok(())
}
_ => Ok(()),
}
}
fn transaction(&mut self, _a: u8, _o: &mut [embedded_hal::i2c::Operation<'_>]) -> Result<(), Self::Error> { Ok(()) }
}
fn dev(address: Address, gain: Gain, dr: DataRate, mux: Mux) -> DeviceConfig {
DeviceConfig::new(address, Config::default().with_gain(gain).with_data_rate(dr), mux)
}
#[test]
fn multi_partial_start_leaves_earlier_devices_running() {
let mut bus = MockI2c::new();
bus.fail_write = Some(2);
let mut m = MultiADS1115::new(
bus,
[
dev(Address::Ground, Gain::V2_048, DataRate::SPS128, Mux::A0),
dev(Address::VDD, Gain::V2_048, DataRate::SPS128, Mux::A1),
dev(Address::SDA, Gain::V2_048, DataRate::SPS128, Mux::A2),
],
)
.unwrap();
let err = m.start_all([Mux::A0, Mux::A1, Mux::A2]).unwrap_err();
match err {
MultiError::I2c { index: 2, address: Address::SDA, .. } => {}
other => panic!("expected I2c error at device 2/SDA, got {other:?}"),
}
let started: std::vec::Vec<u8> = m
.i2c
.writes
.iter()
.filter(|(_, b)| !b.is_empty() && b[0] == REG_CONFIG)
.map(|(a, _)| *a)
.collect();
assert_eq!(started.as_slice(), &[0x48, 0x49]);
}
#[test]
fn multi_rejects_duplicate_addresses() { let r = MultiADS1115::new(MockI2c::new(), [
dev(Address::Ground, Gain::V6_144, DataRate::SPS128, Mux::A0),
dev(Address::Ground, Gain::V2_048, DataRate::SPS860, Mux::A1),
]);
assert!(matches!(r, Err(MultiError::DuplicateAddress { first: 0, second: 1, .. })));
}
#[test]
fn multi_parallel_start_before_read_and_ordering() {
let mut m = MultiADS1115::new(MockI2c::new(), [
dev(Address::Ground, Gain::V6_144, DataRate::SPS128, Mux::A0),
dev(Address::VDD, Gain::V2_048, DataRate::SPS860, Mux::A1),
dev(Address::SDA, Gain::V1_024, DataRate::SPS250, Mux::A2),
dev(Address::SCL, Gain::V0_512, DataRate::SPS64, Mux::A3),
]).unwrap();
let s = m.capture_parallel([Mux::A0, Mux::A1, Mux::A2, Mux::A3]).unwrap();
assert_eq!((s[0].address, s[0].value), (Address::Ground, 100));
assert_eq!((s[1].address, s[1].value), (Address::VDD, -200));
assert_eq!((s[2].address, s[2].value), (Address::SDA, 300));
assert_eq!((s[3].address, s[3].value), (Address::SCL, i16::MIN));
assert_eq!(m.i2c.config_writes(), 4);
let cfgs: std::vec::Vec<[u8; 2]> = m.i2c.writes.iter()
.filter(|(_, b)| b[0] == REG_CONFIG)
.map(|(_, b)| [b[1], b[2]]).collect();
assert_eq!(cfgs.len(), 4);
assert!(cfgs[0][0] & 0x0E != cfgs[1][0] & 0x0E);
for (i, c) in cfgs.iter().enumerate() {
assert_eq!((c[0] >> 4) & 7, (0b100 + i as u8) & 7);
}
}
#[test]
fn multi_diff_and_invalid() {
let mut m = MultiADS1115::new(MockI2c::new(), [
dev(Address::Ground, Gain::V2_048, DataRate::SPS128, Mux::A0N1),
dev(Address::VDD, Gain::V2_048, DataRate::SPS128, Mux::A0N3),
]).unwrap();
let d = m.capture_parallel_diff([Mux::A0N1, Mux::A0N3]).unwrap();
assert_eq!((d[0].negative, d[0].magnitude), (false, 100));
assert_eq!((d[1].negative, d[1].magnitude), (true, 200));
assert!(matches!(m.start_conversion(9, Mux::A0), Err(MultiError::InvalidIndex(9))));
let mut single = ADS1115::new(Address::Ground, MockI2c::new(), Config::default());
assert!(matches!(single.read_channel_raw_bits(8), Err(MuxOrI2c::Mux(MuxError(8)))));
assert!(matches!(single.read_channel_raw_bits(255), Err(MuxOrI2c::Mux(_))));
assert!(single.read_channel_raw_bits(4).is_ok());
}
#[test]
fn multi_continuous_writes_once() {
let mut m = MultiADS1115::new(MockI2c::new(), [
dev(Address::Ground, Gain::V2_048, DataRate::SPS128, Mux::A0),
dev(Address::VDD, Gain::V2_048, DataRate::SPS128, Mux::A1),
]).unwrap();
m.start_continuous_all([Mux::A0, Mux::A1]).unwrap();
for (_, b) in m.i2c.writes.iter().filter(|(_, b)| b[0] == REG_CONFIG) {
assert_eq!(b[1] & 1, 0);
assert_eq!(b[1] & 0x80, 0);
}
let before = m.i2c.config_writes();
let mut delays = std::vec::Vec::new();
let s = m.next_all_timed(|us| delays.push(us)).unwrap();
assert_eq!(m.i2c.config_writes(), before, "next_all must not rewrite CONFIG");
assert_eq!(delays.len(), 1, "single shared delay, not per-device");
assert_eq!(delays[0], conversion_period_us(DataRate::SPS128));
assert_eq!(m.i2c.config_reads, 0, "no CONFIG polling in continuous mode");
assert_eq!(s[0].value, 100);
assert_eq!(s[1].value, -200);
}
struct MockBlockingRdy {
waited: std::cell::Cell<u32>,
armed: std::cell::Cell<u32>,
}
impl BlockingReady for MockBlockingRdy {
type Error = MockErr;
fn arm(&mut self, _pol: ConversionReadyPolarity) {
self.armed.set(self.armed.get() + 1);
}
fn wait_for_ready(&mut self, _pol: ConversionReadyPolarity) -> Result<(), Self::Error> {
assert!(self.armed.get() > 0, "wait without arm");
self.waited.set(self.waited.get() + 1);
Ok(())
}
}
#[test]
fn sync_continuous_uses_rdy_or_delay_not_os_poll() {
let mut adc = ADS1115::new(Address::Ground, MockI2c::new(), Config::default());
adc.enable_conversion_ready_pin().unwrap();
assert!(adc.config.is_conversion_ready());
adc.start_continuous(Mux::A0).unwrap();
let mut rdy = MockBlockingRdy { waited: std::cell::Cell::new(0), armed: std::cell::Cell::new(0) };
let v = adc.next_conversion_blocking(&mut rdy).unwrap();
assert_eq!(v, 100);
assert_eq!(rdy.waited.get(), 1);
assert_eq!(rdy.armed.get(), 1, "single-ADC blocking path re-arms every sample");
assert_eq!(adc.i2c.config_reads, 0, "OS polling invalid in continuous mode");
let mut delays = std::vec::Vec::new();
let v = adc.next_conversion_timed(|us| delays.push(us)).unwrap();
assert_eq!(v, 100);
assert_eq!(delays.as_slice(), &[conversion_period_us(DataRate::SPS860)]);
assert_eq!(adc.i2c.config_reads, 0, "OS polling invalid in continuous mode");
let src = include_str!("lib.rs");
for name in ["fn next_conversion_blocking", "fn next_conversion_timed", "fn next_all_timed"] {
let i = src.find(name).expect(name);
let body = &src[i..src[i..].find("\n }\n").map(|e| i + e).unwrap_or(src.len())];
assert!(!body.contains("wait_complete_sync") && !body.contains("multi_wait"),
"{name} must not OS-poll in continuous mode");
}
let needle = std::string::String::from("pub fn next") + "_all_blocking";
assert!(!src.contains(&needle[..]),
"sequential blocking multi-RDY waits were removed as pulse-unsafe");
}
}