diff --git a/embassy-stm32/src/i2c/mod.rs b/embassy-stm32/src/i2c/mod.rs
index a8dc8e0e..0af291e9 100644
--- a/embassy-stm32/src/i2c/mod.rs
+++ b/embassy-stm32/src/i2c/mod.rs
@@ -1,17 +1,23 @@
#![macro_use]
-use core::marker::PhantomData;
-
-use crate::dma::NoDma;
-use crate::interrupt;
-
#[cfg_attr(i2c_v1, path = "v1.rs")]
#[cfg_attr(i2c_v2, path = "v2.rs")]
mod _version;
-pub use _version::*;
-use embassy_sync::waitqueue::AtomicWaker;
-use crate::peripherals;
+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)]
@@ -33,6 +39,148 @@ pub enum Error {
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 {
+ #[cfg(not(feature = "time"))]
+ #[inline]
+ fn check(self) -> Result<(), Error> {
+ Ok(())
+ }
+
+ #[cfg(feature = "time")]
+ #[inline]
+ fn check(self) -> Result<(), Error> {
+ if Instant::now() > self.deadline {
+ Err(Error::Timeout)
+ } else {
+ Ok(())
+ }
+ }
+
+ #[cfg(not(feature = "time"))]
+ #[inline]
+ fn with(self, fut: impl Future