Arduino Dual Servo Motor Control with OLED Display - Complete Wokwi Simulator Tutorial 2026

Arduino Dual Servo Control with OLED & MPU6050 | MakeMindz
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 WireColorArduino Pin
SignalOrange / YellowPin 9
VCCRed5V
GNDBrown / BlackGND

Servo Motor 2 — Y-axis (Pitch control)

Servo WireColorArduino Pin
SignalOrange / YellowPin 10
VCCRed5V
GNDBrown / BlackGND

OLED Display (SSD1306 I2C)

OLED PinArduino PinNotes
VCC5VPower
GNDGNDGround
SDAA4I2C Data
SCLA5I2C Clock

MPU6050 IMU (I2C — shares bus with OLED)

MPU6050 PinArduino PinNotes
VCC5VPower
GNDGNDGround
SDAA4Shared I2C Data bus
SCLA5Shared 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)
1Increase Servo 1 (X-axis) by 10°
2Decrease Servo 1 (X-axis) by 10°
3Increase Servo 2 (Y-axis) by 10°
4Decrease Servo 2 (Y-axis) by 10°
CCenter 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

© 2026 MakeMindz.com — Arduino Tutorials & Electronics Projects

Comments

try for free