Your "MPU6050" Module Is Probably an MPU6500: WHO_AM_I, AD0, and a Self-Healing I²C Driver

A cheap IMU board labelled MPU6050 returned WHO_AM_I = 0x70, and a floating AD0 pin made it disappear from the bus entirely. How to write a driver that copes with both, plus the streaming design that gets 44 Hz attitude over UDP.

I bought a module listed as an MPU6050 for the robot’s attitude sensing. The first read of the WHO_AM_I register (0x75) returned 0x70. An MPU6050 returns 0x68. A value of 0x70 is an MPU6500.

This is common. The MPU6050 is long discontinued at InvenSense/TDK, the MPU6500 is register-compatible for the basics, and many low-cost boards carry whichever die is cheapest that month while keeping the old silkscreen. If your driver checks WHO_AM_I == 0x68 and gives up, it will reject a perfectly good sensor.

Accept the family, not one part

Part WHO_AM_I Notes
MPU6050 0x68 original
MPU6500 0x70 same core registers, different temperature formula
MPU9250 0x71 6500 + magnetometer
MPU9255 0x73

The driver now accepts any of these and logs which one it found. The temperature conversion is the only thing that differs for basic use:

MPU6050: T = raw / 340.0 + 36.53
MPU6500: T = raw / 333.87 + 21.0

Gyro and accelerometer scaling are identical for the same range setting.

The AD0 pin: “no device at 0x68 or 0x69”

The second problem was worse. Some boots the sensor was at 0x68, some boots at 0x69, and often it was at neither — every probe returned NACK.

The address is set by the AD0 pin: low → 0x68, high → 0x69. On this module AD0 had no pull-down on the board, and I had left it unconnected. The chip samples AD0 continuously, not just at power-up, so a floating pin gave a random, changing address.

The fix is one wire: AD0 to GND. Or to 3.3 V if you need 0x69 because something else is at 0x68.

If your symptom is “the IMU is sometimes there and sometimes not”, check AD0 before anything else.

ESP32-P4 GPIO8 · SDA GPIO7 · SCL SDA SCL 3V3 · 4.7 kΩ OLED 0x3C ES8311 0x18 "MPU6050" = MPU6500 WHO_AM_I 0x70 · addr 0x68 AD0 → GND (0x68) AD0 floating → address flips 0x68 ↔ 0x69 → NACK on both when probed Bus scan still sees 0x3C → bus is fine, module is the problem
The shared bus. A bus scan that still finds the OLED at 0x3C tells you the wiring is fine and the IMU module is the problem.

Make the driver recover on its own

Because the sensor sits on the same I²C bus as the OLED display and the audio codec (GPIO7/8 on this board), I did not want a missing IMU to block boot or require a reflash after fixing a wire. The driver does this:

  1. Probe 0x68, then 0x69, reading WHO_AM_I.
  2. On failure, scan the entire bus (0x08-0x77) and log every address that ACKs.
  3. Retry every 10 seconds, forever.

The bus scan is the useful part. When the IMU is missing, seeing the OLED at 0x3C in the scan tells you the bus itself is fine and the problem is the module. Seeing nothing at all tells you to look at SDA/SCL or pull-ups.

The retry paid off immediately: after grounding AD0 with the robot still powered, the next probe found the sensor and the stream started without a reboot.

Sampling and filtering

Yaw is gyro integration only (no magnetometer on the MPU6500), so it drifts. The app treats yaw as a relative heading and offers a re-zero button. If you need absolute heading, you need an MPU9250 or an external compass.

Axis mapping and sign are two constants (kAxisMap, kAxisSign) so mounting the board in a different orientation on the chassis is a two-line change.

Streaming to the app

Attitude goes out over the same UDP socket as the joystick channel, as a subscription:

{"type": "imu", "action": "start", "rate_hz": 50, "k": "..."}

The firmware then pushes JSON packets (angles, angular rates, acceleration, temperature) at the requested rate, 5-100 Hz. Subscriptions expire after 5 seconds; the app renews every 2 seconds. That way a crashed or backgrounded app stops the stream without any explicit close.

Requested 50 Hz, measured 44.4 Hz delivered — JSON serialisation and the socket path cost about 10 %. Attitude on the phone screen tracks the physical robot with no visible lag.

A slow snapshot (imu: [pitch, roll, yaw]) also rides along in the MQTT heartbeat for dashboards that do not need real-time data.

Checklist for the next person