Electronic projects

[Electronic projects][bleft]

Arduino Projects

[Arduino Projects][bleft]

Metal detectors

[Metal detectors][bleft]

Home projects

[Home projects][bleft]

Self-balancing robot using Arduino development board


After being inspired by Segway's RYNO Motors electric unicycle and other self-balancing scooters, I always wanted to make something similar. Based on this, I decided to use the Arduino development board to make a self-balancing robot. In this way, I will be able to grasp the basic concepts behind all these scooters and understand how the PID algorithm works.

After starting production, I realized that this robot is a bit challenging to make. There are many options to choose from, so from selecting the motor to adjusting the PID value, it is full of doubts. There are many things to consider, such as battery type, battery location, wheel handles, motor drive type, maintaining CoG (center of gravity), and so on.

However, once it is done, you will feel that it is not as difficult as it sounds. Let's start to face it. In this article, we will mainly learn the knowledge of making self-balancing robots. You may be a pure beginner who is just starting out, or you may come here after a long time without the frustration of getting your robot to work. This place aims to be your ultimate destination. let's start......

Parts used

● Arduino UNO development board

● Geared DC motor (yellow)-2Nos

● L298N Motor Driver Module

● MPU6050

● A pair of wheels

● 7.4V lithium ion battery

● Connecting wires

● 3D printed car body

Controller : The controller I use here is Arduino UNO, why? Because it is simple and easy to use. You can also use Arduino Nano or Arduino mini, but I suggest you stick to UNO because we can program directly without any external hardware.

Motor : There is no doubt that the best motor choice you can use a self-balancing robot is a stepper motor. But for the sake of simplicity, I used a DC geared motor. Yes, there is no mandatory requirement for a stepper; robots can also use these cheap yellow DC geared motors.

Motor driver : If you choose a DC geared motor like mine, then you can use an L298N drive module like mine, and even L293D should work normally.

Wheels : Don't be low on these guys; it's hard for me to figure out that the problem is on the wheels. Therefore, make sure that your wheels have good grip on the floor you are using. Observe carefully, your grip should never let your wheels slide on the floor.

Accelerometer and gyroscope : The best choice for the robot's accelerometer and gyroscope is MPU6050. Therefore, don't try to make it with an ordinary accelerometer like ADXL345, it will not work. You will know why at the end of this article.

Battery : We need a battery that is as light as possible, and the working voltage should be greater than 5V, so that we can directly power the Arduino without a booster module. Therefore, the ideal choice is a 7.4V lithium polymer battery. Here, since I have a 7.4V lithium ion battery, I have already used it. But remember, Li-po has more advantages than Li-ion.

Car body : Another area you should not overlook is your robot car body. You can use cardboard, wood, plastic to make anything you like. However, just make sure that the car body is sturdy and should not swing when the robot is trying to balance. I designed my own car body based on other robots on Solidworks and printed it out in 3D. If you have a printer, you can also print the design, and the design file will be attached to the next heading.

3D printing and assembly robots

If you decide to 3D print the same chassis I used to build the robot, you can download the STL file from thingiverse . I also added the design file, so you can also modify it according to personal preference.

These parts have no overhang structure, so you can easily print them without any support, and 25% of the filler can work normally. The design is simple, and any basic printer should be able to handle it easily. I use Cura software to cut the model and use my Tevo Tarantula to print, the settings are as follows.

You need to print the body parts and four motor mounting parts. The assembly is very simple; use 3mm nuts and bolts to hold the motor and circuit board in place. After assembly, it should look like the picture below.

The actual design plan is that the L298N driver module is in the bottom rack of the Arduino, and the battery is on the top of the Arduino, as shown in the figure above. If you follow the same sequence, you can screw the board directly through the holes provided and use the wires of the lithium battery. This arrangement should also work, except for the super flat wheels that I had to change later.

In my robot, I have changed the position of the battery and the Arduino UNO board to facilitate programming, and I must also introduce a perforated board to complete the connection. So my robot does not look like my plan in the early stages. After completing the wiring programming test and everything, my robot finally looks like this

