Balance Robot: Help tunning

Hello everyone!

I have been trying to achieve balance in my self balancing robot; however, the robot does not want to balance... I've tried everything... It would be nice to read about your ideas.

My hardware:

Pololu 30:1 37D motors with encoders
MPU 9250 9DOF Sensor
Arduino Uno
VNH5019 dual motor drivers from Pololu
120 mm all terrain wheels
3S 11.1V Lipo, 350grams

Picture of the robot attached.

Also I've seen some videos explaining the PID tuning process on these kinds of robots, and they did not help a whole lot. I can get the robot to shake a lot or oscilate for like 2 seconds and then fall. Also I've implemented a Kalman filter, made the sampling time 5ms, and no luck...

Thanks for your time in advance, this is driving me crazy. I need some advice.

This is my code:

#include <Kalman.h>

#include "MPU6050.h"

#define cbi(sfr, bit) (_SFR_BYTE(sfr) &= ~_BV(bit))
#define sbi(sfr, bit) (_SFR_BYTE(sfr) |= _BV(bit))

//Ports and pins used to decide the direction of the motors
#define DIR_PORT PORTD
#define M1_ENA PD4
#define M1_ENB PD5
#define M2_ENA PD6
#define M2_ENB PD7
//PID variables
double angle_wanted, angle_present, iTerm, dTerm , error,  last_error = 0 ;
double pwm_out;

bool DIRECTION;

double Min = -511, Max = 511; //Min and Max that the PWM can take

double Kp = 43 , Ki = 0, Kd = 10; //PID PARAMETERS

int sample_time_ms = 5;
double sample_time_s;

unsigned long now, then, time_wait;//Fixed loop vars

int16_t ax, ay, az, gx, gy, gz;
double time01Step, time01, time01Prev;
double  rx;
double gyroScale = 131;


Kalman kalman;
MPU6050 accelgyro;
void setup() {
  pinMode ( 9, OUTPUT);   // PWM Motor 1
  pinMode ( 10, OUTPUT);  // PWM Motor 2
  pinMode ( 4, OUTPUT);   // Enable CW Motor 1
  pinMode ( 5, OUTPUT);   // Enable CCW Motor 1
  pinMode ( 6, OUTPUT);   // Enable CW Motor 2
  pinMode ( 7, OUTPUT);   // Enable CCW Motor 2

  pinMode ( 13, OUTPUT);  // Loop time indicator
  
  TCCR1A = _BV(COM1A1) | _BV(COM1B1) | _BV(WGM12) | _BV(WGM11);
  TCCR1B = _BV(CS10);
  
  //     ( Kp , Ki, Kd )
 // pidTune( 31 , 0 ,  0  );   

  Wire.begin();           // Begin the IIC protocol to get info from IMU
  Wire.setClock(400000UL);
  time01 = millis();
 

  angle_wanted = 178.53;
  Serial.begin(9600);
}

