Home ▸ Arduino ▸ Sensors Interfacing ▸ Interface MPU6050 via I2C

MPU6050 Arduino Tutorial: I2C Wiring, Raw Data, Calibration and Code

The MPU6050 is a 6-axis motion sensor that combines a 3-axis accelerometer and a 3-axis gyroscope on one chip, and it connects to an Arduino with only two signal wires. It is inexpensive, widely available and accurate enough for self-balancing robots, gesture controllers and small drone stabilizers. The sensor reports tilt, motion and rotation, so one module covers most basic motion-tracking needs.

This tutorial shows how to wire the MPU6050 to an Arduino Uno, Nano or Mega and how to read the raw accelerometer and gyroscope registers directly with the Wire library. You will convert the raw numbers into g-force and degrees per second, calibrate all six axes and finish with a complete sketch that prints clean, stable values. No external sensor library is needed, and all of the code runs on any ATmega328P board.

The sensor uses the I2C bus, so the Arduino I2C tutorial is a helpful background read, and the Arduino UNO pinout guide and the Arduino Nano pinout guide show where the I2C pins are on each board. To show the readings on a screen, combine this project with the SSD1306 OLED tutorial. Other sensors that use the same bus include the ADXL345 accelerometer, the BMP180 pressure sensor, the BME280 environmental sensor and the SHT21 humidity sensor, and the full list is in the Arduino sensor tutorials.

MPU6050 Overview

The MPU6050 is a motion-tracking chip that contains an accelerometer and a gyroscope in one small package. Together, the two sensors form a 6-axis IMU (inertial measurement unit) that can measure tilt, motion, rotation and orientation in real time. It communicates over I2C, which is why the wiring needs only two data lines.

MPU6050 breakout module showing the VCC, GND, SCL, SDA, XDA, XCL, ADO and INT pins

Accelerometer and Gyroscope Differences

The two sensors measure different quantities, and each one makes up for a weakness of the other.

  • The 3-axis accelerometer measures acceleration along the X, Y and Z axes. Gravity acts on it all the time, so it can also report the tilt angle of the board when the board is not moving.
  • The 3-axis gyroscope measures angular velocity, which is the speed of rotation around each axis. It tracks fast rotation accurately, but it cannot report the absolute tilt, and its angle estimate drifts over time.

A project that needs a stable angle usually combines both sensors, for example with a complementary filter. The accelerometer corrects the slow drift of the gyroscope, and the gyroscope smooths out the vibration noise of the accelerometer.


  • It provides 6-axis motion sensing in one compact module.
  • It needs only two I2C wires, so it leaves most Arduino pins free.
  • It is inexpensive and available in almost every electronics store.
  • It has selectable measurement ranges for both sensors.
  • It contains a Digital Motion Processor (DMP) for onboard sensor fusion, although this tutorial reads the raw registers and does not use the DMP.

The chip itself runs from about 3.3 V. Common breakout boards such as the GY-521 add a voltage regulator, so the module accepts 3.3 V to 5 V on its VCC pin. A bare chip without that regulator must not be powered from 5 V.


MPU6050 Pinout

The breakout module exposes eight pins, and the diagram and table below describe each one.

MPU6050 pinout diagram showing all eight pins and the function of each pin
PinFunctionDescription
VCCPower inputPowers the module, 3.3 V to 5 V on most breakout boards
GNDGroundConnects to the Arduino ground
SCLI2C clockConnects to A5 on the Uno and the Nano
SDAI2C dataConnects to A4 on the Uno and the Nano
INTInterrupt outputSignals motion or data-ready events, optional
XDAAuxiliary I2C dataConnects extra I2C sensors to the MPU6050, rarely used
XCLAuxiliary I2C clockAuxiliary clock line, rarely used
ADOAddress selectLOW gives the address 0x68, HIGH gives 0x69

Only VCC, GND, SCL and SDA are needed for the examples in this tutorial.


MPU6050 Arduino Wiring

The circuit needs an Arduino Uno, Nano or Mega, an MPU6050 module, four jumper wires and a USB cable. A breadboard is optional.

MPU6050 to Arduino Uno wiring diagram with VCC to 5V, GND to GND, SDA to A4 and SCL to A5
MPU6050 pinArduino Uno and Nano pinDescription
VCC5V or 3.3VPower supply
GNDGNDGround
SCLA5I2C clock
SDAA4I2C data
INTNot connectedOptional interrupt output
ADOGND or not connectedSelects the address 0x68

