I have a problem my self balancing robot the wheels rotate sporadically

Specification:

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

#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 = 28; // Compensate for motor slack range (low PWM values which result in no motor engagement)
float MOTORSLACK_B = 28;
#define BALANCE_PID_MIN -210 // Define PID limits to match PWM max in reverse and foward
#define BALANCE_PID_MAX 210

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 = 180.04; //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.5 //Set this first
#define Kd 0.1 //Set this secound
#define Ki 80 //Finally set this

#define RKp 21.5 //Set this first
#define RKd 0.49 //Set this secound
#define RKi 80 //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 = 180.04; //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, 1);
MotorBspeed = compensate_slack(youtput, output, 0);
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");
}

Please edit your post to add code tags.

For instructions, see the "How to get the best of out the forum" post.

2 Likes

Sorry I cannot read the code unless posted properly as stated by remington. Also post a schematic, not a frizzy picture showing all the connections you made and links to all of the hardware items that show the technical details. Links to azon are normally not helpful as they are sales info. Show all of your power sources as well. I can see potential problems both hardware and software.

1 Like

Here's my video on tuning a balancing bot:
How to Balance Robot PID tutorial in under 2 minutes!

There are a few things to consider
first is the dead zone between forward and reverse. you will want the transition to take as little time as possible
Set the MPU6050 so that level is at 0° and now when balance your power will be zero to the wheels
once this it true you do not need integral so set it to Zero. Also set Dirivitive to zero and just play with proportional as the video shows.

the <PID_v1.h> is not good for balancing bots try my version found here it is a direct swap with additional features.
GitHub ZHomeSlice / Arduino_PID_Library_v3

This code is an excellent example for you to build off of.
Z
ps. Place your code in code tags so it is easier to read please

#include "I2Cdev.h"
#include "MPU6050_6Axis_MotionApps612.h"
#include "Wire.h"
#include "PID_v3.h"// My PID Library

MPU6050 mpu;

// PID Tuning Settings
double consKp = 6, consKi = 0, consKd = .02; // PID values for Balancing
int MinPower = 50;
// supply your own gyro and accell offsets here // use the sample code to generate the offsets with the MPU6050 on a flat surface
// XA      YA      ZA      XG      YG      ZG

int MPUOffsets[6] = {  2471,  -563,  1628,  8, -23, 53};

#define PWM_A     (6)   // 0-255             Arduino Digital pin 6
#define PWM_B     (5)   // 0-255             Arduino Digital pin 3
#define HBgA1HighEn (8) // Enable/Disable    Arduino Digital pin 8
#define HBgA2HighEn (7) // Enable/Disable    Arduino Digital pin 7
#define HBgB1HighEn (4) // Enable/Disable    Arduino Digital pin 4
#define HBgB2HighEn (3) // Enable/Disable    Arduino Digital pin 5
#define Int0_D2     (2)// intreupt 0 or Digital in/out  Pin 2 Breakout on Board D2

#define Disable 0x0   // also defined as LOW
#define Enable  0x1   // also defined as HIGH
#define Forward (1)   // Value for the Forward action
#define Backward (2)  // Value for the Backward Action
#define AllStop (0)   // Value for the all stop action

#define LED_PIN 13 // 



// ================================================================
// ===                      MPU DMP SETUP                       ===
// ================================================================
int FifoAlive = 0; // tests if the interrupt is triggering
int IsAlive = -20;     // counts interrupt start at -20 to get 20+ good values before assuming connected
// MPU control/status vars
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
VectorInt16 aa;         // [x, y, z]            accel sensor measurements
VectorInt16 aaReal;     // [x, y, z]            gravity-free accel sensor measurements
VectorInt16 aaWorld;    // [x, y, z]            world-frame accel sensor measurements
VectorFloat gravity;    // [x, y, z]            gravity vector
float euler[3];         // [psi, theta, phi]    Euler angle container
float ypr[3];           // [yaw, pitch, roll]   yaw/pitch/roll container and gravity vector
byte StartUP = 100; // lets get 100 readings from the MPU before we start trusting them (Bot is not trying to balance at this point it is just starting up.)
volatile bool mpuInterrupt = false;     // indicates whether MPU interrupt pin has gone high


