diff --git a/embassy-nrf/src/spim.rs b/embassy-nrf/src/spim.rs
index 0ca18d88c..b5e9afbc1 100644
--- a/embassy-nrf/src/spim.rs
+++ b/embassy-nrf/src/spim.rs
@@ -196,8 +196,9 @@ impl<'d, T: Instance> FullDuplex<u8> for Spim<'d, T> {
 
     fn read_write<'a>(&'a mut self, rx: &'a mut [u8], tx: &'a [u8]) -> Self::WriteReadFuture<'a> {
         async move {
-            slice_in_ram_or(rx, Error::DMABufferNotInDataMemory)?;
             slice_in_ram_or(tx, Error::DMABufferNotInDataMemory)?;
+            // NOTE: RAM slice check for rx is not necessary, as a mutable
+            // slice can only be built from data located in RAM.
 
             // Conservative compiler fence to prevent optimizations that do not
             // take in to account actions by DMA. The fence has been placed here,
diff --git a/embassy-nrf/src/twim.rs b/embassy-nrf/src/twim.rs
index ea3ac7553..160868d7d 100644
--- a/embassy-nrf/src/twim.rs
+++ b/embassy-nrf/src/twim.rs
@@ -6,11 +6,16 @@
 //!
 //! - nRF52832: Section 33
 //! - nRF52840: Section 6.31
+use core::future::Future;
 use core::marker::PhantomData;
 use core::sync::atomic::{compiler_fence, Ordering::SeqCst};
+use core::task::Poll;
 use embassy::interrupt::{Interrupt, InterruptExt};
+use embassy::traits;
 use embassy::util::{AtomicWaker, Unborrow};
 use embassy_extras::unborrow;
+use futures::future::poll_fn;
+use traits::i2c::I2c;
 
 use crate::chip::{EASY_DMA_SIZE, FORCE_COPY_BUFFER_SIZE};
 use crate::gpio::Pin as GpioPin;
@@ -418,6 +423,26 @@ impl<'d, T: Instance> Twim<'d, T> {
 
         self.write_then_read(address, wr_ram_buffer, rd_buffer)
     }
+
+    fn wait_for_stopped_event(cx: &mut core::task::Context) -> Poll<()> {
+        let r = T::regs();
+        let s = T::state();
+
+        s.end_waker.register(cx.waker());
+        if r.events_stopped.read().bits() != 0 {
+            r.events_stopped.reset();
+
+            return Poll::Ready(());
+        }
+
+        // stop if an error occured
+        if r.events_error.read().bits() != 0 {
+            r.events_error.reset();
+            r.tasks_stop.write(|w| unsafe { w.bits(1) });
+        }
+
+        Poll::Pending
+    }
 }
 
 impl<'a, T: Instance> Drop for Twim<'a, T> {
@@ -437,6 +462,191 @@ impl<'a, T: Instance> Drop for Twim<'a, T> {
     }
 }
 
