diff --git a/embassy-stm32/src/i2c/v1.rs b/embassy-stm32/src/i2c/v1.rs
index cbbc201de..0843f45cf 100644
--- a/embassy-stm32/src/i2c/v1.rs
+++ b/embassy-stm32/src/i2c/v1.rs
@@ -41,6 +41,68 @@ pub unsafe fn on_interrupt<T: Instance>() {
     });
 }
 
+/// Frame type in I2C transaction.
+///
+/// This tells each method what kind of framing to use, to generate a (repeated) start condition (ST
+/// or SR), and/or a stop condition (SP). For read operations, this also controls whether to send an
+/// ACK or NACK after the last byte received.
+///
+/// For write operations, the following options are identical because they differ only in the (N)ACK
+/// treatment relevant for read operations:
+///
+/// - `FirstFrame` and `FirstAndNextFrame`
+/// - `NextFrame` and `LastFrameNoStop`
+///
+/// Abbreviations used below:
+///
+/// - `ST` = start condition
+/// - `SR` = repeated start condition
+/// - `SP` = stop condition
+#[derive(Copy, Clone)]
+enum FrameOptions {
+    /// `[ST/SR]+[NACK]+[SP]` First frame (of this type) in operation and last frame overall in this
+    /// transaction.
+    FirstAndLastFrame,
+    /// `[ST/SR]+[NACK]` First frame of this type in transaction, last frame in a read operation but
+    /// not the last frame overall.
+    FirstFrame,
+    /// `[ST/SR]+[ACK]` First frame of this type in transaction, neither last frame overall nor last
+    /// frame in a read operation.
+    FirstAndNextFrame,
+    /// `[ACK]` Middle frame in a read operation (neither first nor last).
+    NextFrame,
+    /// `[NACK]+[SP]` Last frame overall in this transaction but not the first frame.
+    LastFrame,
+    /// `[NACK]` Last frame in a read operation but not last frame overall in this transaction.
+    LastFrameNoStop,
+}
+
+impl FrameOptions {
+    /// Sends start or repeated start condition before transfer.
+    fn send_start(self) -> bool {
+        match self {
+            Self::FirstAndLastFrame | Self::FirstFrame | Self::FirstAndNextFrame => true,
+            Self::NextFrame | Self::LastFrame | Self::LastFrameNoStop => false,
+        }
+    }
+
+    /// Sends stop condition after transfer.
+    fn send_stop(self) -> bool {
+        match self {
+            Self::FirstAndLastFrame | Self::LastFrame => true,
+            Self::FirstFrame | Self::FirstAndNextFrame | Self::NextFrame | Self::LastFrameNoStop => false,
+        }
+    }
+
+    /// Sends NACK after last byte received, indicating end of read operation.
+    fn send_nack(self) -> bool {
+        match self {
+            Self::FirstAndLastFrame | Self::FirstFrame | Self::LastFrame | Self::LastFrameNoStop => true,
+            Self::FirstAndNextFrame | Self::NextFrame => false,
+        }
+    }
+}
+
 impl<'d, T: Instance, TXDMA, RXDMA> I2c<'d, T, TXDMA, RXDMA> {
     pub(crate) fn init(&mut self, freq: Hertz, _config: Config) {
         T::regs().cr1().modify(|reg| {
@@ -124,46 +186,57 @@ impl<'d, T: Instance, TXDMA, RXDMA> I2c<'d, T, TXDMA, RXDMA> {
         Ok(sr1)
     }
 
-    fn write_bytes(&mut self, addr: u8, bytes: &[u8], timeout: Timeout) -> Result<(), Error> {
-        // Send a START condition
+    fn write_bytes(&mut self, addr: u8, bytes: &[u8], timeout: Timeout, frame: FrameOptions) -> Result<(), Error> {
+        if frame.send_start() {
+            // Send a START condition
 
-        T::regs().cr1().modify(|reg| {
-            reg.set_start(true);
-        });
+            T::regs().cr1().modify(|reg| {
+                reg.set_start(true);
+            });
 
-        // Wait until START condition was generated
-        while !Self::check_and_clear_error_flags()?.start() {
-            timeout.check()?;
+            // Wait until START condition was generated
+            while !Self::check_and_clear_error_flags()?.start() {
+                timeout.check()?;
+            }
+
+            // Also wait until signalled we're master and everything is waiting for us
+            while {
+                Self::check_and_clear_error_flags()?;
+
+                let sr2 = T::regs().sr2().read();
+                !sr2.msl() && !sr2.busy()
+            } {
+                timeout.check()?;
+            }
+
+            // Set up current address, we're trying to talk to
+            T::regs().dr().write(|reg| reg.set_dr(addr << 1));
+
+            // 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() {
+                timeout.check()?;
+            }
+
+            // Clear condition by reading SR2
+            let _ = T::regs().sr2().read();
         }
 
-        // Also wait until signalled we're master and everything is waiting for us
-        while {
-            Self::check_and_clear_error_flags()?;
-
-            let sr2 = T::regs().sr2().read();
-            !sr2.msl() && !sr2.busy()
-        } {
-            timeout.check()?;
-        }
-
-        // Set up current address, we're trying to talk to
-        T::regs().dr().write(|reg| reg.set_dr(addr << 1));
-
-        // 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() {
-            timeout.check()?;
-        }
-
-        // Clear condition by reading SR2
-        let _ = T::regs().sr2().read();
-
         // Send bytes
         for c in bytes {
             self.send_byte(*c, timeout)?;
         }
 
+        if frame.send_stop() {
+            // Send a STOP condition
+            T::regs().cr1().modify(|reg| reg.set_stop(true));
+            // Wait for STOP condition to transmit.
+            while T::regs().cr1().read().stop() {
+                timeout.check()?;
+            }
+        }
+
         // Fallthrough is success
         Ok(())
     }
@@ -205,8 +278,18 @@ impl<'d, T: Instance, TXDMA, RXDMA> I2c<'d, T, TXDMA, RXDMA> {
         Ok(value)
     }
 
-    fn blocking_read_timeout(&mut self, addr: u8, buffer: &mut [u8], timeout: Timeout) -> Result<(), Error> {
-        if let Some((last, buffer)) = buffer.split_last_mut() {
+    fn blocking_read_timeout(
+        &mut self,
+        addr: u8,
+        buffer: &mut [u8],
+        timeout: Timeout,
+        frame: FrameOptions,
+    ) -> Result<(), Error> {
+        let Some((last, buffer)) = buffer.split_last_mut() else {
+            return Err(Error::Overrun);
+        };
+
+        if frame.send_start() {
             // Send a START condition and set ACK bit
             T::regs().cr1().modify(|reg| {
                 reg.set_start(true);
@@ -237,49 +320,45 @@ impl<'d, T: Instance, TXDMA, RXDMA> I2c<'d, T, TXDMA, RXDMA> {
 
             // Clear condition by reading SR2
             let _ = T::regs().sr2().read();
+        }
 
-            // Receive bytes into buffer
-            for c in buffer {
-                *c = self.recv_byte(timeout)?;
-            }
+        // Receive bytes into buffer
+        for c in buffer {
+            *c = self.recv_byte(timeout)?;
+        }
 
-            // Prepare to send NACK then STOP after next byte
-            T::regs().cr1().modify(|reg| {
+        // Prepare to send NACK then STOP after next byte
+        T::regs().cr1().modify(|reg| {
+            if frame.send_nack() {
                 reg.set_ack(false);
+            }
+            if frame.send_stop() {
                 reg.set_stop(true);
-            });
+            }
+        });
 
-            // Receive last byte
-            *last = self.recv_byte(timeout)?;
+        // Receive last byte
+        *last = self.recv_byte(timeout)?;
 
+        if frame.send_stop() {
             // Wait for the STOP to be sent.
             while T::regs().cr1().read().stop() {
                 timeout.check()?;
             }
-
-            // Fallthrough is success
-            Ok(())
-        } else {
-            Err(Error::Overrun)
         }
+
+        // Fallthrough is success
+        Ok(())
     }
 
     /// Blocking read.
     pub fn blocking_read(&mut self, addr: u8, read: &mut [u8]) -> Result<(), Error> {
-        self.blocking_read_timeout(addr, read, self.timeout())
+        self.blocking_read_timeout(addr, read, self.timeout(), FrameOptions::FirstAndLastFrame)
     }
 
     /// Blocking write.
     pub fn blocking_write(&mut self, addr: u8, write: &[u8]) -> Result<(), Error> {
-        let timeout = self.timeout();
-
-        self.write_bytes(addr, write, timeout)?;
-        // Send a STOP condition
-        T::regs().cr1().modify(|reg| reg.set_stop(true));
-        // Wait for STOP condition to transmit.
-        while T::regs().cr1().read().stop() {
-            timeout.check()?;
-        }
+        self.write_bytes(addr, write, self.timeout(), FrameOptions::FirstAndLastFrame)?;
 
         // Fallthrough is success
         Ok(())
@@ -289,8 +368,8 @@ 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> {
         let timeout = self.timeout();
 
-        self.write_bytes(addr, write, timeout)?;
-        self.blocking_read_timeout(addr, read, timeout)?;
+        self.write_bytes(addr, write, timeout, FrameOptions::FirstFrame)?;
+        self.blocking_read_timeout(addr, read, timeout, FrameOptions::FirstAndLastFrame)?;
 
         Ok(())
     }