On the Arduino Mega, connect SDA to pin 20 and SCL to pin 21. The Leonardo has dedicated SDA and SCL pins that are labeled on the board. Most GY-521 modules include pull-up resistors on SDA and SCL, so no external resistors are needed. Because A4 and A5 carry the I2C bus, they cannot be used as analog inputs while the sensor is connected. The Arduino UNO pinout guide shows all pin functions of the board.


MPU6050 I2C Address

The MPU6050 uses a 7-bit I2C address, and the ADO pin selects one of two values.

  • 0x68 is the default address, used when ADO is LOW or left at its onboard pull-down.
  • 0x69 is the alternate address, used when ADO is pulled HIGH.

Most modules tie ADO to GND, so the Arduino talks to the sensor at 0x68. A project with two MPU6050 sensors can use both addresses on the same bus, with ADO pulled HIGH on the second module. If the sensor does not respond, run the I2C scanner from the Arduino I2C tutorial to confirm the address.


MPU6050 Register Map

The MPU6050 stores its measurements in registers, and each axis occupies two bytes, a high byte followed by a low byte. The table lists the registers that this tutorial uses.

DataRegister addressesDescription
Accelerometer X0x3B and 0x3CHigh byte, then low byte
Accelerometer Y0x3D and 0x3EHigh byte, then low byte
Accelerometer Z0x3F and 0x40High byte, then low byte
Temperature0x41 and 0x42Raw temperature value
Gyroscope X0x43 and 0x44High byte, then low byte
Gyroscope Y0x45 and 0x46High byte, then low byte
Gyroscope Z0x47 and 0x48High byte, then low byte

The following configuration registers are also used.

RegisterAddressPurpose
SMPLRT_DIV0x19Sample rate divider
CONFIG0x1ADigital low-pass filter setting
GYRO_CONFIG0x1BGyroscope full-scale range
ACCEL_CONFIG0x1CAccelerometer full-scale range
PWR_MGMT_10x6BPower management and sleep control
WHO_AM_I0x75Device identity register, reads 0x68 on a genuine MPU6050

The sensor starts in sleep mode after power-up, so the first step in every sketch is to write to PWR_MGMT_1 to wake it. Writing 0x01 clears the sleep bit and selects the X-axis gyroscope as the clock source, which the manufacturer recommends because it is more stable than the internal oscillator.


MPU6050 Arduino Raw Data

The first goal is to read the raw measurements, which are signed 16-bit numbers taken straight from the sensor registers. The sensor stores all measurements in consecutive registers, so one request that starts at register 0x3B can read the accelerometer, the temperature and the gyroscope in a single transfer of 14 bytes.

Read Raw Accelerometer and Gyroscope Data

The sketch below wakes the sensor and prints the raw values of all six axes every 200 ms. The helper function readWord() reads the high byte first and the low byte second, and it stores them in separate variables. This is more reliable than the expression (Wire.read() << 8) | Wire.read(), because the C++ standard does not define the order in which the two calls in that expression are evaluated.

#include <Wire.h>

#define MPU_ADDR     0x68   // Default I2C address (ADO = LOW)
#define PWR_MGMT_1   0x6B
#define ACCEL_XOUT_H 0x3B

void writeRegister(byte reg, byte value) {
  Wire.beginTransmission(MPU_ADDR);
  Wire.write(reg);
  Wire.write(value);
  Wire.endTransmission(true);
}

// Reads two bytes (high byte first) and combines them into a signed value
int16_t readWord() {
  int16_t high = Wire.read();
  int16_t low  = Wire.read();
  return (high << 8) | low;
}

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

  // Wake up the sensor and use the X-axis gyro as the clock source
  writeRegister(PWR_MGMT_1, 0x01);

  Serial.println("MPU6050 initialized");
}

void loop() {
  // Start reading at the first accelerometer register
  Wire.beginTransmission(MPU_ADDR);
  Wire.write(ACCEL_XOUT_H);
  Wire.endTransmission(false);
  Wire.requestFrom(MPU_ADDR, 14, true);

  int16_t accX = readWord();
  int16_t accY = readWord();
  int16_t accZ = readWord();
  readWord();                 // Temperature, skipped here
  int16_t gyroX = readWord();
  int16_t gyroY = readWord();
  int16_t gyroZ = readWord();

  Serial.print("Acc: ");
  Serial.print(accX); Serial.print(", ");
  Serial.print(accY); Serial.print(", ");
  Serial.print(accZ);
  Serial.print(" | Gyro: ");
  Serial.print(gyroX); Serial.print(", ");
  Serial.print(gyroY); Serial.print(", ");
  Serial.println(gyroZ);

  delay(200);
}

