diff --git a/.cargo/config.toml b/.cargo/config.toml
index 6c3c3f8e780200345c4278a702b30280d7cff159..971c54803626c62bbb93ae6a47d043394d8c3dc9 100644
--- a/.cargo/config.toml
+++ b/.cargo/config.toml
@@ -8,3 +8,6 @@ rustflags = [
 
 [build]
 target = "thumbv7m-none-eabi"
+
+[env]
+DEFMT_LOG = "info"
diff --git a/Cargo.lock b/Cargo.lock
index 10b48f3ddab3b0caefbc335da7956388622c7965..f5040fab89ca2b83e1f24aba8edc340eda5abbd4 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -145,6 +145,7 @@ dependencies = [
  "embassy-executor",
  "embassy-stm32",
  "embassy-time",
+ "i2c2-slave",
  "panic-probe",
 ]
 
@@ -594,10 +595,6 @@ dependencies = [
  "defmt",
 ]
 
-[[package]]
-name = "pid"
-version = "0.1.0"
-
 [[package]]
 name = "pin-project-lite"
 version = "0.2.13"
diff --git a/README.org b/README.org
index a678f37005958a456b9dadc46ccba6ba874f1dbc..b0cd2a0d592671a9ee2326f3e2e02f2c368fd1b6 100644
--- a/README.org
+++ b/README.org
@@ -48,7 +48,7 @@ the TB6612FNG motor driver.
 
 Values are exchanged in little endian format.
 
-** 0x0F Who am I? (R)
+** [IMPLEMENTED] 0x0F Who am I? (R)
 
 - The I²C address
 
@@ -56,7 +56,7 @@ Values are exchanged in little endian format.
 
 - Frequency in kHz, from 1 to 100 (default: 1)
 
-** 0x11 Max motor percentage (R/W)
+** [IMPLEMENTED] 0x11 Max motor percentage (R/W)
 
 - Limit the maximum speed, between 1% and 100% of the duty cycle
   (default: 100)
@@ -81,16 +81,16 @@ Values are exchanged in little endian format.
 
 - Number of tenths of seconds in before the motor shut down
 
-** 0x30-0x31 Motor speed (R/W)
+** [IMPLEMENTED] 0x30-0x31 Motor speed (R/W)
 
 - Left and right motor speed, between -127 and 127 each, max speed is
   ±127. -128 means standby. ±127 corresponds to the value limited by the
   max motor percentage.
 - If written while in automatic mode, the robot switches to manual mode
 
-** 0x32-0x35 Encoder ticks (R)
+** [IMPLEMENTED] 0x32-0x35 Encoder ticks (R)
 
-- Left and right encoder counters, 2 complement, 2 bytes each
+- Left and right encoder counters since last time, 2 complement, 2 bytes each
 
 ** 0x36 Status (R)
 
diff --git a/controller/Cargo.toml b/controller/Cargo.toml
index 0b6750e78ce4115a516c42809ef19f1f057cc22f..cee26e164b348095bba0761a01ab0e046ac19292 100644
--- a/controller/Cargo.toml
+++ b/controller/Cargo.toml
@@ -12,4 +12,5 @@ defmt-rtt = "0.4.0"
 embassy-executor = { git = "https://github.com/embassy-rs/embassy", features = ["arch-cortex-m", "executor-thread", "defmt", "integrated-timers"] }
 embassy-stm32 = { git = "https://github.com/embassy-rs/embassy", features = ["defmt", "stm32f103c8", "unstable-pac", "time-driver-tim4", "memory-x"] }
 embassy-time = { git = "https://github.com/embassy-rs/embassy", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] }
+i2c2-slave = { path = "../i2c2-slave" }
 panic-probe = { version = "0.3.1", features = ["print-defmt"] }
