Cirkit Designer LogoCirkit DesignerYour all-in-one circuit design IDE Launch Cirkit Designer App

Arduino UNO Self-Balancing Robot with MPU6050 and L298N Motor Driver

View and Edit Project In Cirkit DesignerImage of Arduino UNO Self-Balancing Robot with MPU6050 and L298N Motor Driver

Circuit Documentation

Summary

This circuit is designed to control a motor and wheels system using an Arduino UNO microcontroller, an L298N DC motor driver, and an MPU6050 sensor for motion detection. The system is powered by a 12V battery. The Arduino UNO reads data from the MPU6050 sensor and uses a PID controller to adjust the motor speed and direction to maintain a setpoint angle.

Component List

  1. Arduino UNO

    • Description: Microcontroller board based on the ATmega328P.
    • Pins: UNUSED, IOREF, Reset, 3.3V, 5V, GND, Vin, A0, A1, A2, A3, A4, A5, SCL, SDA, AREF, D13, D12, D11, D10, D9, D8, D7, D6, D5, D4, D3, D2, D1, D0
  2. Motor and Wheels

    • Description: Motor and wheels system for movement.
    • Pins: vcc, GND
  3. L298N DC Motor Driver

    • Description: Dual H-Bridge motor driver for controlling DC motors.
    • Pins: OUT1, OUT2, 12V, GND, 5V, OUT3, OUT4, 5V-ENA-JMP-I, 5V-ENA-JMP-O, +5V-J1, +5V-J2, ENA, IN1, IN2, IN3, IN4, ENB
  4. Battery 12V

    • Description: 12V battery for powering the circuit.
    • Pins: +, -
  5. InvenSense MPU6050

    • Description: 6-axis motion tracking device.
    • Pins: VCC, GND, SCL, SDA, XDA, XCL, AD0, INT

Wiring Details

Arduino UNO

Motor and Wheels

L298N DC Motor Driver

Battery 12V

InvenSense MPU6050

Code Documentation

#include <PID_v1.h>  
#include <Wire.h>
#include "I2Cdev.h"
#include "MPU6050_6Axis_MotionApps20.h"

// Motor pins for L298N (controlling one motor)
int ENA = 9;
int IN1 = 7;
int IN2 = 6;

#define MIN_ABS_SPEED 35  // Increased minimum speed for better response

// MPU6050 object
MPU6050 mpu;

// MPU control/status vars
bool dmpReady = false; 
uint8_t mpuIntStatus;
uint8_t devStatus;
uint16_t packetSize;
uint16_t fifoCount;
uint8_t fifoBuffer[64];

// Orientation/motion vars
Quaternion q;
VectorFloat gravity;
float ypr[3];

// PID parameters
double setpoint = -5.5; // Target angle (upright, adjusted for 0 degrees)
double input, output;
double Kp = 30.0;   // Stronger corrective force for larger pendulum
double Kd = 0.5;    // Increased to help stabilize oscillations
double Ki = 200.0;  // Adjusted to prevent oscillations over time
PID pid(&input, &output, &setpoint, Kp, Ki, Kd, DIRECT);

// Motor control function for one motor
void setMotorSpeed(int speed) {
    // Ensure speed is within -255 to 255
    speed = constrain(speed, -255, 255);

    // Apply minimum speed for movement if needed
    if (speed > 0) {
        speed = max(speed, MIN_ABS_SPEED);
    } else if (speed < 0) {
        speed = min(speed, -MIN_ABS_SPEED);
    }

    if (speed > 0) {
        // Move forward
        analogWrite(ENA, speed);
        digitalWrite(IN1, HIGH);
        digitalWrite(IN2, LOW);
    } else if (speed < 0) {
        // Move backward
        analogWrite(ENA, -speed);
        digitalWrite(IN1, LOW);
        digitalWrite(IN2, HIGH);
    } else {
        // Stop motor
        analogWrite(ENA, 0);
    }
}

volatile bool mpuInterrupt = false;
void dmpDataReady() {
    mpuInterrupt = true;
}

void setup() {
    Serial.begin(2000000);

    // Motor pins setup
    pinMode(ENA, OUTPUT);
    pinMode(IN1, OUTPUT);
    pinMode(IN2, OUTPUT);

    // Wire setup for MPU6050
    Wire.begin();
    TWBR = 24; // Set I2C speed to 400kHz

    // MPU6050 initialization
    mpu.initialize();
    devStatus = mpu.dmpInitialize();

    // Supply your own gyro offsets here, scaled for min sensitivity
    mpu.setXGyroOffset(0);
    mpu.setYGyroOffset(0);
    mpu.setZGyroOffset(0);
    mpu.setZAccelOffset(0); // Adjust based on your calibration

    if (devStatus == 0) {
        mpu.setDMPEnabled(true);
        attachInterrupt(digitalPinToInterrupt(2), dmpDataReady, RISING);
        mpuIntStatus = mpu.getIntStatus();
        dmpReady = true;
        packetSize = mpu.dmpGetFIFOPacketSize();
        
        // PID setup
        pid.SetMode(AUTOMATIC);
        pid.SetSampleTime(10);
        pid.SetOutputLimits(-255, 255); 
    } else {
        Serial.print(F("DMP Initialization failed (code "));
        Serial.print(devStatus);
        Serial.println(F(")"));
    }
}

void loop() {
    if (!dmpReady) return;

    // Wait for MPU interrupt or extra packet(s) available
    while (!mpuInterrupt && fifoCount < packetSize) {
        // No new MPU data, run PID calculations
        pid.Compute();
        setMotorSpeed(output);

        // Print data for visualization
        Serial.print("Setpoint: ");
        Serial.print(setpoint);
        Serial.print(" | Input: ");
        Serial.print(input);
        Serial.print(" | Output: ");
        Serial.println(output);
    }

    mpuInterrupt = false;
    mpuIntStatus = mpu.getIntStatus();
    fifoCount = mpu.getFIFOCount();

    if ((mpuIntStatus & 0x10) || fifoCount == 1024) {
        mpu.resetFIFO();
        Serial.println(F("FIFO overflow!"));
    } else if (mpuIntStatus & 0x02) {
        while (fifoCount < packetSize) fifoCount = mpu.getFIFOCount();

        mpu.getFIFOBytes(fifoBuffer, packetSize);
        fifoCount -= packetSize;

        mpu.dmpGetQuaternion(&q, fifoBuffer);
        mpu.dmpGetGravity(&gravity, &q);
        mpu.dmpGetYawPitchRoll(ypr, &q, &