IoTSimulator

Track Motion and Tilt with an ESP32-C3

Read 3-axis motion and tilt from an MPU6050 accelerometer and change an RGB LED's color based on how the sensor tilts. Learn how I2C communication and motion sensors work on the ESP32-C3.
Muhammad Ichsanul Fadhil
IoTSim Editor
September 30, 2026
Track Motion and Tilt with an ESP32-C3

Live project track

Interactive hardware & logic preview

When you rotate a smartphone from portrait to landscape mode, or when a drone auto-levels itself in mid-air, the device is reading motion using a tiny inertial sensor. These micro-sensors measure acceleration in three dimensions, allowing software to detect orientation, movement, and tilt with remarkable precision.

In this project, you will build an interactive motion and tilt tracker using an ESP32-C3 microcontroller, an MPU6050 6-axis motion sensor, and an RGB LED. The main idea is that Earth's gravitational pull provides a constant downward reference: as you tilt the sensor, gravity shifts between the X and Y axes, and the ESP32-C3 immediately changes the LED's color to show which direction it is leaning.

How an Accelerometer Measures Tilt

Inside the MPU6050 sensor sits a microscopic mechanical mass suspended on tiny silicon springs. Earth's gravity constantly pulls on this mass with an acceleration of approximately 9.8 meters per second squared (9.8 m/s²). By measuring which direction that gravitational force is pulling, the sensor determines its orientation relative to the ground.

Accelerometer gravity vectors on X, Y, and Z axes during level and tilted states
Accelerometer gravity vectors on X, Y, and Z axes during level and tilted states

The diagram above illustrates the three primary orientations. When the sensor sits completely flat on a table, all 9.8 m/s² of gravity acts along the vertical Z-axis, while the horizontal X and Y axes read close to zero. When you tip the board forward or backward (Pitch), gravity bleeds into the X-axis. When you lean it left or right (Roll), gravity shifts into the Y-axis.

Sensor positionX-axis accelerationY-axis accelerationZ-axis acceleration
Flat & LevelNear 0.0 m/s²Near 0.0 m/s²~9.8 m/s² (Downwards)
Tilted forward/back (Pitch)> 3.0 m/s²Near 0.0 m/s²Reduced (~8.5 m/s²)
Tilted left/right (Roll)Near 0.0 m/s²> 3.0 m/s²Reduced (~8.5 m/s²)
Dual diagonal tilt> 3.0 m/s²> 3.0 m/s²Reduced (~7.0 m/s²)

Setting a threshold of 3.0 m/s² corresponds to a tilt angle of approximately 18 degrees. Any tilt greater than this triggers the state change in firmware, ignoring minor table vibrations while catching intentional hand movements.

Showing Tilt States with an RGB LED

To make the orientation visible at a glance without reading text on a computer screen, the project uses a common-cathode RGB LED as a multi-color status beacon. Four clear visual states tell you whether the board is level or leaning.

Four visual RGB LED states: Level Green, Pitch Red, Roll Blue, Compound Magenta
Four visual RGB LED states: Level Green, Pitch Red, Roll Blue, Compound Magenta

The visual above shows the four color states. When both axes are below the 3.0 m/s² threshold, the LED shines solid green to confirm the board is level. Tilting along the Pitch axis switches the LED to solid red. Tilting along Roll switches it to blue. If the board is tilted diagonally along both axes at once, the red and blue channels turn on together to create magenta.

Orientation stateTilt conditionActive LED colorActive pins
Level / BalancedBoth |X| and |Y| ≤ 3.0 m/s²Solid GreenGPIO 4 (Green)
Pitch Tilt|X| > 3.0 m/s², |Y| ≤ 3.0 m/s²Solid RedGPIO 3 (Red)
Roll Tilt|X| ≤ 3.0 m/s², |Y| > 3.0 m/s²Solid BlueGPIO 5 (Blue)
Dual TiltBoth |X| and |Y| > 3.0 m/s²Magenta (Purple)GPIO 3 (Red) + GPIO 5 (Blue)

This direct optical feedback makes the sensor feel alive and responsive, providing immediate feedback in robotics, gaming controllers, or leveling gauges.

Connecting the Circuit

Think of this project like a digital carpenter's spirit level. When the surface is perfectly flat, the system stays calm with a green light; the moment something tips or falls over, the alert light changes immediately.

Bubble level analogy showing safe green light when level and warning red light when tilted
Bubble level analogy showing safe green light when level and warning red light when tilted

As shown in the illustration, the MPU6050 acts as the digital bubble, detecting orientation changes instantly. Connect the motion sensor module and RGB LED indicator as detailed below:

