(DONE) Self Balancing Robot with Arduino Uno, L298N, MPU6050 and HC05

Hi everyone! I am new member of this community and I have finished one of my project Self balancing robot following the code from this man (many thanks)

I didn't change it much, just change the PID and pins used.It works. The problems is when I tried to connect my HC05 module into this schematic (rx/tx:0/1) with the control code from this man

my robot didn't work right. I want to ask someone who has did this project for the combination of balancing and remote code in one.
My schematic:
z5451264368466_ba10e57e175f2b30ee24f198150da8f3
I uploaded the codes below.

#include "PID_v1.h"
#include "LMotorController.h"
#include "I2Cdev.h"
#include <SimpleKalmanFilter.h>
#include "MPU6050_6Axis_MotionApps20.h"
#include <SoftwareSerial.h>

#if I2CDEV_IMPLEMENTATION == I2CDEV_ARDUINO_WIRE
    #include "Wire.h"
#endif
#define LOG_INPUT 0//Hiển thị trên serial plotter
#define MANUAL_TUNING 0//Hiệu chỉnh bằng tay
#define LOG_PID_CONSTANTS 0 //Ghi lại các giá trị hiệu chỉnh
#define MOVE_BACK_FORTH 0//Điều chỉnh setpoint để kéo robot theo hướng ngược lại. Chỉ số movingAngleOffset nên để nhỏ cỡ 0.1 - 0.2. Để lớn quá thì robot dễ mất ổn định
#define MIN_ABS_SPEED 5//Mức tốc độ tối thiểu

//RC
SoftwareSerial mySerial(0, 1);
String data;
char key;

//MPU
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

//PID
#define original_setpoint_offsets -1.7//Mặc định với cấu hình hiện tại (-2.3 -2.2)
#if MANUAL_TUNING
  double kp , ki, kd;
  double prevKp, prevKi, prevKd;
#endif
double originalSetpoint = 0 + original_setpoint_offsets;
double setpoint = originalSetpoint;
double movingAngleOffset = 0.3;//bù vừa phải
double input, output;
int moveState=0; 

//Tham số PID: 19 160 1.7
#define Kp 19
#define Ki 170
#define Kd 1.5
#if MANUAL_TUNING
  PID pid(&input, &output, &setpoint, 0, 0, 0, DIRECT);
#else
  PID pid(&input, &output, &setpoint, Kp, Ki, Kd, DIRECT);
#endif

//MOTOR CONTROLLER

#define ENA  3
#define IN1  4
#define IN2  5
#define IN3  6
#define IN4  7
#define ENB  9

LMotorController motorController(ENA, IN1, IN2, ENB, IN3, IN4, 1, 1);

//timers
long time1Hz = 0;
long time5Hz = 0;
long timeRC=0;
long timePID=0;

volatile bool mpuInterrupt = false;     // indicates whether MPU interrupt pin has gone high
void dmpDataReady()
{
    mpuInterrupt = true;
}
/////////////////////////////////////////////// cac ham function //////////////////////////////////////////////////////
void balance();
void loopAt1Hz();
void loopAt5Hz();
void moveBackForth();
void setPIDTuningValues();
void readPIDTuningValues();
void RC();
void loop_RC();
void forward();
void reverse();
void left();
void right();
void stoprobot();
void boost();
void spinright();
void spinleft();

//////////////////////////////////////////////           setup          //////////////////////////////////////////////////////////
void setup()
{
 
    Serial.println(ypr[1] * 180/M_PI);
    // join I2C bus (I2Cdev library doesn't do this automatically)
    #if I2CDEV_IMPLEMENTATION == I2CDEV_ARDUINO_WIRE
        Wire.begin();
        TWBR = 24; // 400kHz I2C clock (200kHz if CPU is 8MHz)
    #elif I2CDEV_IMPLEMENTATION == I2CDEV_BUILTIN_FASTWIRE
        Fastwire::setup(400, true);
    #endif

    // initialize serial communication
    // (115200 chosen because it is required for Teapot Demo output, but it's
    // really up to you depending on your project)
    Serial.begin(115200);
    while (!Serial); // wait for Leonardo enumeration, others continue immediately

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

    // 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
    Serial.println(F("Initializing DMP..."));
    devStatus = mpu.dmpInitialize();

    // supply your own gyro offsets here, scaled for min sensitivity
    mpu.setXGyroOffset(39);
    mpu.setYGyroOffset(14);
    mpu.setZGyroOffset(6);
    mpu.setZAccelOffset(1788); // 1688 factory default for my test chip

    // 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..."));
        mpu.setDMPEnabled(true);

        // 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.SetMode(AUTOMATIC);
        pid.SetSampleTime(5);
        pid.SetOutputLimits(-255, 255);
    }
    else
    {
        // 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 "));
        Serial.print(devStatus);
        Serial.println(F(")"));
    }
  pinMode(IN1, OUTPUT);
  pinMode(IN2, OUTPUT);
  pinMode(IN3, OUTPUT);
  pinMode(IN4, OUTPUT);
  //pinMode(EN1, OUTPUT);
  //pinMode(EN2, OUTPUT);
  digitalWrite(IN1, LOW);
  digitalWrite(IN2, LOW);
  digitalWrite(IN3, LOW);
  digitalWrite(IN4, LOW);
  //analogWrite(EN1,63);
  //analogWrite(EN2,63);
  mySerial.begin(115200);

}
///////////////////////////////////////////////           loop          //////////////////////////////////////////////////////////
void loop()
{ 
  balance();
  loop_RC();
}