The sketch works in three steps.

  1. The setup function writes 0x01 to PWR_MGMT_1, which wakes the sensor and selects a stable clock.
  2. The loop requests 14 bytes starting at register 0x3B, which covers the accelerometer, the temperature and the gyroscope.
  3. The helper function combines each pair of bytes into a signed 16-bit value, and the loop prints the six values.

Open the Serial Monitor at 9600 baud to see the raw values. The Arduino UART tutorial explains how the Serial Monitor works if you want more background.

Arduino Serial Monitor showing raw MPU6050 accelerometer and gyroscope values for the X, Y and Z axes

With the board lying flat, the Z accelerometer value should be close to 16384, and the other axes should be near zero. The gyroscope values should be small, but they will not be exactly zero, because every sensor has a small offset. The calibration section below removes these offsets.


MPU6050 Unit Conversion (g and °/s)

Raw values are not useful on their own, because they depend on the measurement range that is selected. Dividing a raw value by the sensitivity of the selected range converts it to a physical unit. The accelerometer result is in g, where 1 g is the acceleration of gravity, and the gyroscope result is in degrees per second.

Sensitivity and Range Settings

The sensor starts with the smallest ranges, which are ±2 g and ±250 °/s. These ranges give the highest resolution, but fast motion above the limit clips the reading. The ranges are set through the ACCEL_CONFIG and GYRO_CONFIG registers.

SensorRangeRegister valueSensitivity
Accelerometer±2 g0x0016384 LSB/g
Accelerometer±4 g0x088192 LSB/g
Accelerometer±8 g0x104096 LSB/g
Accelerometer±16 g0x182048 LSB/g
Gyroscope±250 °/s0x00131 LSB/(°/s)
Gyroscope±500 °/s0x0865.5 LSB/(°/s)
Gyroscope±1000 °/s0x1032.8 LSB/(°/s)
Gyroscope±2000 °/s0x1816.4 LSB/(°/s)

The conversion formulas for the default ranges are the following.

acceleration (g)       = raw value / 16384.0
rotation rate (°/s)    = raw value / 131.0

For example, a raw accelerometer value of 8192 equals 0.5 g, and a raw gyroscope value of 262 equals 2 °/s. The raw temperature value converts to degrees Celsius with the formula raw / 340.0 + 36.53.


Print Acceleration, Rotation and Temperature

The following sketch sets the ranges explicitly, enables the digital low-pass filter and prints the converted values. The filter at 44 Hz reduces vibration noise, and with the filter enabled, the sample rate is 1 kHz divided by one more than the divider value, so a divider of 7 gives 125 Hz.

#include <Wire.h>

#define MPU_ADDR     0x68
#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 writeRegister(byte reg, byte value) {
  Wire.beginTransmission(MPU_ADDR);
  Wire.write(reg);
  Wire.write(value);
  Wire.endTransmission(true);
}

int16_t readWord() {
  int16_t high = Wire.read();
  int16_t low  = Wire.read();
  return (high << 8) | low;
}

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

  writeRegister(PWR_MGMT_1, 0x01);    // Wake up, X-axis gyro clock
  writeRegister(SMPLRT_DIV, 0x07);    // 1 kHz / (7 + 1) = 125 Hz
  writeRegister(CONFIG, 0x03);        // Low-pass filter at 44 Hz
  writeRegister(GYRO_CONFIG, 0x00);   // +/-250 deg/s
  writeRegister(ACCEL_CONFIG, 0x00);  // +/-2 g

  Serial.println("MPU6050 initialized");
}

