HomeArduino TutorialsMPU6050 Arduino I2C Tutorial: Pinout, Wiring, Calibration, Raw Data, and Code Examples

MPU6050 Arduino I2C Tutorial: Complete Interfacing, Calibration and Code Examples

The MPU6050 is one of the most powerful and affordable 6-axis motion sensors used in Arduino and IoT projects. It combines a 3-axis accelerometer and a 3-axis gyroscope, which makes it perfect for motion tracking, gesture control, and robotics.
In this tutorial, you will learn how to interface the MPU6050 with Arduino using I2C. We will go step-by-step through the pinout, wiring, registers, and I2C communication. You will also learn how to read raw sensor values, convert them into real units, and calibrate all 3 axes for better accuracy.

We will also compare the MPU6050 with the ADXL345 accelerometer, which we covered earlier in our previous tutorial. This comparison will help you choose the right sensor for your project.

MPU6050 Arduino I2C Tutorial: Complete Interfacing, Calibration and Code Examples

What is MPU6050 Sensor?

The MPU6050 is a popular motion-tracking sensor used in many Arduino and robotics projects. It comes with a 3-axis accelerometer and a 3-axis gyroscope, both packed inside one small chip. This makes it a complete 6-axis IMU sensor. It can measure tilt, motion, rotation, and orientation in real time. Because it works on the I2C interface, it is easy to connect and simple to use with Arduino.

MPU6050 Accelerometer and Gyroscope sensor module breakout board

Overview of the 6-Axis Motion Sensor

The MPU6050 combines two sensors:

  • 3-axis accelerometer
    Measures linear acceleration along X, Y, and Z axes.
    Helps detect tilt, vibration, and movement.
  • 3-axis gyroscope
    Measures rotation rate around X, Y, and Z axes.
    Helps track angular motion and orientation.

Together, these six measurements allow you to track the exact movement of any object. This is why the MPU6050 is often used in drones, self-balancing robots, gaming controllers, and IoT devices.


Why MPU6050 is Popular in Arduino Projects

The MPU6050 is very common in Arduino projects because:

  • It offers 6-axis motion tracking in a single compact module.
  • It uses the simple I2C protocol, so only two wires are needed.
  • It provides stable readings with a built-in Digital Motion Processor (DMP).
  • It is cheap, easily available, and works on both 3.3V and 5V boards.
  • It has a wide measurement range for both accelerometer and gyroscope.

Thanks to these advantages, makers use the MPU6050 in projects like self-balancing robots, gesture control, quadcopters, and wearable devices.


Difference Between Accelerometer and Gyroscope

To understand the MPU6050, you must know how its two main sensors work:

  • Accelerometer
    Measures acceleration in three directions: X, Y, and Z.
    It tells you how fast the sensor is moving in a straight line.
    It can also measure the tilt angle because gravity affects its readings.
  • Gyroscope
    Measures angular velocity around three axes.
    It tells you how fast the sensor is rotating or turning.
    It cannot detect tilt, but it tracks rotational motion very accurately.

In simple words:
Accelerometer = linear movement + tilt
Gyroscope = rotation + angular speed

MPU6050 Pinout and Features

Before we connect the MPU6050 to Arduino, it is important to understand its pinout and main features. The module comes with only a few pins, which makes wiring simple and quick. It operates on the I2C communication protocol and works smoothly with most Arduino boards.

MPU6050 Pinout Table

The image below shows the pinout of MPU6050.

Pinout of MPU6050 sensor module breakout board

The pins and their functions are explained in the table below:

Pin NameFunctionDescription
VCCPower InputProvides power to the module. Supports 3.3V–5V on most breakout boards.
GNDGroundConnects to Arduino ground.
SCLI2C ClockSerial Clock Line for I2C. Connect to A5 on Uno/Nano.
SDAI2C DataSerial Data Line for I2C. Connect to A4 on Uno/Nano.
INTInterruptUsed for motion interrupts or DMP features. Optional.
XDAAux DataAuxiliary I2C data line for connecting extra I2C devices. Rarely used.
XCLAux ClockAuxiliary I2C clock line. Rarely used.
ADOAddress SelectChanges I2C address from 0x68 (default) to 0x69 when pulled high.

Electrical Characteristics