diff --git a/controller/python/controller.py b/controller/python/controller.py
new file mode 100644
index 0000000000000000000000000000000000000000..baa0fd52815a6c00b83ed4b8901c259c18e52a69
--- /dev/null
+++ b/controller/python/controller.py
@@ -0,0 +1,58 @@
+import smbus
+import struct
+from typing import Optional
+
+class Controller:
+
+    I2C_BUS = 1
+    I2C_ADDR = 0x57
+
+    WHO_AM_I = 0x0f
+    MAX_MOTOR_PERCENTAGE = 0x11
+    MOTOR_SPEED = 0x30
+    ENCODER_TICKS = 0x32
+
+    def __init__(self):
+        self.i2c = smbus.SMBus(1)
+
+    def who_am_i(self) -> int:
+        """Check that the motors controller board is present. This should return
+        the same value as Controller.I2C_ADDR."""
+        return self.i2c.read_i2c_block_data(self.I2C_ADDR, self.WHO_AM_I, 1)[0]
+
+    def set_max_percentage(self, percent: int):
+        """Set the maximum percentage of power which will be used (between 1 and 100)."""
+        if percent <= 0 or percent > 100:
+            raise ValueError
+        self.i2c.write_i2c_block_data(self.I2C_ADDR, self.MAX_MOTOR_PERCENTAGE, [percent])
+        
+    def set_motor_speed(self, left: Optional[int], right: Optional[int]):
+        """Set the motor speed between -127 and 127. None means not to change the
+        motor value. Using None for both motors will put the controller board in standby
+        mode and motors will stop."""
+        def convert(v, arg):
+            if v is None:
+                return -128
+            if not isinstance(v, int) or v < -127 or v > 127:
+                raise ValueError(f"{arg} must be an integer between -127 and 127 or None")
+            return v
+        self.i2c.write_i2c_block_data(self.I2C_ADDR, self.MOTOR_SPEED,
+                                      list(struct.pack('bb', convert(left, "left"), convert(right, "right"))))
+
+    def set_left_motor_speed(self, speed: int):
+        """Set the left motor speed between -127 and 127."""
+        self.set_motor_speed(speed, None)
+
+    def set_right_motor_speed(self, speed: int):
+        """Set the right motor speed between -127 and 127."""
+        self.set_motor_speed(None, speed)
+
+    def standby(self):
+        """Stop the motors by putting the controller board in standby mode."""
+        self.set_motor_speed(None, None)
+        
+    def get_encoder_ticks(self) -> (int, int):
+        """Retrieve the encoder ticks since the last time it was queried. The ticks must be
+        retrieved before they overflow a 2 byte signed integer (-32768..32767) or the result will
+        make no sense. Return a pair with left and right data."""
+        return struct.unpack('hh', bytes(self.i2c.read_i2c_block_data(self.I2C_ADDR, self.ENCODER_TICKS, 4)))
diff --git a/controller/src/logic.rs b/controller/src/logic.rs
new file mode 100644
index 0000000000000000000000000000000000000000..c6d3e7b52f34beb03e026e438adc5a7fda690c2e
--- /dev/null
+++ b/controller/src/logic.rs
@@ -0,0 +1,114 @@
+use crate::{
+    encoders::Encoders,
+    tb6612fng::{Movement, Tb6612fng},
+};
+use i2c2_slave::I2C2;
+
+struct State {
+    i2c: I2C2,
+    motors: Tb6612fng<'static>,
+    encoders: Encoders<'static>,
+    max_motor_percentage: u8,
+    motor_speed: [u8; 2],
+    standby: bool,
+}
+
+impl State {
+    fn new(i2c2: I2C2, motors: Tb6612fng<'static>, encoders: Encoders<'static>) -> Self {
+        Self {
+            i2c: i2c2,
+            motors,
+            encoders,
+            max_motor_percentage: 100,
+            motor_speed: [0, 0],
+            standby: true,
+        }
+    }
+}
+
+#[derive(Debug)]
+enum Error {
+    #[allow(unused)]
+    I2c(i2c2_slave::Error),
+    #[allow(unused)]
+    UnknownRegister(u8),
+}
+
+impl From<i2c2_slave::Error> for Error {
+    fn from(value: i2c2_slave::Error) -> Self {
+        Error::I2c(value)
+    }
+}
+
+type Result<T, E = Error> = core::result::Result<T, E>;
+
+#[embassy_executor::task]
+pub async fn main_loop(i2c2: I2C2, mut motors: Tb6612fng<'static>, encoders: Encoders<'static>) {
+    motors.standby_enter();
+    let mut state = State::new(i2c2, motors, encoders);
+    let mut buffer = [0; 8];
+    loop {
+        match state.i2c.receive(&mut buffer).await {
+            Ok(n) => {
+                if let Err(e) = handle_command(&buffer[..n], &mut state) {
+                    defmt::warn!("Error processing command: {:?}", defmt::Debug2Format(&e));
+                }
+            }
+            Err(e) => {
+                defmt::warn!("I2C receive error: {:?}", defmt::Debug2Format(&e));
+            }
+        }
+    }
+}
+
+const WHO_AM_I: u8 = 0x0f;
+const MAX_MOTOR_PERCENTAGE: u8 = 0x11;
+const MOTOR_SPEED: u8 = 0x30;
+const ENCODER_TICKS: u8 = 0x32;
+
+fn handle_command(command: &[u8], state: &mut State) -> Result<()> {
+    defmt::trace!("Processing command {:?}", command);
+    match command {
+        [WHO_AM_I, ..] => state.i2c.send(&[0x57])?,
+        &[MAX_MOTOR_PERCENTAGE, p, ..] => {
+            if (1..=100).contains(&p) {
+                state.max_motor_percentage = p;
+            } else {
+                defmt::warn!("Incorrect max percentage {}", p);
+            }
+        }
+        [MAX_MOTOR_PERCENTAGE] => state.i2c.send(&[state.max_motor_percentage])?,
+        [MOTOR_SPEED, ms @ ..] if ms.len() >= 2 => {
+            state.motor_speed = [ms[0], ms[1]];
+            if ms[0] == 0x80 && ms[1] == 0x80 {
+                state.motors.standby_enter();
+                state.standby = true;
+            } else {
+                if state.standby {
+                    state.motors.standby_leave();
+                    state.standby = false;
+                }
+                let max_pwm = state.motors.max_pwm() / 100;
+                if ms[0] != 0x80 {
+                    state.motors.move_left(Movement::Advance(
+                        ms[0] as i8 as i32 * state.max_motor_percentage as i32 * max_pwm / 100,
+                    ));
+                }
+                if ms[1] != 0x80 {
+                    state.motors.move_right(Movement::Advance(
+                        ms[1] as i8 as i32 * state.max_motor_percentage as i32 * max_pwm / 100,
+                    ));
+                }
+            }
+        }
+        [MOTOR_SPEED, ..] => state.i2c.send(&state.motor_speed)?,
+        [ENCODER_TICKS, ..] => {
+            let (left, right) = state.encoders.ticks();
+            let (left, right) = (left.to_le_bytes(), right.to_le_bytes());
+            state.i2c.send(&[left[0], left[1], right[0], right[1]])?;
+        }
+        &[idx, ..] => return Err(Error::UnknownRegister(idx)),
+        [] => (),
+    }
+    Ok(())
+}
diff --git a/controller/src/main.rs b/controller/src/main.rs
index f67e326bd7da3d0e6105f7b41c65a50d7a921f6f..6b20a278e688a9ccd02a805d48cc50b2fe6bd286 100644
--- a/controller/src/main.rs
+++ b/controller/src/main.rs
@@ -1,31 +1,23 @@
 #![no_std]
 #![no_main]
 