Circuit schematic

Designing the connection for this Arduino-based self-balancing robot is very simple. We only need to connect MPU6050 with Arduino, and then connect the motor through the motor drive module. The entire device is powered by a 7.4V lithium ion battery. The circuit diagram is shown below.

The Arduino and L298N motor driver modules are directly powered through the Vin pin and 12V terminal, respectively. The on-board voltage regulator on the Arduino board converts the input 7.4V to 5V, and the ATmega IC and MPU6050 will be powered by it. The DC motor can run from 5V to 12V. But we connect the 7.4V positive wire from the battery to the 12V input terminal of the motor drive module. This will make the motor run at 7.4V. The following table lists how to connect MPU6050 and L298N motor driver modules with Arduino.

Component pin
Arduino pins

+ 5V


MPU6050 communicates with Arduino through the I2C interface, so we use Arduino's SPI pins A4 and A5. The DC motors are connected to PWM pins D6, D9, D10, and D11, respectively. We need to connect them to the PWM pins because we will control the speed of the DC motor by changing the duty cycle of the PWM signal.

Programming a self-balancing robot

Now we need to program the Arduino UNO development board to balance the robot. This is where all the magic happens; the concept behind it is simple. We need to check whether the robot is tilted forward or backward by using MPU6050, and then if it tilts forward we must rotate the wheel forward, and if it tilts backward we must rotate the wheel in the opposite direction.

At the same time, we must also control the rotation speed of the wheels. If the robot is slightly disoriented from the center position, the wheels will rotate slowly, and the speed will increase as the vehicle moves further and further away from the center position. In order to realize this logic, we use PID algorithm, whose center position is the set point, and the intensity of disorientation is the output.

To know the current position of the robot, we use MPU6050, which is a combination of 6-axis accelerometer and gyroscope sensor. In order to obtain a reliable position value from the sensor, we need to use the accelerometer and gyroscope value, because the accelerometer value has a noise problem, and the gyroscope value will drift over time. Therefore, we must combine the two and obtain the yaw pitch and roll values ​​of our robot, where we will only use the yaw value.

Does it sound dizzy? But don’t worry, in the Arduino community, we have ready-made libraries that can perform PID calculations and also get the yaw value from MPU6050. The library was developed by br3ttb and jrowberg respectively . Before downloading these libraries from the link below, add them to your Arduino lib directory first.



Now, we have added the library to the Arduino IDE. Let's start programming the self-balancing robot. We have given the complete code of the project at the end of this page, here we just explain the most important code snippets in the code. Before introducing these codes, we first declare that the code is based on the MPU6050 sample code . We just optimize the code for the purpose and add PID and control technology to our self-balancing robot.

First, we include the libraries needed for this program to work. Including the built-in I2C library, the PID library we just downloaded and the MPU6050 library.

  1. #include "I2Cdev.h"
  2. #include <PID_v1.h> //From https://github.com/br3ttb/Arduino-PID-Library/blob/master/PID_v1.h
  3. #include "MPU6050_6Axis_MotionApps20.h" //https://github.com/jrowberg/i2cdevlib/tree/master/Arduino/MPU6050

Then we declare the variables needed to obtain data from the MPU6050 sensor. We read the gravity vector and quaternion values, and then calculate the yaw pitch and roll values ​​of the robot. The floating-point array ypr[3] will hold the final result.

  1. // MPU control/status vars
  2. bool dmpReady = false; // set true if DMP init was successful
  3. uint8_t mpuIntStatus; // holds actual interrupt status byte from MPU
  4. uint8_t devStatus; // return status after each device operation (0 = success, !0 = error)
  5. uint16_t packetSize; // expected DMP packet size (default is 42 bytes)
  6. uint16_t fifoCount; // count of all bytes currently in FIFO
  7. uint8_t fifoBuffer[64]; // FIFO storage buffer

  8. // orientation/motion vars
  9. Quaternion q; // [w, x, y, z] quaternion container
  10. VectorFloat gravity; // [x, y, z] gravity vector
  11. float ypr[3]; // [yaw, pitch, roll] yaw/pitch/roll container and gravity vector