These basic electrical specs help you understand how the sensor behaves:

  • Operating Voltage: 2.3V to 3.4V core (breakout boards allow 3.3V–5V input)
  • Communication: I2C up to 400kHz
  • Accelerometer Range: ±2g, ±4g, ±8g, ±16g
  • Gyroscope Range: ±250, ±500, ±1000, ±2000 °/s
  • Current Consumption: ~3.6mA
  • I2C Address: 0x68 (default), 0x69 (when ADO pulled high)

These ranges can be selected in the code, depending on your accuracy and speed requirements.


Key Features of MPU6050 Sensor

Some powerful features make the MPU6050 stand out:

  • 6-axis IMU (3-axis accelerometer + 3-axis gyroscope)
  • Built-in Digital Motion Processor (DMP) for sensor fusion
  • Low cost and high availability
  • High sensitivity and wide measurement ranges
  • Low power consumption
  • Stable and smooth real-time motion readings
  • Supports fast I2C communication
  • Small size, perfect for compact IoT and robotics projects

MPU6050 vs ADXL345: Which One Should You Use?

Before selecting a motion sensor for your Arduino project, it is important to understand the difference between the MPU6050 and the ADXL345. Both sensors measure acceleration, but the MPU6050 also includes a 3-axis gyroscope, making it more powerful for projects that need rotation or orientation tracking.
If you haven’t seen our previous tutorial on ADXL345, you can check it here:
ADXL345 Arduino I2C Tutorial: https://controllerstech.com/adxl345-arduino-i2c-tutorial/

Advantages of MPU6050 Over ADXL345

The MPU6050 offers several strong advantages:

  • 6-axis sensing
    It combines a 3-axis accelerometer and a 3-axis gyroscope in one chip, making it a complete IMU.
  • Better motion tracking
    Perfect for applications like self-balancing robots, drones, and gesture control.
  • Built-in DMP (Digital Motion Processor)
    Helps reduce noise and performs sensor fusion for smoother results.
  • Wide measurement ranges
    Both accelerometer and gyroscope ranges are flexible and ideal for fast or complex movement.
  • More stable orientation data
    Because gyroscope data helps reduce the noise of accelerometer readings.

In short, MPU6050 is a better choice for motion tracking, orientation, and dynamic movement projects.


When ADXL345 is Still Useful

Even though MPU6050 is powerful, the ADXL345 is still a good choice for many situations:

  • When you need only acceleration data
    ADXL345 does not have a gyroscope, but for basic tilt or vibration detection, it works well.
  • Lower power consumption
    Useful for battery-operated IoT projects.
  • When cost matters
    ADXL345 modules are slightly cheaper and easier to find.
  • For simple, static applications
    Such as shock detection, tilt sensing, or step counting.

If your project does not need rotation or angular speed, ADXL345 is still a solid option.


Summary of Differences

Here is a quick comparison between both sensors:

FeatureMPU6050ADXL345
Sensor Type6-axis IMU3-axis accelerometer
GyroscopeYesNo
AccelerometerYesYes
Motion TrackingExcellentBasic
Power ConsumptionMediumLow
Best UseRobots, drones, gesture control, IMU systemsTilt sensing, vibration, simple motion detection
InterfaceI2CI2C/SPI
ComplexityModerateEasy

In simple words:

  • Choose MPU6050 for advanced motion and rotation tracking.
  • Choose ADXL345 for basic acceleration sensing with lower power needs.

Interfacing MPU6050 with Arduino via I2C

The MPU6050 uses the I2C communication protocol, which makes the connection very simple. You only need two data lines to connect it with any Arduino board. In this section, we will look at the components needed, the wiring diagram, and how the I2C address works. Once connected, you can start reading raw accelerometer and gyroscope data easily.

Required Components

You will need the following items:

  • Arduino Uno / Nano / Mega
  • MPU6050 Module
  • Jumper Wires
  • Breadboard (optional)
  • USB Cable to power the Arduino

Wiring Diagram of MPU6050 with Arduino

The wiring is very simple because MPU6050 works on I2C. The image below shows the wiring connection between MPU6050 and Arduino.

Image showing the wiring connection between MPU6050 and Arduino. The sensor is connected via I2C.

Here is the connection table:

MPU6050 PinArduino Uno / Nano PinDescription
VCC5V or 3.3VPower supply
GNDGNDGround
SCLA5I2C Clock
SDAA4I2C Data
INT(Optional)Interrupt pin
ADOGNDSets I2C address to 0x68