void MPU6050Connect() {
  static int MPUInitCntr = 0;
  // initialize device
  mpu.initialize(); // same
  // load and configure the DMP
  devStatus = mpu.dmpInitialize();// same

  mpu.setXAccelOffset(MPUOffsets[0]);
  mpu.setYAccelOffset(MPUOffsets[1]);
  mpu.setZAccelOffset(MPUOffsets[2]);
  mpu.setXGyroOffset(MPUOffsets[3]);
  mpu.setYGyroOffset(MPUOffsets[4]);
  mpu.setZGyroOffset(MPUOffsets[5]);

  Serial.println(F("Enabling DMP..."));
  mpu.setDMPEnabled(true);
  // enable Arduino interrupt detection
  Serial.println(F("Enabling interrupt detection (Arduino external interrupt pin 2 on the Uno)..."));
  Serial.print("mpu.getInterruptDrive=  "); Serial.println(mpu.getInterruptDrive());

  //mpuIntStatus = mpu.getIntStatus(); // Same
  packetSize = mpu.dmpGetFIFOPacketSize(); // get expected DMP packet size for later comparison
  delay(1000); // Let it Stabalize
  mpu.resetFIFO(); // Clear fifo buffer
  mpu.getIntStatus(); // Clears the Status Buffer
  mpuInterrupt = false; // wait for next interrupt
  /* attachInterrupt(0, []() {
     mpuInterrupt = true;
    }, RISING); //pin 2 on the Uno on interrupt set mpuInterrupt to ture (using Lambda anonymouns funciotn)
  */
}


// ================================================================
// ===                      i2c SETUP Items                     ===
// ================================================================
void i2cSetup() {
  // join I2C bus (I2Cdev library doesn't do this automatically)
#if I2CDEV_IMPLEMENTATION == I2CDEV_ARDUINO_WIRE
  Wire.begin();
  Wire.setClock(400000); // 400kHz I2C clock. Comment this line if having compilation difficulties
#ifdef __AVR__  
  Wire.setWireTimeout(3000, true); //timeout value in uSec
#endif
#elif I2CDEV_IMPLEMENTATION == I2CDEV_BUILTIN_FASTWIRE
  Fastwire::setup(400, true);
#endif
}



// ================================================================
// ===                         PID Setup                        ===
// ================================================================
double InputABS;
bool Mode = false;
double SPZero = 0;// Setpoint trim adjustment in Degrees
double SPAdjust = 0, Setpoint = 0, Input, Output;

// Yaw PID Control
bool TurnControlEnable = false; // To Enable Turn control = true
double DegSetpoint = 0;        // Degree Setpoint should always be Zero!
double YawInput;               //(YawInput = YawMPU + YawOffset) with rollover at +-180°
double Turn;                   // Outout to drive motors
double YawOffset;              // YawOffset adds to YawImput to get an alternate reading for 0° (YawInput = YawMPU + YawOffset) with rollover at +-180°
double YawMPU;                 // Actual reading from the MPU
PID myPID(&Input, &Output, &Setpoint); //, consKp, consKi, consKd, REVERSE

// These functions are set into the myPID library as Callback functions

void CostA() {
  Drive(PWM_A, HBgA1HighEn, HBgA2HighEn, 0, LOW, LOW);
}
void CostB() {
  Drive(PWM_B, HBgB1HighEn, HBgB2HighEn, 0, LOW, LOW);
}
void ForwardA(double Speed) {
  Drive(PWM_A, HBgA1HighEn, HBgA2HighEn, Speed, HIGH, LOW);
}
void ReverseA(double Speed) {
  Drive(PWM_A, HBgA1HighEn, HBgA2HighEn, Speed, LOW, HIGH );
}
void ForwardB(double Speed) {
  Drive(PWM_B, HBgB1HighEn, HBgB2HighEn, Speed, HIGH, LOW);
}
void ReverseB(double Speed) {
  Drive(PWM_B, HBgB1HighEn, HBgB2HighEn, Speed, LOW, HIGH);
}



