From bc65b8f7ec1df181c793846b7c0657f689963d3a Mon Sep 17 00:00:00 2001
From: Dario Nieuwenhuis <dirbaio@dirbaio.net>
Date: Sat, 18 Nov 2023 01:18:23 +0100
Subject: [PATCH 1/2] stm32/i2c: add async, dual interrupt scaffolding.

---
 embassy-stm32/Cargo.toml                      |   4 +-
 embassy-stm32/build.rs                        |  20 +++
 embassy-stm32/src/i2c/mod.rs                  | 157 +++++++++++++++++-
 embassy-stm32/src/i2c/v1.rs                   | 122 ++++----------
 embassy-stm32/src/i2c/v2.rs                   | 157 ++----------------
 examples/stm32f4/src/bin/i2c.rs               |   3 +-
 examples/stm32h5/src/bin/i2c.rs               |   3 +-
 examples/stm32h7/src/bin/camera.rs            |   3 +-
 examples/stm32h7/src/bin/i2c.rs               |   3 +-
 examples/stm32l4/src/bin/i2c.rs               |   3 +-
 .../stm32l4/src/bin/i2c_blocking_async.rs     |   3 +-
 examples/stm32l4/src/bin/i2c_dma.rs           |   3 +-
 .../src/bin/spe_adin1110_http_server.rs       |   3 +-
 13 files changed, 239 insertions(+), 245 deletions(-)

diff --git a/embassy-stm32/Cargo.toml b/embassy-stm32/Cargo.toml
index 65434ceca..d04000a1d 100644
--- a/embassy-stm32/Cargo.toml
+++ b/embassy-stm32/Cargo.toml
@@ -58,7 +58,7 @@ rand_core = "0.6.3"
 sdio-host = "0.5.0"
 embedded-sdmmc = { git = "https://github.com/embassy-rs/embedded-sdmmc-rs", rev = "a4f293d3a6f72158385f79c98634cb8a14d0d2fc", optional = true }
 critical-section = "1.1"
-stm32-metapac = { git = "https://github.com/embassy-rs/stm32-data-generated", tag = "stm32-data-fbb8f77326dd066aa6c0d66b3b46e76a569dda8b" }
+stm32-metapac = { git = "https://github.com/embassy-rs/stm32-data-generated", tag = "stm32-data-f6d1ffc1a25f208b5cd6b1024bff246592da1949" }
 vcell = "0.1.3"
 bxcan = "0.7.0"
 nb = "1.0.0"
@@ -76,7 +76,7 @@ critical-section = { version = "1.1", features = ["std"] }
 [build-dependencies]
 proc-macro2 = "1.0.36"
 quote = "1.0.15"
-stm32-metapac = { git = "https://github.com/embassy-rs/stm32-data-generated", tag = "stm32-data-fbb8f77326dd066aa6c0d66b3b46e76a569dda8b", default-features = false, features = ["metadata"]}
+stm32-metapac = { git = "https://github.com/embassy-rs/stm32-data-generated", tag = "stm32-data-f6d1ffc1a25f208b5cd6b1024bff246592da1949", default-features = false, features = ["metadata"]}
 
 
 [features]
diff --git a/embassy-stm32/build.rs b/embassy-stm32/build.rs
index a7dac5f9d..4aae58229 100644
--- a/embassy-stm32/build.rs
+++ b/embassy-stm32/build.rs
@@ -1138,6 +1138,23 @@ fn main() {
         }
     }
 