void loop() {
  now = millis();
 
  angle_present = angleRead(); //Read the angle
  //Serial.println(angle_present);
  pwm_out = pidCalc( angle_present );//Input the read angle and then calculate output
  /*if ( pwm_out >= 0 ){
    pwm_out = scale( pwm_out, 0 , 511, 230 , 739);
  }
  else{
    pwm_out = ( 0 - pwm_out );
    pwm_out = scale( pwm_out, 0 , 511, 230 , 739);
    pwm_out = ( 0 - pwm_out );
  }
  */
  /*
  if ( (angle_wanted <= angle_wanted + 1) && (angle_wanted >= angle_wanted - 1 )){
    pwm_out = 0;
  }*/

  //Serial.println( pwm_out );
  
  //Serial.println( pwm_out );
//  Serial.println(error);
  
  if ( (error < 45)&&(error > -45)){ // Stop the motors when the angle is over 45
  motorSpeed ( 9  , pwm_out );
  motorSpeed ( 10 , pwm_out );
 // Serial.println("HAHAHAHA");
  }
  else{
  //Serial.println("NAAAAH");
  motorSpeed ( 9  , 0 );
  motorSpeed ( 10 , 0 );
  }
 


  
  then = millis();// Fixed loop time
  time_wait =  sample_time_ms - (then - now);
  if (time_wait > 0 ){
    PORTB = B100000;
    delay(time_wait);//Indicate in LED 13  that the loop has time to run 
  }
  else{
    PORTB = B000000;
  }
  
}
//Scale formula from TKJ Electronics
double scale(double input, double inputMin, double inputMax, double outputMin, double outputMax) { // Like map() just returns a double
  double output;
  if(inputMin < inputMax)
    output = (input-inputMin)/((inputMax-inputMin)/(outputMax-outputMin));              
  else
    output = (inputMin-input)/((inputMin-inputMax)/(outputMax-outputMin));
  if(output > outputMax)
    output = outputMax;
  else if(output < outputMin)
    output = outputMin;
  return output;
}
//Configure PWM 
void setPWM(uint8_t pin, int dutyCycle) { 
  
  if ( pin == 9 ){
    OCR1A = dutyCycle;
  }
  else if ( pin == 10){
    OCR1B = dutyCycle;
  }
}
//Drive the motors, it can take from -511  to 511 and then set the motors accordingly
void motorSpeed(uint8_t pin_motor, int Speed){
  if ( Speed >= 0 ){
     DIRECTION = true;
  }else{
     DIRECTION = false;
    Speed = ( 0 - Speed ); 
  }
  
  if ( pin_motor == 9 ){
    setPWM( 9 , Speed );
    
    if ( DIRECTION == true ) {  //
      sbi( DIR_PORT , M1_ENA );
      cbi( DIR_PORT , M1_ENB );
    }else{                      //
      cbi( DIR_PORT , M1_ENA );
      sbi( DIR_PORT , M1_ENB );
    }
  
  }else if ( pin_motor == 10 ){
    setPWM( 10, Speed );
    if ( DIRECTION == true ) {  //
      sbi( DIR_PORT , M2_ENA );
      cbi( DIR_PORT , M2_ENB );
    }else{                      //
      cbi( DIR_PORT , M2_ENA );
      sbi( DIR_PORT , M2_ENB );
    }
  }
  
}
/*
float calibrateSensors(){
  double zeroval;
  for (int j=0; j<100 ; j++){
    zeroval+= angleRead();
  }
  zeroval/=100;
  return zeroval;
}
*/

double pidCalc( double angle_read ){//Calculation of the PID values
  error = angle_wanted - angle_read;
  //Serial.println( error ); 
  iTerm += ( Ki * error );
  
  if ( iTerm > Max) iTerm = Max;
  
  else if ( iTerm < Min ) iTerm = Min;
  
  dTerm = Kd * ( error - last_error );

  double out = Kp * error + iTerm + dTerm ;//PID complete
  
  //Serial.println( out );
  if ( out > Max ) out = Max;//SAturate the output of the controller 
  else if ( out < Min ) out = Min; 

  last_error = error; 
  //Serial.println(out);
  return out; 
 
}
float angleRead(){
  time01Prev = time01;
  time01 = millis();
  
  time01Step = (time01 - time01Prev) / 1000; //Calculate timestep 

  //Get the IMU data from the MPU 9250 same as MPU6050
  ax = accelgyro.getAccelerationX();
  az = accelgyro.getAccelerationZ();

  gx = accelgyro.getRotationX();

  float gyroRate = -((double)((double)gx) / 1.0323);

  // apply gyro scale from datasheet
  gyroRate = gyroRate/gyroScale;
  //gyroAngle += gyroRate * ((double)(time01Step));
  
  float newangle = (atan2(-ax,-az)+PI)*RAD_TO_DEG;
  
  rx = kalman.getAngle( newangle , gyroRate , time01Step ); //Kalman Filter by TKJ Electronics
  
  // print result
 // Serial.println(rx);
  return rx;

Also, I tried to calibrate the sensors by subtracting the bias ( the average of the first 100 readings) to the accelerometers AX and AZ and the gyro GX. But the the robot is still not balancing. That is why I set the wanted_angle to 178.5, because with the serial monitor I was able to compensate manually looking at the values at a completely vertical position.

Don't worry about kalman filters for now. Just focus on making sure your angle measurements are reliable. And avoid having significant delays in the programming loops..... like too many text print commands.

Do you use the usual 'PID' control techniques?

Stop using the kalman filter for now, stop averaging readings, try and implement a complimentary filter, and then focus on the PID tuning.

Rule of thumb for PID tuning:
1.) Set P = 1; I =D = 0;
2.) Increase P until the system begins to oscillate
3.) Increase D until the system smooths out the oscillations
4.) Increase I until the overall offset diminishes