//loop_RC

void loop_RC()
{
  //unsigned long currentMillis_RC = millis();
  if (mySerial.available() < 0)                              
    {
     Serial.println("No Bluetooth Data ");          
    }
  else
  {
    balance();
    while (mySerial.available())
    { 
      {
        data= mySerial.readStringUntil('\n');
        //Serial.print(str);
      
      }
      key = (data.toInt()); 
      switch (key) 
      {  
      //Tay cầm bên trái
      case 'F':                                
        Serial.println("Forward");
        forward();
        break;
      case 'B':                 
       Serial.println("Reverse");
        reverse();
        break;
      case 'L':         
       Serial.println("Left");
        left();
        break;
      //Tay cầm bên phải
      case 'R':                     
        Serial.println("Right");
        right();
        break;    
      case 'T':                     
        Serial.println("Right");
        boost();
        break;  
      case 'C':                     
        Serial.println("Right");
        spinright();
        break;  
      case 'S':                     
        Serial.println("Right");
        spinleft();
        break;  
      case 'X':                     
        Serial.println("Right");
        //balance();
        stoprobot();
        break;  
      //case 'A':                     
        //Serial.println("Start");
        //balance();
        //break;  

  }
  }
  }
}

//balance

//////////////////////////////
void balance()
{
     // 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
        
        pid.Compute();
        motorController.move(output, MIN_ABS_SPEED);
        
        unsigned long currentMillis = millis();
        
        if (currentMillis - time1Hz >= 100)
        {
            loopAt1Hz();
            time1Hz = currentMillis;
        }
        
        if (currentMillis - time5Hz >= 300)
        {
            loopAt5Hz();
            time5Hz = currentMillis;
        }
    }

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

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

   
    if ((mpuIntStatus & 0x10) || fifoCount == 1024)
    {
        // reset so we can continue cleanly
        mpu.resetFIFO();
        Serial.println(F("FIFO overflow!"));

    
    }
    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);
        
      
        fifoCount -= packetSize;

        mpu.dmpGetQuaternion(&q, fifoBuffer);
        mpu.dmpGetGravity(&gravity, &q);
        mpu.dmpGetYawPitchRoll(ypr, &q, &gravity);
        #if LOG_INPUT
            Serial.print("Yaw:");
            Serial.println(ypr[0] * 180/M_PI);
            Serial.print("Pitch:");
            Serial.println(ypr[1] * 180/M_PI);
            Serial.print("Roll:");
            Serial.println(ypr[2] * 180/M_PI);
            Serial.print("setpoint:");
            Serial.println(setpoint);
            Serial.println();
        #endif
        input = ypr[1] * 180/M_PI;
   }
}
////////////////////////

//movement

void forward()
{
  digitalWrite(IN1, HIGH);
  digitalWrite(IN2, LOW);
  digitalWrite(IN3, HIGH);
  digitalWrite(IN4, LOW);
  analogWrite(ENA, 130);
  analogWrite(ENB, 130);
}

void reverse()
{
  digitalWrite(IN1, LOW);
  digitalWrite(IN2, HIGH);
  digitalWrite(IN3, LOW);
  digitalWrite(IN4, HIGH);
  analogWrite(ENA, 130);
  analogWrite(ENB, 130);
}

void left()
{
  digitalWrite(IN1, LOW);
  digitalWrite(IN2, LOW);
  digitalWrite(IN3, HIGH);
  digitalWrite(IN4, LOW);
  analogWrite(ENA, 130);
}

void right()
{
  digitalWrite(IN1, HIGH);
  digitalWrite(IN2, LOW);
  digitalWrite(IN3, LOW);
  digitalWrite(IN4, LOW);
  analogWrite(ENB, 130);
}

void stoprobot()
{
  digitalWrite(IN1, LOW);
  digitalWrite(IN2, LOW);
  digitalWrite(IN3, LOW);
  digitalWrite(IN4, LOW);
  analogWrite(ENA, 0);
  analogWrite(ENA, 0);
}
void boost()
{
  digitalWrite(IN1, HIGH);
  digitalWrite(IN2, LOW);
  digitalWrite(IN3, HIGH);
  digitalWrite(IN4, LOW);
  analogWrite(ENA, 200);
  analogWrite(ENB, 200);
}
void spinright()
{
  digitalWrite(IN1, HIGH);
  digitalWrite(IN2, LOW);
  digitalWrite(IN3, LOW);
  digitalWrite(IN4, HIGH);
  analogWrite(ENB, 80);
  analogWrite(ENA, 80);
}
void spinleft()
{
  digitalWrite(IN1, LOW);
  digitalWrite(IN2, HIGH);
  digitalWrite(IN3, HIGH);
  digitalWrite(IN4, LOW);
  analogWrite(ENB, 80);
  analogWrite(ENA, 80);
}

Post the code here in code tags and explain what you mean by "didn't work right".

1 Like

The self balancing code uses interrupt 0

 attachInterrupt(0, dmpDataReady, RISING);

interrupt 0 is on pin 2 of the Uno

In the Bluetoooth code, SoftwareSerial uses pin 2

SoftwareSerial mySerial(2, 3); // RX, TX