+    // ========
+    // Write peripheral_interrupts module.
+    let mut mt = TokenStream::new();
+    for p in METADATA.peripherals {
+        let mut pt = TokenStream::new();
+
+        for irq in p.interrupts {
+            let iname = format_ident!("{}", irq.interrupt);
+            let sname = format_ident!("{}", irq.signal);
+            pt.extend(quote!(pub type #sname = crate::interrupt::typelevel::#iname;));
+        }
+
+        let pname = format_ident!("{}", p.name);
+        mt.extend(quote!(pub mod #pname { #pt }));
+    }
+    g.extend(quote!(#[allow(non_camel_case_types)] pub mod peripheral_interrupts { #mt }));
+
     // ========
     // Write foreach_foo! macrotables
 
@@ -1296,6 +1313,9 @@ fn main() {
 
     let mut m = String::new();
 
+    // DO NOT ADD more macros like these.
+    // These turned to be a bad idea!
+    // Instead, make build.rs generate the final code.
     make_table(&mut m, "foreach_flash_region", &flash_regions_table);
     make_table(&mut m, "foreach_interrupt", &interrupts_table);
     make_table(&mut m, "foreach_peripheral", &peripherals_table);
diff --git a/embassy-stm32/src/i2c/mod.rs b/embassy-stm32/src/i2c/mod.rs
index dde1a5040..19346d707 100644
--- a/embassy-stm32/src/i2c/mod.rs
+++ b/embassy-stm32/src/i2c/mod.rs
@@ -1,11 +1,14 @@
 #![macro_use]
 
+use core::marker::PhantomData;
+
 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;
 
@@ -23,6 +26,20 @@ pub enum Error {
 
 pub(crate) mod sealed {
     use super::*;
+
+    pub struct State {
+        #[allow(unused)]
+        pub waker: AtomicWaker,
+    }
+
+    impl State {
+        pub const fn new() -> Self {
+            Self {
+                waker: AtomicWaker::new(),
+            }
+        }
+    }
+
     pub trait Instance: crate::rcc::RccPeripheral {
         fn regs() -> crate::pac::i2c::I2c;
         fn state() -> &'static State;
@@ -30,7 +47,8 @@ pub(crate) mod sealed {
 }
 
 pub trait Instance: sealed::Instance + 'static {
-    type Interrupt: interrupt::typelevel::Interrupt;
+    type EventInterrupt: interrupt::typelevel::Interrupt;
+    type ErrorInterrupt: interrupt::typelevel::Interrupt;
 }
 
 pin_trait!(SclPin, Instance);
@@ -38,21 +56,148 @@ pin_trait!(SdaPin, Instance);
 dma_trait!(RxDma, Instance);
 dma_trait!(TxDma, Instance);
 
-foreach_interrupt!(
-    ($inst:ident, i2c, $block:ident, EV, $irq:ident) => {
+/// Interrupt handler.
+pub struct EventInterruptHandler<T: Instance> {
+    _phantom: PhantomData<T>,
+}
+
+impl<T: Instance> interrupt::typelevel::Handler<T::EventInterrupt> for EventInterruptHandler<T> {
+    unsafe fn on_interrupt() {
+        _version::on_interrupt::<T>()
+    }
+}
+
+pub struct ErrorInterruptHandler<T: Instance> {
+    _phantom: PhantomData<T>,
+}
+
+impl<T: Instance> interrupt::typelevel::Handler<T::ErrorInterrupt> for ErrorInterruptHandler<T> {
+    unsafe fn on_interrupt() {
+        _version::on_interrupt::<T>()
+    }
+}
+
+foreach_peripheral!(
+    (i2c, $inst:ident) => {
         impl sealed::Instance for peripherals::$inst {
             fn regs() -> crate::pac::i2c::I2c {
                 crate::pac::$inst
             }
 
-            fn state() -> &'static State {
-                static STATE: State = State::new();
+            fn state() -> &'static sealed::State {
+                static STATE: sealed::State = sealed::State::new();
                 &STATE
             }
         }
 
         impl Instance for peripherals::$inst {
-            type Interrupt = crate::interrupt::typelevel::$irq;
+            type EventInterrupt = crate::_generated::peripheral_interrupts::$inst::EV;
+            type ErrorInterrupt = crate::_generated::peripheral_interrupts::$inst::ER;
         }
     };
 );
+
+mod eh02 {
+    use super::*;
+
+    impl<'d, T: Instance> embedded_hal_02::blocking::i2c::Read for I2c<'d, T> {
+        type Error = Error;
+
+        fn read(&mut self, address: u8, buffer: &mut [u8]) -> Result<(), Self::Error> {
+            self.blocking_read(address, buffer)
+        }
+    }
+
+    impl<'d, T: Instance> embedded_hal_02::blocking::i2c::Write for I2c<'d, T> {
+        type Error = Error;
+
+        fn write(&mut self, address: u8, write: &[u8]) -> Result<(), Self::Error> {
+            self.blocking_write(address, write)
+        }
+    }
+
+    impl<'d, T: Instance> embedded_hal_02::blocking::i2c::WriteRead for I2c<'d, T> {
+        type Error = Error;
+
+        fn write_read(&mut self, address: u8, write: &[u8], read: &mut [u8]) -> Result<(), Self::Error> {
+            self.blocking_write_read(address, write, read)
+        }
+    }
+}
+
+#[cfg(feature = "unstable-traits")]
+mod eh1 {
+    use super::*;
+    use crate::dma::NoDma;
+
+    impl embedded_hal_1::i2c::Error for Error {
+        fn kind(&self) -> embedded_hal_1::i2c::ErrorKind {
+            match *self {
+                Self::Bus => embedded_hal_1::i2c::ErrorKind::Bus,
+                Self::Arbitration => embedded_hal_1::i2c::ErrorKind::ArbitrationLoss,
+                Self::Nack => {
+                    embedded_hal_1::i2c::ErrorKind::NoAcknowledge(embedded_hal_1::i2c::NoAcknowledgeSource::Unknown)
+                }
+                Self::Timeout => embedded_hal_1::i2c::ErrorKind::Other,
+                Self::Crc => embedded_hal_1::i2c::ErrorKind::Other,
+                Self::Overrun => embedded_hal_1::i2c::ErrorKind::Overrun,
+                Self::ZeroLengthTransfer => embedded_hal_1::i2c::ErrorKind::Other,
+            }
+        }
+    }
+
+    impl<'d, T: Instance, TXDMA, RXDMA> embedded_hal_1::i2c::ErrorType for I2c<'d, T, TXDMA, RXDMA> {
+        type Error = Error;
+    }
+
+    impl<'d, T: Instance> embedded_hal_1::i2c::I2c for I2c<'d, T, NoDma, NoDma> {
+        fn read(&mut self, address: u8, read: &mut [u8]) -> Result<(), Self::Error> {
+            self.blocking_read(address, read)
+        }
+
+        fn write(&mut self, address: u8, write: &[u8]) -> Result<(), Self::Error> {
+            self.blocking_write(address, write)
+        }
+
+        fn write_read(&mut self, address: u8, write: &[u8], read: &mut [u8]) -> Result<(), Self::Error> {
+            self.blocking_write_read(address, write, read)
+        }
+
+        fn transaction(
+            &mut self,
+            _address: u8,
+            _operations: &mut [embedded_hal_1::i2c::Operation<'_>],
+        ) -> Result<(), Self::Error> {
+            todo!();
+        }
+    }
+}
+
+#[cfg(all(feature = "unstable-traits", feature = "nightly"))]
+mod eha {
+    use super::*;
+
+    impl<'d, T: Instance, TXDMA: TxDma<T>, RXDMA: RxDma<T>> embedded_hal_async::i2c::I2c for I2c<'d, T, TXDMA, RXDMA> {
+        async fn read(&mut self, address: u8, read: &mut [u8]) -> Result<(), Self::Error> {
+            self.read(address, read).await
+        }
+
+        async fn write(&mut self, address: u8, write: &[u8]) -> Result<(), Self::Error> {
+            self.write(address, write).await
+        }
+
+        async fn write_read(&mut self, address: u8, write: &[u8], read: &mut [u8]) -> Result<(), Self::Error> {
+            self.write_read(address, write, read).await
+        }
+
+        async fn transaction(
+            &mut self,
+            address: u8,
+            operations: &mut [embedded_hal_1::i2c::Operation<'_>],
+        ) -> Result<(), Self::Error> {
+            let _ = address;
+            let _ = operations;
+            todo!()
+        }
+    }
+}
diff --git a/embassy-stm32/src/i2c/v1.rs b/embassy-stm32/src/i2c/v1.rs
index ab59f5ab9..03f07c4fe 100644
--- a/embassy-stm32/src/i2c/v1.rs
+++ b/embassy-stm32/src/i2c/v1.rs
@@ -3,21 +3,17 @@ use core::marker::PhantomData;
 use embassy_embedded_hal::SetConfig;
 use embassy_hal_internal::{into_ref, PeripheralRef};
 
+use super::*;
 use crate::dma::NoDma;
 use crate::gpio::sealed::AFType;
 use crate::gpio::Pull;
-use crate::i2c::{Error, Instance, SclPin, SdaPin};
+use crate::interrupt::typelevel::Interrupt;
 use crate::pac::i2c;
 use crate::time::Hertz;
 use crate::{interrupt, Peripheral};
 
-/// Interrupt handler.
-pub struct InterruptHandler<T: Instance> {
-    _phantom: PhantomData<T>,
-}
-
-impl<T: Instance> interrupt::typelevel::Handler<T::Interrupt> for InterruptHandler<T> {
-    unsafe fn on_interrupt() {}
+pub unsafe fn on_interrupt<T: Instance>() {
+    // todo
 }
 
 #[non_exhaustive]
@@ -27,14 +23,6 @@ pub struct Config {
     pub scl_pullup: bool,
 }
 
-pub struct State {}
-
-impl State {
-    pub(crate) const fn new() -> Self {
-        Self {}
-    }
-}
-
 pub struct I2c<'d, T: Instance, TXDMA = NoDma, RXDMA = NoDma> {
     phantom: PhantomData<&'d mut T>,
     #[allow(dead_code)]
@@ -48,7 +36,9 @@ impl<'d, T: Instance, TXDMA, RXDMA> I2c<'d, T, TXDMA, RXDMA> {
         _peri: impl Peripheral<P = T> + 'd,
         scl: impl Peripheral<P = impl SclPin<T>> + 'd,
         sda: impl Peripheral<P = impl SdaPin<T>> + 'd,
-        _irq: impl interrupt::typelevel::Binding<T::Interrupt, InterruptHandler<T>> + 'd,
+        _irq: impl interrupt::typelevel::Binding<T::EventInterrupt, EventInterruptHandler<T>>
+            + interrupt::typelevel::Binding<T::ErrorInterrupt, ErrorInterruptHandler<T>>
+            + 'd,
         tx_dma: impl Peripheral<P = TXDMA> + 'd,
         rx_dma: impl Peripheral<P = RXDMA> + 'd,
         freq: Hertz,
@@ -98,6 +88,9 @@ impl<'d, T: Instance, TXDMA, RXDMA> I2c<'d, T, TXDMA, RXDMA> {
             reg.set_pe(true);
         });
 
+        unsafe { T::EventInterrupt::enable() };
+        unsafe { T::ErrorInterrupt::enable() };
+
         Self {
             phantom: PhantomData,
             tx_dma,
@@ -336,6 +329,30 @@ impl<'d, T: Instance, TXDMA, RXDMA> I2c<'d, T, TXDMA, RXDMA> {
     pub fn blocking_write_read(&mut self, addr: u8, write: &[u8], read: &mut [u8]) -> Result<(), Error> {
         self.blocking_write_read_timeout(addr, write, read, || Ok(()))
     }
+
+    // Async
+
+    pub async fn write(&mut self, _address: u8, _write: &[u8]) -> Result<(), Error>
+    where
+        TXDMA: crate::i2c::TxDma<T>,
+    {
+        todo!()
+    }
+
+    pub async fn read(&mut self, _address: u8, _buffer: &mut [u8]) -> Result<(), Error>
+    where
+        RXDMA: crate::i2c::RxDma<T>,
+    {
+        todo!()
+    }
+
+    pub async fn write_read(&mut self, _address: u8, _write: &[u8], _read: &mut [u8]) -> Result<(), Error>
+    where
+        RXDMA: crate::i2c::RxDma<T>,
+        TXDMA: crate::i2c::TxDma<T>,
+    {
+        todo!()
+    }
 }
 
 impl<'d, T: Instance, TXDMA, RXDMA> Drop for I2c<'d, T, TXDMA, RXDMA> {
@@ -344,77 +361,6 @@ impl<'d, T: Instance, TXDMA, RXDMA> Drop for I2c<'d, T, TXDMA, RXDMA> {
     }
 }
 
-impl<'d, T: Instance> embedded_hal_02::blocking::i2c::Read for I2c<'d, T> {
-    type Error = Error;
-
-    fn read(&mut self, addr: u8, read: &mut [u8]) -> Result<(), Self::Error> {
-        self.blocking_read(addr, read)
-    }
-}
-
-impl<'d, T: Instance> embedded_hal_02::blocking::i2c::Write for I2c<'d, T> {
-    type Error = Error;
-
-    fn write(&mut self, addr: u8, write: &[u8]) -> Result<(), Self::Error> {
-        self.blocking_write(addr, write)
-    }
-}
-
-impl<'d, T: Instance> embedded_hal_02::blocking::i2c::WriteRead for I2c<'d, T> {
-    type Error = Error;
-
-    fn write_read(&mut self, addr: u8, write: &[u8], read: &mut [u8]) -> Result<(), Self::Error> {
-        self.blocking_write_read(addr, write, read)
-    }
-}
-
-#[cfg(feature = "unstable-traits")]
-mod eh1 {
-    use super::*;
-
-    impl embedded_hal_1::i2c::Error for Error {
-        fn kind(&self) -> embedded_hal_1::i2c::ErrorKind {
-            match *self {
-                Self::Bus => embedded_hal_1::i2c::ErrorKind::Bus,
-                Self::Arbitration => embedded_hal_1::i2c::ErrorKind::ArbitrationLoss,
-                Self::Nack => {
-                    embedded_hal_1::i2c::ErrorKind::NoAcknowledge(embedded_hal_1::i2c::NoAcknowledgeSource::Unknown)
-                }
-                Self::Timeout => embedded_hal_1::i2c::ErrorKind::Other,
-                Self::Crc => embedded_hal_1::i2c::ErrorKind::Other,
-                Self::Overrun => embedded_hal_1::i2c::ErrorKind::Overrun,
-                Self::ZeroLengthTransfer => embedded_hal_1::i2c::ErrorKind::Other,
-            }
-        }
-    }
-
-    impl<'d, T: Instance> embedded_hal_1::i2c::ErrorType for I2c<'d, T> {
-        type Error = Error;
-    }
-
-    impl<'d, T: Instance> embedded_hal_1::i2c::I2c for I2c<'d, T> {
-        fn read(&mut self, address: u8, read: &mut [u8]) -> Result<(), Self::Error> {
-            self.blocking_read(address, read)
-        }
-
-        fn write(&mut self, address: u8, write: &[u8]) -> Result<(), Self::Error> {
-            self.blocking_write(address, write)
-        }
-
-        fn write_read(&mut self, address: u8, write: &[u8], read: &mut [u8]) -> Result<(), Self::Error> {
-            self.blocking_write_read(address, write, read)
-        }
-
-        fn transaction(
-            &mut self,
-            _address: u8,
-            _operations: &mut [embedded_hal_1::i2c::Operation<'_>],
-        ) -> Result<(), Self::Error> {
-            todo!();
-        }
-    }
-}
-
 enum Mode {
     Fast,
     Standard,
diff --git a/embassy-stm32/src/i2c/v2.rs b/embassy-stm32/src/i2c/v2.rs
index 40bcaa9bc..8c20e1c54 100644
--- a/embassy-stm32/src/i2c/v2.rs
+++ b/embassy-stm32/src/i2c/v2.rs
@@ -1,19 +1,17 @@
 use core::cmp;
 use core::future::poll_fn;
-use core::marker::PhantomData;
 use core::task::Poll;
 
 use embassy_embedded_hal::SetConfig;
 use embassy_hal_internal::drop::OnDrop;
 use embassy_hal_internal::{into_ref, PeripheralRef};
-use embassy_sync::waitqueue::AtomicWaker;
 #[cfg(feature = "time")]
 use embassy_time::{Duration, Instant};
 
+use super::*;
 use crate::dma::{NoDma, Transfer};
 use crate::gpio::sealed::AFType;
 use crate::gpio::Pull;
-use crate::i2c::{Error, Instance, SclPin, SdaPin};
 use crate::interrupt::typelevel::Interrupt;
 use crate::pac::i2c;
 use crate::time::Hertz;
@@ -36,25 +34,18 @@ pub fn no_timeout_fn() -> impl Fn() -> Result<(), Error> {
     move || Ok(())
 }
 
-/// Interrupt handler.
-pub struct InterruptHandler<T: Instance> {
-    _phantom: PhantomData<T>,
-}
+pub unsafe fn on_interrupt<T: Instance>() {
+    let regs = T::regs();
+    let isr = regs.isr().read();
 
-impl<T: Instance> interrupt::typelevel::Handler<T::Interrupt> for InterruptHandler<T> {
-    unsafe fn on_interrupt() {
-        let regs = T::regs();
-        let isr = regs.isr().read();
-
-        if isr.tcr() || isr.tc() {
-            T::state().waker.wake();
-        }
-        // The flag can only be cleared by writting to nbytes, we won't do that here, so disable
-        // the interrupt
-        critical_section::with(|_| {
-            regs.cr1().modify(|w| w.set_tcie(false));
-        });
+    if isr.tcr() || isr.tc() {
+        T::state().waker.wake();
     }
+    // The flag can only be cleared by writting to nbytes, we won't do that here, so disable
+    // the interrupt
+    critical_section::with(|_| {
+        regs.cr1().modify(|w| w.set_tcie(false));
+    });
 }
 
 #[non_exhaustive]
@@ -77,18 +68,6 @@ impl Default for Config {
     }
 }
 
-pub struct State {
-    waker: AtomicWaker,
-}
-
-impl State {
-    pub(crate) const fn new() -> Self {
-        Self {
-            waker: AtomicWaker::new(),
-        }
-    }
-}
-
 pub struct I2c<'d, T: Instance, TXDMA = NoDma, RXDMA = NoDma> {
     _peri: PeripheralRef<'d, T>,
     #[allow(dead_code)]
@@ -104,7 +83,9 @@ impl<'d, T: Instance, TXDMA, RXDMA> I2c<'d, T, TXDMA, RXDMA> {
         peri: impl Peripheral<P = T> + 'd,
         scl: impl Peripheral<P = impl SclPin<T>> + 'd,
         sda: impl Peripheral<P = impl SdaPin<T>> + 'd,
-        _irq: impl interrupt::typelevel::Binding<T::Interrupt, InterruptHandler<T>> + 'd,
+        _irq: impl interrupt::typelevel::Binding<T::EventInterrupt, EventInterruptHandler<T>>
+            + interrupt::typelevel::Binding<T::ErrorInterrupt, ErrorInterruptHandler<T>>
+            + 'd,
         tx_dma: impl Peripheral<P = TXDMA> + 'd,
         rx_dma: impl Peripheral<P = RXDMA> + 'd,
         freq: Hertz,
@@ -150,8 +131,8 @@ impl<'d, T: Instance, TXDMA, RXDMA> I2c<'d, T, TXDMA, RXDMA> {
             reg.set_pe(true);
         });
 
-        T::Interrupt::unpend();
-        unsafe { T::Interrupt::enable() };
+        unsafe { T::EventInterrupt::enable() };
+        unsafe { T::ErrorInterrupt::enable() };
 
         Self {
             _peri: peri,
@@ -987,35 +968,6 @@ impl<'d, T: Instance, TXDMA, RXDMA> Drop for I2c<'d, T, TXDMA, RXDMA> {
     }
 }
 
-#[cfg(feature = "time")]
-mod eh02 {
-    use super::*;
-
-    impl<'d, T: Instance> embedded_hal_02::blocking::i2c::Read for I2c<'d, T> {
-        type Error = Error;
-
-        fn read(&mut self, address: u8, buffer: &mut [u8]) -> Result<(), Self::Error> {
-            self.blocking_read(address, buffer)
-        }
-    }
-
-    impl<'d, T: Instance> embedded_hal_02::blocking::i2c::Write for I2c<'d, T> {
-        type Error = Error;
-
-        fn write(&mut self, address: u8, write: &[u8]) -> Result<(), Self::Error> {
-            self.blocking_write(address, write)
-        }
-    }
-
-    impl<'d, T: Instance> embedded_hal_02::blocking::i2c::WriteRead for I2c<'d, T> {
-        type Error = Error;
-
-        fn write_read(&mut self, address: u8, write: &[u8], read: &mut [u8]) -> Result<(), Self::Error> {
-            self.blocking_write_read(address, write, read)
-        }
-    }
-}
-
 /// I2C Stop Configuration
 ///
 /// Peripheral options for generating the STOP condition
@@ -1140,83 +1092,6 @@ impl Timings {
     }
 }
 
-#[cfg(feature = "unstable-traits")]
-mod eh1 {
-    use super::*;
-
-    impl embedded_hal_1::i2c::Error for Error {
-        fn kind(&self) -> embedded_hal_1::i2c::ErrorKind {
-            match *self {
-                Self::Bus => embedded_hal_1::i2c::ErrorKind::Bus,
-                Self::Arbitration => embedded_hal_1::i2c::ErrorKind::ArbitrationLoss,
-                Self::Nack => {
-                    embedded_hal_1::i2c::ErrorKind::NoAcknowledge(embedded_hal_1::i2c::NoAcknowledgeSource::Unknown)
-                }
-                Self::Timeout => embedded_hal_1::i2c::ErrorKind::Other,
-                Self::Crc => embedded_hal_1::i2c::ErrorKind::Other,
-                Self::Overrun => embedded_hal_1::i2c::ErrorKind::Overrun,
-                Self::ZeroLengthTransfer => embedded_hal_1::i2c::ErrorKind::Other,
-            }
-        }
-    }
-
-    impl<'d, T: Instance, TXDMA, RXDMA> embedded_hal_1::i2c::ErrorType for I2c<'d, T, TXDMA, RXDMA> {
-        type Error = Error;
-    }
-
-    impl<'d, T: Instance> embedded_hal_1::i2c::I2c for I2c<'d, T, NoDma, NoDma> {
-        fn read(&mut self, address: u8, read: &mut [u8]) -> Result<(), Self::Error> {
-            self.blocking_read(address, read)
-        }
-
-        fn write(&mut self, address: u8, write: &[u8]) -> Result<(), Self::Error> {
-            self.blocking_write(address, write)
-        }
-
-        fn write_read(&mut self, address: u8, write: &[u8], read: &mut [u8]) -> Result<(), Self::Error> {
-            self.blocking_write_read(address, write, read)
-        }
-
-        fn transaction(
-            &mut self,
-            _address: u8,
-            _operations: &mut [embedded_hal_1::i2c::Operation<'_>],
-        ) -> Result<(), Self::Error> {
-            todo!();
-        }
-    }
-}
-
-#[cfg(all(feature = "unstable-traits", feature = "nightly"))]
-mod eha {
-    use super::super::{RxDma, TxDma};
-    use super::*;
-
-    impl<'d, T: Instance, TXDMA: TxDma<T>, RXDMA: RxDma<T>> embedded_hal_async::i2c::I2c for I2c<'d, T, TXDMA, RXDMA> {
-        async fn read(&mut self, address: u8, read: &mut [u8]) -> Result<(), Self::Error> {
-            self.read(address, read).await
-        }
-
-        async fn write(&mut self, address: u8, write: &[u8]) -> Result<(), Self::Error> {
-            self.write(address, write).await
-        }
-
-        async fn write_read(&mut self, address: u8, write: &[u8], read: &mut [u8]) -> Result<(), Self::Error> {
-            self.write_read(address, write, read).await
-        }
-
-        async fn transaction(
-            &mut self,
-            address: u8,
-            operations: &mut [embedded_hal_1::i2c::Operation<'_>],
-        ) -> Result<(), Self::Error> {
-            let _ = address;
-            let _ = operations;
-            todo!()
-        }
-    }
-}
-
 impl<'d, T: Instance> SetConfig for I2c<'d, T> {
     type Config = Hertz;
     type ConfigError = ();
diff --git a/examples/stm32f4/src/bin/i2c.rs b/examples/stm32f4/src/bin/i2c.rs
index 032bd97ee..4f4adde28 100644
--- a/examples/stm32f4/src/bin/i2c.rs
+++ b/examples/stm32f4/src/bin/i2c.rs
@@ -14,7 +14,8 @@ const ADDRESS: u8 = 0x5F;
 const WHOAMI: u8 = 0x0F;
 
 bind_interrupts!(struct Irqs {
-    I2C2_EV => i2c::InterruptHandler<peripherals::I2C2>;
+    I2C2_EV => i2c::EventInterruptHandler<peripherals::I2C2>;
+    I2C2_ER => i2c::ErrorInterruptHandler<peripherals::I2C2>;
 });
 
 #[embassy_executor::main]
diff --git a/examples/stm32h5/src/bin/i2c.rs b/examples/stm32h5/src/bin/i2c.rs
index 8b1662f39..31783a2bf 100644
--- a/examples/stm32h5/src/bin/i2c.rs
+++ b/examples/stm32h5/src/bin/i2c.rs
@@ -13,7 +13,8 @@ const ADDRESS: u8 = 0x5F;
 const WHOAMI: u8 = 0x0F;
 
 bind_interrupts!(struct Irqs {
-    I2C2_EV => i2c::InterruptHandler<peripherals::I2C2>;
+    I2C2_EV => i2c::EventInterruptHandler<peripherals::I2C2>;
+    I2C2_ER => i2c::ErrorInterruptHandler<peripherals::I2C2>;
 });
 
 #[embassy_executor::main]
diff --git a/examples/stm32h7/src/bin/camera.rs b/examples/stm32h7/src/bin/camera.rs
index 23ece1c38..489fb03dd 100644
--- a/examples/stm32h7/src/bin/camera.rs
+++ b/examples/stm32h7/src/bin/camera.rs
@@ -19,7 +19,8 @@ const HEIGHT: usize = 100;
 static mut FRAME: [u32; WIDTH * HEIGHT / 2] = [0u32; WIDTH * HEIGHT / 2];
 
 bind_interrupts!(struct Irqs {
-    I2C1_EV => i2c::InterruptHandler<peripherals::I2C1>;
+    I2C1_EV => i2c::EventInterruptHandler<peripherals::I2C1>;
+    I2C1_ER => i2c::ErrorInterruptHandler<peripherals::I2C1>;
     DCMI => dcmi::InterruptHandler<peripherals::DCMI>;
 });
 
diff --git a/examples/stm32h7/src/bin/i2c.rs b/examples/stm32h7/src/bin/i2c.rs
index 9aa0ca08b..aea21ec6f 100644
--- a/examples/stm32h7/src/bin/i2c.rs
+++ b/examples/stm32h7/src/bin/i2c.rs
@@ -13,7 +13,8 @@ const ADDRESS: u8 = 0x5F;
 const WHOAMI: u8 = 0x0F;
 
 bind_interrupts!(struct Irqs {
-    I2C2_EV => i2c::InterruptHandler<peripherals::I2C2>;
+    I2C2_EV => i2c::EventInterruptHandler<peripherals::I2C2>;
+    I2C2_ER => i2c::ErrorInterruptHandler<peripherals::I2C2>;
 });
 
 #[embassy_executor::main]
diff --git a/examples/stm32l4/src/bin/i2c.rs b/examples/stm32l4/src/bin/i2c.rs
index d0060d20c..07dc12e8c 100644
--- a/examples/stm32l4/src/bin/i2c.rs
+++ b/examples/stm32l4/src/bin/i2c.rs
@@ -14,7 +14,8 @@ const ADDRESS: u8 = 0x5F;
 const WHOAMI: u8 = 0x0F;
 
 bind_interrupts!(struct Irqs {
-    I2C2_EV => i2c::InterruptHandler<peripherals::I2C2>;
+    I2C2_EV => i2c::EventInterruptHandler<peripherals::I2C2>;
+    I2C2_ER => i2c::ErrorInterruptHandler<peripherals::I2C2>;
 });
 
 #[embassy_executor::main]
diff --git a/examples/stm32l4/src/bin/i2c_blocking_async.rs b/examples/stm32l4/src/bin/i2c_blocking_async.rs
index eca59087b..60a4e2eb3 100644
--- a/examples/stm32l4/src/bin/i2c_blocking_async.rs
+++ b/examples/stm32l4/src/bin/i2c_blocking_async.rs
@@ -16,7 +16,8 @@ const ADDRESS: u8 = 0x5F;
 const WHOAMI: u8 = 0x0F;
 
 bind_interrupts!(struct Irqs {
-    I2C2_EV => i2c::InterruptHandler<peripherals::I2C2>;
+    I2C2_EV => i2c::EventInterruptHandler<peripherals::I2C2>;
+    I2C2_ER => i2c::ErrorInterruptHandler<peripherals::I2C2>;
 });
 
 #[embassy_executor::main]
diff --git a/examples/stm32l4/src/bin/i2c_dma.rs b/examples/stm32l4/src/bin/i2c_dma.rs
index cf6f3da67..4c2c224a6 100644
--- a/examples/stm32l4/src/bin/i2c_dma.rs
+++ b/examples/stm32l4/src/bin/i2c_dma.rs
@@ -13,7 +13,8 @@ const ADDRESS: u8 = 0x5F;
 const WHOAMI: u8 = 0x0F;
 
 bind_interrupts!(struct Irqs {
-    I2C2_EV => i2c::InterruptHandler<peripherals::I2C2>;
+    I2C2_EV => i2c::EventInterruptHandler<peripherals::I2C2>;
+    I2C2_ER => i2c::ErrorInterruptHandler<peripherals::I2C2>;
 });
 
 #[embassy_executor::main]
diff --git a/examples/stm32l4/src/bin/spe_adin1110_http_server.rs b/examples/stm32l4/src/bin/spe_adin1110_http_server.rs
index 3a7e5370c..4826e0bed 100644
--- a/examples/stm32l4/src/bin/spe_adin1110_http_server.rs
+++ b/examples/stm32l4/src/bin/spe_adin1110_http_server.rs
@@ -40,7 +40,8 @@ use static_cell::make_static;
 use {embassy_stm32 as hal, panic_probe as _};
 
 bind_interrupts!(struct Irqs {
-    I2C3_EV => i2c::InterruptHandler<peripherals::I2C3>;
+    I2C3_EV => i2c::EventInterruptHandler<peripherals::I2C3>;
+    I2C3_ER => i2c::ErrorInterruptHandler<peripherals::I2C3>;
     RNG => rng::InterruptHandler<peripherals::RNG>;
 });
 

From 3efc3eee5700d2a39e397f1b1b821885301c0862 Mon Sep 17 00:00:00 2001
From: Barnaby Walters <barnaby@waterpigs.co.uk>
Date: Mon, 20 Nov 2023 01:29:02 +0100
Subject: [PATCH 2/2] stm32/i2c: implement async i2c v1.

---
 embassy-stm32/src/i2c/v1.rs                | 404 +++++++++++++++++++--
 examples/stm32f4/src/bin/i2c_async.rs      |  62 ++++
 examples/stm32f4/src/bin/i2c_comparison.rs | 135 +++++++
 3 files changed, 578 insertions(+), 23 deletions(-)
 create mode 100644 examples/stm32f4/src/bin/i2c_async.rs
 create mode 100644 examples/stm32f4/src/bin/i2c_comparison.rs

diff --git a/embassy-stm32/src/i2c/v1.rs b/embassy-stm32/src/i2c/v1.rs
index 03f07c4fe..b62ee8246 100644
--- a/embassy-stm32/src/i2c/v1.rs
+++ b/embassy-stm32/src/i2c/v1.rs
@@ -1,10 +1,14 @@
+use core::future::poll_fn;
 use core::marker::PhantomData;
+use core::task::Poll;
 
 use embassy_embedded_hal::SetConfig;
+use embassy_futures::select::{select, Either};
+use embassy_hal_internal::drop::OnDrop;
 use embassy_hal_internal::{into_ref, PeripheralRef};
 
 use super::*;
-use crate::dma::NoDma;
+use crate::dma::{NoDma, Transfer};
 use crate::gpio::sealed::AFType;
 use crate::gpio::Pull;
 use crate::interrupt::typelevel::Interrupt;
@@ -13,7 +17,17 @@ use crate::time::Hertz;
 use crate::{interrupt, Peripheral};
 
 pub unsafe fn on_interrupt<T: Instance>() {
-    // todo
+    let regs = T::regs();
+    // i2c v2 only woke the task on transfer complete interrupts. v1 uses interrupts for a bunch of
+    // other stuff, so we wake the task on every interrupt.
+    T::state().waker.wake();
+    critical_section::with(|_| {
+        // Clear event interrupt flag.
+        regs.cr2().modify(|w| {
+            w.set_itevten(false);
+            w.set_iterren(false);
+        });
+    });
 }
 
 #[non_exhaustive]
@@ -98,40 +112,58 @@ impl<'d, T: Instance, TXDMA, RXDMA> I2c<'d, T, TXDMA, RXDMA> {
         }
     }
 
-    fn check_and_clear_error_flags(&self) -> Result<i2c::regs::Sr1, Error> {
+    fn check_and_clear_error_flags() -> Result<i2c::regs::Sr1, Error> {
         // Note that flags should only be cleared once they have been registered. If flags are
         // cleared otherwise, there may be an inherent race condition and flags may be missed.
         let sr1 = T::regs().sr1().read();
 
         if sr1.timeout() {
-            T::regs().sr1().modify(|reg| reg.set_timeout(false));
+            T::regs().sr1().write(|reg| {
+                reg.0 = !0;
+                reg.set_timeout(false);
+            });
             return Err(Error::Timeout);
         }
 
         if sr1.pecerr() {
-            T::regs().sr1().modify(|reg| reg.set_pecerr(false));
+            T::regs().sr1().write(|reg| {
+                reg.0 = !0;
+                reg.set_pecerr(false);
+            });
             return Err(Error::Crc);
         }
 
         if sr1.ovr() {
-            T::regs().sr1().modify(|reg| reg.set_ovr(false));
+            T::regs().sr1().write(|reg| {
+                reg.0 = !0;
+                reg.set_ovr(false);
+            });
             return Err(Error::Overrun);
         }
 
         if sr1.af() {
-            T::regs().sr1().modify(|reg| reg.set_af(false));
+            T::regs().sr1().write(|reg| {
+                reg.0 = !0;
+                reg.set_af(false);
+            });
             return Err(Error::Nack);
         }
 
         if sr1.arlo() {
-            T::regs().sr1().modify(|reg| reg.set_arlo(false));
+            T::regs().sr1().write(|reg| {
+                reg.0 = !0;
+                reg.set_arlo(false);
+            });
             return Err(Error::Arbitration);
         }
 
         // The errata indicates that BERR may be incorrectly detected. It recommends ignoring and
         // clearing the BERR bit instead.
         if sr1.berr() {
-            T::regs().sr1().modify(|reg| reg.set_berr(false));
+            T::regs().sr1().write(|reg| {
+                reg.0 = !0;
+                reg.set_berr(false);
+            });
         }
 
         Ok(sr1)
@@ -150,13 +182,13 @@ impl<'d, T: Instance, TXDMA, RXDMA> I2c<'d, T, TXDMA, RXDMA> {
         });
 
         // Wait until START condition was generated
-        while !self.check_and_clear_error_flags()?.start() {
+        while !Self::check_and_clear_error_flags()?.start() {
             check_timeout()?;
         }
 
         // Also wait until signalled we're master and everything is waiting for us
         while {
-            self.check_and_clear_error_flags()?;
+            Self::check_and_clear_error_flags()?;
 
             let sr2 = T::regs().sr2().read();
             !sr2.msl() && !sr2.busy()
@@ -170,7 +202,7 @@ impl<'d, T: Instance, TXDMA, RXDMA> I2c<'d, T, TXDMA, RXDMA> {
         // Wait until address was sent
         // Wait for the address to be acknowledged
         // Check for any I2C errors. If a NACK occurs, the ADDR bit will never be set.
-        while !self.check_and_clear_error_flags()?.addr() {
+        while !Self::check_and_clear_error_flags()?.addr() {
             check_timeout()?;
         }
 
@@ -190,7 +222,7 @@ impl<'d, T: Instance, TXDMA, RXDMA> I2c<'d, T, TXDMA, RXDMA> {
         // Wait until we're ready for sending
         while {
             // Check for any I2C errors. If a NACK occurs, the ADDR bit will never be set.
-            !self.check_and_clear_error_flags()?.txe()
+            !Self::check_and_clear_error_flags()?.txe()
         } {
             check_timeout()?;
         }
@@ -201,7 +233,7 @@ impl<'d, T: Instance, TXDMA, RXDMA> I2c<'d, T, TXDMA, RXDMA> {
         // Wait until byte is transferred
         while {
             // Check for any potential error conditions.
-            !self.check_and_clear_error_flags()?.btf()
+            !Self::check_and_clear_error_flags()?.btf()
         } {
             check_timeout()?;
         }
@@ -212,7 +244,7 @@ impl<'d, T: Instance, TXDMA, RXDMA> I2c<'d, T, TXDMA, RXDMA> {
     fn recv_byte(&self, check_timeout: impl Fn() -> Result<(), Error>) -> Result<u8, Error> {
         while {
             // Check for any potential error conditions.
-            self.check_and_clear_error_flags()?;
+            Self::check_and_clear_error_flags()?;
 
             !T::regs().sr1().read().rxne()
         } {
@@ -237,7 +269,7 @@ impl<'d, T: Instance, TXDMA, RXDMA> I2c<'d, T, TXDMA, RXDMA> {
             });
 
             // Wait until START condition was generated
-            while !self.check_and_clear_error_flags()?.start() {
+            while !Self::check_and_clear_error_flags()?.start() {
                 check_timeout()?;
             }
 
@@ -254,7 +286,7 @@ impl<'d, T: Instance, TXDMA, RXDMA> I2c<'d, T, TXDMA, RXDMA> {
 
             // Wait until address was sent
             // Wait for the address to be acknowledged
-            while !self.check_and_clear_error_flags()?.addr() {
+            while !Self::check_and_clear_error_flags()?.addr() {
                 check_timeout()?;
             }
 
@@ -332,26 +364,352 @@ impl<'d, T: Instance, TXDMA, RXDMA> I2c<'d, T, TXDMA, RXDMA> {
 
     // Async
 
-    pub async fn write(&mut self, _address: u8, _write: &[u8]) -> Result<(), Error>
+    #[inline] // pretty sure this should always be inlined
+    fn enable_interrupts() -> () {
+        T::regs().cr2().modify(|w| {
+            w.set_iterren(true);
+            w.set_itevten(true);
+        });
+    }
+
+    async fn write_with_stop(&mut self, address: u8, write: &[u8], send_stop: bool) -> Result<(), Error>
     where
         TXDMA: crate::i2c::TxDma<T>,
     {
-        todo!()
+        let dma_transfer = unsafe {
+            let regs = T::regs();
+            regs.cr2().modify(|w| {
+                // DMA mode can be enabled for transmission by setting the DMAEN bit in the I2C_CR2 register.
+                w.set_dmaen(true);
+                w.set_itbufen(false);
+            });
+            // Set the I2C_DR register address in the DMA_SxPAR register. The data will be moved to this address from the memory after each TxE event.
+            let dst = regs.dr().as_ptr() as *mut u8;
+
+            let ch = &mut self.tx_dma;
+            let request = ch.request();
+            Transfer::new_write(ch, request, write, dst, Default::default())
+        };
+
+        let on_drop = OnDrop::new(|| {
+            let regs = T::regs();
+            regs.cr2().modify(|w| {
+                w.set_dmaen(false);
+                w.set_iterren(false);
+                w.set_itevten(false);
+            })
+        });
+
+        Self::enable_interrupts();
+
+        // Send a START condition
+        T::regs().cr1().modify(|reg| {
+            reg.set_start(true);
+        });
+
+        let state = T::state();
+
+        // Wait until START condition was generated
+        poll_fn(|cx| {
+            state.waker.register(cx.waker());
+
+            match Self::check_and_clear_error_flags() {
+                Err(e) => Poll::Ready(Err(e)),
+                Ok(sr1) => {
+                    if sr1.start() {
+                        Poll::Ready(Ok(()))
+                    } else {
+                        Poll::Pending
+                    }
+                }
+            }
+        })
+        .await?;
+
+        // Also wait until signalled we're master and everything is waiting for us
+        Self::enable_interrupts();
+        poll_fn(|cx| {
+            state.waker.register(cx.waker());
+
+            match Self::check_and_clear_error_flags() {
+                Err(e) => Poll::Ready(Err(e)),
+                Ok(_) => {
+                    let sr2 = T::regs().sr2().read();
+                    if !sr2.msl() && !sr2.busy() {
+                        Poll::Pending
+                    } else {
+                        Poll::Ready(Ok(()))
+                    }
+                }
+            }
+        })
+        .await?;
+
+        // Set up current address, we're trying to talk to
+        Self::enable_interrupts();
+        T::regs().dr().write(|reg| reg.set_dr(address << 1));
+
+        poll_fn(|cx| {
+            state.waker.register(cx.waker());
+            match Self::check_and_clear_error_flags() {
+                Err(e) => Poll::Ready(Err(e)),
+                Ok(sr1) => {
+                    if sr1.addr() {
+                        // Clear the ADDR condition by reading SR2.
+                        T::regs().sr2().read();
+                        Poll::Ready(Ok(()))
+                    } else {
+                        Poll::Pending
+                    }
+                }
+            }
+        })
+        .await?;
+        Self::enable_interrupts();
+        let poll_error = poll_fn(|cx| {
+            state.waker.register(cx.waker());
+
+            match Self::check_and_clear_error_flags() {
+                // Unclear why the Err turbofish is necessary here? The compiler didn’t require it in the other
+                // identical poll_fn check_and_clear matches.
+                Err(e) => Poll::Ready(Err::<T, Error>(e)),
+                Ok(_) => Poll::Pending,
+            }
+        });
+
+        // Wait for either the DMA transfer to successfully finish, or an I2C error to occur.
+        match select(dma_transfer, poll_error).await {
+            Either::Second(Err(e)) => Err(e),
+            _ => Ok(()),
+        }?;
+
+        // The I2C transfer itself will take longer than the DMA transfer, so wait for that to finish too.
+
+        // 18.3.8 “Master transmitter: In the interrupt routine after the EOT interrupt, disable DMA
+        // requests then wait for a BTF event before programming the Stop condition.”
+
+        // TODO: If this has to be done “in the interrupt routine after the EOT interrupt”, where to put it?
+        T::regs().cr2().modify(|w| {
+            w.set_dmaen(false);
+        });
+
+        Self::enable_interrupts();
+        poll_fn(|cx| {
+            state.waker.register(cx.waker());
+
+            match Self::check_and_clear_error_flags() {
+                Err(e) => Poll::Ready(Err(e)),
+                Ok(sr1) => {
+                    if sr1.btf() {
+                        if send_stop {
+                            T::regs().cr1().modify(|w| {
+                                w.set_stop(true);
+                            });
+                        }
+
+                        Poll::Ready(Ok(()))
+                    } else {
+                        Poll::Pending
+                    }
+                }
+            }
+        })
+        .await?;
+
+        drop(on_drop);
+
+        // Fallthrough is success
+        Ok(())
     }
 
-    pub async fn read(&mut self, _address: u8, _buffer: &mut [u8]) -> Result<(), Error>
+    pub async fn write(&mut self, address: u8, write: &[u8]) -> Result<(), Error>
+    where
+        TXDMA: crate::i2c::TxDma<T>,
+    {
+        self.write_with_stop(address, write, true).await?;
+
+        // Wait for STOP condition to transmit.
+        Self::enable_interrupts();
+        poll_fn(|cx| {
+            T::state().waker.register(cx.waker());
+            // TODO: error interrupts are enabled here, should we additional check for and return errors?
+            if T::regs().cr1().read().stop() {
+                Poll::Pending
+            } else {
+                Poll::Ready(Ok(()))
+            }
+        })
+        .await?;
+
+        Ok(())
+    }
+
+    pub async fn read(&mut self, address: u8, buffer: &mut [u8]) -> Result<(), Error>
     where
         RXDMA: crate::i2c::RxDma<T>,
     {
-        todo!()
+        let state = T::state();
+        let buffer_len = buffer.len();
+
+        let dma_transfer = unsafe {
+            let regs = T::regs();
+            regs.cr2().modify(|w| {
+                // DMA mode can be enabled for transmission by setting the DMAEN bit in the I2C_CR2 register.
+                w.set_itbufen(false);
+                w.set_dmaen(true);
+            });
+            // Set the I2C_DR register address in the DMA_SxPAR register. The data will be moved to this address from the memory after each TxE event.
+            let src = regs.dr().as_ptr() as *mut u8;
+
+            let ch = &mut self.rx_dma;
+            let request = ch.request();
+            Transfer::new_read(ch, request, src, buffer, Default::default())
+        };
+
+        let on_drop = OnDrop::new(|| {
+            let regs = T::regs();
+            regs.cr2().modify(|w| {
+                w.set_dmaen(false);
+                w.set_iterren(false);
+                w.set_itevten(false);
+            })
+        });
+
+        Self::enable_interrupts();
+
+        // Send a START condition and set ACK bit
+        T::regs().cr1().modify(|reg| {
+            reg.set_start(true);
+            reg.set_ack(true);
+        });
+
+        // Wait until START condition was generated
+        poll_fn(|cx| {
+            state.waker.register(cx.waker());
+
+            match Self::check_and_clear_error_flags() {
+                Err(e) => Poll::Ready(Err(e)),
+                Ok(sr1) => {
+                    if sr1.start() {
+                        Poll::Ready(Ok(()))
+                    } else {
+                        Poll::Pending
+                    }
+                }
+            }
+        })
+        .await?;
+
+        // Also wait until signalled we're master and everything is waiting for us
+        Self::enable_interrupts();
+        poll_fn(|cx| {
+            state.waker.register(cx.waker());
+
+            // blocking read didn’t have a check_and_clear call here, but blocking write did so
+            // I’m adding it here in case that was an oversight.
+            match Self::check_and_clear_error_flags() {
+                Err(e) => Poll::Ready(Err(e)),
+                Ok(_) => {
+                    let sr2 = T::regs().sr2().read();
+                    if !sr2.msl() && !sr2.busy() {
+                        Poll::Pending
+                    } else {
+                        Poll::Ready(Ok(()))
+                    }
+                }
+            }
+        })
+        .await?;
+
+        // Set up current address, we're trying to talk to
+        T::regs().dr().write(|reg| reg.set_dr((address << 1) + 1));
+
+        // Wait for the address to be acknowledged
+
+        Self::enable_interrupts();
+        poll_fn(|cx| {
+            state.waker.register(cx.waker());
+
+            match Self::check_and_clear_error_flags() {
+                Err(e) => Poll::Ready(Err(e)),
+                Ok(sr1) => {
+                    if sr1.addr() {
+                        // 18.3.8: When a single byte must be received: the NACK must be programmed during EV6
+                        // event, i.e. program ACK=0 when ADDR=1, before clearing ADDR flag.
+                        if buffer_len == 1 {
+                            T::regs().cr1().modify(|w| {
+                                w.set_ack(false);
+                            });
+                        }
+                        Poll::Ready(Ok(()))
+                    } else {
+                        Poll::Pending
+                    }
+                }
+            }
+        })
+        .await?;
+
+        // Clear ADDR condition by reading SR2
+        T::regs().sr2().read();
+
+        // 18.3.8: When a single byte must be received: [snip] Then the
+        // user can program the STOP condition either after clearing ADDR flag, or in the
+        // DMA Transfer Complete interrupt routine.
+        if buffer_len == 1 {
+            T::regs().cr1().modify(|w| {
+                w.set_stop(true);
+            });
+        } else {
+            // If, in the I2C_CR2 register, the LAST bit is set, I2C
+            // automatically sends a NACK after the next byte following EOT_1. The user can
+            // generate a Stop condition in the DMA Transfer Complete interrupt routine if enabled.
+            T::regs().cr2().modify(|w| {
+                w.set_last(true);
+            })
+        }
+
+        // Wait for bytes to be received, or an error to occur.
+        Self::enable_interrupts();
+        let poll_error = poll_fn(|cx| {
+            state.waker.register(cx.waker());
+
+            match Self::check_and_clear_error_flags() {
+                Err(e) => Poll::Ready(Err::<T, Error>(e)),
+                _ => Poll::Pending,
+            }
+        });
+
+        match select(dma_transfer, poll_error).await {
+            Either::Second(Err(e)) => Err(e),
+            _ => Ok(()),
+        }?;
+
+        // Wait for the STOP to be sent (STOP bit cleared).
+        Self::enable_interrupts();
+        poll_fn(|cx| {
+            state.waker.register(cx.waker());
+            // TODO: error interrupts are enabled here, should we additional check for and return errors?
+            if T::regs().cr1().read().stop() {
+                Poll::Pending
+            } else {
+                Poll::Ready(Ok(()))
+            }
+        })
+        .await?;
+        drop(on_drop);
+
+        // Fallthrough is success
+        Ok(())
     }
 
-    pub async fn write_read(&mut self, _address: u8, _write: &[u8], _read: &mut [u8]) -> Result<(), Error>
+    pub async fn write_read(&mut self, address: u8, write: &[u8], read: &mut [u8]) -> Result<(), Error>
     where
         RXDMA: crate::i2c::RxDma<T>,
         TXDMA: crate::i2c::TxDma<T>,
     {
-        todo!()
+        self.write_with_stop(address, write, false).await?;
+        self.read(address, read).await
     }
 }
 
diff --git a/examples/stm32f4/src/bin/i2c_async.rs b/examples/stm32f4/src/bin/i2c_async.rs
new file mode 100644
index 000000000..9f59e4d41
--- /dev/null
+++ b/examples/stm32f4/src/bin/i2c_async.rs
@@ -0,0 +1,62 @@
+#![no_std]
+#![no_main]
+#![feature(type_alias_impl_trait)]
+
+// Example originally designed for stm32f411ceu6 reading an A1454 hall effect sensor on I2C1
+// DMA peripherals changed to compile for stm32f429zi, for the CI.
+
+use defmt::*;
+use embassy_executor::Spawner;
+use embassy_stm32::i2c::I2c;
+use embassy_stm32::time::Hertz;
+use embassy_stm32::{bind_interrupts, i2c, peripherals};
+use {defmt_rtt as _, panic_probe as _};
+
+const ADDRESS: u8 = 96;
+
+bind_interrupts!(struct Irqs {
+    I2C1_EV => i2c::EventInterruptHandler<peripherals::I2C1>;
+    I2C1_ER => i2c::ErrorInterruptHandler<peripherals::I2C1>;
+});
+
+#[embassy_executor::main]
+async fn main(_spawner: Spawner) {
+    info!("Hello world!");
+    let p = embassy_stm32::init(Default::default());
+
+    let mut i2c = I2c::new(
+        p.I2C1,
+        p.PB8,
+        p.PB7,
+        Irqs,
+        p.DMA1_CH6,
+        p.DMA1_CH0,
+        Hertz(100_000),
+        Default::default(),
+    );
+
+    loop {
+        let a1454_read_sensor_command = [0x1F];
+        let mut sensor_data_buffer: [u8; 4] = [0, 0, 0, 0];
+
+        match i2c
+            .write_read(ADDRESS, &a1454_read_sensor_command, &mut sensor_data_buffer)
+            .await
+        {
+            Ok(()) => {
+                // Convert 12-bit signed integer into 16-bit signed integer.
+                // Is the 12 bit number negative?
+                if (sensor_data_buffer[2] & 0b00001000) == 0b0001000 {
+                    sensor_data_buffer[2] = sensor_data_buffer[2] | 0b11110000;
+                }
+
+                let mut sensor_value_raw: u16 = sensor_data_buffer[3].into();
+                sensor_value_raw |= (sensor_data_buffer[2] as u16) << 8;
+                let sensor_value: u16 = sensor_value_raw.into();
+                let sensor_value = sensor_value as i16;
+                info!("Data: {}", sensor_value);
+            }
+            Err(e) => error!("I2C Error during read: {:?}", e),
+        }
+    }
+}
diff --git a/examples/stm32f4/src/bin/i2c_comparison.rs b/examples/stm32f4/src/bin/i2c_comparison.rs
new file mode 100644
index 000000000..6d23c0ed8
--- /dev/null
+++ b/examples/stm32f4/src/bin/i2c_comparison.rs
@@ -0,0 +1,135 @@
+#![no_std]
+#![no_main]
+#![feature(type_alias_impl_trait)]
+
+// Example originally designed for stm32f411ceu6 with three A1454 hall effect sensors, connected to I2C1, 2 and 3
+// on the pins referenced in the peripheral definitions.
+// Pins and DMA peripherals changed to compile for stm32f429zi, to work with the CI.
+// MUST be compiled in release mode to see actual performance, otherwise the async transactions take 2x
+// as long to complete as the blocking ones!
+
+use defmt::*;
+use embassy_executor::Spawner;
+use embassy_stm32::i2c::I2c;
+use embassy_stm32::time::Hertz;
+use embassy_stm32::{bind_interrupts, i2c, peripherals};
+use embassy_time::Instant;
+use futures::future::try_join3;
+use {defmt_rtt as _, panic_probe as _};
+
+const ADDRESS: u8 = 96;
+
+bind_interrupts!(struct Irqs {
+    I2C1_EV => i2c::EventInterruptHandler<peripherals::I2C1>;
+    I2C1_ER => i2c::ErrorInterruptHandler<peripherals::I2C1>;
+    I2C2_EV => i2c::EventInterruptHandler<peripherals::I2C2>;
+    I2C2_ER => i2c::ErrorInterruptHandler<peripherals::I2C2>;
+    I2C3_EV => i2c::EventInterruptHandler<peripherals::I2C3>;
+    I2C3_ER => i2c::ErrorInterruptHandler<peripherals::I2C3>;
+});
+
+/// Convert 12-bit signed integer within a 4 byte long buffer into 16-bit signed integer.
+fn a1454_buf_to_i16(buffer: &[u8; 4]) -> i16 {
+    let lower = buffer[3];
+    let mut upper = buffer[2];
+    // Fill in additional 1s if the 12 bit number is negative.
+    if (upper & 0b00001000) == 0b0001000 {
+        upper = upper | 0b11110000;
+    }
+
+    let mut sensor_value_raw: u16 = lower.into();
+    sensor_value_raw |= (upper as u16) << 8;
+    let sensor_value: u16 = sensor_value_raw.into();
+    let sensor_value = sensor_value as i16;
+    sensor_value
+}
+
+#[embassy_executor::main]
+async fn main(_spawner: Spawner) {
+    info!("Setting up peripherals.");
+    let p = embassy_stm32::init(Default::default());
+
+    let mut i2c1 = I2c::new(
+        p.I2C1,
+        p.PB8,
+        p.PB7,
+        Irqs,
+        p.DMA1_CH6,
+        p.DMA1_CH0,
+        Hertz(100_000),
+        Default::default(),
+    );
+
+    let mut i2c2 = I2c::new(
+        p.I2C2,
+        p.PB10,
+        p.PB11,
+        Irqs,
+        p.DMA1_CH7,
+        p.DMA1_CH3,
+        Hertz(100_000),
+        Default::default(),
+    );
+
+    let mut i2c3 = I2c::new(
+        p.I2C3,
+        p.PA8,
+        p.PC9,
+        Irqs,
+        p.DMA1_CH4,
+        p.DMA1_CH2,
+        Hertz(100_000),
+        Default::default(),
+    );
+
+    let a1454_read_sensor_command = [0x1F];
+    let mut i2c1_buffer: [u8; 4] = [0, 0, 0, 0];
+    let mut i2c2_buffer: [u8; 4] = [0, 0, 0, 0];
+    let mut i2c3_buffer: [u8; 4] = [0, 0, 0, 0];
+    loop {
+        // Blocking reads one after the other. Completes in about 2000us.
+        let blocking_read_start_us = Instant::now().as_micros();
+        match i2c1.blocking_write_read(ADDRESS, &a1454_read_sensor_command, &mut i2c1_buffer) {
+            Ok(()) => {}
+            Err(e) => error!("I2C Error: {:?}", e),
+        }
+        match i2c2.blocking_write_read(ADDRESS, &a1454_read_sensor_command, &mut i2c2_buffer) {
+            Ok(()) => {}
+            Err(e) => error!("I2C Error: {:?}", e),
+        }
+        match i2c3.blocking_write_read(ADDRESS, &a1454_read_sensor_command, &mut i2c3_buffer) {
+            Ok(()) => {}
+            Err(e) => error!("I2C Error: {:?}", e),
+        }
+        let blocking_read_total_us = Instant::now().as_micros() - blocking_read_start_us;
+        info!(
+            "Blocking reads completed in {}us: i2c1: {} i2c2: {} i2c3: {}",
+            blocking_read_total_us,
+            a1454_buf_to_i16(&i2c1_buffer),
+            a1454_buf_to_i16(&i2c2_buffer),
+            a1454_buf_to_i16(&i2c3_buffer)
+        );
+
+        // Async reads overlapping. Completes in about 1000us.
+        let async_read_start_us = Instant::now().as_micros();
+
+        let i2c1_result = i2c1.write_read(ADDRESS, &a1454_read_sensor_command, &mut i2c1_buffer);
+        let i2c2_result = i2c2.write_read(ADDRESS, &a1454_read_sensor_command, &mut i2c2_buffer);
+        let i2c3_result = i2c3.write_read(ADDRESS, &a1454_read_sensor_command, &mut i2c3_buffer);
+
+        // Wait for all three transactions to finish, or any one of them to fail.
+        match try_join3(i2c1_result, i2c2_result, i2c3_result).await {
+            Ok(_) => {
+                let async_read_total_us = Instant::now().as_micros() - async_read_start_us;
+                info!(
+                    "Async reads completed in {}us: i2c1: {} i2c2: {} i2c3: {}",
+                    async_read_total_us,
+                    a1454_buf_to_i16(&i2c1_buffer),
+                    a1454_buf_to_i16(&i2c2_buffer),
+                    a1454_buf_to_i16(&i2c3_buffer)
+                );
+            }
+            Err(e) => error!("I2C Error during async write-read: {}", e),
+        };
+    }
+}