The so-called "Kalman filter" from TKJ Electronics is not a Kalman filter, and doesn't work very well. It is really a shame that so many people have been misled by the author of that web page.

Your problem will be mostly solved by getting rid of it and replacing the sensor with the BNO055, or use the sensor you have with a genuine, correctly functioning Kalman filter, RTImulib.

jremington:
The so-called "Kalman filter" from TKJ Electronics is not a Kalman filter, and doesn't work very well. It is really a shame that so many people have been misled by the author of that web page.

Thanks JRem. Looks like the link at that page (to the misleading implementation) is gone. But yeah..... if there are no covariance matrices and a bunch of steps for getting through the procedure for Kalman's method, then it's not the kalman filter technique.

Thanks for your response guys. I got rid of the Kalman filter, and I've been experimenting with complementary filters. I got working an example from this hacking mom :

This example got me thinking that I might not be handling the I2C protocol well. I tried just doing the angles and the filter with my previous I2C setup, but the results were slow and nothing like expected, or like in the mom's example.

I am going to read up some more of the setup process...

I've replaced the Kalman filter with a complentary filter. However, now the problem, I think is in the PID or the sensor. Power_Broker's method is the one that I have used so far since the beginning, but no luck. Now, the robot has "lag" and does not respond to the angle sometimes.

float angleRead(){
  //Collect raw data from the sensor.
  Wire.beginTransmission(MPU_ADRESS);
  Wire.write(0x3B);  // starting with register 0x3B (ACCEL_XOUT_H)
  Wire.endTransmission(false);
  Wire.requestFrom(MPU_ADRESS,14,true);  // request a total of 14 registers
  ax=Wire.read()<<8|Wire.read();  // 0x3B (ACCEL_XOUT_H) & 0x3C (ACCEL_XOUT_L)     
  ay=Wire.read()<<8|Wire.read();  // 0x3D (ACCEL_YOUT_H) & 0x3E (ACCEL_YOUT_L)
  az=Wire.read()<<8|Wire.read();  // 0x3F (ACCEL_ZOUT_H) & 0x40 (ACCEL_ZOUT_L)
  heisseman=Wire.read()<<8|Wire.read();  // 0x41 (TEMP_OUT_H) & 0x42 (TEMP_OUT_L)
  gx=Wire.read()<<8|Wire.read();  // 0x43 (GYRO_XOUT_H) & 0x44 (GYRO_XOUT_L)
  gy=Wire.read()<<8|Wire.read();  // 0x45 (GYRO_YOUT_H) & 0x46 (GYRO_YOUT_L)
  gz=Wire.read()<<8|Wire.read();  // 0x47 (GYRO_ZOUT_H) & 0x48 (GYRO_ZOUT_L)

  double dt = (double)(micros() - timer) / 1000000; //This line does three things: 1) stops the timer, 2)converts the timer's output to seconds from microseconds, 3)casts the value as a double saved to "dt".
  timer = micros(); //start the timer again so that we can calculate the next dt.

  double pitch = degconvert * atan(-ax / sqrt( pow(ay, 2) + pow(az, 2)));

  double gyroYrate = gy/131.0;
  
  filthy_angle_y = 0.99 * (filthy_angle_y + gyroYrate * dt) + 0.01 * pitch; 

  return filthy_angle_y;
  
}

Now, the robot has "lag" and does not respond to the angle sometimes.

Post all your code. What led to this choice of filter parameters?

 filthy_angle_y = 0.99 * (filthy_angle_y + gyroYrate * dt) + 0.01 * pitch;

sqrt( pow(ay, 2) + pow(az, 2))Very, very slow and inaccurate. pow() uses logarithms.

Write this instead as sqrt(ayay + azaz))

jremington:
Post all your code. What led to this choice of filter parameters?

I got them from this site: http://www.pitt.edu/~mpd41/Angle.ino

I have changed them to .9 and .1, is there a better way to tune this parameters? I just know that they should add to 1.