Can you see a problem ?
What have you got connected to pin 2 of the Uno ?

1 Like

Thanks for your answer but I have used rx for 0, tx for 1.

Pins 0 and 1 are connected to the hardware serial line. If you use Serial then you can't use them for anything else. So trying to use them for SoftwareSerial creates a conflict

Do you mean that you have put SoftwareSerial on pins 0 and 1 of the Uno ?

Does your code use Serial.print(), Serial.println() or Serial.read() ?

Do you have any recommends to change the hardware? I am pleased to read an example of the code as I said in the description

Start by taking SoftwareSerial off pins 0 and if that is what you have used. Pins 0 and 1 are used by the hardware Serial interface

Many thanks. I'll try

You have not described what "does not work right"

With this code I have posted: I turn the bot on, it balance. When I connect to my controller on app, press any button, the bot "sleep" for 1 sec then balance again. That's the problem

        data= mySerial.readStringUntil('\n');

Are you sending a Newline character at the end of the message to Bluetooth ?

If not, then readStringUntil() will wait for 1 second and assume that the String has been received.

      key = (data.toInt()); 
      switch (key) 
      {  
      //Tay cầm bên trái
      case 'F':                                
        Serial.println("Forward");
        forward();
        break;

Why use readStringUntil() when all you are receiving is a single character so you could just use read() ?

To make things worse you convert the String to an integer then test to see which single char you have received. You are comparing data of one type with a value of another

My suggestion. If a single character is available then read it into a char variable and use that in switch/case

If I want to redesign my schematic for bluetooth controller, shoud I abandon all the encoder pins and then connect rx/tx to pin 10/11?

Any pins except 0 and 1 will do

NOTE : SoftwareSerial() will not run reliable at at 115200 baud. Reduce the baud rate considerably

Thanks man!!

I have tried to change the code then I can controled it, but only when I lay down the bot. When it balance I cannot do anything to make it move. Do you have any suggestion to fix this? Also changed the wire connection to pin 10/11.

#include "PID_v1.h"
#include "LMotorController.h"
#include "I2Cdev.h"
#include <SimpleKalmanFilter.h>
#include "MPU6050_6Axis_MotionApps20.h"
#include<SoftwareSerial.h>

//RC
SoftwareSerial mySerial(10,11); // RX, TX

String data;
int btVal;

#if I2CDEV_IMPLEMENTATION == I2CDEV_ARDUINO_WIRE
    #include "Wire.h"
#endif
#define LOG_INPUT 0//Hiển thị trên serial plotter
#define MANUAL_TUNING 0//Hiệu chỉnh bằng tay
#define LOG_PID_CONSTANTS 0 //Ghi lại các giá trị hiệu chỉnh
#define MOVE_BACK_FORTH 1//Điều chỉnh setpoint để kéo robot theo hướng ngược lại. Chỉ số movingAngleOffset nên để nhỏ cỡ 0.1 - 0.2. Để lớn quá thì robot dễ mất ổn định
#define MIN_ABS_SPEED 5//Mức tốc độ tối thiểu

//MPU
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

//PID
#define original_setpoint_offsets -1.7//Mặc định với cấu hình hiện tại (-2.3 -2.2)
#if MANUAL_TUNING
  double kp , ki, kd;
  double prevKp, prevKi, prevKd;
#endif
double originalSetpoint = 0 + original_setpoint_offsets;
double setpoint = originalSetpoint;
double movingAngleOffset = 0.05;//bù vừa phải
double input, output;
int moveState=0; 

//Tham số PID: 19 160 1.7
#define Kp 19
#define Ki 160
#define Kd 1.7
#if MANUAL_TUNING
  PID pid(&input, &output, &setpoint, 0, 0, 0, DIRECT);
#else
  PID pid(&input, &output, &setpoint, Kp, Ki, Kd, DIRECT);
#endif

//MOTOR CONTROLLER

int ENA = 3;
int IN1 = 4;
int IN2 = 5;
int IN3 = 6;
int IN4 = 7;
int ENB = 9;

LMotorController motorController(ENA, IN1, IN2, ENB, IN3, IN4, 1, 1);

//timers

long time1Hz = 0;
long time5Hz = 0;
long timePID = 0;
long timeRC = 0;
volatile bool mpuInterrupt = false;     // indicates whether MPU interrupt pin has gone high
void dmpDataReady()
{
    mpuInterrupt = true;
}

void setup()
{ //RC
  pinMode(IN1, OUTPUT);
  pinMode(IN2, OUTPUT);
  pinMode(IN3, OUTPUT);
  pinMode(IN4, OUTPUT);
  //pinMode(EN1, OUTPUT);
  //pinMode(EN2, OUTPUT);
  digitalWrite(IN1, LOW);
  digitalWrite(IN2, LOW);
  digitalWrite(IN3, LOW);
  digitalWrite(IN4, LOW);
  //analogWrite(EN1,63);
  //analogWrite(EN2,63);
  mySerial.begin(115200);

  Serial.println(ypr[1] * 180/M_PI);
    // join I2C bus (I2Cdev library doesn't do this automatically)
    #if I2CDEV_IMPLEMENTATION == I2CDEV_ARDUINO_WIRE
        Wire.begin();
        TWBR = 24; // 400kHz I2C clock (200kHz if CPU is 8MHz)
    #elif I2CDEV_IMPLEMENTATION == I2CDEV_BUILTIN_FASTWIRE
        Fastwire::setup(400, true);
    #endif

    // initialize serial communication
    // (115200 chosen because it is required for Teapot Demo output, but it's
    // really up to you depending on your project)
    Serial.begin(115200);
    while (!Serial); // wait for Leonardo enumeration, others continue immediately

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

    // 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
    Serial.println(F("Initializing DMP..."));
    devStatus = mpu.dmpInitialize();

    // supply your own gyro offsets here, scaled for min sensitivity
    mpu.setXGyroOffset(39);
    mpu.setYGyroOffset(14);
    mpu.setZGyroOffset(6);
    mpu.setZAccelOffset(1788); // 1688 factory default for my test chip

    // 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..."));
        mpu.setDMPEnabled(true);

        // 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.SetMode(AUTOMATIC);
        pid.SetSampleTime(5);
        pid.SetOutputLimits(-255, 255);
    }
    else
    {
        // 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 "));
        Serial.print(devStatus);
        Serial.println(F(")"));
    }
}

