Advanced
MPU6050 IMU
Servo Control
I2C / OLED
Arduino Uno
Dual Servo Control with OLED Display & MPU6050
Tilt the MPU6050 IMU and watch two servo motors respond in real time — roll controls Servo X, pitch controls Servo Y. Live angles display on an SSD1306 OLED screen. Perfect for robotic arms, pan-tilt cameras, and motion-based control systems.
Servo X (Roll)
Servo Y (Pitch)
MPU6050 Tilt → 2 Servos
Roll (X): 0.0 deg
Servo X: 90 CENTER
Pitch (Y): 0.0 deg
Servo Y: 90 CENTER
01 Components Required
Arduino Uno
ATmega328P
2 × Servo Motor
Standard 180° SG90
SSD1306 OLED
128×64 I2C, 0x3C
MPU6050 IMU
6-axis, I2C 0x68
Half Breadboard
For I2C bus wiring
Jumper Wires
Male-to-male
Required Libraries
built-in Servo
built-in Wire (I2C)
install Adafruit SSD1306
install Adafruit GFX Library
Install libraries: In Arduino IDE open the Library Manager (book icon), search for "Adafruit SSD1306" and install it along with its dependency "Adafruit GFX Library". In Wokwi these are available automatically.
02 Circuit Wiring Guide
Servo Motor 1 — X-axis (Roll control)
| Servo Wire | Color | Arduino Pin |
|---|---|---|
| Signal | Orange / Yellow | Pin 9 |
| VCC | Red | 5V |
| GND | Brown / Black | GND |
Servo Motor 2 — Y-axis (Pitch control)
| Servo Wire | Color | Arduino Pin |
|---|---|---|
| Signal | Orange / Yellow | Pin 10 |
| VCC | Red | 5V |
| GND | Brown / Black | GND |
OLED Display (SSD1306 I2C)
| OLED Pin | Arduino Pin | Notes |
|---|---|---|
| VCC | 5V | Power |
| GND | GND | Ground |
| SDA | A4 | I2C Data |
| SCL | A5 | I2C Clock |
MPU6050 IMU (I2C — shares bus with OLED)
| MPU6050 Pin | Arduino Pin | Notes |
|---|---|---|
| VCC | 5V | Power |
| GND | GND | Ground |
| SDA | A4 | Shared I2C Data bus |
| SCL | A5 | Shared I2C Clock bus |
I2C Addresses: The OLED uses
0x3C and the MPU6050 uses 0x68 — both can share the same SDA/SCL lines since they have different addresses.03 Step-by-Step Build Instructions
1
Create a new Wokwi project
Go to wokwi.com, click New Project, and select Arduino Uno. The board appears in your workspace automatically.
2
Add components
Click the "+" button and add: 2× Servo Motor (Standard 180°), 1× OLED Display 128×64 SSD1306, 1× MPU6050 IMU, and 1× Half Breadboard (for shared I2C wiring).
3
Wire both servos to PWM pins
Connect Servo 1's signal wire to pin 9 and Servo 2's signal wire to pin 10. Both servos' VCC and GND connect to the 5V and GND rails via the breadboard.
4
Wire the I2C bus (OLED + MPU6050)
Both devices share the I2C bus. Connect A4 (SDA) and A5 (SCL) from the Arduino to the breadboard's SDA and SCL rows. Then connect both the OLED's and MPU6050's SDA/SCL pins into those rows. Power each from the 5V and GND rails.
5
Paste the diagram.json
In Wokwi, click the diagram.json tab on the left panel and paste the JSON from Section 06 below. This auto-builds the entire circuit layout and wiring for you.
6
Paste the code and add libraries
Paste the sketch from Section 05 into the code editor. In Wokwi's Library Manager, add Adafruit SSD1306 and Adafruit GFX Library. Servo and Wire are built-in.
7
Start simulation and tilt!
Click the green ▶ Start Simulation button. Click on the MPU6050 sensor in the simulation and drag sliders to simulate tilting. Both servos respond immediately, and the OLED updates in real time.
04 Control Modes
IMU Tilt Mode (Default)
Tilt the MPU6050 physically (or drag sliders in Wokwi). Roll angle → Servo X. Pitch angle → Servo Y. Tilt range ±45° maps to 0°–180°.
Serial Monitor Commands
Open the Serial Monitor in Wokwi and type numeric commands for manual angle adjustments and centering both servos instantly.
Potentiometer Upgrade
Add 2 potentiometers to A0 and A1. Rotate to manually set each servo angle independently — great for robotic arm joint control.
Serial Monitor Command Reference
Serial Commands (9600 baud)
| 1 | Increase Servo 1 (X-axis) by 10° |
| 2 | Decrease Servo 1 (X-axis) by 10° |
| 3 | Increase Servo 2 (Y-axis) by 10° |
| 4 | Decrease Servo 2 (Y-axis) by 10° |
| C | Center both servos to 90° |
05 Arduino Code
Arduino C++ — MPU6050 + Dual Servo + OLED
#include <Wire.h>
#include <Servo.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
// ---------- OLED ----------
#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64
#define OLED_ADDR 0x3C
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, -1);
// ---------- MPU6050 ----------
#define MPU_ADDR 0x68
#define REG_PWR_MGMT_1 0x6B
#define REG_ACCEL_XOUT_H 0x3B
// ---------- Servos ----------
#define SERVO_X_PIN 9 // X-axis (roll)
#define SERVO_Y_PIN 10 // Y-axis (pitch)
Servo servoX;
Servo servoY;
// ---------- Timing (non-blocking) ----------
const unsigned long SENSOR_INTERVAL_MS = 20; // ~50 Hz
const unsigned long OLED_INTERVAL_MS = 100; // 10 Hz
unsigned long lastSensorMs = 0;
unsigned long lastOledMs = 0;
// ---------- State ----------
float rollDeg = 0.0;
float pitchDeg = 0.0;
int servoXAngle = 90;
int servoYAngle = 90;
int lastServoXAngle = 90;
int lastServoYAngle = 90;
// ---------- Helpers ----------
static float clampf(float x, float lo, float hi) {
if (x < lo) return lo;
if (x > hi) return hi;
return x;
}
static int clampi(int x, int lo, int hi) {
if (x < lo) return lo;
if (x > hi) return hi;
return x;
}
// Map tilt ±45° → servo 0°–180° (center at 90°)
int tiltToServo(float tiltDeg) {
const float TILT_MAX = 45.0f;
float t = clampf(tiltDeg, -TILT_MAX, TILT_MAX);
float servo = (t + TILT_MAX) * (180.0f / (2.0f * TILT_MAX));
return clampi((int)(servo + 0.5f), 0, 180);
}
const char* dirLeftRight(int angle) {
if (angle > 95) return "RIGHT";
if (angle < 85) return "LEFT";
return "CENTER";
}
const char* dirForwardBack(int angle) {
if (angle > 95) return "FORWARD";
if (angle < 85) return "BACK";
return "CENTER";
}
// ---------- MPU6050 low-level ----------
void mpuWrite(byte reg, byte value) {
Wire.beginTransmission(MPU_ADDR);
Wire.write(reg);
Wire.write(value);
Wire.endTransmission(true);
}
bool mpuReadAccelRaw(int16_t &ax, int16_t &ay, int16_t &az) {
Wire.beginTransmission(MPU_ADDR);
Wire.write(REG_ACCEL_XOUT_H);
if (Wire.endTransmission(false) != 0) return false;
if (Wire.requestFrom(MPU_ADDR, (uint8_t)6, (uint8_t)true) != 6) return false;
ax = (int16_t)((Wire.read() << 8) | Wire.read());
ay = (int16_t)((Wire.read() << 8) | Wire.read());
az = (int16_t)((Wire.read() << 8) | Wire.read());
return true;
}
void mpuInit() {
mpuWrite(REG_PWR_MGMT_1, 0x00); // Wake up MPU6050
}
void computeTiltFromAccel(int16_t axRaw, int16_t ayRaw, int16_t azRaw) {
float ax = (float)axRaw;
float ay = (float)ayRaw;
float az = (float)azRaw;
rollDeg = atan2(ay, az) * 180.0f / PI;
pitchDeg = atan2(-ax, sqrt(ay*ay + az*az)) * 180.0f / PI;
}
// ---------- OLED Display ----------
void drawOLED() {
display.clearDisplay();
display.setTextSize(1);
display.setTextColor(SSD1306_WHITE);
display.setCursor(0, 0);
display.print("MPU6050 Tilt -> 2 Servos");
display.setCursor(0, 16);
display.print("Roll (X): "); display.print(rollDeg, 1); display.println(" deg");
display.setCursor(0, 26);
display.print("Servo X: "); display.print(servoXAngle);
display.print(" "); display.println(dirLeftRight(servoXAngle));
display.setCursor(0, 42);
display.print("Pitch (Y): "); display.print(pitchDeg, 1); display.println(" deg");
display.setCursor(0, 52);
display.print("Servo Y: "); display.print(servoYAngle);
display.print(" "); display.println(dirForwardBack(servoYAngle));
display.display();
}
void setup() {
Wire.begin();
Serial.begin(115200);
mpuInit();
servoX.attach(SERVO_X_PIN);
servoY.attach(SERVO_Y_PIN);
servoX.write(90);
servoY.write(90);
if (!display.begin(SSD1306_SWITCHCAPVCC, OLED_ADDR)) {
Serial.println("OLED init failed");
} else {
display.clearDisplay();
display.display();
}
}
void loop() {
unsigned long now = millis();
// 1) Read sensor + update servos at ~50 Hz
if (now - lastSensorMs >= SENSOR_INTERVAL_MS) {
lastSensorMs = now;
int16_t ax, ay, az;
if (mpuReadAccelRaw(ax, ay, az)) {
computeTiltFromAccel(ax, ay, az);
servoXAngle = tiltToServo(rollDeg);
servoYAngle = tiltToServo(pitchDeg);
// Only write if changed (reduces jitter)
if (abs(servoXAngle - lastServoXAngle) >= 1) {
servoX.write(servoXAngle); lastServoXAngle = servoXAngle;
}
if (abs(servoYAngle - lastServoYAngle) >= 1) {
servoY.write(servoYAngle); lastServoYAngle = servoYAngle;
}
Serial.print("Roll="); Serial.print(rollDeg, 1);
Serial.print(" Pitch="); Serial.print(pitchDeg, 1);
Serial.print(" | SX="); Serial.print(servoXAngle);
Serial.print(" SY="); Serial.println(servoYAngle);
} else {
Serial.println("MPU read failed");
}
}
// 2) Refresh OLED at 10 Hz
if (now - lastOledMs >= OLED_INTERVAL_MS) {
lastOledMs = now;
drawOLED();
}
}
06 Wokwi diagram.json
Paste this into your Wokwi project's diagram.json tab to instantly recreate the complete circuit with all components positioned and wired correctly.
📄 diagram.json — Wokwi Circuit File
{
"version": 1,
"author": "MakeMindz",
"editor": "wokwi",
"parts": [
{ "type": "wokwi-breadboard-half", "id": "bb1", "top": -252.6, "left": 118, "attrs": {} },
{ "type": "wokwi-arduino-uno", "id": "uno", "top": -585, "left": 287.4, "attrs": {} },
{ "type": "wokwi-mpu6050", "id": "imu1", "top": -207.38, "left": 568.72, "attrs": {} },
{
"type": "board-ssd1306",
"id": "oled1",
"top": -25.66,
"left": 297.83,
"attrs": { "i2cAddress": "0x3c" }
},
{ "type": "wokwi-servo", "id": "servo1", "top": -434, "left": 28.8, "attrs": {} },
{ "type": "wokwi-servo", "id": "servo2", "top": -568.4, "left": 28.8, "attrs": {} }
],
"connections": [
[ "uno:GND.1", "bb1:tn.25", "black", [ "v0" ] ],
[ "uno:5V", "bb1:tp.25", "red", [ "v0" ] ],
[ "uno:A4", "bb1:25t.a", "green", [ "v0" ] ],
[ "uno:A5", "bb1:24t.b", "green", [ "v0" ] ],
[ "imu1:SDA", "bb1:25t.c", "green", [ "v0" ] ],
[ "oled1:SDA", "bb1:25t.d", "green", [ "v0" ] ],
[ "imu1:SCL", "bb1:24t.c", "green", [ "v0" ] ],
[ "oled1:SCL", "bb1:24t.e", "green", [ "v0" ] ],
[ "imu1:VCC", "bb1:tp.24", "red", [ "v0" ] ],
[ "imu1:GND", "bb1:tn.23", "black", [ "v0" ] ],
[ "oled1:VCC", "bb1:tp.15", "red", [ "v0" ] ],
[ "oled1:GND", "bb1:tn.15", "black", [ "v0" ] ],
[ "servo2:GND", "bb1:tn.1", "black", [ "h0" ] ],
[ "servo1:GND", "bb1:tn.2", "black", [ "h0" ] ],
[ "servo1:V+", "bb1:tp.3", "green", [ "h0" ] ],
[ "servo2:V+", "bb1:tp.4", "green", [ "h0" ] ],
[ "servo2:PWM", "uno:9", "green", [ "h0" ] ],
[ "servo1:PWM", "uno:10", "green", [ "h0" ] ]
],
"dependencies": {}
}
07 Run the Simulation
Try it instantly — no hardware needed!
Run the full dual servo + OLED + IMU simulation in your browser on Wokwi. Drag the MPU6050 tilt sliders to see both servos respond and watch the OLED update live.
✓ MPU6050 tilt sliders
✓ Live OLED display
✓ Real servo animation
✓ Serial Monitor
✓ Free forever
▶ Open Free Simulation on Wokwi
08 Expected Behavior
OLED Display Shows
- ✓ "MPU6050 Tilt → 2 Servos" title header
- ✓ Roll (X) angle in degrees with live decimal value
- ✓ Servo X angle + directional label (LEFT / CENTER / RIGHT)
- ✓ Pitch (Y) angle in degrees with live decimal value
- ✓ Servo Y angle + directional label (BACK / CENTER / FORWARD)
Servo Motors
- ✓ Smooth 0°–180° rotation from tilt input
- ✓ Jitter suppression — only moves when angle changes by ≥1°
- ✓ Instant response at 50 Hz update rate
- ✓ Both servos operate fully independently
Serial Monitor
- ✓ Continuous output: Roll, Pitch, Servo X, Servo Y angles
- ✓ "MPU read failed" message if I2C connection drops
09 Learning Outcomes
- PWM-based servo motor control
- I2C communication protocol
- Reading MPU6050 accelerometer data
- Roll & pitch tilt calculation
- SSD1306 OLED graphics with Adafruit GFX
- Non-blocking timing with millis()
- Multi-device shared I2C bus
- Real-time sensor-to-actuator mapping
10 Real-World Applications
Robotic Arms
Tilt-based joint control for 2-axis robot manipulation
Pan-Tilt Camera
Self-stabilizing or remote-controlled camera gimbal
CNC Prototypes
Dual-axis positioning for automation mechanisms
Mechatronics
Ideal STEM classroom demonstration project
Drone Gimbal
Camera stabilization using IMU feedback
Comments
Post a Comment