How bluetooth control self-balancing robot

With the same code I asked in the previous question. I need to find out how bluetooth can control self-balancing robot in a code lines.

Specification:

  1. Arduino UNO
  2. L298N Module Driver (4 pinouts with enA and enB disabled - covered with jumper)
  3. TT Gear Motor (Yellow)
  4. Bluetooth HC-05
  5. MPU6050

Here's the source code (current self-balancing robot has its balance enough):

#include "I2Cdev.h"
#include <Wire.h>
#include <PID_v1.h>
#include "MPU6050_6Axis_MotionApps20.h"
#include <SoftwareSerial.h>
#include <digitalIOPerformance.h>                //library for faster pin R/W
//#include <Ultrasonic.h>

#if I2CDEV_IMPLEMENTATION == I2CDEV_ARDUINO_WIRE
#include "Wire.h"
#endif

#define d_speed 1.5
#define d_dir 3


#define IN1 11
#define IN2 10
#define IN3 9
#define IN4 3

char content = 'P';
int MotorAspeed, MotorBspeed;
float MOTORSLACK_A = 26;                   // Compensate for motor slack range (low PWM values which result in no motor engagement)
float MOTORSLACK_B = 26;
#define BALANCE_PID_MIN -190 // Define PID limits to match PWM max in reverse and foward
#define BALANCE_PID_MAX 190

MPU6050 mpu;

const int rxpin = 6;       //Bluetooth serial stuff
const int txpin = 5;
SoftwareSerial blue(rxpin, txpin);

//Ultrasonic ultrasonic(A0, A1);
//int distance;

// MPU control/status vars
bool dmpReady = true;  // 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 = 177.64; //set the value when the bot is perpendicular to ground using serial monitor.
double originalSetpoint;
// Read the project documentation on circuitdigest.com to learn how to set these values

#define Kp  21 //Set this first
#define Kd  1.01 //Set this secound
#define Ki  40 //Finally set this

#define RKp  21 //Set this first
#define RKd 1.01 //Set this secound
#define RKi  40 //Finally set this

/******End of values setting*********/



double ysetpoint;
double yoriginalSetpoint;
double input, yinput, youtput, output, Buffer[3];

PID pid(&input, &output, &setpoint, Kp, Ki, Kd, DIRECT);
PID rot(&yinput, &youtput, &ysetpoint, RKp, RKi, RKd, DIRECT);

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


void setup() {

    // 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
    
  Serial.begin(115200);
  blue.begin(9600);
  blue.setTimeout(40);
  init_imu();           //initialiser le MPU6050
  initmot();            //initialiser les moteurs
  originalSetpoint = 177.64;  //consigne
  yoriginalSetpoint = 0.1;
  setpoint = originalSetpoint ;
  ysetpoint = yoriginalSetpoint ;
}



void loop() {
    getvalues();
    Bt_control();
    printval();
}







void init_imu() {
  // initialize device
  Serial.println(F("Initializing I2C devices..."));
  Wire.begin();
  TWBR = 24;
  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
  devStatus = mpu.dmpInitialize();


  // supply your own gyro offsets here, scaled for min sensitivity
  mpu.setXGyroOffset(120);
  mpu.setYGyroOffset(46);
  mpu.setZGyroOffset(-85);
  mpu.setZAccelOffset(1688);

  // 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(10);
    pid.SetOutputLimits(-255, 255); // -255, 255
    rot.SetMode(AUTOMATIC);
    rot.SetSampleTime(10);
    rot.SetOutputLimits(-20, 20);  // -20, 20
  }
  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 getvalues() {
  // 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) {
    new_pid();
  }
  // 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
    mpu.resetFIFO();
    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;
    yinput = ypr[0] * 180 / M_PI;
  }
}


void new_pid() {
  //Compute error
  pid.Compute();
  rot.Compute();
  // Convert PID output to motor control
  MotorAspeed = compensate_slack(youtput, output, 140);
  MotorBspeed = compensate_slack(youtput, output, 140);
  motorspeed(MotorAspeed, MotorBspeed);            //change speed
}
//Fast digitalWrite is implemented