I have also taken some videos of the robot performing, or lack thereof. The videos are short, max 14 secs.

Full code ahead:

#include<Wire.h>
#define cbi(sfr, bit) (_SFR_BYTE(sfr) &= ~_BV(bit))
#define sbi(sfr, bit) (_SFR_BYTE(sfr) |= _BV(bit))

//Ports and pins used to decide the direction of the motors
#define DIR_PORT PORTD
#define M1_ENA PD4
#define M1_ENB PD5
#define M2_ENA PD6
#define M2_ENB PD7
//PID variables
double angle_wanted, angle_present, iTerm, dTerm , error,  last_error = 0 ;
double pwm_out;

bool DIRECTION;

double Min = -511, Max = 511; //Min and Max that the PWM can take

double Kp = 21 , Ki = 0, Kd = 0; //PID PARAMETERS

int sample_time_ms = 7;
double sample_time_s;

unsigned long now, then, time_wait;//Fixed loop vars

const int MPU_ADRESS = 0x68 ;
double ax, ay, az, heisseman, gx, gy, gz ;
uint32_t timer;
double filthy_angle_x, filthy_angle_y;
#define degconvert 57.2957786
double gyroScale = 131;

double zeroval;

void setup() {
  //Enable outputs
  pinMode ( 9, OUTPUT);   // PWM Motor 1
  pinMode ( 10, OUTPUT);  // PWM Motor 2
  pinMode ( 4, OUTPUT);   // Enable CW Motor 1
  pinMode ( 5, OUTPUT);   // Enable CCW Motor 1
  pinMode ( 6, OUTPUT);   // Enable CW Motor 2
  pinMode ( 7, OUTPUT);   // Enable CCW Motor 2
  pinMode ( 13, OUTPUT);  // Loop time indicator

  //PWM custom config straight from the datasheet
  TCCR1A = _BV(COM1A1) | _BV(COM1B1) | _BV(WGM12) | _BV(WGM11);
  TCCR1B = _BV(CS10);

  //I2C sensor initialization
  Wire.begin();
  #if ARDUINO >= 157
  Wire.setClock(400000UL); // Set I2C frequency to 400kHz
  #else
    TWBR = ((F_CPU / 400000UL) - 16) / 2; // Set I2C frequency to 400kHz
  #endif
  Wire.beginTransmission(MPU_ADRESS);
  Wire.write(0x6B);  // PWR_MGMT_1 register
  Wire.write(0);     // set to zero (wakes up the MPU-6050)
  Wire.endTransmission(true);
  delay(100);

  timer = micros();

  angle_wanted = calibrateSetpoint();
 
  Serial.begin(115200);
}

