//! Inter-Integrated-Circuit (I2C)
#![macro_use]
#[cfg_attr(i2c_v1, path = "v1.rs")]
#[cfg_attr(i2c_v2, path = "v2.rs")]
mod _version;
use core::future::Future;
use core::marker::PhantomData;
use embassy_hal_internal::{into_ref, Peripheral, PeripheralRef};
use embassy_sync::waitqueue::AtomicWaker;
#[cfg(feature = "time")]
use embassy_time::{Duration, Instant};
use crate::dma::NoDma;
use crate::gpio::sealed::AFType;
use crate::gpio::Pull;
use crate::interrupt::typelevel::Interrupt;
use crate::time::Hertz;
use crate::{interrupt, peripherals};
/// I2C error.
#[derive(Debug, PartialEq, Eq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum Error {
/// Bus error
Bus,
/// Arbitration lost
Arbitration,
/// ACK not received (either to the address or to a data byte)
Nack,
/// Timeout
Timeout,
/// CRC error
Crc,
/// Overrun error
Overrun,
/// Zero-length transfers are not allowed.
ZeroLengthTransfer,
}
/// I2C config
#[non_exhaustive]
#[derive(Copy, Clone)]
pub struct Config {
/// Enable internal pullup on SDA.
///
/// Using external pullup resistors is recommended for I2C. If you do
/// have external pullups you should not enable this.
pub sda_pullup: bool,
/// Enable internal pullup on SCL.
///
/// Using external pullup resistors is recommended for I2C. If you do
/// have external pullups you should not enable this.
pub scl_pullup: bool,
/// Timeout.
#[cfg(feature = "time")]
pub timeout: embassy_time::Duration,
}
impl Default for Config {
fn default() -> Self {
Self {
sda_pullup: false,
scl_pullup: false,
#[cfg(feature = "time")]
timeout: embassy_time::Duration::from_millis(1000),
}
}
}
/// I2C driver.
pub struct I2c<'d, T: Instance, TXDMA = NoDma, RXDMA = NoDma> {
_peri: PeripheralRef<'d, T>,
#[allow(dead_code)]
tx_dma: PeripheralRef<'d, TXDMA>,
#[allow(dead_code)]
rx_dma: PeripheralRef<'d, RXDMA>,
#[cfg(feature = "time")]
timeout: Duration,
}
impl<'d, T: Instance, TXDMA, RXDMA> I2c<'d, T, TXDMA, RXDMA> {
/// Create a new I2C driver.
pub fn new(
peri: impl Peripheral
+ 'd,
scl: impl Peripheral
> + 'd,
sda: impl Peripheral
> + 'd,
_irq: impl interrupt::typelevel::Binding>
+ interrupt::typelevel::Binding>
+ 'd,
tx_dma: impl Peripheral + 'd,
rx_dma: impl Peripheral
+ 'd,
freq: Hertz,
config: Config,
) -> Self {
into_ref!(peri, scl, sda, tx_dma, rx_dma);
T::enable_and_reset();
scl.set_as_af_pull(
scl.af_num(),
AFType::OutputOpenDrain,
match config.scl_pullup {
true => Pull::Up,
false => Pull::None,
},
);
sda.set_as_af_pull(
sda.af_num(),
AFType::OutputOpenDrain,
match config.sda_pullup {
true => Pull::Up,
false => Pull::None,
},
);
unsafe { T::EventInterrupt::enable() };
unsafe { T::ErrorInterrupt::enable() };
let mut this = Self {
_peri: peri,
tx_dma,
rx_dma,
#[cfg(feature = "time")]
timeout: config.timeout,
};
this.init(freq, config);
this
}
fn timeout(&self) -> Timeout {
Timeout {
#[cfg(feature = "time")]
deadline: Instant::now() + self.timeout,
}
}
}
#[derive(Copy, Clone)]
struct Timeout {
#[cfg(feature = "time")]
deadline: Instant,
}
#[allow(dead_code)]
impl Timeout {
#[inline]
fn check(self) -> Result<(), Error> {
#[cfg(feature = "time")]
if Instant::now() > self.deadline {
return Err(Error::Timeout);
}
Ok(())
}
#[inline]
fn with(self, fut: impl Future