Pin Connection Map
MPU6050 6-Axis Sensor
VCC
→
ESP32-C3 DevKit
3V3 (3.3V Power)
Explanation
Powers the sensor module's internal electronics.
MPU6050 6-Axis Sensor
GND
→
ESP32-C3 DevKit
GND (Pin GND.1)
Explanation
Connects to system common ground.
MPU6050 6-Axis Sensor
SDA (Data)
→
ESP32-C3 DevKit
GPIO 8
Explanation
I2C Serial Data line carrying acceleration registers.
MPU6050 6-Axis Sensor
SCL (Clock)
→
ESP32-C3 DevKit
GPIO 9
Explanation
I2C Serial Clock line synchronizing bus communications.

Next, wire the three color channels of the RGB LED indicator to GPIO 3, GPIO 4, and GPIO 5 to visually display tilt orientation:

Pin Connection Map
Common-Cathode RGB LED
Red Anode (R)
→
ESP32-C3 DevKit
GPIO 3
Explanation
Digital output driving the red channel during pitch tilt.
Common-Cathode RGB LED
Green Anode (G)
→
ESP32-C3 DevKit
GPIO 4
Explanation
Digital output driving the green channel when level.
Common-Cathode RGB LED
Blue Anode (B)
→
ESP32-C3 DevKit
GPIO 5
Explanation
Digital output driving the blue channel during roll tilt.
Common-Cathode RGB LED
Cathode (Longest Leg)
→
ESP32-C3 DevKit
GND (Pin GND.2)
Explanation
Common ground return for all three color channels.

The MPU6050 operates on 3.3V power. Connecting it to 5V could damage the sensitive internal MEMS silicon. Always verify that VCC connects to the 3.3V rail on the ESP32-C3.

Complete Code

Upload the following sketch to your ESP32-C3 DevKit. It initializes the I2C bus, configures the MPU6050 sensor's internal low-pass filter, and continuously updates the RGB LED while streaming telemetry to the serial console.

C++ Source
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
// ============================================================================
// ESP32-C3 6-Axis Motion & Tilt Tracker with MPU6050
// Demonstrates I2C IMU Telemetry, Gravitational Vectors & RGB State Feedback
// ============================================================================

#include <Wire.h>
#include <Adafruit_MPU6050.h>
#include <Adafruit_Sensor.h>

Adafruit_MPU6050 mpu;

const int RED_PIN   = 3; // GPIO 3 - Pitch Tilt Alert (Red)
const int GREEN_PIN = 4; // GPIO 4 - Level Stable Indicator (Green)
const int BLUE_PIN  = 5; // GPIO 5 - Roll Tilt Alert (Blue)

void setup() {
  Serial.begin(115200);
  while (!Serial && millis() < 2000);
  
  // Configure hardware I2C pins on ESP32-C3 (SDA: GPIO 8, SCL: GPIO 9)
  Wire.begin(8, 9);

  pinMode(RED_PIN, OUTPUT);
  pinMode(GREEN_PIN, OUTPUT);
  pinMode(BLUE_PIN, OUTPUT);

  Serial.println(F("===================================================="));
  Serial.println(F("  ESP32-C3 6-Axis Motion & Tilt Tracker Initializing"));
  Serial.println(F("===================================================="));

  if (!mpu.begin()) {
    Serial.println(F("[ERROR] Failed to detect MPU6050 sensor on I2C bus!"));
    while (1) { delay(100); }
  }

  mpu.setAccelerometerRange(MPU6050_RANGE_8_G);
  mpu.setGyroRange(MPU6050_RANGE_500_DEG);
  mpu.setFilterBandwidth(MPU6050_BAND_21_HZ);
  
  Serial.println(F("MPU6050 Sensor Calibrated & Online."));
}

void loop() {
  sensors_event_t a, g, temp;
  mpu.getEvent(&a, &g, &temp);

  // Evaluate tilt vectors along Pitch (X-axis) and Roll (Y-axis)
  // Threshold 3.0 m/s^2 corresponds to approximately 17.8 degrees tilt
  bool pitchHigh = abs(a.acceleration.x) > 3.0;
  bool rollHigh  = abs(a.acceleration.y) > 3.0;

  // Visual Orientation Mapping:
  // Level (Balanced) -> Solid Green
  // Pitch Tilt       -> Solid Red
  // Roll Tilt        -> Solid Blue
  // Dual Tilt        -> Magenta (Red + Blue)
  digitalWrite(RED_PIN, pitchHigh ? HIGH : LOW);
  digitalWrite(GREEN_PIN, (!pitchHigh && !rollHigh) ? HIGH : LOW);
  digitalWrite(BLUE_PIN, rollHigh ? HIGH : LOW);

  // Stream telemetry to Serial
  Serial.print(F("Accel [m/s^2] X: ")); Serial.print(a.acceleration.x, 2);
  Serial.print(F(" | Y: ")); Serial.print(a.acceleration.y, 2);
  Serial.print(F(" | Z: ")); Serial.print(a.acceleration.z, 2);
  Serial.print(F(" | Status: "));
  if (!pitchHigh && !rollHigh) Serial.println(F("[LEVEL]"));
  else if (pitchHigh && rollHigh) Serial.println(F("[DUAL TILT]"));
  else if (pitchHigh) Serial.println(F("[PITCH TILT]"));
  else Serial.println(F("[ROLL TILT]"));

  delay(100);
}