void loop() {
  now = millis();
 
  angle_present = angleRead(); //Read the angle
  //Serial.println(angle_present);
  pwm_out = pidCalc( angle_present );//Input the read angle and then calculate output
  /*if ( pwm_out >= 0 ){
    pwm_out = scale( pwm_out, 0 , 511, 230 , 739);
  }
  else{
    pwm_out = ( 0 - pwm_out );
    pwm_out = scale( pwm_out, 0 , 511, 230 , 739);
    pwm_out = ( 0 - pwm_out );
  }
  */
  /*
  if ( (angle_wanted <= angle_wanted + 1) && (angle_wanted >= angle_wanted - 1 )){
    pwm_out = 0;
  }*/

  //Serial.println( pwm_out );
  
  //Serial.println( pwm_out );
  Serial.println(pwm_out);
  
  if ( (error < 45)&&(error > -45)){ // Stop the motors when the angle is over 45
  motorSpeed ( 9  , pwm_out );
  motorSpeed ( 10 , pwm_out );
 // Serial.println("HAHAHAHA");
  }
  else{
  //Serial.println("NAAAAH");
  motorSpeed ( 9  , 0 );
  motorSpeed ( 10 , 0 );
  }
 


  
  then = millis();// Fixed loop time
  time_wait =  sample_time_ms - (then - now);
  if (time_wait > 0 ){
    PORTB = B100000;
    delay(time_wait);//Indicate in LED 13  that the loop has time to run 
  }
  else{
    PORTB = B000000;
  }
  
}
//Scale formula from TKJ Electronics
double scale(double input, double inputMin, double inputMax, double outputMin, double outputMax) { // Like map() just returns a double
  double output;
  if(inputMin < inputMax)
    output = (input-inputMin)/((inputMax-inputMin)/(outputMax-outputMin));              
  else
    output = (inputMin-input)/((inputMin-inputMax)/(outputMax-outputMin));
  if(output > outputMax)
    output = outputMax;
  else if(output < outputMin)
    output = outputMin;
  return output;
}
//Configure PWM 
void setPWM(uint8_t pin, int dutyCycle) { 
  
  if ( pin == 9 ){
    OCR1A = dutyCycle;
  }
  else if ( pin == 10){
    OCR1B = dutyCycle;
  }
}
//Drive the motors, it can take from -511  to 511 and then set the motors accordingly
void motorSpeed(uint8_t pin_motor, int Speed){
  if ( Speed >= 0 ){
     DIRECTION = true;
  }else{
     DIRECTION = false;
    Speed = ( 0 - Speed ); 
  }
  
  if ( pin_motor == 9 ){
    setPWM( 9 , Speed );
    
    if ( DIRECTION == true ) {  //
      sbi( DIR_PORT , M1_ENA );
      cbi( DIR_PORT , M1_ENB );
    }else{                      //
      cbi( DIR_PORT , M1_ENA );
      sbi( DIR_PORT , M1_ENB );
    }
  
  }else if ( pin_motor == 10 ){
    setPWM( 10, Speed );
    if ( DIRECTION == true ) {  //
      sbi( DIR_PORT , M2_ENA );
      cbi( DIR_PORT , M2_ENB );
    }else{                      //
      cbi( DIR_PORT , M2_ENA );
      sbi( DIR_PORT , M2_ENB );
    }
  }
  
}

float calibrateSetpoint(){
  for (int j=0; j<100 ; j++){
    zeroval+= angleRead();
  }
  zeroval/=100;
  return zeroval;
}


double pidCalc( double angle_read ){//Calculation of the PID values
  error = angle_wanted - angle_read;
  //Serial.println( error ); 
  iTerm += ( Ki * error );
  
  if ( iTerm > Max) iTerm = Max;
  
  else if ( iTerm < Min ) iTerm = Min;
  
  dTerm = Kd * ( error - last_error );

  double out = Kp * error + iTerm + dTerm ;//PID complete
  
  //Serial.println( out );
  if ( out > Max ) out = Max;//SAturate the output of the controller 
  else if ( out < Min ) out = Min; 

  last_error = error; 
  //Serial.println(out);
  return out; 
 
}
float angleRead(){
  //Collect raw data from the sensor.
  Wire.beginTransmission(MPU_ADRESS);
  Wire.write(0x3B);  // starting with register 0x3B (ACCEL_XOUT_H)
  Wire.endTransmission(false);
  Wire.requestFrom(MPU_ADRESS,14,true);  // request a total of 14 registers
  ax=Wire.read()<<8|Wire.read();  // 0x3B (ACCEL_XOUT_H) & 0x3C (ACCEL_XOUT_L)     
  ay=Wire.read()<<8|Wire.read();  // 0x3D (ACCEL_YOUT_H) & 0x3E (ACCEL_YOUT_L)
  az=Wire.read()<<8|Wire.read();  // 0x3F (ACCEL_ZOUT_H) & 0x40 (ACCEL_ZOUT_L)
  heisseman=Wire.read()<<8|Wire.read();  // 0x41 (TEMP_OUT_H) & 0x42 (TEMP_OUT_L)
  gx=Wire.read()<<8|Wire.read();  // 0x43 (GYRO_XOUT_H) & 0x44 (GYRO_XOUT_L)
  gy=Wire.read()<<8|Wire.read();  // 0x45 (GYRO_YOUT_H) & 0x46 (GYRO_YOUT_L)
  gz=Wire.read()<<8|Wire.read();  // 0x47 (GYRO_ZOUT_H) & 0x48 (GYRO_ZOUT_L)

  double dt = (double)(micros() - timer) / 1000000; //This line does three things: 1) stops the timer, 2)converts the timer's output to seconds from microseconds, 3)casts the value as a double saved to "dt".
  timer = micros(); //start the timer again so that we can calculate the next dt.

  double pitch = degconvert * atan(-ax / sqrt( pow(ay, 2) + pow(az, 2)));

  double gyroYrate = gy/131.0;
  
  filthy_angle_y = 0.9 * (filthy_angle_y + gyroYrate * dt) + 0.1 * pitch; 

  return filthy_angle_y;
  
}