Next is the very important part of the code, which is where you spend a long time adjusting the correct value set. If your robot has a very good center of gravity and the components are arranged symmetrically (which is not in most cases), then your setpoint value will be 180. Otherwise connect your robot to the Arduino serial monitor and tilt it until you find a good balance position, read the value displayed on the serial monitor, which is your set value. The values ​​of Kp, Kd and Ki must be adjusted according to your robot. No two identical robots will have the same Kp, Kd, ​​and Ki values, so there is no way to avoid it.

  1. /*********Tune these 4 values ​​for your BOT*********/
  2. double setpoint = 176; //set the value when the bot is perpendicular to ground using serial monitor.
  3. //Read the project documentation on circuitdigest.com to learn how to set these values
  4. double Kp = 21; //Set this first
  5. double Kd = 0.8; //Set this secound
  6. double Ki = 140; //Finally set this
  7. /******End of values ​​setting*********/

In the following code, we initialize the PID algorithm by passing the input variables input, output, set point, Kp, Ki, and Kd. Among them, we have set the values ​​of the set points Kp, Ki and Kd in the above code snippet. The input value will be the current value of yaw read from the MPU6050 sensor, and the output value will be the value calculated by the PID algorithm. Therefore, basically the PID algorithm will provide us with an output value that should be used to correct the input value to be close to the set point.

  1. PID pid(&input, &output, &setpoint, Kp, Ki, Kd, ​​DIRECT);

In the void setup() function, we initialize MPU6050 by configuring DMP (Digital Motion Processor). This will help us combine accelerometer data with gyroscope data and provide reliable yaw, pitch and roll values. We will not delve into this point, because it goes far beyond the subject. In any case, the piece of code you must look for in the setup function is the gyroscope offset value. Each MPU6050 sensor has its own offset value, you can use this Arduino sketch to calculate the offset value of the sensor and update the following lines in the program accordingly.

  1.     // supply your own gyro offsets here, scaled for min sensitivity
  2.     mpu.setXGyroOffset(220);
  3.     mpu.setYGyroOffset(76);
  4.     mpu.setZGyroOffset(-85);
  5.     mpu.setZAccelOffset(1688);

We must also initialize the digital PWM pins that we use to connect to the motor. In our example, D6, D9, D10, and D11 are used. Therefore, we initialize these pins as output pins, and the default is low level.

  1. //Initialise the Motor outpu pins
  2.     pinMode (6, OUTPUT);
  3.     pinMode (9, OUTPUT);
  4.     pinMode (10, OUTPUT);
  5.     pinMode (11, OUTPUT);

  6. //By default turn off both the motors
  7.     analogWrite(6,LOW);
  8.     analogWrite(9,LOW);
  9.     analogWrite(10,LOW);
  10.     analogWrite(11,LOW);

Inside the loop function, we check whether the data of MPU6050 can be read. If it is, we use it to calculate the PID value, and then display the PID input and output values ​​on the serial monitor to check how the PID responds. Then based on the output value we decide whether the robot moves forward or backward or stands still.

Because we assume that the MPU6050 will return 180 when the robot is upright. When the robot is tilted forward, we will get a positive correction value, if the robot is tilted backward, we will get a negative value. So we check this condition and call the corresponding function to move the robot forward or backward.

  1.     while (!mpuInterrupt && fifoCount <packetSize)
  2.     {
  3.         //no mpu data-performing PID calculations and output to motors   
  4.         pid.Compute();  
  6.         //Print the value of Input and Output on serial monitor to check how it is working.
  7.         Serial.print(input); Serial.print(" =>"); Serial.println(output);
  9.         if (input>150 && input<200){//If the Bot is falling
  11.         if (output>0) //Falling towards front
  12.         Forward(); //Rotate the wheels forward
  13.         else if (output<0) //Falling towards back
  14.         Reverse(); //Rotate the wheels backward
  15.         }
  16.         else //If Bot not falling
  17.         Stop(); //Hold the wheels still
  18.     }