void Bt_control() {
  if (blue.available()) {
    content = blue.read();
    if (content == 'F')
      setpoint = originalSetpoint - d_speed;//Serial.println(setpoint);}            //forward
    else if (content == 'B')
      setpoint = originalSetpoint + d_speed;//Serial.println(setpoint);}            //backward
    else if (content == 'L')
      ysetpoint = constrain((ysetpoint + yoriginalSetpoint - d_dir), -180, 180); //Serial.println(ysetpoint);}      //left
    else if (content == 'R')
      ysetpoint = constrain(ysetpoint + yoriginalSetpoint + d_dir, -180, 180); //Serial.println(ysetpoint);}        //right
    else if (content == 'S') {
      setpoint = originalSetpoint;
    }
  }
  else { content = 'P';}
}

void initmot() {
  //Initialise the Motor outpu pins
  pinMode (IN4, OUTPUT);
  pinMode (IN3, OUTPUT);
  pinMode (IN2, OUTPUT);
  pinMode (IN1, OUTPUT);

  //By default turn off both the motor
  analogWrite(IN4, LOW);
  analogWrite(IN3, LOW);
  analogWrite(IN2, LOW);
  analogWrite(IN1, LOW);
}

double compensate_slack(double yOutput, double Output, bool A) {
  // Compensate for DC motor non-linear "dead" zone around 0 where small values don't result in movement
  //yOutput is for left,right control
  if (A)
  {
    if (Output >= 0)
      Output = Output + MOTORSLACK_A - yOutput;
    if (Output < 0)
      Output = Output - MOTORSLACK_A - yOutput;
  }
  else
  {
    if (Output >= 0)
      Output = Output + MOTORSLACK_B + yOutput;
    if (Output < 0)
      Output = Output - MOTORSLACK_B + yOutput;
  }
  Output = constrain(Output, BALANCE_PID_MIN, BALANCE_PID_MAX);
  return Output;
}


void motorspeed(int MotorAspeed, int MotorBspeed) {

  // Motor A control
  if (MotorAspeed >= 0) {
    analogWrite(IN1, abs(MotorAspeed));
    digitalWrite(IN2, LOW);
  }
  else {
    digitalWrite(IN1, LOW);
    analogWrite(IN2, abs(MotorAspeed));
  }

  // Motor B control
  if (MotorBspeed >= 0) {
    analogWrite(IN3, abs(MotorBspeed));
    digitalWrite(IN4, LOW);
  }
  else {
    digitalWrite(IN3, LOW);
    analogWrite(IN4, abs(MotorBspeed));
  }
}

void printval()
{
  Serial.print(yinput); Serial.print("\t");
  Serial.print(yoriginalSetpoint); Serial.print("\t");
  Serial.print(ysetpoint); Serial.print("\t");
  Serial.print(youtput); Serial.print("\t"); Serial.print("\t");
  Serial.print(input); Serial.print("\t");
  Serial.print(originalSetpoint); Serial.print("\t");
  Serial.print(setpoint); Serial.print("\t");
  Serial.print(output); Serial.print("\t"); Serial.print("\t");
  Serial.print(MotorAspeed); Serial.print("\t");
  Serial.print(MotorBspeed); Serial.print("\t"); Serial.print(content); Serial.println("\t");
}

From the source code, I see a part of bluetooth code lines as follows:

void Bt_control() {
  if (blue.available()) {
    content = blue.read();
    if (content == 'F')
      setpoint = originalSetpoint - d_speed;//Serial.println(setpoint);}            //forward
    else if (content == 'B')
      setpoint = originalSetpoint + d_speed;//Serial.println(setpoint);}            //backward
    else if (content == 'L')
      ysetpoint = constrain((ysetpoint + yoriginalSetpoint - d_dir), -180, 180); //Serial.println(ysetpoint);}      //left
    else if (content == 'R')
      ysetpoint = constrain(ysetpoint + yoriginalSetpoint + d_dir, -180, 180); //Serial.println(ysetpoint);}        //right
    else if (content == 'S') {
      setpoint = originalSetpoint;
    }
  }
  else { content = 'P';}
}