The code is pretty much a mess. Since you have been experimenting by adding in and removing large chunks of code, I recommend to go back to the basics.

  1. Get your angle measurement working properly, using the complementary filter. That means just have the robot measure the tilt angle and print that out on the serial monitor, while you tilt the robot by hand. Make sure that the tilt angle agrees with the actual tilt, with as little noise as possible and no obvious lag when you wiggle the robot back and forth. The lag comes from improper choice of the weights in the complementary filter.

  2. Leave out all the complicated scaling, mapping, range checks and implement a simple Kp (P only) response. Check that the motors respond in the correct direction to a given small tilt. At this point you can start to adjust Kp to get an idea of what will be a reasonable value of Kp, that does not lead to a wild overshoot. Kp can be less than 1.

  3. Implement the full PID algorithm to stop oscillations and have reasonably quick response. Add back in only those parameter restrictions (like PID output) as required. Some will be obvious, like speed values that cannot exceed an unsigned byte maximum (255).

I got a fresh code now, stuck to the basics. Checked to get the best possible sensor data adjusting the weights of the filter, tested in a separate sketch and worked great. Downloaded some libraries to aid in the readability of the code. All necessary computations occur in-loop, and no other functions are created.

As of right now, I have to still test it, but am unable to do so right now. Will post results of this simplified code.

Code:

#include <PWM.h>

#include <digitalWriteFast.h>

#include <Wire.h>

uint32_t now, then, time_wait, sample_rate_us = 10000; // SAMPLE RATE SET TO 10,000 us that is 10 ms, or 0.01 secs
double AcX,AcY,AcZ,Tmp,GyX,GyY,GyZ;
uint32_t timer;
const int MPU_addr=0x68;
double compAngleX, compAngleY;
#define degconvert 57.2957786
double dt, pitch, gyroYrate;

double error , last_error = 0, set_point, Pterm, Iterm, Dterm, pid_out;
double Kp = 1, Ki = 0, Kd = 0;

void setup() {
  // SENSOR SETUP
  Wire.begin();
  #if ARDUINO >= 157
  Wire.setClock(400000UL); 
  #else
    TWBR = ((F_CPU / 400000UL) - 16) / 2; 
  #endif
  //END SENSOR SETUP

  //PWM SETUP
  InitTimersSafe();
  SetPinFrequencySafe( 9  , 20000);
  SetPinFrequencySafe( 10 , 20000);
  //END PWM SETUP

  //PIN SETUP
  pinMode( 4 , OUTPUT );
  pinMode( 5 , OUTPUT );
  pinMode( 6 , OUTPUT );
  pinMode( 7 , OUTPUT );
  //END PIN SETUP

  //STARTING TIME KEEPING VARIABLES
  now = micros();
  timer = now;
}