For Arduino Mega, connect SDA → 20 and SCL → 21.
For Arduino Leonardo, connect to the dedicated SDA/SCL pins.


I2C Address and How It Works

The MPU6050 communicates over the I2C bus using a 7-bit address.
It has two possible addresses:

  • 0x68 → Default address (ADO pin LOW)
  • 0x69 → Alternate address (ADO pin HIGH)

Most breakout boards keep ADO connected to GND, so the default address is 0x68. This is the address your Arduino will use to read and write data from the sensor.

If you ever need two MPU6050 sensors on the same I2C bus, simply pull the ADO pin HIGH on one module to change the address to 0x69.

Reading Raw Accelerometer and Gyroscope Values

Once your MPU6050 is connected to the Arduino, the next step is to read the raw sensor data. These raw values come directly from the sensor registers before any conversion or calibration. Understanding these values is important because you will later convert them into g-force and degrees per second. In this section, we will learn about the required registers and read raw data for both accelerometer and gyroscope.

MPU6050 Register Overview

The MPU6050 stores all accelerometer and gyroscope readings in specific registers. Each axis uses two registers (high byte + low byte). Here are the important ones:

SensorRegister AddressDescription
Accelerometer X0x3B & 0x3CHigh and Low bytes
Accelerometer Y0x3D & 0x3EHigh and Low bytes
Accelerometer Z0x3F & 0x40High and Low bytes
Temperature0x41 & 0x42Raw temperature
Gyroscope X0x43 & 0x44High and Low bytes
Gyroscope Y0x45 & 0x46High and Low bytes
Gyroscope Z0x47 & 0x48High and Low bytes

Before reading these registers, you must wake up the MPU6050 because it starts in sleep mode. To do that, write 0 to register 0x6B (PWR_MGMT_1).


Read Raw Accelerometer Values

Below is the code to read the RAW acceleration values from the MPU6050.

#include <Wire.h>

#define MPU_ADDR 0x68  // Default I2C address of MPU6050

int16_t AccX, AccY, AccZ;

void setup() {
  Wire.begin();               // Start I2C communication
  Serial.begin(9600);         // Start Serial Monitor

  // ----- MPU6050 Initialization -----
  Wire.beginTransmission(MPU_ADDR);
  Wire.write(0x6B);           // PWR_MGMT_1 register
  Wire.write(0x00);           // Wake up MPU6050 (set sleep = 0)
  Wire.endTransmission(true);

  // Set accelerometer range to ±2g (most stable)
  Wire.beginTransmission(MPU_ADDR);
  Wire.write(0x1C);           // ACCEL_CONFIG register
  Wire.write(0x00);           // ±2g range
  Wire.endTransmission(true);

  // Set sample rate (optional)
  Wire.beginTransmission(MPU_ADDR);
  Wire.write(0x19);           // SMPLRT_DIV register
  Wire.write(0x07);           // Sample rate = 1kHz / (7+1) = 125Hz
  Wire.endTransmission(true);

  Serial.println("MPU6050 Initialized for Raw Accelerometer Readings");
}

void loop() {
  // Request accelerometer registers starting from 0x3B
  Wire.beginTransmission(MPU_ADDR);
  Wire.write(0x3B);
  Wire.endTransmission(false);
  Wire.requestFrom(MPU_ADDR, 6, true);

  // Read raw data (16-bit signed)
  AccX = (Wire.read() << 8) | Wire.read();
  AccY = (Wire.read() << 8) | Wire.read();
  AccZ = (Wire.read() << 8) | Wire.read();

  Serial.print("AccX: "); Serial.print(AccX);
  Serial.print(" | AccY: "); Serial.print(AccY);
  Serial.print(" | AccZ: "); Serial.println(AccZ);

  delay(200);
}

Code Explained (Accelerometer Section)

1. Initialization
  • Wake up the sensor from sleep mode
  • Set accelerometer range to ±2g for highest accuracy
  • Set sample rate to 125Hz

This ensures stable and clean raw readings.

2. Reading Data
  • Start reading from register 0x3B
  • Read 6 bytes for X, Y, Z
  • Combine high and low bytes into a 16-bit signed integer
3. Print Raw Values

Telemeter raw values directly to Serial Monitor.


Output on the serial monitor

The image below shows the output of the code. The Raw values are displayed on the serial monitor.

Image shows the RAW acceleration values are printed on the serial monitor of the Arduino IDE.