void loop() {
  Wire.beginTransmission(MPU_ADDR);
  Wire.write(ACCEL_XOUT_H);
  Wire.endTransmission(false);
  Wire.requestFrom(MPU_ADDR, 14, true);

  int16_t rawAccX  = readWord();
  int16_t rawAccY  = readWord();
  int16_t rawAccZ  = readWord();
  int16_t rawTemp  = readWord();
  int16_t rawGyroX = readWord();
  int16_t rawGyroY = readWord();
  int16_t rawGyroZ = readWord();

  float ax = rawAccX / 16384.0;
  float ay = rawAccY / 16384.0;
  float az = rawAccZ / 16384.0;

  float gx = rawGyroX / 131.0;
  float gy = rawGyroY / 131.0;
  float gz = rawGyroZ / 131.0;

  float tempC = rawTemp / 340.0 + 36.53;

  Serial.print("Accel (g): ");
  Serial.print(ax); Serial.print(", ");
  Serial.print(ay); Serial.print(", ");
  Serial.println(az);

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

  Serial.print("Temperature (C): ");
  Serial.println(tempC);
  Serial.println("----------------------------");

  delay(200);
}
Arduino Serial Monitor showing MPU6050 acceleration in g, rotation in degrees per second and temperature in Celsius

These values were taken with the sensor lying flat on a table. The Z acceleration is close to 1 g, but the other readings show small offsets, and the gyroscope does not read exactly zero. The next section removes these errors. The temperature value is the temperature of the chip, which is usually a few degrees higher than the room temperature.


MPU6050 Calibration

Every MPU6050 has small factory offsets, so the readings are slightly wrong even when the sensor is still. These offsets cause drift in angle calculations and unstable behavior in balancing projects. Calibration measures the offsets once and subtracts them from every later reading.

The method has four steps.

  1. Place the sensor flat on a table with the Z axis pointing up, and keep it perfectly still.
  2. Read several hundred samples from the accelerometer and the gyroscope.
  3. Average the samples for each axis to get the offsets.
  4. Subtract the offsets from every later raw reading.

Repeat the calibration when you mount the sensor in a new position, and let the sensor run for a minute first, because the offsets change slightly while the chip warms up.

Calibrate the Accelerometer and Gyroscope

The sketch below averages 500 samples and prints the six offsets. The gyroscope should read zero when it is still, so its average is the offset directly. The accelerometer Z axis should read 1 g, which is 16384 counts in the ±2 g range, so that value is subtracted from the Z average before it is stored. If your sensor is mounted with the Z axis pointing down, use -16384 instead.

#include <Wire.h>

#define MPU_ADDR     0x68
#define PWR_MGMT_1   0x6B
#define ACCEL_XOUT_H 0x3B

#define SAMPLES 500

void writeRegister(byte reg, byte value) {
  Wire.beginTransmission(MPU_ADDR);
  Wire.write(reg);
  Wire.write(value);
  Wire.endTransmission(true);
}

int16_t readWord() {
  int16_t high = Wire.read();
  int16_t low  = Wire.read();
  return (high << 8) | low;
}

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

  writeRegister(PWR_MGMT_1, 0x01);
  delay(1000);

  long sumAccX = 0, sumAccY = 0, sumAccZ = 0;
  long sumGyroX = 0, sumGyroY = 0, sumGyroZ = 0;

  Serial.println("Calibrating... keep the sensor still and flat.");

  for (int i = 0; i < SAMPLES; i++) {
    Wire.beginTransmission(MPU_ADDR);
    Wire.write(ACCEL_XOUT_H);
    Wire.endTransmission(false);
    Wire.requestFrom(MPU_ADDR, 14, true);

    sumAccX += readWord();
    sumAccY += readWord();
    sumAccZ += readWord();
    readWord();                 // Temperature, skipped
    sumGyroX += readWord();
    sumGyroY += readWord();
    sumGyroZ += readWord();

    delay(3);
  }

  Serial.println("Calibration done. Offsets:");
  Serial.print("accX_offset = ");  Serial.println(sumAccX / SAMPLES);
  Serial.print("accY_offset = ");  Serial.println(sumAccY / SAMPLES);
  Serial.print("accZ_offset = ");  Serial.println(sumAccZ / SAMPLES - 16384);
  Serial.print("gyroX_offset = "); Serial.println(sumGyroX / SAMPLES);
  Serial.print("gyroY_offset = "); Serial.println(sumGyroY / SAMPLES);
  Serial.print("gyroZ_offset = "); Serial.println(sumGyroZ / SAMPLES);
}

void loop() {
}

The values that the sketch prints are the offsets for your own sensor. You can copy them into a final sketch as constants, or you can run the calibration routine at every start-up, as the complete sketch in the next section does.

Arduino Serial Monitor showing the MPU6050 accelerometer and gyroscope offset values after calibration

Apply the Offsets

After the calibration, subtract the offsets from the raw values before the conversion.