void loop() {
  //READ THE SENSOR
  Wire.beginTransmission(MPU_addr);
  Wire.write(0x3B);  // starting with register 0x3B (ACCEL_XOUT_H)
  Wire.endTransmission(false);
  Wire.requestFrom(MPU_addr,14,true);  // request a total of 14 registers
  AcX=Wire.read()<<8|Wire.read();  // 0x3B (ACCEL_XOUT_H) & 0x3C (ACCEL_XOUT_L)     
  AcY=Wire.read()<<8|Wire.read();  // 0x3D (ACCEL_YOUT_H) & 0x3E (ACCEL_YOUT_L)
  AcZ=Wire.read()<<8|Wire.read();  // 0x3F (ACCEL_ZOUT_H) & 0x40 (ACCEL_ZOUT_L)
  Tmp=Wire.read()<<8|Wire.read();  // 0x41 (TEMP_OUT_H) & 0x42 (TEMP_OUT_L)
  GyX=Wire.read()<<8|Wire.read();  // 0x43 (GYRO_XOUT_H) & 0x44 (GYRO_XOUT_L)
  GyY=Wire.read()<<8|Wire.read();  // 0x45 (GYRO_YOUT_H) & 0x46 (GYRO_YOUT_L)
  GyZ=Wire.read()<<8|Wire.read();  // 0x47 (GYRO_ZOUT_H) & 0x48 (GYRO_ZOUT_L)
  
  dt = (double)(micros() - timer) / 1000000; //This line does three things: 1) stops the timer, 2)converts the timer's output to seconds from microseconds, 3)casts the value as a double saved to "dt".
  timer = micros();

  pitch = (180/3.141592) * atan(-AcX / sqrt( pow(AcY, 2) + pow(AcZ, 2))); 

  gyroYrate = GyY/131.0;
  
  compAngleY = 0.98 * (compAngleY + gyroYrate * dt) + 0.02 * pitch; 
  //READ THE SENSOR END

  //START THE PID 
  error = set_point - compAngleY ;
  Pterm = Kp * error ;
  Iterm += Ki * error ;
  Dterm = Kd * (error -last_error);
  last_error = error;
  pid_out = Pterm + Iterm + Dterm ;
  //END PID 
  
  //START PWM WRITE WITH DIRECTION OF MOTORS
  if ( pid_out >= 0){
    pwmWrite( 9  , pid_out );
    pwmWrite( 10 , pid_out );
    digitalWriteFast( 4 , LOW );
    digitalWriteFast( 5 , HIGH );
    digitalWriteFast( 6 , LOW );
    digitalWriteFast( 7 , HIGH );
  }
  else{
    pid_out = 0 - pid_out ;
    pwmWrite( 9  , pid_out );
    pwmWrite( 10 , pid_out );
    digitalWriteFast( 4 , HIGH );
    digitalWriteFast( 5 , LOW );
    digitalWriteFast( 6 , HIGH );
    digitalWriteFast( 7 , LOW );
  }
  //END PWM WRITE WITH DIRECTION

  //FIXED TIME LOOP 
  then = micros();
  time_wait = sample_rate_us -( then - now);
  delayMicroseconds(time_wait);
  now = micros();
  //END FIXED TIME LOOP
}

This is weird: when I tilt the robot towards the positive side I get some sort of weird readings:

0.97
0.67
0.62
0.56
0.49
0.38
0.22///POSITIVE 
-0.01
-0.34
-0.77
-1.36
-2.06
-2.92
-3.92
-5.05
-6.33
-7.74
-9.22
-10.72
-12.23
-13.67
-14.98
-16.14
-17.14
-18.24
-19.94
-20.34
-19.82
-19.17
-18.48
-17.79
-17.08
-16.42
-15.75
-15.10
-14.40
-13.70
-12.98
-12.25
-11.52
-10.77
-10.01
-9.23
-8.43
-7.63
-6.84
-6.04
-5.20
-4.35
-3.47
-2.58
-1.69
-0.78
0.09///END WEIRD
0.95/// NORMAL BEHAVIOUR
1.79
2.61
3.42
4.20
4.98
5.73
6.49
7.28
8.09
8.94
9.81
10.69
11.58
12.46
13.34
14.21
15.07
16.01
16.97
17.95
18.98
20.05
21.18
22.33
23.49
24.65
25.87
27.14
28.34
29.40
30.36
31.19
31.88
32.44
32.91
33.27
33.45
33.48
33.41
33.25
33.06
32.79
32.48
32.13
31.73
31.34
30.99
30.67
30.39
30.06
29.84
29.61
29.41
29.17
28.95
28.70
28.45
28.17
27.83
27.48
27.10
26.71
26.31
25.90
25.47
25.03
24.57
24.12
23.67
23.22
22.78
22.35
21.90
21.44
20.97
20.51
20.05
19.61
19.17
18.75
18.34
17.95
17.56
17.17
16.79
16.42

As if the reading where from the other side, and then they correct themselves.

Are those angle outputs in degrees?

Also, don't use your he pow() function. Also, why are you doing vector addition within the atan() function call? Also, why is the acceleration in the x direction negated (also within the atan() function call)?

No wonder you are having so much trouble.

You really need to back up and attempt to understand what is happening with the tilt angle measurement.