void loop()
{
    // if programming failed, don't try to do anything
    if (!dmpReady) return;
    unsigned long currentMillis = millis();
    // wait for MPU interrupt or extra packet(s) available
    if (currentMillis - timeRC >= 30)
        {
            loop_RC();
            timeRC = currentMillis;
        }
    while (!mpuInterrupt && fifoCount < packetSize)
    {
        //no mpu data - performing PID calculations and output to motors
        
        if (currentMillis - time1Hz >= 1000)
        {
            loopAt1Hz();
            time1Hz = currentMillis;
        }
        
        if (currentMillis - time5Hz >= 3000)
        {
            loopAt5Hz();
            time5Hz = currentMillis;
        }
        else (currentMillis - timePID >=5);
        {
            pid.Compute();
            motorController.move(output, MIN_ABS_SPEED);
            timePID = currentMillis;
        }
    }
      
    // reset interrupt flag and get INT_STATUS byte
    mpuInterrupt = false;
    mpuIntStatus = mpu.getIntStatus();

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

   
    if ((mpuIntStatus & 0x10) || fifoCount == 1024)
    {
        // reset so we can continue cleanly
        mpu.resetFIFO();
        Serial.println(F("FIFO overflow!"));

    
    }
    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);
        
      
        fifoCount -= packetSize;

        mpu.dmpGetQuaternion(&q, fifoBuffer);
        mpu.dmpGetGravity(&gravity, &q);
        mpu.dmpGetYawPitchRoll(ypr, &q, &gravity);
        #if LOG_INPUT
            Serial.print("Yaw:");
            Serial.println(ypr[0] * 180/M_PI);
            Serial.print("Pitch:");
            Serial.println(ypr[1] * 180/M_PI);
            Serial.print("Roll:");
            Serial.println(ypr[2] * 180/M_PI);
            Serial.print("setpoint:");
            Serial.println(setpoint);
            Serial.println();
        #endif
        input = ypr[1] * 180/M_PI;
   }
     
}
void loop_RC(){
    if (mySerial.available())                              
    {
     //Serial.println("No Bluetooth Data ");    
     data = mySerial.read();      
    }
    btVal = (data.toInt());
    //Serial.print("BlueTooth Value ");
    //Serial.println(btVal);  
    RC();
}
void RC()
{
  switch (btVal) 
   {
      //Tay cầm bên trái
      case 'F':                                
        Serial.println("Forward");
        forward();
        break;
      case 'B':                 
       Serial.println("Reverse");
        reverse();
        break;
      case 'L':         
       Serial.println("Left");
        left();
        break;
      //Tay cầm bên phải
      case 'R':                     
        Serial.println("Right");
        right();
        break;    
      case 'T':                     
        Serial.println("boost");
        boost();
        break;  
      case 'C':                     
        Serial.println("spinright");
        spinright();
        break;  
      case 'S':                     
        Serial.println("spinleft");
        spinleft();
        break;  
      case 'X':                     
        Serial.println("Right");
        //balance();
        stoprobot();
        break;
  }

                                               
   if (mySerial.available() < 0)                              
    {
     //Serial.println("No Bluetooth Data ");          
    }
}

void loopAt1Hz()
{
#if MANUAL_TUNING
    setPIDTuningValues();
#endif
}

void loopAt5Hz()
{
    #if MOVE_BACK_FORTH
        moveBackForth();
    #endif
}

//move back and forth


void moveBackForth()
{
    moveState++;
    if (moveState > 2) moveState = 0;
    
    if (moveState == 0)
      setpoint = originalSetpoint;
    else if (moveState == 1)
      setpoint = originalSetpoint - movingAngleOffset;
    else
      setpoint = originalSetpoint + movingAngleOffset;
}

//PID Tuning (3 potentiometers)

#if MANUAL_TUNING
void setPIDTuningValues()
{
    readPIDTuningValues();
    
    if (kp != prevKp || ki != prevKi || kd != prevKd)
    {
#if LOG_PID_CONSTANTS
        Serial.print(kp);Serial.print(", ");Serial.print(ki);Serial.print(", ");Serial.println(kd);
#endif

        pid.SetTunings(kp, ki, kd);
        prevKp = kp; prevKi = ki; prevKd = kd;
    }
}