The PID output variable also determines the speed at which the motor rotates. If the robot is about to fall, then we make minor corrections by slowly rotating the wheels. If these small corrections are still effective, but if the robot falls, we will increase the speed of the motor. The speed value of the wheel rotation will be determined by the PI algorithm. Note that for the Reverse function, we multiply the output value by -1 so that we can convert negative values ​​to positive values.

  1. void Forward() //Code to rotate the wheel forward
  2. {
  3.     analogWrite(6,output);
  4.     analogWrite(9,0);
  5.     analogWrite(10,output);
  6.     analogWrite(11,0);
  7.     Serial.print("F"); //Debugging information
  8. }

  9. void Reverse() //Code to rotate the wheel Backward
  10. {
  11.     analogWrite(6,0);
  12.     analogWrite(9,output*-1);
  13.     analogWrite(10,0);
  14.     analogWrite(11,output*-1);
  15.    Serial.print("R");
  16. }

  17. void Stop() //Code to stop both the wheels
  18. {
  19.     analogWrite(6,0);
  20.     analogWrite(9,0);
  21.     analogWrite(10,0);
  22.     analogWrite(11,0);
  23.     Serial.print("S");
  24. }

Working process of Arduino self-balancing robot

After preparing the hardware, you can upload the code to the Arduino development board. Make sure that the connection is correct, because we are using lithium-ion batteries, we need to be especially careful. Therefore, please check carefully whether there is a short circuit and make sure that even if your robot encounters some minor effects, the terminals will not be shorted. Start the module and open the serial monitor, if your Arduino can successfully communicate with the MPU6050, and if everything is normal, you should see the following interface.

Here, we see the input and output values ​​of the PID algorithm in input => output format. If the robot is fully balanced, the output value will be 0. The input value is the current value of the MPU6050 sensor. The letter "F" means that the robot moves forward, and "R" means that the robot moves backward.

In the initial stage of PID, I recommend connecting the Arduino cable to the robot so that you can easily monitor the input and output values, and it is easy to correct and upload the Kp, Ki and Kd values ​​of your program.

I hope this article will help make your own self-balancing robot. If you encounter any problems during the production process, please reply to this post.

Complete Code:

The following is the complete code used in this article: 


/*Arduino Self Balancing Robot
 * Code by: B.Aswinth Raj
 * Build on top of Lib: https://github.com/jrowberg/i2cdevlib/tree/master/Arduino/MPU6050
 * Website: circuitdigest.com

#include "I2Cdev.h"
#include <PID_v1.h> //From https://github.com/br3ttb/Arduino-PID-Library/blob/master/PID_v1.h
#include "MPU6050_6Axis_MotionApps20.h" //https://github.com/jrowberg/i2cdevlib/tree/master/Arduino/MPU6050

MPU6050 mpu;

// MPU control/status vars
bool dmpReady = false;  // set true if DMP init was successful
uint8_t mpuIntStatus;   // holds actual interrupt status byte from MPU
uint8_t devStatus;      // return status after each device operation (0 = success, !0 = error)
uint16_t packetSize;    // expected DMP packet size (default is 42 bytes)
uint16_t fifoCount;     // count of all bytes currently in FIFO
uint8_t fifoBuffer[64]; // FIFO storage buffer

// orientation/motion vars
Quaternion q;           // [w, x, y, z]         quaternion container
VectorFloat gravity;    // [x, y, z]            gravity vector
float ypr[3];           // [yaw, pitch, roll]   yaw/pitch/roll container and gravity vector


/*********Tune these 4 values for your BOT*********/
double setpoint= 176; //set the value when the bot is perpendicular to ground using serial monitor.
//Read the project documentation on circuitdigest.com to learn how to set these values
double Kp = 21; //Set this first
double Kd = 0.8; //Set this secound
double Ki = 140; //Finally set this
/******End of values setting*********/

double input, output;
PID pid(&input, &output, &setpoint, Kp, Ki, Kd, DIRECT);