Read Raw Gyroscope Values

Below is the code to read the RAW gyroscope values from the MPU6050.

#include <Wire.h>

#define MPU_ADDR 0x68

int16_t GyroX, GyroY, GyroZ;

void setup() {
  Wire.begin();
  Serial.begin(9600);

  // ----- MPU6050 Initialization -----
  Wire.beginTransmission(MPU_ADDR);
  Wire.write(0x6B);     // Wake up sensor
  Wire.write(0x00);
  Wire.endTransmission(true);

  // Configure gyroscope range to ±250 °/s
  Wire.beginTransmission(MPU_ADDR);
  Wire.write(0x1B);     // GYRO_CONFIG register
  Wire.write(0x00);     // ±250dps
  Wire.endTransmission(true);

  Serial.println("MPU6050 Initialized for Raw Gyroscope Readings");
}

void loop() {
  // Request gyroscope registers starting from 0x43
  Wire.beginTransmission(MPU_ADDR);
  Wire.write(0x43);
  Wire.endTransmission(false);
  Wire.requestFrom(MPU_ADDR, 6, true);

  GyroX = (Wire.read() << 8) | Wire.read();
  GyroY = (Wire.read() << 8) | Wire.read();
  GyroZ = (Wire.read() << 8) | Wire.read();

  Serial.print("GyroX: "); Serial.print(GyroX);
  Serial.print(" | GyroY: "); Serial.print(GyroY);
  Serial.print(" | GyroZ: "); Serial.println(GyroZ);

  delay(200);
}

Explaination

1. Initialization
  • Wake up sensor
  • Set gyroscope range to ±250°/s
    This gives maximum precision for slow movements.
2. Reading Data
  • Read registers starting from 0x43
  • Fetch 6 bytes → X, Y, Z
  • Combine into signed 16-bit values
3. Print Raw Values

Shows the raw rotation speed (not degrees yet).


Output on the serial monitor

The image below shows the output of the code. The Raw values are displayed on the serial monitor.

Image shows the RAW gyroscope values are printed on the serial monitor of the Arduino IDE.

Convert Raw Values to ‘g’ and °/s

When you read data from the MPU6050, the accelerometer and gyroscope values are given as raw 16-bit numbers. These numbers don’t immediately tell you how much acceleration or rotation the sensor is experiencing.
To make the data useful, we convert accelerometer raw values into g-force (g) and gyroscope raw values into degrees per second (°/s). This section explains how these conversions work and provides the full code.

Convert Accelerometer Raw Data to g

The accelerometer measures acceleration along X, Y, Z axes.
The raw values depend on the scale you choose. The most commonly used scale is ±2g, where:

  • LSB Sensitivity = 16384 LSB/g

This means:

g_value = raw_value / 16384.0

Example:
If raw X = 8192 : X-axis acceleration = 8192 / 16384 = 0.5 g


Convert Gyroscope Raw Data to Degrees per Second

The gyroscope measures rotation around X, Y, Z axes.
With the default full-scale range ±250°/s:

  • LSB Sensitivity = 131 LSB/(°/s)

So conversion is:

deg_per_sec = raw_value / 131.0

Example:
If raw Z = 262 : Rotation = 262 / 131 = 2 °/s


Complete Code to Print Converted Values

Below is the full Arduino code to read Raw data and convert it to g and °/s.

#include <Wire.h>

#define MPU6050_ADDR 0x68

// MPU6050 Register Addresses
#define SMPLRT_DIV   0x19
#define CONFIG       0x1A
#define GYRO_CONFIG  0x1B
#define ACCEL_CONFIG 0x1C
#define PWR_MGMT_1   0x6B
#define ACCEL_XOUT_H 0x3B

void setup() {
  Serial.begin(9600);
  Wire.begin();

  // Wake up MPU6050
  Wire.beginTransmission(MPU6050_ADDR);
  Wire.write(PWR_MGMT_1);
  Wire.write(0x00);   // Clears sleep bit, enables the sensor
  Wire.endTransmission();

  // Sample Rate Divider
  Wire.beginTransmission(MPU6050_ADDR);
  Wire.write(SMPLRT_DIV);
  Wire.write(0x07);   // Sample rate = 1kHz / (7+1) = 125Hz
  Wire.endTransmission();

  // Digital Low Pass Filter (DLPF)
  Wire.beginTransmission(MPU6050_ADDR);
  Wire.write(CONFIG);
  Wire.write(0x03);   // DLPF = 44Hz
  Wire.endTransmission();

  // Gyroscope configuration
  Wire.beginTransmission(MPU6050_ADDR);
  Wire.write(GYRO_CONFIG);
  Wire.write(0x00);   // ±250°/s
  Wire.endTransmission();

  // Accelerometer configuration
  Wire.beginTransmission(MPU6050_ADDR);
  Wire.write(ACCEL_CONFIG);
  Wire.write(0x00);   // ±2g
  Wire.endTransmission();

  Serial.println("MPU6050 Initialized Successfully");
}