void readPIDTuningValues()
{
    int potKp = analogRead(A0);
    int potKi = analogRead(A1);
    int potKd = analogRead(A2);
        
    kp = map(potKp, 0, 1023, 0, 25000) / 100.0; //0 - 250
    ki = map(potKi, 0, 1023, 0, 100000) / 100.0; //0 - 1000
    kd = map(potKd, 0, 1023, 0, 500) / 100.0; //0 - 5
}
#endif

void forward()
{
  digitalWrite(IN1, HIGH);
  digitalWrite(IN2, LOW);
  digitalWrite(IN3, HIGH);
  digitalWrite(IN4, LOW);
  analogWrite(ENA, 130);
  analogWrite(ENB, 130);
}

void reverse()
{
  digitalWrite(IN1, LOW);
  digitalWrite(IN2, HIGH);
  digitalWrite(IN3, LOW);
  digitalWrite(IN4, HIGH);
  analogWrite(ENA, 130);
  analogWrite(ENB, 130);
}

void left()
{
  digitalWrite(IN1, LOW);
  digitalWrite(IN2, LOW);
  digitalWrite(IN3, HIGH);
  digitalWrite(IN4, LOW);
  analogWrite(ENA, 130);
}

void right()
{
  digitalWrite(IN1, HIGH);
  digitalWrite(IN2, LOW);
  digitalWrite(IN3, LOW);
  digitalWrite(IN4, LOW);
  analogWrite(ENB, 130);
}

void stoprobot()
{
  digitalWrite(IN1, LOW);
  digitalWrite(IN2, LOW);
  digitalWrite(IN3, LOW);
  digitalWrite(IN4, LOW);
  analogWrite(ENA, 0);
  analogWrite(ENA, 0);
}
void boost()
{
  digitalWrite(IN1, HIGH);
  digitalWrite(IN2, LOW);
  digitalWrite(IN3, HIGH);
  digitalWrite(IN4, LOW);
  analogWrite(ENA, 200);
  analogWrite(ENB, 200);
}
void spinright()
{
  digitalWrite(IN1, HIGH);
  digitalWrite(IN2, LOW);
  digitalWrite(IN3, LOW);
  digitalWrite(IN4, HIGH);
  analogWrite(ENB, 80);
  analogWrite(ENA, 80);
}
void spinleft()
{
  digitalWrite(IN1, LOW);
  digitalWrite(IN2, HIGH);
  digitalWrite(IN3, HIGH);
  digitalWrite(IN4, LOW);
  analogWrite(ENB, 80);
  analogWrite(ENA, 80);
}

I don't think this code will work.

If you receive some value and call this function, your robot will fall over because the motors will go to full speed and the PID loop won't be controlling them.

void forward()
{
  digitalWrite(IN1, HIGH);
  digitalWrite(IN2, LOW);
  digitalWrite(IN3, HIGH);
  digitalWrite(IN4, LOW);
  analogWrite(ENA, 130);
  analogWrite(ENB, 130);
}

To make the robot go forward or backward you have to set the target angle for the PID. To handle steering you have to split the PID output to the two wheels. There's a bit of math to understand before you can get to the code.

Here's one that I put together recently.

Look through that thread. We talk a lot about what makes a balancer work.

Nice idea, I did it work forward and backward. But please help me how to code it turn right or left. Probably just lock one side of wheel then it will turn around it self