-use crate::{
-    encoders::Encoders,
-    tb6612fng::{Movement, Tb6612fng},
-};
+use crate::{encoders::Encoders, tb6612fng::Tb6612fng};
 use defmt_rtt as _;
 use embassy_executor::Spawner;
 use embassy_stm32::{
-    bind_interrupts,
-    dma::NoDma,
     gpio::{Level, Output, Speed},
-    peripherals::{PB15, USART2},
+    interrupt::Priority,
+    peripherals::PB15,
     time::{khz, mhz},
-    usart::{self, Uart},
     Config,
 };
 use embassy_time::Timer;
 use panic_probe as _;
 
 mod encoders;
+mod logic;
 mod tb6612fng;
 
-bind_interrupts!(struct Irqs {
-    USART2 => usart::InterruptHandler<USART2>;
-});
-
 #[embassy_executor::main]
 async fn main(spawner: Spawner) {
     let mut config = Config::default();
@@ -49,22 +41,15 @@ async fn main(spawner: Spawner) {
         p.TIM1,
         khz(100),
     );
-    spawner.spawn(calibrate_motors(motors)).unwrap();
-
-    let mut serial = Uart::new(
-        p.USART2,
-        p.PA3,
-        p.PA2,
-        Irqs,
-        p.DMA1_CH7,
-        NoDma,
-        Default::default(),
-    )
-    .unwrap();
-    serial.write(b"Hello, world\r\n").await.unwrap();
+    // spawner.spawn(calibrate_motors(motors)).unwrap();
 
     let encoders = Encoders::new(p.PA0, p.PA1, p.PA6, p.PA7, p.TIM2, p.TIM3);
-    spawner.spawn(debug_encoders(encoders)).unwrap();
+    // spawner.spawn(debug_encoders(encoders)).unwrap();
+
+    let i2c2 = i2c2_slave::I2C2::new(p.I2C2, p.PB10, p.PB11, 0x57, Priority::P4, Priority::P3);
+    spawner
+        .spawn(logic::main_loop(i2c2, motors, encoders))
+        .unwrap();
 }
 
 #[embassy_executor::task]