float ax = (rawAccX - accX_offset) / 16384.0;
float gx = (rawGyroX - gyroX_offset) / 131.0;

The expression in brackets is calculated as a long integer, which avoids an overflow of the 16-bit raw value when a large offset is subtracted.


MPU6050 Complete Arduino Code

The sketch below combines everything from the previous sections. It checks that the sensor responds, calibrates both sensors at start-up, subtracts the offsets, converts the values to g and °/s, and prints the results together with the tilt angles that the accelerometer provides. Keep the sensor flat and still for the first two seconds after you reset the board.

#include <Wire.h>

#define MPU_ADDR     0x68
#define WHO_AM_I     0x75
#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

#define SAMPLES 500

const float ACC_SCALE  = 16384.0;  // +/-2 g
const float GYRO_SCALE = 131.0;    // +/-250 deg/s

long accX_offset = 0, accY_offset = 0, accZ_offset = 0;
long gyroX_offset = 0, gyroY_offset = 0, gyroZ_offset = 0;

void writeRegister(byte reg, byte value) {
  Wire.beginTransmission(MPU_ADDR);
  Wire.write(reg);
  Wire.write(value);
  Wire.endTransmission(true);
}

int16_t readWord() {
  int16_t high = Wire.read();
  int16_t low  = Wire.read();
  return (high << 8) | low;
}

// Reads all 14 data bytes starting at the accelerometer registers
void readSensor(int16_t &ax, int16_t &ay, int16_t &az,
                int16_t &gx, int16_t &gy, int16_t &gz) {
  Wire.beginTransmission(MPU_ADDR);
  Wire.write(ACCEL_XOUT_H);
  Wire.endTransmission(false);
  Wire.requestFrom(MPU_ADDR, 14, true);

  ax = readWord();
  ay = readWord();
  az = readWord();
  readWord();               // Temperature, skipped
  gx = readWord();
  gy = readWord();
  gz = readWord();
}

void calibrate() {
  long sAx = 0, sAy = 0, sAz = 0, sGx = 0, sGy = 0, sGz = 0;
  int16_t ax, ay, az, gx, gy, gz;

  Serial.println("Calibrating... keep the sensor still and flat.");

  for (int i = 0; i < SAMPLES; i++) {
    readSensor(ax, ay, az, gx, gy, gz);
    sAx += ax; sAy += ay; sAz += az;
    sGx += gx; sGy += gy; sGz += gz;
    delay(3);
  }

  accX_offset  = sAx / SAMPLES;
  accY_offset  = sAy / SAMPLES;
  accZ_offset  = sAz / SAMPLES - 16384;   // Remove the 1 g of gravity
  gyroX_offset = sGx / SAMPLES;
  gyroY_offset = sGy / SAMPLES;
  gyroZ_offset = sGz / SAMPLES;

  Serial.println("Calibration done.");
}

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

  // Check that the sensor answers at the expected address
  Wire.beginTransmission(MPU_ADDR);
  if (Wire.endTransmission() != 0) {
    Serial.println("MPU6050 not found. Check the wiring and the address.");
    while (true);
  }

  writeRegister(PWR_MGMT_1, 0x01);    // Wake up, X-axis gyro clock
  writeRegister(SMPLRT_DIV, 0x07);    // 125 Hz sample rate
  writeRegister(CONFIG, 0x03);        // Low-pass filter at 44 Hz
  writeRegister(GYRO_CONFIG, 0x00);   // +/-250 deg/s
  writeRegister(ACCEL_CONFIG, 0x00);  // +/-2 g
  delay(1000);

  calibrate();
}

void loop() {
  int16_t rawAx, rawAy, rawAz, rawGx, rawGy, rawGz;
  readSensor(rawAx, rawAy, rawAz, rawGx, rawGy, rawGz);

  // Subtract the offsets, then convert to g and deg/s
  float ax = (rawAx - accX_offset) / ACC_SCALE;
  float ay = (rawAy - accY_offset) / ACC_SCALE;
  float az = (rawAz - accZ_offset) / ACC_SCALE;

  float gx = (rawGx - gyroX_offset) / GYRO_SCALE;
  float gy = (rawGy - gyroY_offset) / GYRO_SCALE;
  float gz = (rawGz - gyroZ_offset) / GYRO_SCALE;

  // Tilt angles from the accelerometer, valid while the board is not accelerating
  float roll  = atan2(ay, az) * 180.0 / PI;
  float pitch = atan2(-ax, sqrt(ay * ay + az * az)) * 180.0 / PI;

  Serial.print("Acc (g): ");
  Serial.print(ax); Serial.print(", ");
  Serial.print(ay); Serial.print(", ");
  Serial.println(az);

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

  Serial.print("Roll: ");  Serial.print(roll);
  Serial.print("  Pitch: "); Serial.println(pitch);
  Serial.println("---------------------");

  delay(200);
}