void RC()
{
  switch (btVal) 
   {
      //left hand controllers
      case 'F':                                
        Serial.println("Forward");
        //forward();
        setpoint = originalSetpoint + 3;
        break;
      case 'B':                 
       Serial.println("Reverse");
        //reverse();
        setpoint = originalSetpoint - 3;
        break;

Not if it is a two wheel balancer. You have to set the wheel speeds according to the PID or it will fall over.

Please read the post I already made.

1 Like

(This is for someone who is seeking some codes related to a self balancing robot with arduino Uno, L298N, mpu6050, HC05 bluetooth)
My latest update for this project: I have finished bt controller for my robot, and to me it's fine. It can turn around, run forward and backward with a little bit jitter. If someone have recommendation, you are wellcome!

Self_balancing robot_1_loop_remote.ino

#include "PID_v1.h"
#include "LMotorController.h"
#include "I2Cdev.h"
#include <SimpleKalmanFilter.h>
#include "MPU6050_6Axis_MotionApps20.h"
#include<SoftwareSerial.h>//RC
SoftwareSerial mySerial(0,1); // RX, TX
String data;
char btVal;

unsigned long startTimeTask1 = 0;
unsigned long startTimeTask2 = 0;
unsigned long startTime_RC = 0;
// Chu kỳ thực thi cho mỗi tác vụ
const int cycleTask1 = 5; //PID loop 5ms
const int cycleTask2 = 20; // càng thấp thì điều khiển càng nhạy

#if I2CDEV_IMPLEMENTATION == I2CDEV_ARDUINO_WIRE
    #include "Wire.h"
#endif
#define LOG_INPUT 0//Hiển thị trên serial plotter
#define MANUAL_TUNING 0//Hiệu chỉnh bằng tay
#define LOG_PID_CONSTANTS 0 //Ghi lại các giá trị hiệu chỉnh
#define MOVE_BACK_FORTH 1//Điều chỉnh setpoint để kéo robot theo hướng ngược lại. Chỉ số movingAngleOffset nên để nhỏ cỡ 0.1 - 0.2. Để lớn quá thì robot dễ mất ổn định
#define MIN_ABS_SPEED 5//Mức tốc độ tối thiểu

//MPU
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

//PID
#define original_setpoint_offsets -1.7//Mặc định với cấu hình hiện tại (-2.3 -2.2)
#if MANUAL_TUNING
  double kp , ki, kd;
  double prevKp, prevKi, prevKd;
#endif
double originalSetpoint = 0 + original_setpoint_offsets;
double setpoint = originalSetpoint;
double movingAngleOffset = 0.05;//bù vừa phải
double input, output;
int moveState=0; 

//Tham số PID: 19 160 1.7
#define Kp 20
#define Ki 230
#define Kd 2.2
#if MANUAL_TUNING
  PID pid(&input, &output, &setpoint, 0, 0, 0, DIRECT);
#else
  PID pid(&input, &output, &setpoint, Kp, Ki, Kd, DIRECT);
#endif

//MOTOR CONTROLLER

#define ENA 3
#define IN1 4
#define IN2 5
#define IN3 6
#define IN4 7
#define ENB 9


LMotorController motorController(ENA, IN1, IN2, ENB, IN3, IN4, 1, 1);

//timers

long time1Hz = 0;
long time5Hz = 0;

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

void setup()
{
pinMode(IN1, OUTPUT);
pinMode(IN2, OUTPUT);
pinMode(IN3, OUTPUT);
pinMode(IN4, OUTPUT);
pinMode(ENA, OUTPUT);
pinMode(ENB, OUTPUT);

  Serial.println(ypr[1] * 180/M_PI);
    // join I2C bus (I2Cdev library doesn't do this automatically)
    #if I2CDEV_IMPLEMENTATION == I2CDEV_ARDUINO_WIRE
        Wire.begin();
        TWBR = 24; // 400kHz I2C clock (200kHz if CPU is 8MHz)
    #elif I2CDEV_IMPLEMENTATION == I2CDEV_BUILTIN_FASTWIRE
        Fastwire::setup(400, true);
    #endif

    // initialize serial communication
    // (115200 chosen because it is required for Teapot Demo output, but it's
    // really up to you depending on your project)
    Serial.begin(115200);
    while (!Serial); // wait for Leonardo enumeration, others continue immediately

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

    // 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
    Serial.println(F("Initializing DMP..."));
    devStatus = mpu.dmpInitialize();

    // supply your own gyro offsets here, scaled for min sensitivity
    mpu.setXGyroOffset(39);
    mpu.setYGyroOffset(14);
    mpu.setZGyroOffset(6);
    mpu.setZAccelOffset(1788); // 1688 factory default for my test chip

    // 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..."));
        mpu.setDMPEnabled(true);

        // 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.SetMode(AUTOMATIC);
        pid.SetSampleTime(5);
        pid.SetOutputLimits(-255, 255);
    }
    else
    {
        // 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 "));
        Serial.print(devStatus);
        Serial.println(F(")"));
    }
    mySerial.begin(9600);//RC
}
///////////////////////////////////////////////////////////    LOOP    /////////////////////////////////////////////////////////////////////////////
void loop()
{
  unsigned long currentTime_control = millis();
  loop_PID();
  Serial.println("PID loop");
  
  // Kiểm tra thời gian thực thi cho RC
  if (currentTime_control - startTimeTask2 >= cycleTask2) {
    // Thực hiện RC
    Serial.println("Remote");
    loop_RC();
    startTimeTask2 = currentTime_control;
  }

}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void loop_PID()
{
     // 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
        
        pid.Compute();
        motorController.move(output, MIN_ABS_SPEED);
        
        unsigned long currentMillis = millis();
        
        if (currentMillis - time1Hz >= 1000)
        {
            loopAt1Hz();
            time1Hz = currentMillis;
        }
        
        if (currentMillis - time5Hz >= 3000)
        {
            loopAt5Hz();
            time5Hz = currentMillis;
        }
    }

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

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

   
    if ((mpuIntStatus & 0x10) || fifoCount == 1024)
    {
        // reset so we can continue cleanly
        mpu.resetFIFO();
        //Serial.println(F("FIFO overflow!"));

    
    }
    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);
        
      
        fifoCount -= packetSize;

        mpu.dmpGetQuaternion(&q, fifoBuffer);
        mpu.dmpGetGravity(&gravity, &q);
        mpu.dmpGetYawPitchRoll(ypr, &q, &gravity);
        #if LOG_INPUT
            Serial.print("Yaw:");
            Serial.println(ypr[0] * 180/M_PI);
            Serial.print("Pitch:");
            Serial.println(ypr[1] * 180/M_PI);
            Serial.print("Roll:");
            Serial.println(ypr[2] * 180/M_PI);
            Serial.print("setpoint:");
            Serial.println(setpoint);
            Serial.println();
        #endif
        input = ypr[1] * 180/M_PI;
   }
}

void loopAt1Hz()
{
#if MANUAL_TUNING
    setPIDTuningValues();
#endif
}

void loopAt5Hz()
{
    #if MOVE_BACK_FORTH
        moveBackForth();
    #endif
}

//move back and forth


void moveBackForth()
{
    moveState++;
    if (moveState > 2) moveState = 0;
    
    if (moveState == 0)
      setpoint = originalSetpoint;
    else if (moveState == 1)
      setpoint = originalSetpoint - movingAngleOffset;
    else
      setpoint = originalSetpoint + movingAngleOffset;
}

