If you are building a balancing robot, a motion-tracking glove, or a drone flight controller, you need reliable 6-axis motion data. The TDK InvenSense MPU6050 remains the undisputed workhorse for hobbyist and prototyping gyroscope Arduino projects. It combines a 3-axis gyroscope and a 3-axis accelerometer with a 16-bit ADC and an onboard Digital Motion Processor (DMP), all communicating over a 400kHz I2C bus.
This guide targets the Arduino Uno R3 (and is fully compatible with the Uno R4 Minima). We will bypass bloated third-party libraries and write raw, compilable I2C C++ code. This gives you exact control over the registers, drastically reduces compiled sketch size, and teaches you how to properly handle I2C bus errors when the sensor inevitably stops responding.
Parts List & Spec Sheet
Before wiring, verify your breakout board. The market is flooded with GY-521 clones. While most work fine, some omit critical pull-up resistors on the I2C lines, which will cause bus failures.
| Component | Exact Variant / Spec | Notes & Bench Tips |
|---|---|---|
| Microcontroller | Arduino Uno R3 (ATmega328P) | 5V logic. Code also runs on Uno R4 Minima. |
| IMU Sensor | GY-521 Breakout (MPU6050) | Look for a board with an onboard 3.3V LDO and 4.7kΩ SMD pull-ups. |
| Pull-up Resistors | 4.7kΩ (1/4W or SMD) | Only required if your specific GY-521 clone lacks them. |
| Wiring | 22 AWG solid core jumper wires | Keep I2C runs under 30cm (12 inches) to avoid capacitance issues. |
| Power | USB 5V or 7-12V Barrel Jack | MPU6050 draws ~3.9mA in active mode; power budget is negligible. |
Pin Mapping & Wiring Steps
The MPU6050 operates internally at 3.3V. The GY-521 breakout includes a voltage regulator and logic-level translation, allowing you to safely power it from the Arduino's 5V pin. If you are using a raw MPU6050 chip or a 3.3V-only breakout (like the Adafruit 3885), you must use the 3.3V pin and a logic level shifter for the I2C lines.
Wiring Table (Arduino Uno R3)
| GY-521 Pin | Arduino Uno R3 Pin | Function |
|---|---|---|
| VCC | 5V | Power input (feeds onboard LDO) |
| GND | GND | Common ground |
| SCL | A5 | I2C Clock |
| SDA | A4 | I2C Data |
| XDA | Not Connected | Auxiliary I2C Data (for external magnetometer) |
| XCL | Not Connected | Auxiliary I2C Clock |
| ADO | GND | I2C Address Select (GND = 0x68, VCC = 0x69) |
| INT | D2 | Interrupt (Optional, used for DMP data ready) |
- Power the Bus: Connect the GY-521 VCC to the Arduino 5V pin, and GND to GND. Do not power the sensor from a separate supply unless you tie the grounds together.
- Connect I2C: Wire SDA to A4 and SCL to A5. Keep these wires as short and as parallel to each other as possible to minimize crosstalk.
- Set the Address: Ensure the ADO pin is tied to GND. This sets the 7-bit I2C address to
0x68. - Verify Pull-ups: If your multimeter reads infinite resistance between SDA and VCC (with power off), your breakout lacks pull-ups. Solder 4.7kΩ resistors between SDA-VCC and SCL-VCC.
Complete Compilable Raw I2C Code
Many tutorials rely on the Adafruit_MPU6050 library. While excellent for quick starts, it abstracts away the register map and adds overhead. The code below uses the native Wire.h library to read raw accelerometer and gyroscope data directly from the 14-byte data register block, parsing the 16-bit two's complement values.
Target Board: Arduino Uno R3 / R4 Minima. No external libraries required.
#include <Wire.h>
// MPU6050 I2C Address (ADO pin tied to GND)
const uint8_t MPU_ADDR = 0x68;
// Register Map
const uint8_t PWR_MGMT_1 = 0x6B;
const uint8_t ACCEL_XOUT_H = 0x3B;
const uint8_t GYRO_CONFIG = 0x1B;
const uint8_t ACCEL_CONFIG = 0x1C;
// Calibration offsets (determine these by averaging readings while flat)
float accel_offset_x = 0, accel_offset_y = 0, accel_offset_z = 0;
float gyro_offset_x = 0, gyro_offset_y = 0, gyro_offset_z = 0;
void setup() {
Serial.begin(115200);
while (!Serial); // Wait for serial monitor (Uno R4 / Leonardo)
Wire.begin();
Wire.setClock(400000); // Set I2C to Fast Mode (400kHz)
// Wake up the MPU6050 (it starts in sleep mode)
writeRegister(PWR_MGMT_1, 0x00);
// Configure Gyro to +/- 500 deg/s (FS_SEL = 1)
writeRegister(GYRO_CONFIG, 0x08);
// Configure Accel to +/- 2g (AFS_SEL = 0)
writeRegister(ACCEL_CONFIG, 0x00);
Serial.println("MPU6050 Initialized. Reading raw data...");
delay(100);
}
void loop() {
// Request 14 bytes starting from ACCEL_XOUT_H
// This reads Accel X,Y,Z, Temp, and Gyro X,Y,Z in one burst
Wire.beginTransmission(MPU_ADDR);
Wire.write(ACCEL_XOUT_H);
uint8_t err = Wire.endTransmission(false); // Repeated start
if (err != 0) {
handleI2CError(err);
return;
}
uint8_t bytesRequested = Wire.requestFrom(MPU_ADDR, (uint8_t)14, (uint8_t)true);
if (bytesRequested != 14) {
Serial.println("Error: Failed to read 14 bytes from MPU6050.");
return;
}
// Parse 16-bit two's complement values
int16_t raw_ax = Wire.read() << 8 | Wire.read();
int16_t raw_ay = Wire.read() << 8 | Wire.read();
int16_t raw_az = Wire.read() << 8 | Wire.read();
int16_t raw_temp = Wire.read() << 8 | Wire.read();
int16_t raw_gx = Wire.read() << 8 | Wire.read();
int16_t raw_gy = Wire.read() << 8 | Wire.read();
int16_t raw_gz = Wire.read() << 8 | Wire.read();
// Convert to physical units
// Accel: 16384 LSB = 1g (for +/- 2g setting)
float ax = (raw_ax / 16384.0) - accel_offset_x;
float ay = (raw_ay / 16384.0) - accel_offset_y;
float az = (raw_az / 16384.0) - accel_offset_z;
// Gyro: 65.5 LSB = 1 deg/s (for +/- 500 deg/s setting)
float gx = (raw_gx / 65.5) - gyro_offset_x;
float gy = (raw_gy / 65.5) - gyro_offset_y;
float gz = (raw_gz / 65.5) - gyro_offset_z;
// Temperature in Celsius
float temp_c = (raw_temp / 340.0) + 36.53;
Serial.print("Accel(g): "); Serial.print(ax); Serial.print(", "); Serial.print(ay); Serial.print(", "); Serial.println(az);
Serial.print("Gyro(dps): "); Serial.print(gx); Serial.print(", "); Serial.print(gy); Serial.print(", "); Serial.println(gz);
delay(100); // 10Hz sample rate for serial printing
}
void writeRegister(uint8_t reg, uint8_t value) {
Wire.beginTransmission(MPU_ADDR);
Wire.write(reg);
Wire.write(value);
uint8_t err = Wire.endTransmission();
if (err != 0) {
Serial.print("Failed to write register 0x");
Serial.println(reg, HEX);
handleI2CError(err);
}
}
void handleI2CError(uint8_t errCode) {
Serial.print("I2C Fatal Error: endTransmission returned ");
Serial.println(errCode);
// Halt or attempt reset depending on application
while(1);
}
Debugging: I2C Read Errors & Common Failures
When working with the MPU6050, the most common failure mode is an I2C bus lockup or address rejection. If your serial monitor outputs the exact string:
I2C Fatal Error: endTransmission returned 2
This means the Arduino sent the address 0x68, but no device acknowledged it (NACK on address). Here are the first three things to check, ranked by probability:
- SDA/SCL Swapped or Broken Jumper: The A4/A5 pinout on the Uno R3 is notoriously easy to miswire. Use a multimeter in continuity mode to verify the physical trace from the Arduino pin to the breakout header. A single broken strand inside a cheap jumper wire will cause a NACK.
- Missing Pull-Up Resistors: I2C is an open-drain bus. It requires pull-up resistors to pull the lines HIGH. If your GY-521 clone omitted the 4.7kΩ SMD resistors, the bus will float, and the MPU6050 will not recognize the clock edges. Measure resistance between SDA and VCC; it should read ~4.7kΩ.
- VCC vs VIO Voltage Mismatch: If you are using a raw MPU6050 module without an LDO, powering VCC with 5V while the I2C lines are driven at 5V can brownout the internal logic. Ensure VCC is exactly 3.3V if there is no onboard regulator.
Returned 1: Data too long to fit in transmit buffer.Returned 3: NACK on data (address was accepted, but register write failed).Returned 4: Unknown error (usually a physical bus short or severe noise).
Extending or Simplifying the Build
Raw sensor data is great for learning, but calculating pitch, roll, and yaw from raw accelerometer and gyroscope vectors requires complex sensor fusion (like a Kalman or Madgwick filter). The MPU6050 suffers from gyro drift over time, meaning your calculated angles will slowly wander.
How to Simplify: Switch to a Sensor Fusion IMU
If your project requires stable, drift-free Euler angles or Quaternions without writing complex math, abandon the MPU6050 and use the Bosch BNO055 or BNO085. These chips have a built-in ARM Cortex-M0 that handles sensor fusion internally, outputting clean, drift-corrected orientation data over I2C. They cost roughly $15-$25 more than an MPU6050 but save hours of algorithmic debugging.
How to Extend: Use the DMP and SD Logging
To push the MPU6050 to its limits, enable the onboard Digital Motion Processor (DMP). By flashing a 3KB firmware image into the MPU's auxiliary memory, the chip will calculate quaternions internally and trigger the INT pin when data is ready. Pair this with an SPI SD card module (like the Adafruit MicroSD breakout) to log high-speed motion data at 200Hz without bogging down the Arduino's main loop.
Frequently Asked Questions
Why is my gyroscope Arduino data drifting over time?
Gyroscopes measure the rate of rotation (degrees per second), not absolute position. To find the angle, the Arduino must integrate the gyro data over time. Any tiny noise or bias in the raw sensor reading gets accumulated during integration, resulting in 'drift'. To fix this, you must use sensor fusion to blend the short-term accuracy of the gyroscope with the long-term stability of the accelerometer, or upgrade to an IMU with a hardware fusion engine like the BNO055.
Can I connect multiple MPU6050 gyroscopes to one Arduino?
Yes, but the MPU6050 only has two hardware I2C addresses: 0x68 (ADO low) and 0x69 (ADO high). To connect more than two, you have three options: use an I2C multiplexer (like the TCA9548A), use multiple Arduinos communicating via UART, or bit-bang a secondary software I2C bus using the SoftwareWire library on different digital pins.
What is the difference between a gyroscope and an accelerometer in an IMU?
An accelerometer measures linear acceleration and the static pull of gravity, making it excellent for determining absolute tilt (pitch and roll) when the sensor is stationary. A gyroscope measures angular velocity (how fast it is spinning), making it perfect for tracking fast, dynamic movements. An IMU (Inertial Measurement Unit) like the MPU6050 contains both, allowing you to combine their strengths.
How do I calibrate the MPU6050 on a breadboard?
Place the breakout board on a perfectly level surface using a machinist's bubble level. Run a sketch that reads the raw values 1,000 times and averages them. For the accelerometer, the X and Y axes should average to 0, and the Z axis should average to 16384 (1g). For the gyroscope, all three axes should average to 0. Store these averages as offset variables in your code and subtract them from every subsequent reading.