+impl<'d, T> I2c for Twim<'d, T>
+where
+    T: Instance,
+{
+    type Error = Error;
+
+    #[rustfmt::skip]
+    type WriteFuture<'a> where Self: 'a = impl Future<Output = Result<(), Self::Error>> + 'a;
+    #[rustfmt::skip]
+    type ReadFuture<'a> where Self: 'a = impl Future<Output = Result<(), Self::Error>> + 'a;
+    #[rustfmt::skip]
+    type WriteReadFuture<'a> where Self: 'a = impl Future<Output = Result<(), Self::Error>> + 'a;
+
+    fn read<'a>(&'a mut self, address: u8, buffer: &'a mut [u8]) -> Self::ReadFuture<'a> {
+        async move {
+            // NOTE: RAM slice check for buffer is not necessary, as a mutable
+            // slice can only be built from data located in RAM.
+
+            let r = T::regs();
+
+            // Conservative compiler fence to prevent optimizations that do not
+            // take in to account actions by DMA. The fence has been placed here,
+            // before any DMA action has started.
+            compiler_fence(SeqCst);
+
+            r.address.write(|w| unsafe { w.address().bits(address) });
+
+            // Set up the DMA read.
+            unsafe { self.set_rx_buffer(buffer)? };
+
+            // Reset events
+            r.events_stopped.reset();
+            r.events_error.reset();
+            self.clear_errorsrc();
+
+            // Enable events
+            r.intenset.write(|w| w.stopped().set().error().set());
+
+            // Start read operation.
+            r.shorts.write(|w| w.lastrx_stop().enabled());
+            r.tasks_startrx.write(|w|
+            // `1` is a valid value to write to task registers.
+            unsafe { w.bits(1) });
+
+            // Conservative compiler fence to prevent optimizations that do not
+            // take in to account actions by DMA. The fence has been placed here,
+            // after all possible DMA actions have completed.
+            compiler_fence(SeqCst);
+
+            // Wait for 'stopped' event.
+            poll_fn(Self::wait_for_stopped_event).await;
+
+            self.read_errorsrc()?;
+
+            if r.rxd.amount.read().bits() != buffer.len() as u32 {
+                return Err(Error::Receive);
+            }
+
+            Ok(())
+        }
+    }
+
+    fn write<'a>(&'a mut self, address: u8, bytes: &'a [u8]) -> Self::WriteFuture<'a> {
+        async move {
+            slice_in_ram_or(bytes, Error::DMABufferNotInDataMemory)?;
+
+            // Conservative compiler fence to prevent optimizations that do not
+            // take in to account actions by DMA. The fence has been placed here,
+            // before any DMA action has started.
+            compiler_fence(SeqCst);
+
+            let r = T::regs();
+
+            // Set up current address we're trying to talk to
+            r.address.write(|w| unsafe { w.address().bits(address) });
+
+            // Set up DMA write.
+            unsafe {
+                self.set_tx_buffer(bytes)?;
+            }
+
+            // Reset events
+            r.events_stopped.reset();
+            r.events_error.reset();
+            r.events_lasttx.reset();
+            self.clear_errorsrc();
+
+            // Enable events
+            r.intenset.write(|w| w.stopped().set().error().set());
+
+            // Start write operation.
+            r.shorts.write(|w| w.lasttx_stop().enabled());
+            r.tasks_starttx.write(|w|
+            // `1` is a valid value to write to task registers.
+            unsafe { w.bits(1) });
+
+            // Conservative compiler fence to prevent optimizations that do not
+            // take in to account actions by DMA. The fence has been placed here,
+            // after all possible DMA actions have completed.
+            compiler_fence(SeqCst);
+
+            // Wait for 'stopped' event.
+            poll_fn(Self::wait_for_stopped_event).await;
+
+            self.read_errorsrc()?;
+
+            if r.txd.amount.read().bits() != bytes.len() as u32 {
+                return Err(Error::Transmit);
+            }
+
+            Ok(())
+        }
+    }
+
+    fn write_read<'a>(
+        &'a mut self,
+        address: u8,
+        bytes: &'a [u8],
+        buffer: &'a mut [u8],
+    ) -> Self::WriteReadFuture<'a> {
+        async move {
+            slice_in_ram_or(bytes, Error::DMABufferNotInDataMemory)?;
+            // NOTE: RAM slice check for buffer is not necessary, as a mutable
+            // slice can only be built from data located in RAM.
+
+            // Conservative compiler fence to prevent optimizations that do not
+            // take in to account actions by DMA. The fence has been placed here,
+            // before any DMA action has started.
+            compiler_fence(SeqCst);
+
+            let r = T::regs();
+
+            // Set up current address we're trying to talk to
+            r.address.write(|w| unsafe { w.address().bits(address) });
+
+            // Set up DMA buffers.
+            unsafe {
+                self.set_tx_buffer(bytes)?;
+                self.set_rx_buffer(buffer)?;
+            }
+
+            // Reset events
+            r.events_stopped.reset();
+            r.events_error.reset();
+            r.events_lasttx.reset();
+            self.clear_errorsrc();
+
+            // Enable events
+            r.intenset.write(|w| w.stopped().set().error().set());
+
+            // Start write+read operation.
+            r.shorts.write(|w| {
+                w.lasttx_startrx().enabled();
+                w.lastrx_stop().enabled();
+                w
+            });
+            // `1` is a valid value to write to task registers.
+            r.tasks_starttx.write(|w| unsafe { w.bits(1) });
+
+            // Conservative compiler fence to prevent optimizations that do not
+            // take in to account actions by DMA. The fence has been placed here,
+            // after all possible DMA actions have completed.
+            compiler_fence(SeqCst);
+
+            // Wait for 'stopped' event.
+            poll_fn(Self::wait_for_stopped_event).await;
+
+            self.read_errorsrc()?;
+
+            let bad_write = r.txd.amount.read().bits() != bytes.len() as u32;
+            let bad_read = r.rxd.amount.read().bits() != buffer.len() as u32;
+
+            if bad_write {
+                return Err(Error::Transmit);
+            }
+
+            if bad_read {
+                return Err(Error::Receive);
+            }
+
+            Ok(())
+        }
+    }
+}
+
 impl<'a, T: Instance> embedded_hal::blocking::i2c::Write for Twim<'a, T> {
     type Error = Error;
 
diff --git a/embassy-traits/src/i2c.rs b/embassy-traits/src/i2c.rs
index abe932d9c..e426a00b0 100644
--- a/embassy-traits/src/i2c.rs
+++ b/embassy-traits/src/i2c.rs
@@ -94,9 +94,15 @@ pub trait I2c<A: AddressMode = SevenBitAddress> {
     /// Error type
     type Error;
 
-    type ReadFuture<'a>: Future<Output = Result<(), Self::Error>> + 'a;
-    type WriteFuture<'a>: Future<Output = Result<(), Self::Error>> + 'a;
-    type WriteReadFuture<'a>: Future<Output = Result<(), Self::Error>> + 'a;
+    type WriteFuture<'a>: Future<Output = Result<(), Self::Error>> + 'a
+    where
+        Self: 'a;
+    type ReadFuture<'a>: Future<Output = Result<(), Self::Error>> + 'a
+    where
+        Self: 'a;
+    type WriteReadFuture<'a>: Future<Output = Result<(), Self::Error>> + 'a
+    where
+        Self: 'a;
 
     /// Reads enough bytes from slave with `address` to fill `buffer`
     ///
@@ -116,7 +122,7 @@ pub trait I2c<A: AddressMode = SevenBitAddress> {
     /// - `MAK` = master acknowledge
     /// - `NMAK` = master no acknowledge
     /// - `SP` = stop condition
-    fn read<'a>(&'a mut self, address: A, buffer: &mut [u8]) -> Self::ReadFuture<'a>;
+    fn read<'a>(&'a mut self, address: A, buffer: &'a mut [u8]) -> Self::ReadFuture<'a>;
 
     /// Sends bytes to slave with address `address`
     ///
@@ -134,7 +140,7 @@ pub trait I2c<A: AddressMode = SevenBitAddress> {
     /// - `SAK` = slave acknowledge
     /// - `Bi` = ith byte of data
     /// - `SP` = stop condition
-    fn write<'a>(&'a mut self, address: A, bytes: &[u8]) -> Self::WriteFuture<'a>;
+    fn write<'a>(&'a mut self, address: A, bytes: &'a [u8]) -> Self::WriteFuture<'a>;
 
     /// Sends bytes to slave with address `address` and then reads enough bytes to fill `buffer` *in a
     /// single transaction*
@@ -161,7 +167,7 @@ pub trait I2c<A: AddressMode = SevenBitAddress> {
     fn write_read<'a>(
         &'a mut self,
         address: A,
-        bytes: &[u8],
-        buffer: &mut [u8],
+        bytes: &'a [u8],
+        buffer: &'a mut [u8],
     ) -> Self::WriteReadFuture<'a>;
 }