//PID Tuning (3 potentiometers)

#if MANUAL_TUNING
void setPIDTuningValues()
{
    readPIDTuningValues();
    
    if (kp != prevKp || ki != prevKi || kd != prevKd)
    {
#if LOG_PID_CONSTANTS
        Serial.print(kp);Serial.print(", ");Serial.print(ki);Serial.print(", ");Serial.println(kd);
#endif

        pid.SetTunings(kp, ki, kd);
        prevKp = kp; prevKi = ki; prevKd = kd;
    }
}

void readPIDTuningValues()
{
    int potKp = analogRead(A0);
    int potKi = analogRead(A1);
    int potKd = analogRead(A2);
        
    kp = map(potKp, 0, 1023, 0, 25000) / 100.0; //0 - 250
    ki = map(potKi, 0, 1023, 0, 100000) / 100.0; //0 - 1000
    kd = map(potKd, 0, 1023, 0, 500) / 100.0; //0 - 5
}
#endif

Remote.ino

//App name: Arduino Bluetooth Control
//Source: Google Play
#define FORWARD 'F'
#define BACKWARD 'B'
#define LEFT 'L'
#define RIGHT 'R'
#define CIRCLE 'C'
#define CROSS 'X'
#define TRIANGLE 'T'
#define SQUARE 'S'
#define START 'A'
#define PAUSE 'P'

void loop_RC()
{
    
    if (mySerial.available())                              
    {
     Serial.println("Read Bluetooth Data ");    
     data = mySerial.read();  //ko cần khai báo kiểu dữ liệu cho biến data.
    }
    btVal = (data.toInt());
    RC();
}

void RC()
{
  switch (btVal) 
   {
      //Tay cầm bên trái
      case FORWARD://press                                
        setpoint = originalSetpoint + 2;
        forward();
        break;
        loop_PID();
      case 'F1'://release press (in app setting)
        setpoint = originalSetpoint;
        break;
        loop_PID();

      case BACKWARD:                 
        setpoint = originalSetpoint - 2;
        backward();
        break;
        loop_PID();
      case 'B1':
        setpoint = originalSetpoint;
        break;
        loop_PID();

      case LEFT:         
        turnleft();
        delay(10);
        break;
        loop_PID();

      case RIGHT:                     
        turnright();
        delay(10);
        break;    
        loop_PID();

      //Tay cầm bên phải
      case TRIANGLE: //boost                    
        setpoint = originalSetpoint + 3;
        forward();
        break;  
        loop_PID();
      case 'T1'://End boost
      setpoint = originalSetpoint;
        break;
        loop_PID();

      case CIRCLE:                     
        //spin right
        spinright();
        delay(10);
        break; 
        loop_PID();

      case SQUARE:                     
        //spin left
        spinleft();
        delay(10);
        break;  
        loop_PID();

      case CROSS:                     
        //STOP
        setpoint = originalSetpoint;
        stop();
        delay(10);
        break;
        loop_PID();
  }

                                               
   if (mySerial.available() < 0)                              
    {
     Serial.println("No Bluetooth Data ");          
    }
  
}

void forward()//FIRST TEST SPIN DIRECTION 
{
  digitalWrite(IN1,LOW);
  digitalWrite(IN2,HIGH);
  digitalWrite(IN3,LOW);
  digitalWrite(IN4,HIGH);
  analogWrite(ENA,80);
  analogWrite(ENB,80);
}
void backward()
{
  digitalWrite(IN1,HIGH);
  digitalWrite(IN2,LOW);
  digitalWrite(IN3,HIGH);
  digitalWrite(IN4,LOW);
  analogWrite(ENA,80);
  analogWrite(ENB,80);
}
void turnleft()
{
  digitalWrite(IN1,HIGH);
  digitalWrite(IN2,HIGH);
  digitalWrite(IN3,LOW);
  digitalWrite(IN4,HIGH);
  analogWrite(ENB,255);
  analogWrite(ENA,0);
}
void turnright()
{
  digitalWrite(IN1,LOW);
  digitalWrite(IN2,HIGH);
  digitalWrite(IN3,HIGH);
  digitalWrite(IN4,HIGH);
  analogWrite(ENA,255);
  analogWrite(ENB,0);
}
void spinleft()
{
  digitalWrite(IN1,HIGH);
  digitalWrite(IN2,LOW);
  digitalWrite(IN3,LOW);
  digitalWrite(IN4,HIGH);
  analogWrite(ENA,200);
  analogWrite(ENB,200);
}
void spinright()
{
  digitalWrite(IN1,LOW);
  digitalWrite(IN2,HIGH);
  digitalWrite(IN3,HIGH);
  digitalWrite(IN4,LOW);
  analogWrite(ENA,200);
  analogWrite(ENB,200);
}
void stop()
{
  digitalWrite(IN1,LOW);
  digitalWrite(IN2,LOW);
  digitalWrite(IN3,LOW);
  digitalWrite(IN4,LOW);
  analogWrite(ENA,0);
  analogWrite(ENB,0);
}


LmotorController.cpp

#include "LMotorController.h"
#include "Arduino.h"