void loop() {
  int16_t raw_acc_x, raw_acc_y, raw_acc_z;
  int16_t raw_gyro_x, raw_gyro_y, raw_gyro_z;

  Wire.beginTransmission(MPU6050_ADDR);
  Wire.write(ACCEL_XOUT_H);
  Wire.endTransmission(false);
  Wire.requestFrom(MPU6050_ADDR, 14, true);

  // Reading accelerometer raw values
  raw_acc_x = (Wire.read() << 8) | Wire.read();
  raw_acc_y = (Wire.read() << 8) | Wire.read();
  raw_acc_z = (Wire.read() << 8) | Wire.read();

  // Reading temperature (not used)
  Wire.read(); Wire.read();

  // Reading gyroscope raw values
  raw_gyro_x = (Wire.read() << 8) | Wire.read();
  raw_gyro_y = (Wire.read() << 8) | Wire.read();
  raw_gyro_z = (Wire.read() << 8) | Wire.read();

  // Convert raw values to g
  float ax = raw_acc_x / 16384.0;
  float ay = raw_acc_y / 16384.0;
  float az = raw_acc_z / 16384.0;

  // Convert raw values to degrees per second
  float gx = raw_gyro_x / 131.0;
  float gy = raw_gyro_y / 131.0;
  float gz = raw_gyro_z / 131.0;

  // Print converted values
  Serial.print("Accel (g): ");
  Serial.print(ax); Serial.print(", ");
  Serial.print(ay); Serial.print(", ");
  Serial.println(az);

  Serial.print("Gyro (°/s): ");
  Serial.print(gx); Serial.print(", ");
  Serial.print(gy); Serial.print(", ");
  Serial.println(gz);

  Serial.println("----------------------------");
  delay(200);
}

Output on the serial monitor

The image below shows the output of the code. The Acceleration values in g and Gyroscope values in °/s are printed on the console.

Image shows the Acceleration values in g and Gyroscope values in °/s are printed on the serial monitor of the Arduino IDE.

These values are obtained when the sensor is placed flat on the table. You can see both acceleration and gyroscope values have offset in them. Therefore we now need to calibrate the sensor.

MPU6050 Calibration for All 3 Axes

Calibrating the MPU6050 is an important step before using the sensor for real measurements or motion-based projects. Even small factory offsets can cause inaccurate readings, drift, and unstable results. In this section, we will learn why calibration is required and how to perform it using simple Arduino code.

Why Calibration Is Needed

Every MPU6050 has tiny internal errors that affect the accelerometer and gyroscope. These errors appear even when the sensor is perfectly still.

Typical issues include:

  • Accelerometer not showing 0g on X & Y or 1g on Z
  • Gyroscope not showing 0°/s on all axes
  • Drift when the sensor is used for motion sensing
  • Incorrect angle or orientation calculations

Calibration removes these fixed offsets and improves accuracy.


Simple Calibration Method

The easiest calibration method is:

  1. Place MPU6050 perfectly still on a flat table.
  2. Read 200–500 samples from the accelerometer and gyroscope.
  3. Compute the average offset for each axis.
  4. Subtract this offset from future raw readings.

You only need to do this once unless you remount the sensor.


Code for Accelerometer Calibration

Below is the full Arduino code to calibrate the acceleration values in the MPU6050 sensor.

#include <Wire.h>

#define MPU6050_ADDR 0x68
#define ACCEL_XOUT_H 0x3B

long accX_offset = 0, accY_offset = 0, accZ_offset = 0;

void setup() {
  Serial.begin(9600);
  Wire.begin();

  // Wake up MPU6050
  Wire.beginTransmission(MPU6050_ADDR);
  Wire.write(0x6B);
  Wire.write(0);
  Wire.endTransmission();

  delay(1000);

  calibrateAccelerometer();
}

