From 4667c8bb3beefb21ddd7fd3db8930122e3a9417b Mon Sep 17 00:00:00 2001 From: David Montero Crespo Date: Wed, 8 Apr 2026 08:48:14 -0300 Subject: [PATCH] feat: Enhance MPU6050Slave I2C handling with improved WHO_AM_I read tracking; update tests for clarity --- backend/app/services/esp32_i2c_slaves.py | 30 +++++++++++++++++------- backend/test_esp32_i2c_slaves.py | 22 +++++++++++++---- 2 files changed, 38 insertions(+), 14 deletions(-) diff --git a/backend/app/services/esp32_i2c_slaves.py b/backend/app/services/esp32_i2c_slaves.py index 4b6abe21..0beaf5ea 100644 --- a/backend/app/services/esp32_i2c_slaves.py +++ b/backend/app/services/esp32_i2c_slaves.py @@ -43,7 +43,13 @@ class MPU6050Slave: # WRITE, as happens with Adafruit BusIO write-then-read) returns 0x68. self.reg_ptr = 0x75 self.first_byte = True - self._first_read_done = False # True after begin() WHO_AM_I is read + # Adafruit_I2CDevice::begin() fires TWO WHO_AM_I reads before the + # library moves on to actual data reads: + # 1. detected() uses requestFrom as fallback — fires START+READ + # 2. chip_id_register.read() — fires START+READ + # Only after both have returned 0x68 do we switch reg_ptr to 0x3B + # (start of accel/gyro/temp block) for subsequent data transactions. + self._who_am_i_count = 0 # WHO_AM_I self.regs[0x75] = 0x68 @@ -68,13 +74,17 @@ class MPU6050Slave: if op == I2C_START: self.first_byte = True - if self._first_read_done: - # picsimlab does not fire WRITE callbacks for write-then-read - # transactions (endTransmission(false) + requestFrom). After - # begin() has succeeded, reset reg_ptr to the accel/gyro/temp - # block so data reads return the right bytes even without a - # prior WRITE setting the register address. - self.reg_ptr = 0x3B + # picsimlab does not fire WRITE callbacks for write-then-read + # transactions (endTransmission(false) + requestFrom). + # Adafruit_I2CDevice::begin() fires TWO START+READ sequences + # before any data reads: + # 1. detected() → requestFrom fallback → START+READ(WHO_AM_I) + # 2. chip_id_register.read() → START+READ(WHO_AM_I) + # Only after both have returned 0x68 do we switch to data mode. + if self._who_am_i_count >= 2: + self.reg_ptr = 0x3B # sensor data block + else: + self.reg_ptr = 0x75 # WHO_AM_I register return 1 # ACK — device present elif op in _I2C_WRITE_CODES: if self.first_byte: @@ -90,8 +100,10 @@ class MPU6050Slave: return 1 # ACK elif op == I2C_READ: val = self.regs[self.reg_ptr] + # Track WHO_AM_I reads to know when begin() has confirmed device + if self.reg_ptr == 0x75 and val == 0x68: + self._who_am_i_count += 1 self.reg_ptr = (self.reg_ptr + 1) & 0xFF - self._first_read_done = True return val else: # STOP / unknown self.first_byte = True diff --git a/backend/test_esp32_i2c_slaves.py b/backend/test_esp32_i2c_slaves.py index 90947ee5..22e6fb50 100644 --- a/backend/test_esp32_i2c_slaves.py +++ b/backend/test_esp32_i2c_slaves.py @@ -400,16 +400,27 @@ class TestMPU6050Slave(unittest.TestCase): f"Expected WHO_AM_I=0x68 without WRITE, got 0x{result:02x}") def test_data_read_after_begin(self): - """After begin() succeeds, START should reset reg_ptr to 0x3B (accel block).""" + """After begin() succeeds (two WHO_AM_I reads), START resets reg_ptr to 0x3B. + + Adafruit_I2CDevice::begin() fires TWO START+READ sequences before data reads: + 1. detected() requestFrom fallback → START+READ(WHO_AM_I) + 2. chip_id_register.read() → START+READ(WHO_AM_I) + Only after both return 0x68 does the slave switch to data mode. + """ m = MPU6050Slave() - # Simulate begin(): START + READ (no WRITE) → gets WHO_AM_I + # First WHO_AM_I read: detected() requestFrom fallback m.handle_event(I2C_START) - m.handle_event(I2C_READ) # _first_read_done = True - # Next transaction: START should reset reg_ptr to 0x3B + r1 = m.handle_event(I2C_READ) + self.assertEqual(r1, 0x68, "First WHO_AM_I read must return 0x68") + # Second WHO_AM_I read: actual chip_id check — count reaches 2 + m.handle_event(I2C_START) + r2 = m.handle_event(I2C_READ) + self.assertEqual(r2, 0x68, "Second WHO_AM_I read must return 0x68") + # Now _who_am_i_count=2 → next START switches to data mode m.handle_event(I2C_START) first_accel_byte = m.handle_event(I2C_READ) self.assertEqual(first_accel_byte, m.regs[0x3B], - "After begin(), START should reset reg_ptr to 0x3B (accel block)") + "After two WHO_AM_I reads, START should reset reg_ptr to 0x3B (accel block)") def test_accel_z_default_1g(self): """ACCEL_Z should default to +1g = 0x4000 (MSB=0x40, LSB=0x00).""" @@ -454,6 +465,7 @@ if __name__ == '__main__': TestDS1307Slave, TestDS3231Slave, TestI2CWriteSink, + TestMPU6050Slave, ]: suite.addTests(loader.loadTestsFromTestCase(cls))