As a beginner in Arduino, I can see that setPoint = 177.64 - d_speed (where d_speed has value "1.5") make the robot goes forward.
I can see that setPoint 177.64 is the nearest real value from Serial Monitor.

Using the custom MIT App which is appointed by the owner of the code, The robot can not be controlled.

What I want to know ... "Is the bluetooth code lines proper to push the robot forward and backward."

NOTE:
This following config has been in its correct pins:

const int rxpin = 6;       //Bluetooth serial stuff
const int txpin = 5;
SoftwareSerial blue(rxpin, txpin);

NB: Circuit

Start by making Yourself confident with Bluetooth writing a little sketch that just reads BT and blinks the onboard LED.
When that works You easily incorporates BT in the project.

1 Like

Thanks. I'm NOW writing it ... and I hope all can help me when It gets failed.
(I'm trying to combine and revise some code lines and try to sync it logically the best I can) :slight_smile:

Good. The worst way to get a project working is putting all functions into a huge amount of code, and the try to make it work. Building, testing and verifying the project parts, one by one, is the least troubling way to reach the goal.
Even genius, experts, work that way, in military, space.... projects.

I started a GPS based project by using an example code that did the basic reading of the GPS. The rest was old, well known code bending.

1 Like

The picture You posted makes me worried. That powering can't possibly work. Let's attack that later.

1 Like

yes, I tested. It works 9volt (using adaptor) :slight_smile: I guess it needs 3.7v x 3 to run from battery. (I'm still coding and test my modification code). I already bookmarked this page :smiley:

Please post real schematics, not Fritzings.

Not now. What I really need is HOW bluetooth works in self balancing robot and NOW, I assume that Bluetooth CAN NOT control the bot AT ALL. Why? because the bot has been fully handled by mpu6050.

I tried to PICK the mpu6050 device from the bot and I held it - I saw the bot acted "brutally" (since I shake the mpu device). That means that NOTHING can control it but mpu6050.

So, what bluetooth can do is to CHANGE the current setpoint into "more" or "less". "more" means "forward" - "less" means "backward".

Is it correct? (I ask you because you're the expert and I need it)

From the schematic above is actually the same. The difference is - I pin connector D3 to D6 => 6, 9, 10 and 11. The original source was 3, 9, 10 and 11.

Yes. That must be the way to do it, adding a "BT adjust level".

In order to avoid disaster values tipping the robot You should make a range check of the accumulated adjust level. Make tests in order to find out a useful step size per arrow.

1 Like

I'm in 80% towards the target. Well, tired weeks. Now I have been in this step:

  1. The bot can be a self balancing robot (with no bluetooth).
  2. The bot can be controlled Arduino car (4 wheels with bluetooth).

Both are separated in coding. As you know that I'm a beginner in this coding, So far I'm just able to make both separated in two actions.

Here's the code:

/*
 * ************************************************ INCL.LIBRARIES **************************************************** *
 */
#include <SoftwareSerial.h>
#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
/*
 * ****************************************** DECLARE ALL VARS AND DATA TYPES ***************************************** *
 */
SoftwareSerial bt(3,4);
char comm;

#if I2CDEV_IMPLEMENTATION == I2CDEV_ARDUINO_WIRE
#include "Wire.h"
#endif

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

//set the value when the bot is perpendicular to ground using serial monitor.
double setpoint= 176;
double Kp = 21; //Set this first
double Kd = 0.8; //Set this secound
double Ki = 140; //Finally set this

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

volatile bool mpuInterrupt = false;
void dmpDataReady(){
    mpuInterrupt = true;
    }
/*
 * ************************************************ VOID SETUP **************************************************** *
 */    
void setup(){
Serial.begin(115200);
bt.begin(9600);
if(bt.available()){
    pinMode(13,OUTPUT);//left motors forward
    pinMode(12,OUTPUT);//left motors reverse
    pinMode(11,OUTPUT);//right motors forward
    pinMode(10,OUTPUT);//right motors reverse   
    }
else{  
    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
    devStatus = mpu.dmpInitialize();
    //supply your own gyro offsets here, scaled for min sensitivity
    mpu.setXGyroOffset(220);
    mpu.setYGyroOffset(76);
    mpu.setZGyroOffset(-85);
    mpu.setZAccelOffset(1688); 
    //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(10);
        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(")"));
        }
    //Initialising
    pinMode(13,OUTPUT);//left motors forward
    pinMode(12,OUTPUT);//left motors reverse
    pinMode(11,OUTPUT);//right motors forward
    pinMode(10,OUTPUT);//right motors reverse
    //Turning off both the motors
    analogWrite(10,LOW);
    analogWrite(11,LOW);
    analogWrite(12,LOW);
    analogWrite(13,LOW);        
    }
}
/*
 * ************************************************ VOID LOOP **************************************************** *
 */
void loop() {
if(bt.available()){
    comm = bt.read();
    bt.println(comm);
    delay(100);
    if(comm == '1'){//move forward
        digitalWrite(13,HIGH);
        digitalWrite(12,LOW);
        digitalWrite(11,HIGH);
        digitalWrite(10,LOW);
        }
    else if(comm == '2'){//move reverse
        digitalWrite(13,LOW);
        digitalWrite(12,HIGH);
        digitalWrite(11,LOW);
        digitalWrite(10,HIGH);
        }
    else if(comm == '3'){//turn right
        digitalWrite(13,LOW);
        digitalWrite(12,LOW);
        digitalWrite(11,HIGH);
        digitalWrite(10,LOW);
        }
    else if(comm == '4'){//turn left
        digitalWrite(13,LOW);
        digitalWrite(12,HIGH);
        digitalWrite(11,LOW);
        digitalWrite(10,LOW);
        }
    else if(comm == '5'){//STOP (Motors stop)
        digitalWrite(13,LOW);
        digitalWrite(12,LOW);
        digitalWrite(11,LOW);
        digitalWrite(10,LOW);
        }
    }

    // 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  
    //If bt not available or car in perpendicular position, run this follows
    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();   
        //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
            mpu.resetFIFO();
            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;
            }
        }