LMotorController::LMotorController(int ena, int in1, int in2, int enb, int in3, int in4, double motorAConst, double motorBConst)
{
_motorAConst = motorAConst;
_motorBConst = motorBConst;

_ena = ena;
_in1 = in1;
_in2 = in2;
_enb = enb;
_in3 = in3;
_in4 = in4;

pinMode(_ena, OUTPUT);
pinMode(_in1, OUTPUT);
pinMode(_in2, OUTPUT);

pinMode(_enb, OUTPUT);
pinMode(_in3, OUTPUT);
pinMode(_in4, OUTPUT);

}

void LMotorController::move(int leftSpeed, int rightSpeed, int minAbsSpeed)
{
if (rightSpeed < 0)
{
rightSpeed = min(rightSpeed, -1*minAbsSpeed);
rightSpeed = max(rightSpeed, -200);
}
else if (rightSpeed > 0)
{
rightSpeed = max(rightSpeed, minAbsSpeed);
rightSpeed = min(rightSpeed, 200);
}

int realRightSpeed = map(abs(rightSpeed), 0, 255, minAbsSpeed, 255);

if (leftSpeed < 0)
{
    leftSpeed = min(leftSpeed, -1*minAbsSpeed);
    leftSpeed = max(leftSpeed, -200);
}
else if (leftSpeed > 0)
{
    leftSpeed = max(leftSpeed, minAbsSpeed);
    leftSpeed = min(leftSpeed, 200);
}

int realLeftSpeed = map(abs(leftSpeed), 0, 255, minAbsSpeed, 255);

digitalWrite(_in3, rightSpeed > 0 ? HIGH : LOW);
digitalWrite(_in4, rightSpeed > 0 ? LOW : HIGH);
digitalWrite(_in1, leftSpeed > 0 ? HIGH : LOW);
digitalWrite(_in2, leftSpeed > 0 ? LOW : HIGH);
analogWrite(_ena, realRightSpeed * _motorAConst);
analogWrite(_enb, realLeftSpeed * _motorBConst);

}

void LMotorController::move(int speed, int minAbsSpeed)
{
int direction = 1;

if (speed < 0)
{
    direction = -1;
    
    speed = min(speed, -1*minAbsSpeed);
    speed = max(speed, -200);
}
else
{
    speed = max(speed, minAbsSpeed);
    speed = min(speed, 200);
}

if (speed == _currentSpeed) return;

int realSpeed = max(minAbsSpeed, abs(speed));

digitalWrite(_in1, speed > 0 ? HIGH : LOW);
digitalWrite(_in2, speed > 0 ? LOW : HIGH);
digitalWrite(_in3, speed > 0 ? HIGH : LOW);
digitalWrite(_in4, speed > 0 ? LOW : HIGH);
analogWrite(_ena, realSpeed * _motorAConst);
analogWrite(_enb, realSpeed * _motorBConst);

_currentSpeed = direction * realSpeed;

}

void LMotorController::move(int speed)
{
if (speed == _currentSpeed) return;

if (speed > 200) speed = 200;
else if (speed < -200) speed = -200;

digitalWrite(_in1, speed > 0 ? HIGH : LOW);
digitalWrite(_in2, speed > 0 ? LOW : HIGH);
digitalWrite(_in3, speed > 0 ? HIGH : LOW);
digitalWrite(_in4, speed > 0 ? LOW : HIGH);
analogWrite(_ena, abs(speed) * _motorAConst);
analogWrite(_enb, abs(speed) * _motorBConst);

_currentSpeed = speed;

}

void LMotorController::turnLeft(int speed, bool kick)
{
digitalWrite(_in1, HIGH);
digitalWrite(_in2, LOW);
digitalWrite(_in3, LOW);
digitalWrite(_in4, HIGH);

if (kick)
{
    analogWrite(_ena, 100);
    analogWrite(_enb, 100);
    delay(250);        
}

analogWrite(_ena, speed * _motorAConst);
analogWrite(_enb, speed * _motorBConst);

}

void LMotorController::turnRight(int speed, bool kick)
{
digitalWrite(_in1, LOW);
digitalWrite(_in2, HIGH);
digitalWrite(_in3, HIGH);
digitalWrite(_in4, LOW);

if (kick)
{
    analogWrite(_ena, 100);
    analogWrite(_enb, 100);
    delay(250);
}
analogWrite(_ena, speed * _motorAConst);
analogWrite(_enb, speed * _motorBConst);

}

void LMotorController::stopMoving()
{
digitalWrite(_in1, LOW);
digitalWrite(_in2, LOW);
digitalWrite(_in3, LOW);
digitalWrite(_in4, LOW);
digitalWrite(_ena, HIGH);
digitalWrite(_enb, HIGH);

_currentSpeed = 0;

}

LmotorController.h

#ifndef LMotorController_h
#define LMotorController_h


#include "Arduino.h"


class LMotorController
{
protected:
    int _ena, _in1, _in2, _enb, _in3, _in4;
    int _currentSpeed;
    double _motorAConst, _motorBConst;
public:
    LMotorController(int ena, int in1, int in2, int enb, int in3, int in4, double motorAConst, double motorBConst);
    void move(int leftSpeed, int rightSpeed, int minAbsSpeed);
    void move(int speed);
    void move(int speed, int minAbsSpeed);
    void turnLeft(int speed, bool kick);
    void turnRight(int speed, bool kick);
    void stopMoving();
};


#endif