void loop() {
  // Nothing here - calibration prints automatically
}

void calibrateAccelerometer() {
  long sumX = 0, sumY = 0, sumZ = 0;

  Serial.println("Calibrating Accelerometer... Keep the sensor still.");

  for (int i = 0; i < 200; i++) {
    Wire.beginTransmission(MPU6050_ADDR);
    Wire.write(ACCEL_XOUT_H);
    Wire.endTransmission(false);
    Wire.requestFrom(MPU6050_ADDR, 6, true);

    int16_t rawX = (Wire.read() << 8) | Wire.read();
    int16_t rawY = (Wire.read() << 8) | Wire.read();
    int16_t rawZ = (Wire.read() << 8) | Wire.read();

    sumX += rawX;
    sumY += rawY;
    sumZ += rawZ;

    delay(5);
  }

  accX_offset = sumX / 200;
  accY_offset = sumY / 200;
  accZ_offset = (sumZ / 200) - 16384; // 1g correction (Z-axis)

  Serial.println("Accelerometer Calibration Done:");
  Serial.print("X Offset = "); Serial.println(accX_offset);
  Serial.print("Y Offset = "); Serial.println(accY_offset);
  Serial.print("Z Offset = "); Serial.println(accZ_offset);
}

Explanation

  • The MPU6050 is first woken up by writing 0 to register 0x6B.
  • The code reads raw accelerometer values 200 times.
  • Averages of these raw values become the offset.
  • For Z-axis, 16384 is subtracted because this is the 1g value in ±2g mode.
  • These offsets are printed for applying in future programs.

Output on the serial monitor

The image below shows the output of the code. The offsets in X, Y and Z axes are printed on the serial console.

Image shows the X, Y and Z offsets in the Accelerometer are printed after calibration on the serial monitor of the Arduino IDE.

Code for Gyroscope Calibration

Below is the full Arduino code to calibrate the gyroscope values in the MPU6050 sensor.

#include <Wire.h>

#define MPU6050_ADDR 0x68
#define GYRO_XOUT_H 0x43

long gyroX_offset = 0, gyroY_offset = 0, gyroZ_offset = 0;

void setup() {
  Serial.begin(9600);
  Wire.begin();

  // Wake up MPU6050
  Wire.beginTransmission(MPU6050_ADDR);
  Wire.write(0x6B);
  Wire.write(0);
  Wire.endTransmission();

  delay(1000);

  calibrateGyroscope();
}

void loop() {
  // Nothing here - calibration prints automatically
}

void calibrateGyroscope() {
  long sumX = 0, sumY = 0, sumZ = 0;

  Serial.println("Calibrating Gyroscope... Keep the sensor still.");

  for (int i = 0; i < 200; i++) {
    Wire.beginTransmission(MPU6050_ADDR);
    Wire.write(GYRO_XOUT_H);
    Wire.endTransmission(false);
    Wire.requestFrom(MPU6050_ADDR, 6, true);

    int16_t rawX = (Wire.read() << 8) | Wire.read();
    int16_t rawY = (Wire.read() << 8) | Wire.read();
    int16_t rawZ = (Wire.read() << 8) | Wire.read();

    sumX += rawX;
    sumY += rawY;
    sumZ += rawZ;

    delay(5);
  }

  gyroX_offset = sumX / 200;
  gyroY_offset = sumY / 200;
  gyroZ_offset = sumZ / 200;

  Serial.println("Gyroscope Calibration Done:");
  Serial.print("X Offset = "); Serial.println(gyroX_offset);
  Serial.print("Y Offset = "); Serial.println(gyroY_offset);
  Serial.print("Z Offset = "); Serial.println(gyroZ_offset);
}

Explanation

  • The code reads raw gyroscope values 200 times.
  • The averages of X, Y, and Z form the gyroscope offsets.
  • Since the gyro should read 0°/s when stationary, these offsets represent the unwanted drift.
  • These values must be subtracted from future gyro data.

Output on the serial monitor

The image below shows the output of the code. The offsets in X, Y and Z axes are printed on the serial console.

Image shows the X, Y and Z offsets in the gyroscope are printed after calibration on the serial monitor of the Arduino IDE.

Applying Offsets in Your Main Code

You will use the calibration values like this:

accX = rawAccX - accX_offset;
accY = rawAccY - accY_offset;
accZ = rawAccZ - accZ_offset;