/*    
 * ************************************* CALL THESE FOLLOWS WHEN MPU6050 ACTIVATED ******************************************
 */
void Forward(){
    analogWrite(13,output);
    analogWrite(12,0);
    analogWrite(11,output);
    analogWrite(10,0);
    Serial.print("F"); //Debugging information 
    }
void Reverse(){
    analogWrite(13,0);
    analogWrite(12,output*-1);
    analogWrite(11,0);
    analogWrite(10,output*-1); 
    Serial.print("R");
    }
void Stop(){
    analogWrite(13,0);
    analogWrite(12,0);
    analogWrite(11,0);
    analogWrite(10,0); 
    Serial.print("S");
    } 

Then, here's the bot looks like:

(note: I place the mpu6050 below the bot (among the wheels). The bot can stand (self balancing) using 2 wheels = when not using the bluetooth or if bt serial is not Available.


This is the real diagram circuit:

(really sorry for bad drawing).

NOTE:
In void_setup you can see that I use if to make it separated as in below:

void setup(){
Serial.begin(115200);
bt.begin(9600);
if(bt.available()){
    pinMode(13,OUTPUT);//left motors forward
    pinMode(12,OUTPUT);//left motors reverse
    pinMode(11,OUTPUT);//right motors forward
    pinMode(10,OUTPUT);//right motors reverse   
    }
else{  
    Serial.println(F("Initializing I2C devices..."));
    mpu.initialize();
    //verify connection
    Serial.println(

I do the same as in void loop

CURRENT TRY:
NOW, I'M trying to code how to combine both = the bot can stand (self balancing), can be controlled by bluetooth and can be controlled by bluetooth when it becomes a 4wd arduino car.

I'm a web developer and C++ is new for me :smiley: (now I begin loving it).

1 Like

This topic was automatically closed 120 days after the last reply. New replies are no longer allowed.