MPU-6050 Finally Working — Clone Chip, Raw I2C
Cleared the VCC-GND solder bridge with the new iron. Then hit a second problem: the clone chip returns 0x70 on WHO_AM_I instead of 0x68, and the Adafruit library rejects it. Fixed by talking to the registers directly.
The Backstory
The MPU-6050 has been sitting shelved since the first soldering session, when a bridge between VCC and GND made it unsafe to power. The WS2812B ring session yesterday was partly a warm-up — get one clean solder under the belt before coming back to the gyro.
Two problems to solve today: the bridge, and whatever state the chip is actually in.
Clearing the Bridge
The new iron made this straightforward. Set to 330°C, pressed solder wick against the bridge, held it there for a couple of seconds. The braid pulled the excess solder cleanly. Lifted the wick, checked visually — the two pads were separate, both intact.
The module was never powered in the shorted state, so the chip itself was never at risk.
First Boot — Library Rejection
Wired the same as before: VCC → 3.3V, GND → GND, SCL → A5, SDA → A4.
Installed the Adafruit MPU6050 library and tried mpu.begin(). It returned false. No connection.
Checked the I2C address with a scanner sketch — the chip was responding at 0x68, exactly where it should be. The bus was fine.
The problem turned out to be the WHO_AM_I register. The MPU-6050 has a register at address 0x75 that is supposed to return 0x68 — a fixed hardware identifier baked into the chip. The Adafruit library reads this register during begin() and refuses to proceed if the value doesn’t match.
This chip returned 0x70.
It’s a clone. The InvenSense-compatible clones that come in most cheap sensor kits from Indian electronics suppliers use a slightly different die that answers 0x70 instead of 0x68 on WHO_AM_I. The chip functions identically — same register map, same protocol, same behavior — but the library treats the mismatched ID as a connection failure and stops.
Fix — Raw I2C
The workaround: skip the library entirely and talk to the chip directly through the Wire library.
The MPU-6050 register map is fully documented. Everything the library does — wake the chip, configure ranges, read sensor data — is just I2C reads and writes to specific registers. There’s no reason to go through a library that won’t initialize.
#include <Wire.h>
#define MPU_ADDR 0x68
void writeReg(uint8_t reg, uint8_t val) {
Wire.beginTransmission(MPU_ADDR);
Wire.write(reg);
Wire.write(val);
Wire.endTransmission();
}
uint8_t readReg(uint8_t reg) {
Wire.beginTransmission(MPU_ADDR);
Wire.write(reg);
Wire.endTransmission(false);
Wire.requestFrom(MPU_ADDR, 1);
return Wire.read();
}
void setup() {
Serial.begin(9600);
Wire.begin();
uint8_t whoami = readReg(0x75);
Serial.print("WHO_AM_I: 0x");
Serial.println(whoami, HEX); // prints 0x70 on the clone
// PWR_MGMT_1 (0x6B): write 0x00 to clear sleep bit and wake the chip
writeReg(0x6B, 0x00);
delay(100);
Serial.println("MPU-6050 ready");
}
void loop() {
// Accel data: registers 0x3B–0x40 (ACCEL_XOUT_H through ACCEL_ZOUT_L)
Wire.beginTransmission(MPU_ADDR);
Wire.write(0x3B);
Wire.endTransmission(false);
Wire.requestFrom(MPU_ADDR, 6);
int16_t ax = (Wire.read() << 8) | Wire.read();
int16_t ay = (Wire.read() << 8) | Wire.read();
int16_t az = (Wire.read() << 8) | Wire.read();
// Gyro data: registers 0x43–0x48 (GYRO_XOUT_H through GYRO_ZOUT_L)
Wire.beginTransmission(MPU_ADDR);
Wire.write(0x43);
Wire.endTransmission(false);
Wire.requestFrom(MPU_ADDR, 6);
int16_t gx = (Wire.read() << 8) | Wire.read();
int16_t gy = (Wire.read() << 8) | Wire.read();
int16_t gz = (Wire.read() << 8) | Wire.read();
Serial.print("ax="); Serial.print(ax);
Serial.print(" ay="); Serial.print(ay);
Serial.print(" az="); Serial.print(az);
Serial.print(" | gx="); Serial.print(gx);
Serial.print(" gy="); Serial.print(gy);
Serial.print(" gz="); Serial.println(gz);
delay(200);
}
Uploaded, opened Serial Monitor. WHO_AM_I: 0x70 printed first, then MPU-6050 ready, then a stream of accel and gyro values that shifted when the board was tilted.
Working.
What the Registers Are Doing
0x75 — WHO_AM_I: Read-only. Returns a chip identifier. On genuine InvenSense MPU-6050: 0x68. On this clone: 0x70. No functional difference beyond this value — both respond identically to every other register operation.
0x6B — PWR_MGMT_1: Power management. Bit 6 is the sleep bit, set to 1 on power-up. Writing 0x00 clears it and wakes the chip. Nothing else in the register matters for basic use.
0x3B–0x40 — Accelerometer output: Six bytes, high byte first per axis. Combined into a signed 16-bit integer. At the default ±2g full-scale range, divide by 16384.0 to get g-force.
0x43–0x48 — Gyroscope output: Same structure. At the default ±250°/s range, divide by 131.0 to get degrees per second.
The Wire.endTransmission(false) on reads is a repeated start — keeps the I2C bus held between writing the register address and reading back the data. Without it, some devices drop the context between transactions.
What’s Next
The gyro is reading. The OLED and servo from the first test session are still working. The original plan from that session was to pipe gyro data into the servo — tilt the board, the servo follows. That’s next.