Until you understand this completely and have verified that tilt angle measurement is working properly, your robot has no chance at all of balancing.

The tilt angle measurement comes from the accelerometer, and that measurement is valid only when the robot is motionless. Make sure that that angle is correct, with the correct sign, and is zero when the robot is standing vertical.

The gyro cannot measure a fixed tilt angle. It is used to help correct the errors made when the robot is accelerating or rotating, and the tilt angle as measured by the accelerometer is not correct.

The complementary filter balances the two contributions, and it must be tuned correctly or it will not work.

Google for tutorials and application notes on all these topics, but here is a start: How_to_Use_a_Three-Axis_Accelerometer_for_Tilt_Sensing-DFRobot

@jremington

I have tested and tested this matter, but no progress can be made with the complementary filter. Let me explain:

-What happens? The readings go to the opposite direction of the the actual motion when the robot, or sensor, is tilted.

-How was it tested? I tested the sensor in the robot, bolted to the bottom wood support. Then proceeded to tilt the robot in the air (no contact with ground), and on the ground (contact with floor).

-What was found? for the first case (in the air) I noticed that giving a bit more weight to the Accelerometer part got rid of the problem. This was tested many times and with different weights, I got .93 and .07 as the best weights for this scenario.

THEN, when the robot was in the ground the problem was repeated, the weird values got in. This time I could not get rid of the problem; besides, giving the Accelerometer too much weight gives the filtered readings susceptible to vibrations which translate to reading angles of -20 or 15 degrees.

I think I might have a faulty sensor, or maybe might have to give the Kalman filter another go. Maybe I'll buy another sensor.

Excel sheet attached with sensor data for in the air testing.

@Power_Broker

I did change now to a better way of doing the math:

double pitch = (180/3.141592) * atan2(-AcX , sqrt( AcY*AcY + AcZ*AcZ));

I do the math like this because that is the way normally the angle from the Accelerometers is obtained.
Like in the link: How_to_Use_a_Three-Axis_Accelerometer_for_Tilt_Sensing-DFRobot

from @jremington.

The readings go to the opposite direction of the the actual motion when the robot, or sensor, is tilted.

Then you have mounted the sensor incorrectly, or are using the wrong sign convention to calculate the angle, or using the wrong angle formula.

There are TWO angles that the accelerometer can measure, tilt (or pitch) and roll. Go back and study those angle definitions again, paying extra attention to the sign of positive rotation. Make absolutely sure that you know which directions are positive X, Y and Z on your sensor.

There is no international standard that defines a positive rotation, and there are at least 3 different definitions for Euler angles. So you have to use the definitions that are given in the application note you are studying.

Change the signs in the formula, the formula itself or remount the sensor until it measures the angle correctly. It is really that simple.

Jrem is correct. Until you put in the work and effort to ensure that your angle measurements are working and reliable, you have absolutely no chance of getting the robot to balance.

The first step is to make arduino output the angle readings to the computer screen.... and use something to hold the bot steady.... like in an upright position. And then have some kind of supporting structure that allows you to vary tilt angle of your robot, to ensure that the sensors are outputting values reliably.

Any weird/odd values popping up will need to be addressed. Need to learn to crawl before learning to walk, and learning to walk before learning to run..... for humans that is.

I find that it works OK just to hold the sensor in my hand, and tilt it around while outputting the two angles measured by the accelerometer on the serial monitor:

  roll = atan2(gy , gz) * 57.3;
  pitch = atan2( -gx , sqrt(gy*gy + gz*gz) ) * 57.3;
  Serial.print(roll);
  Serial.print(", ");
  Serial.println(pitch);

Compare the angles printed with the tilt angles you are applying, until you understand the coordinate system.

A point that often confuses beginners is when the accelerometer Z axis points straight up, gz is normally reported as positive. This is because the accelerometer is actually measuring the force required to keep the accelerometer from falling.

I tried modifying the formulas with no luck, I think my problem is that the sensor is not well mounted. Picture attached. I will be mounting the sensor on the bottom and the center of piece of plywood, in between both motors.

EDIT: I did study all the angles and how the axes are related to the pitch, roll, and yaw movements. am sure of the orientation of the sensor, maybe having it in the dead center might help.