volatile bool mpuInterrupt = false;     // indicates whether MPU interrupt pin has gone high
void dmpDataReady()
    mpuInterrupt = true;

void setup() {

  // initialize device
    Serial.println(F("Initializing I2C devices..."));

     // verify connection
    Serial.println(F("Testing device connections..."));
    Serial.println(mpu.testConnection() ? F("MPU6050 connection successful") : F("MPU6050 connection failed"));

    // load and configure the DMP
    devStatus = mpu.dmpInitialize();

    // supply your own gyro offsets here, scaled for min sensitivity

      // make sure it worked (returns 0 if so)
    if (devStatus == 0)
        // turn on the DMP, now that it's ready
        Serial.println(F("Enabling DMP..."));

        // enable Arduino interrupt detection
        Serial.println(F("Enabling interrupt detection (Arduino external interrupt 0)..."));
        attachInterrupt(0, dmpDataReady, RISING);
        mpuIntStatus = mpu.getIntStatus();

        // set our DMP Ready flag so the main loop() function knows it's okay to use it
        Serial.println(F("DMP ready! Waiting for first interrupt..."));
        dmpReady = true;

        // get expected DMP packet size for later comparison
        packetSize = mpu.dmpGetFIFOPacketSize();
        //setup PID
        pid.SetOutputLimits(-255, 255);  
        // ERROR!
        // 1 = initial memory load failed
        // 2 = DMP configuration updates failed
        // (if it's going to break, usually the code will be 1)
        Serial.print(F("DMP Initialization failed (code "));

//Initialise the Motor outpu pins
    pinMode (6, OUTPUT);
    pinMode (9, OUTPUT);
    pinMode (10, OUTPUT);
    pinMode (11, OUTPUT);

//By default turn off both the motors


void loop() {
    // if programming failed, don't try to do anything
    if (!dmpReady) return;

    // wait for MPU interrupt or extra packet(s) available
    while (!mpuInterrupt && fifoCount < packetSize)
        //no mpu data - performing PID calculations and output to motors     
        //Print the value of Input and Output on serial monitor to check how it is working.
        Serial.print(input); Serial.print(" =>"); Serial.println(output);
        if (input>150 && input<200){//If the Bot is falling
        if (output>0) //Falling towards front
        Forward(); //Rotate the wheels forward
        else if (output<0) //Falling towards back
        Reverse(); //Rotate the wheels backward
        else //If Bot not falling
        Stop(); //Hold the wheels still

    // reset interrupt flag and get INT_STATUS byte
    mpuInterrupt = false;
    mpuIntStatus = mpu.getIntStatus();

    // get current FIFO count
    fifoCount = mpu.getFIFOCount();

    // check for overflow (this should never happen unless our code is too inefficient)
    if ((mpuIntStatus & 0x10) || fifoCount == 1024)
        // reset so we can continue cleanly
        Serial.println(F("FIFO overflow!"));

    // otherwise, check for DMP data ready interrupt (this should happen frequently)
    else if (mpuIntStatus & 0x02)
        // wait for correct available data length, should be a VERY short wait
        while (fifoCount < packetSize) fifoCount = mpu.getFIFOCount();

        // read a packet from FIFO
        mpu.getFIFOBytes(fifoBuffer, packetSize);
        // track FIFO count here in case there is > 1 packet available
        // (this lets us immediately read more without waiting for an interrupt)
        fifoCount -= packetSize;

        mpu.dmpGetQuaternion(&q, fifoBuffer); //get value for q
        mpu.dmpGetGravity(&gravity, &q); //get value for gravity
        mpu.dmpGetYawPitchRoll(ypr, &q, &gravity); //get value for ypr

        input = ypr[1] * 180/M_PI + 180;


void Forward() //Code to rotate the wheel forward
    Serial.print("F"); //Debugging information

void Reverse() //Code to rotate the wheel Backward  

void Stop() //Code to stop both the wheels





Arduino Projects

[Arduino Projects][twocolumns]