@@ -78,48 +63,3 @@ async fn blink(pin: PB15) {
         Timer::after_secs(1).await;
     }
 }
-
-#[embassy_executor::task]
-async fn calibrate_motors(mut motors: Tb6612fng<'static>) {
-    let max = motors.max_pwm();
-    defmt::info!("Will start motors in 1 second – max duty cycle is {}", max);
-    Timer::after_secs(1).await;
-    motors.move_both(Movement::Stop, Movement::Stop);
-    motors.standby_leave();
-    let speed = max / 2;
-    let (forward, backward) = (Movement::Advance(speed), Movement::Advance(-speed));
-    defmt::info!("Left motor, forward 10%");
-    motors.move_both(forward, Movement::Stop);
-    Timer::after_secs(1).await;
-    defmt::info!("Left motor, backward 10%");
-    motors.move_both(backward, Movement::Stop);
-    Timer::after_secs(1).await;
-    defmt::info!("Right motor, forward 10%");
-    motors.move_both(Movement::Stop, forward);
-    Timer::after_secs(1).await;
-    defmt::info!("Right motor, backward 10%");
-    motors.move_both(Movement::Stop, backward);
-    Timer::after_secs(1).await;
-    for i in 0..100 {
-        let val = max * i / 100;
-        motors.move_both(Movement::Advance(val), Movement::Advance(val));
-        Timer::after_millis(30).await;
-    }
-    motors.move_both(Movement::Brake, Movement::Brake);
-    defmt::info!("Returning to standby mode");
-    motors.standby_enter();
-}
-
-#[embassy_executor::task]
-async fn debug_encoders(mut encoders: Encoders<'static>) {
-    loop {
-        let (left, right) = encoders.read();
-        defmt::info!(
-            "left = {:?}, right = {:?}",
-            defmt::Debug2Format(&left),
-            defmt::Debug2Format(&right)
-        );
-        defmt::info!("ticks = {:?}", encoders.ticks());
-        Timer::after_millis(100).await;
-    }
-}
diff --git a/controller/src/tb6612fng.rs b/controller/src/tb6612fng.rs
index 1661e5ebf435ee3e622d26d52f1839c643261699..1b23882856cf8c643e7065107e4dae3b1435e9a1 100644
--- a/controller/src/tb6612fng.rs
+++ b/controller/src/tb6612fng.rs
@@ -22,6 +22,7 @@ pub struct Tb6612fng<'a> {
 #[derive(Clone, Copy)]
 pub enum Movement {
     Advance(i32),
+    #[allow(unused)]
     Brake,
     Stop,
 }