gyroX = rawGyroX - gyroX_offset;
gyroY = rawGyroY - gyroY_offset;
gyroZ = rawGyroZ - gyroZ_offset;

Final Combined Code : Raw + Converted + Calibrated Values

In this final section, we will combine everything we have learned: reading raw data, converting the values into g and degrees per second, and applying calibration offsets. This single Arduino sketch gives you clean, stable accelerometer and gyroscope readings. You can use it directly in your projects such as robotics, gesture sensing, motion tracking, and IMU-based applications.

Full Arduino Code

The code below includes:

  • MPU6050 initialization
  • Calibration for accelerometer and gyroscope
  • Reading raw values
  • Applying offsets
  • Converting data to g and °/s
  • Printing everything on the Serial Monitor
#include <Wire.h>

// MPU6050 Registers
#define MPU6050_ADDR   0x68
#define ACCEL_XOUT_H   0x3B
#define GYRO_XOUT_H    0x43

// Calibration Offsets
long accX_offset = 0, accY_offset = 0, accZ_offset = 0;
long gyroX_offset = 0, gyroY_offset = 0, gyroZ_offset = 0;

// Scales
float accScale = 16384.0;   // For ±2g
float gyroScale = 131.0;    // For ±250 °/s

void setup() {
  Serial.begin(9600);
  Wire.begin();

  // Wake up MPU6050 – write 0 to power management register
  Wire.beginTransmission(MPU6050_ADDR);
  Wire.write(0x6B);
  Wire.write(0); 
  Wire.endTransmission();

  delay(1000);

  calibrateAccelerometer();
  calibrateGyroscope();

  Serial.println("\nStarting Final Reading...\n");
}