void PIDSetup() {
  myPID.SetOutputLimits(-255, 255);
  myPID.SetSampleTime(10);
  myPID.SetControllerDirection(REVERSE);
  myPID.SetDriveMin(MinPower);
  myPID.SetDriveMax(255);
  myPID.SetTunings(consKp, consKi, consKd); //Load the tunings

  // The Following functions are callback relate to what happens in each mode the functions that are called are above
  myPID.onCostA(CostA);
  myPID.onCostB(CostB);
  myPID.onForwardA(ForwardA);
  myPID.onReverseA(ReverseA);
  myPID.onForwardB(ForwardB);
  myPID.onReverseB(ReverseB);

}

// ================================================================
// ===                     Motor SETUP Items                    ===
// ================================================================
void MotorSetup() {
  pinMode(PWM_A, OUTPUT); // A PWM
  pinMode(HBgA1HighEn, OUTPUT); // A1 Enable/Disable
  pinMode(HBgA2HighEn, OUTPUT); // A2 Enable/Disable
  pinMode(PWM_B, OUTPUT); // B PWM
  pinMode(HBgB1HighEn, OUTPUT); // B1 Enable/Disable
  pinMode(HBgB2HighEn, OUTPUT); // B2 Enable/Disable
  STOP();
  Output = 0;
  FullBreaking (); // freezes the motors by setting both sides of the H-Bridge to LOW Side on 100%
}
void FullBreaking () {
  Drive(PWM_B, HBgA1HighEn, HBgA2HighEn, 255, LOW, LOW);
  Drive(PWM_B, HBgB1HighEn, HBgB2HighEn, 255, LOW, LOW);
}

void Drive(int PPin, int xPin1, int xPin2, double Speed, int High1En, int High2En) {
  digitalWrite(xPin1, High1En);
  digitalWrite(xPin2, High2En);
  analogWrite(PPin, (int) Speed);
}





// ================================================================
// ===                      INITIAL SETUP                       ===
// ================================================================
void setup() {
  Serial.begin(115200); //115200
  Serial.println(F("Alive"));
  PIDSetup();
  MotorSetup();
  i2cSetup();
  MPU6050Connect();
  STOP();
}





// ================================================================
// ===                    MAIN PROGRAM LOOP                     ===
// ================================================================

void loop() {
  // read a packet from FIFO
  if (mpu.dmpGetCurrentFIFOPacket(fifoBuffer)) { // Get the Latest packet
    BalanceMath();
  }

}

void STOP() {
  Mode = false;
  myPID.SetMode(MANUAL);
  Output = 0;
  Turn = 0;
  FullBreaking ();
  return;
}

void BalanceMath() {
  mpu.dmpGetQuaternion(&q, fifoBuffer);
  mpu.dmpGetGravity(&gravity, &q);
  mpu.dmpGetYawPitchRoll(ypr, &q, &gravity);
  Input = ((ypr[1] * 180 / M_PI) - SPZero);
  InputABS = abs(Input);
  // Bring your balancing bot to vertical and the PID is set to automatic
  if (InputABS < 1) { // 1°
    Mode = true;
    myPID.SetMode(AUTOMATIC);
  }
  if (InputABS > 55) { // 45.0 degrees
    STOP();
    StartUP = 10;
  } else if (StartUP) StartUP -= 1;
  if (!StartUP) {

    Setpoint = SPAdjust; // SPAdjust is for offsetting the balance for driving the robot
    myPID.Compute();
    myPID.BalanceBotDrive(0);
  }
}
1 Like

I do not see the schematic. Good job of posting code.

thank you so much.

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