Check the Output

After the calibration, the accelerometer should read close to 0, 0 and 1 g on a flat table, and the gyroscope should read close to 0 °/s on all axes. Small variations of a few hundredths are normal because of sensor noise.

Arduino Serial Monitor showing calibrated MPU6050 output with Z acceleration near 1 g and gyroscope values near zero

Two quick tests show that the sensor works correctly.

  1. Tilt the board forward and sideways. The matching accelerometer axes and the roll and pitch values should change smoothly, without sudden jumps.
  2. Rotate the board and stop. The gyroscope values should rise during the rotation and return to nearly zero when the board is still.

MPU6050 Troubleshooting

Most problems with the MPU6050 come from the wiring, the address or a sensor that is still in sleep mode.

The Sensor Is Not Found

The sketch above stops with an error message when the sensor does not answer. Check that SDA is on A4 and SCL is on A5, that the module has power, and that the connections are firm. Run the I2C scanner from the Arduino I2C tutorial to find the address, and change MPU_ADDR to 0x69 if the scanner reports that value.


All Values Are Zero or -1

If every raw value is -1, the Arduino received no data, because Wire.read() returns -1 when nothing is available. If all values are zero or never change, the sensor is probably still in sleep mode, so make sure the sketch writes to the PWR_MGMT_1 register before it reads any data.


The Module Is Not a Genuine MPU6050

Some modules that are sold as an MPU6050 contain a different chip, such as an MPU6500. You can read the WHO_AM_I register at 0x75 to check the chip. A genuine MPU6050 returns 0x68, and a different value means that the chip is a different part. The register map is similar, so the examples often still work, but the offsets and the noise may differ.


Large Offsets or Drifting Angles

Large offsets usually mean that the board was moving or tilted during the calibration. Calibrate again on a flat, steady surface. A slow drift of an angle that is calculated from the gyroscope is normal, because integrating a small constant error grows over time. Projects that need a stable angle combine the gyroscope and the accelerometer with a complementary filter.


MPU6050 Arduino Projects

The MPU6050 gives both acceleration and rotation data in one module, so it suits many motion-based projects.

Self-Balancing Robots

Two-wheeled balancing robots use the sensor to measure the pitch angle and the rotation speed. The controller drives the motors to keep the robot upright, and the gyroscope reacts quickly to sudden changes while the accelerometer keeps the long-term angle correct.


Gesture Control

Tilt and rotation readings can control a robot, a cursor or a game with hand movements. A small wearable board that sends the angles over a wireless link is a common build.


Motion Tracking and Stabilization

DIY head trackers, game controllers, wearable motion loggers and small gimbal or drone stabilizers use the same 6-axis data to follow movement and orientation over time.


Tilt Alarm and Display Projects

A tilt alarm can play a sound on a passive buzzer when the angle passes a limit, and the SSD1306 OLED can show the angles on a small screen. Other I2C devices, such as the DS3231 real-time clock, can share the same two wires, so a motion logger with time stamps needs no extra pins.


Conclusion

This tutorial showed how to wire the MPU6050 to an Arduino, read its raw accelerometer and gyroscope registers, convert the values to g and °/s, and calibrate all six axes. The important points are the sleep bit that must be cleared before the first read, the sensitivity values for the selected range and the need to calibrate on a flat, still surface. The complete sketch gives you a clean starting point for tilt, balance and motion projects.




About the Author
Arun Rawat
Arun Rawat
Embedded Systems Engineer · Founder, ControllersTech

Arun is an embedded systems engineer with 10+ years of experience in STM32, ESP32, and AVR microcontrollers. He created ControllersTech to share practical tutorials on embedded software, HAL drivers, RTOS, and hardware design — grounded in real industrial automation experience.

Subscribe
Notify of

0 Comments
Newest
Oldest Most Voted
×

Don’t Miss Future STM32 Tutorials

Join thousands of developers getting free guides, code examples, and updates.