void loop() {
  int16_t rawAccX, rawAccY, rawAccZ;
  int16_t rawGyroX, rawGyroY, rawGyroZ;

  // ------ Read Accelerometer ------
  Wire.beginTransmission(MPU6050_ADDR);
  Wire.write(ACCEL_XOUT_H);
  Wire.endTransmission(false);
  Wire.requestFrom(MPU6050_ADDR, 6, true);

  rawAccX = (Wire.read() << 8) | Wire.read();
  rawAccY = (Wire.read() << 8) | Wire.read();
  rawAccZ = (Wire.read() << 8) | Wire.read();

  // Apply offsets
  rawAccX -= accX_offset;
  rawAccY -= accY_offset;
  rawAccZ -= accZ_offset;

  // Convert to g
  float ax = rawAccX / accScale;
  float ay = rawAccY / accScale;
  float az = rawAccZ / accScale;

  // ------ Read Gyroscope ------
  Wire.beginTransmission(MPU6050_ADDR);
  Wire.write(GYRO_XOUT_H);
  Wire.endTransmission(false);
  Wire.requestFrom(MPU6050_ADDR, 6, true);

  rawGyroX = (Wire.read() << 8) | Wire.read();
  rawGyroY = (Wire.read() << 8) | Wire.read();
  rawGyroZ = (Wire.read() << 8) | Wire.read();

  // Apply offsets
  rawGyroX -= gyroX_offset;
  rawGyroY -= gyroY_offset;
  rawGyroZ -= gyroZ_offset;

  // Convert to degrees/sec
  float gx = rawGyroX / gyroScale;
  float gy = rawGyroY / gyroScale;
  float gz = rawGyroZ / gyroScale;

  // -------- Print Results --------
  Serial.print("Acc(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);

  Serial.println("---------------------");
  delay(200);
}

// ---------------- Calibration Functions ---------------- //

void calibrateAccelerometer() {
  long sumX = 0, sumY = 0, sumZ = 0;

  Serial.println("Calibrating Accelerometer... Do not move!");

  for (int i = 0; i < 200; i++) {
    Wire.beginTransmission(MPU6050_ADDR);
    Wire.write(ACCEL_XOUT_H);
    Wire.endTransmission(false);
    Wire.requestFrom(MPU6050_ADDR, 6, true);

    sumX += (Wire.read() << 8) | Wire.read();
    sumY += (Wire.read() << 8) | Wire.read();
    sumZ += (Wire.read() << 8) | Wire.read();

    delay(5);
  }

  accX_offset = sumX / 200;
  accY_offset = sumY / 200;
  accZ_offset = (sumZ / 200) - 16384; // 1g adjustment

  Serial.println("Accelerometer Calibration Done.");
}

void calibrateGyroscope() {
  long sumX = 0, sumY = 0, sumZ = 0;

  Serial.println("Calibrating Gyroscope... Do not move!");

  for (int i = 0; i < 200; i++) {
    Wire.beginTransmission(MPU6050_ADDR);
    Wire.write(GYRO_XOUT_H);
    Wire.endTransmission(false);
    Wire.requestFrom(MPU6050_ADDR, 6, true);

    sumX += (Wire.read() << 8) | Wire.read();
    sumY += (Wire.read() << 8) | Wire.read();
    sumZ += (Wire.read() << 8) | Wire.read();

    delay(5);
  }

  gyroX_offset = sumX / 200;
  gyroY_offset = sumY / 200;
  gyroZ_offset = sumZ / 200;

  Serial.println("Gyroscope Calibration Done.");
}

Output on the serial monitor

The image below shows the output of the code.

Image shows calibrated acceleration and gyroscope data from the MPU6050 is printed on the serial console of Arduino IDE.

You can see in the image, after calibration, the acceleration values are precise. The acceleration in z-axis is 1g, whereas in x-axis and y-axis it is 0.
The gyroscope also shows values near to 0 as the sensor is kept stationary.

Meaning of the values:

  • Accelerometer values near 0, 0, 1 indicate proper calibration.
  • Gyroscope values near 0°/s show stable results.
  • Small variations are normal because of sensor noise.

How to Verify the Working

You can easily check the sensor by:

1. Tilting the board

  • If you tilt the sensor forward or sideways, the corresponding axis should move smoothly.
  • No sudden jumps or drift.

2. Rotate the board

  • Gyroscope values should increase only during movement.
  • When still, they should return to zero.

Applications of MPU6050 in Arduino Projects

The MPU6050 is widely used in many Arduino projects because it provides both accelerometer and gyroscope data in a single compact module. Its fast response, low noise, and 6-axis sensing make it perfect for motion-based applications. Here are some of the most common and practical uses of the MPU6050 in Arduino projects.

Self-Balancing Robots

Self-balancing robots, such as two-wheel Segway-style robots, depend heavily on MPU6050.
The sensor provides pitch and roll angles, which help the robot stay upright by constantly correcting its position.
The gyroscope detects quick rotational changes while the accelerometer keeps the overall tilt stable. Together, they allow smooth and responsive balancing.


Gesture Control Systems

MPU6050 is also used in gesture-controlled projects such as:

  • Controlling robots with hand movements
  • Wireless mouse or cursor control
  • Swipe, tilt, or rotation-based controls

The fast accelerometer and gyro readings make it easy to track hand gestures and map them to actions.


IMU Based Motion Tracking

In many DIY IMU (Inertial Measurement Unit) projects, the MPU6050 is the first choice.
It helps track movement and orientation over time.

Common examples include:

  • VR head tracking
  • Game controllers
  • Drone flight stabilization
  • Wearable motion trackers

Its combination of 3-axis acceleration + 3-axis rotation gives accurate and real-time movement detection, making it ideal for motion-tracking applications.

Conclusion

The MPU6050 is a highly reliable and beginner-friendly motion sensor that combines a 3-axis accelerometer and 3-axis gyroscope in a single module, making it ideal for Arduino-based robotics and IoT projects. In this tutorial, you learned how the sensor works internally, explored its pinout and features, and compared it with the ADXL345 to understand when each sensor is the better choice. You also saw how to connect the MPU6050 to Arduino over I2C and how to read the raw accelerometer and gyroscope data it provides.

Beyond basic interfacing, the tutorial walked you through converting raw readings into meaningful units like g-force and degrees per second, followed by a full 3-axis calibration procedure to ensure accuracy. You also implemented a final combined sketch that delivers clean, stable, and reliable measurements suitable for real-world applications such as self-balancing robots, gesture-controlled systems, and IMU-based motion tracking. For further learning, you can explore related sensor guides, including the ADXL345 Arduino I2C tutorial on Controllerstech.

Browse More Arduino Sensors Tutorials

Arduino MPU6050 Project Download

Info

You can help with the development by DONATING Below.
To download the project, click the DOWNLOAD button.

Arduino MPU6050 FAQs

Subscribe
Notify of

0 Comments
Newest
Oldest Most Voted
Inline Feedbacks
View all comments