diff --git a/i2c-bootloader/src/main.rs b/i2c-bootloader/src/main.rs
index b209a13597152f898b4081f0d9fef87c62169ae6..e8ef00bccb7cd32f56f08b73be529f98b1365d22 100644
--- a/i2c-bootloader/src/main.rs
+++ b/i2c-bootloader/src/main.rs
@@ -25,7 +25,7 @@ async fn main(spawner: Spawner) {
 
     spawner.spawn(blink(p.PB15)).unwrap();
 
-    let mut i2c2 = i2c2_slave::I2C2::new(p.I2C2, p.PB10, p.PB11, 0x57, Priority::P3);
+    let mut i2c2 = i2c2_slave::I2C2::new(p.I2C2, p.PB10, p.PB11, 0x57, Priority::P4, Priority::P3);
     loop {
         let mut buffer = [0; 8];
         match i2c2.receive(&mut buffer).await {
diff --git a/i2c2-slave/src/lib.rs b/i2c2-slave/src/lib.rs
index 34485029a5499c76cca7428c0298e7f4fbd6e05b..2b280b43d087976c8edde1900aac67c6383b5669 100644
--- a/i2c2-slave/src/lib.rs
+++ b/i2c2-slave/src/lib.rs
@@ -60,7 +60,15 @@ pub struct I2C2 {
 
 impl I2C2 {
     #[must_use]
-    pub fn new(i2c2: peripherals::I2C2, pb10: PB10, pb11: PB11, addr: u16, prio: Priority) -> Self {
+    pub fn new(
+        i2c2: peripherals::I2C2,
+        pb10: PB10,
+        pb11: PB11,
+        addr: u16,
+        prio: Priority,
+        prio_urg: Priority,
+    ) -> Self {
+        assert!(prio_urg < prio);
         // Set pins in AF mode
         pb10.set_as_af(SclPin::af_num(&pb10), AFType::OutputOpenDrain);
         pb11.set_as_af(i2c::SdaPin::af_num(&pb11), AFType::OutputOpenDrain);
@@ -87,7 +95,7 @@ impl I2C2 {
             w.set_ack(true);
         });
         interrupt::I2C2_EV.set_priority(prio);
-        interrupt::I2C2_ER.set_priority(prio);
+        interrupt::I2C2_ER.set_priority(prio_urg);
         unsafe {
             interrupt::I2C2_EV.enable();
             interrupt::I2C2_ER.enable();
@@ -108,7 +116,11 @@ impl I2C2 {
         }
         for extra in 0..usize::MAX {
             if matches!(RX_QUEUE.receive().await, I2cRxEvent::Stop) {
-                return Err(Error::ExtraBytes(extra));
+                if extra > 0 {
+                    return Err(Error::ExtraBytes(extra));
+                } else {
+                    return Ok(buffer.len());
+                }
             }
         }
         unreachable!();
@@ -116,29 +128,21 @@ impl I2C2 {
 
     pub fn send(&mut self, data: &[u8]) -> Result<()> {
         defmt::trace!("Enqueuing {:?}", data);
-        // Enqueue data to send next
+        // Enqueue the data to send next
         for &b in data {
             if TX_QUEUE.try_send(b).is_err() {
                 return Err(Error::FifoFull);
             }
         }
-        // If the controller is already waiting for data (and
-        // stretching the clock down), send it right away by
-        // trigerring an interrupt.
-        if matches!(
-            State::get_state(),
-            State::ReadInProgress {
-                waiting_for_data: true
-            }
-        ) {
-            pac::I2C2.cr2().modify(|w| {
-                w.set_itbufen(true);
-            });
-        }
+        pac::I2C2.cr2().modify(|w| {
+            defmt::trace!("Setting itbufen to start receiving TXE events");
+            w.set_itbufen(true);
+        });
         Ok(())
     }
 
-    fn empty_tx_queue() {
+    fn clear_tx_queue() {
+        defmt::trace!("Emptying TX queue");
         while TX_QUEUE.try_receive().is_ok() {}
     }
 }
@@ -146,12 +150,45 @@ impl I2C2 {
 #[interrupt]
 unsafe fn I2C2_EV() {
     let sr1 = pac::I2C2.sr1().read();
-    defmt::info!(
+    defmt::trace!(
         "I2C2_EV, sr1 = {:#06x}, cr1 = {:#06x}, cr2 = {:#06x}",
         sr1.0,
         pac::I2C2.cr1().read().0,
         pac::I2C2.cr2().read().0
     );
+    if sr1.stopf() {
+        defmt::trace!(
+            "I2C2_EV STOPF while state is {:?}",
+            defmt::Debug2Format(&State::get_state())
+        );
+        // Clear stopf by writing to cr1
+        pac::I2C2.cr1().modify(|_w| ());
+        // Opt out of the TXE/RXNE interrupt
+        pac::I2C2.cr2().modify(|w| {
+            w.set_itbufen(false);
+        });
+        // Do a clearing sequence as recommended in the reference manual
+        // by reading and writing dr
+        pac::I2C2.dr().modify(|_w| ());
+        // If receiving, indicate the end of data
+        let previous_state = State::get_state();
+        if matches!(previous_state, State::WriteInProgress) {
+            if RX_QUEUE.try_send(I2cRxEvent::Stop).is_err() {
+                defmt::error!("Unable to send stop event to the buffer");
+            }
+            State::set_state(State::Idle { after_write: true });
+        } else {
+            State::set_state(State::Idle { after_write: false });
+        }
+    }
+    if sr1.rxne() {
+        defmt::trace!("I2C2_EV RXNE");
+        let b = pac::I2C2.dr().read().0 as u8;
+        defmt::trace!("Received byte {:#04x}", &b);
+        if RX_QUEUE.try_send(I2cRxEvent::Byte(b)).is_err() {
+            defmt::error!("I2C byte has overfilled the buffer");
+        }
+    }
     if sr1.addr() {
         defmt::trace!("I2C2_EV addr");
         // Addr event is cleared by reading sr2
@@ -168,7 +205,7 @@ unsafe fn I2C2_EV() {
             let outgoing = if !previous_state.is_write() {
                 // Another read transaction happened just before this one,
                 // send 0 to avoid locking the bus.
-                defmt::trace!("Read transaction without write, sending 0 to prevent bus locking");
+                defmt::warn!("Read transaction without write, sending 0 to prevent bus locking");
                 Some(0)
             } else if let Ok(b) = TX_QUEUE.try_receive() {
                 // If bytes are ready to send already, send the first
@@ -177,7 +214,7 @@ unsafe fn I2C2_EV() {
                 defmt::trace!("Sending already ready byte {:#04x}", b);
                 Some(b)
             } else {
-                // Otherwise, indicate that data is waiting.
+                // Otherwise, indicate that data is waiting and do not generate TXE events.
                 defmt::trace!("No data ready, stretching clock");
                 None
             };
@@ -202,7 +239,7 @@ unsafe fn I2C2_EV() {
             defmt::trace!("Current state of CR2: {:#06x}", pac::I2C2.cr2().read().0);
             // Empty a previously filled TX queue in case a read request
             // arrives right after.
-            I2C2::empty_tx_queue();
+            I2C2::clear_tx_queue();
             defmt::trace!("Waiting for RXNE or STOPF event");
             State::WriteInProgress
         };
@@ -215,48 +252,25 @@ unsafe fn I2C2_EV() {
                 }
             ))
         });
-    } else if sr1.rxne() {
-        defmt::trace!("I2C2_EV RXNE");
-        let b = pac::I2C2.dr().read().0 as u8;
-        defmt::trace!("Received byte {:#04x}", &b);
-        if RX_QUEUE.try_send(I2cRxEvent::Byte(b)).is_err() {
-            defmt::error!("I2C byte has overfilled the buffer");
-        }
-    } else if sr1.txe() {
+    }
+    if sr1.txe() && pac::I2C2.cr2().read().itbufen() {
         defmt::trace!("I2C2_EV TXE");
         if let Ok(b) = TX_QUEUE.try_receive() {
             defmt::trace!("Sending byte {:#04x}", b);
             pac::I2C2.dr().write(|w| w.set_dr(b));
         } else {
-            defmt::trace!("Nothing to send, storing 0 in DR to bus locking");
-            pac::I2C2.dr().write(|w| w.set_dr(0));
-        }
-    } else if sr1.stopf() {
-        defmt::trace!(
-            "I2C2_EV STOPF while state is {:?}",
-            defmt::Debug2Format(&State::get_state())
-        );
-        // Clear stopf by writing to cr1
-        pac::I2C2.cr1().modify(|_w| ());
-        // Opt out of the TXE/RXNE interrupt
-        pac::I2C2.cr2().modify(|w| {
-            w.set_itbufen(false);
-        });
-        // Do a clearing sequence as recommended in the reference manual
-        // by reading and writing dr
-        pac::I2C2.dr().modify(|_w| ());
-        // If receiving, indicate the end of data
-        let previous_state = State::get_state();
-        if matches!(previous_state, State::WriteInProgress) {
-            if RX_QUEUE.try_send(I2cRxEvent::Stop).is_err() {
-                defmt::error!("Unable to send stop event to the buffer");
-            }
-            State::set_state(State::Idle { after_write: true });
-        } else {
-            State::set_state(State::Idle { after_write: false });
+            defmt::trace!("Inhibiting TXE interrupts and noting that we are waiting");
+            pac::I2C2.cr2().modify(|w| w.set_itbufen(false));
+            State::set_state(State::ReadInProgress {
+                waiting_for_data: true,
+            });
+            // defmt::trace!("Nothing to send, storing 0 in DR to avoid bus locking");
+            // pac::I2C2.dr().write(|w| w.set_dr(0));
         }
-    } else {
-        defmt::error!("I2C2_EV interrupt without cause");
+    } else if sr1.txe() && sr1.btf() {
+        defmt::trace!("I2C2_EV TXE + BTF");
+        defmt::warn!("Sending 0 to avoid locking the bus");
+        pac::I2C2.dr().write(|w| w.set_dr(0));
     }
 }