How the Code Works, Part by Part

The sketch coordinates between the sensor's I2C communications and output pin control. Here is how each section functions.

▸ Libraries and I2C Setup

The sketch starts by including Wire.h for I2C and the Adafruit_MPU6050 library to read the sensor.

C++ Source
1
2
3
4
5
6
7
8
9
#include <Wire.h>
#include <Adafruit_MPU6050.h>
#include <Adafruit_Sensor.h>

Adafruit_MPU6050 mpu;

const int RED_PIN   = 3;
const int GREEN_PIN = 4;
const int BLUE_PIN  = 5;

Assigning named constants to the output pins keeps the code readable and easy to adapt if pins change.

▸ Configuring the MPU6050 Sensor in setup()

Inside setup(), the code calls Wire.begin(8, 9) to route I2C signals to GPIO 8 (SDA) and GPIO 9 (SCL), then initializes the sensor.

C++ Source
1
2
3
4
5
6
7
8
Wire.begin(8, 9);

if (!mpu.begin()) {
  Serial.println(F("[ERROR] Failed to detect MPU6050!"));
  while (1) { delay(100); }
}

mpu.setFilterBandwidth(MPU6050_BAND_21_HZ);

The setFilterBandwidth(MPU6050_BAND_21_HZ) setting activates an on-chip digital filter that removes mechanical jitter and table vibrations, ensuring steady tilt readings.

▸ Reading Acceleration and Deciding Tilt States

In loop(), mpu.getEvent() reads the newest sensor measurements. The code tests whether either horizontal axis exceeds 3.0 m/s².

C++ Source
1
2
3
4
5
sensors_event_t a, g, temp;
mpu.getEvent(&a, &g, &temp);

bool pitchHigh = abs(a.acceleration.x) > 3.0;
bool rollHigh  = abs(a.acceleration.y) > 3.0;

Using abs() checks for tilt in either direction along an axis (forward or backward for Pitch, left or right for Roll).

▸ Controlling the RGB LED and Streaming Telemetry

Finally, the code drives the three LED pins based on the detected tilt states.

C++ Source
1
2
3
digitalWrite(RED_PIN, pitchHigh ? HIGH : LOW);
digitalWrite(GREEN_PIN, (!pitchHigh && !rollHigh) ? HIGH : LOW);
digitalWrite(BLUE_PIN, rollHigh ? HIGH : LOW);

The program repeats this measurement every 100 milliseconds (10 times per second), providing rapid, fluid visual feedback.

Fixing Common Problems

If the sensor fails to start or the LED shows incorrect colors, consult the quick solutions below.

What you seeLikely causeWhat to try
Serial says '[ERROR] Failed to detect MPU6050'I2C wires swapped or loose connectionEnsure SDA is wired to GPIO 8 and SCL to GPIO 9; verify 3.3V power is connected
LED colors are inverted (LED turns off when it should turn on)Common-anode LED used instead of common-cathodeVerify common leg connects to GND; common-anode LEDs need 5V and inverted HIGH/LOW logic
Colors flicker rapidly when board is barely movedSensor filter bandwidth disabledVerify mpu.setFilterBandwidth(MPU6050_BAND_21_HZ) is present in setup()
LED stays green even when fully tiltedThreshold set too high in softwareConfirm the condition checks 3.0 m/s² rather than raw integer sensor units

The most common issue with I2C sensors is swapping the SDA and SCL wires. If the sensor is not found at startup, reversing those two pins will usually fix the issue immediately.

Try It in the Simulator

Click the Start Simulation (▶) button in the top toolbar to begin. The RGB LED will turn solid green to indicate the board is level. Click on the MPU6050 Sensor on the canvas to open its 3D rotation controls. Drag the tilt sliders along the Pitch and Roll axes to watch the LED switch between red, blue, and magenta in real time!

Project Category
Keywords
#ESP32-C3 #MPU6050 #RGB LED #I2C #Inertial Tracking #Motion #RISC-V
Total word count: 1161 words

Project discussion

Questions, feedback, and community insights
No discussions yet. Be the first to start!