Quad copter stabilization

Hi, I have purchased a 6dof IMU which is the MPU 6050 and managed to get the data that I needed for the pitch and roll angles. However, I would like to use the data that is being read by the MPU 6050 for auto leveling and I don't know how to start it. I hope someone will be able to guide me in writing a code in arduino to allow me to achieve the auto level function.

Is it a must to use a PID control loop to allow the quad copter to stabilize as I know that the stabilization comes from sensing the data from the gyroscope and accelerometer?

Thanks

Yes, you need two PID loops, one for pitch and one for roll.

And one for yaw. A spinning copter will be difficult to control.

Thanks for the reply. Is there a simple way to write the PID loop for both the pitch and roll?

It would be 'cleanest' to create a class. Then you can just create as many instances of that class as you need e.g. pitch, roll, yaw.

This site gives a good explanation of both the most basic version of a PID controller, and goes through the steps required to make a more robust one (which you may or may not need).

It's a good place to start - and if you decide in the end you don't want to write the PID class yourself you can just use this library.

Thanks for the link

I have written this code for the PID with the help from electronoobs PID tutorial (PID control arduino drones mpu6050 mpu9250 gyro accelerometer euler msp432 rtos) and I still haven't test it out but wanted to ask you guys for your opinion on whether will it work. Thanks

#include <Wire.h>
#include <Servo.h>

Servo esc1;
Servo esc2;

int16_t Acc_rawX, Acc_rawY, Acc_rawZ, Gyr_rawX, Gyr_rawY, Gyr_rawZ;

int ch1 = 4; 
int ch2 = 5;
int ch3 = 6;
int ch4 = 7;

int roll_channel = 8;
int pitch_channel = 9;
int throttle_channel = 10;
int yaw_channel = 11;

float Acceleration_angle[2];
float Gyro_angle[2];
float Total_angle[2];

float elapsedTime, time, timePrev;
int i;
float rad_to_deg = 180/3.141592654;

float PID, PID_1, pwmleft, pwmRight, error, error_1, previous_error, previous_error_1;
float pid_p = 0;
float pid_p1 = 0;
float pid_i = 0;
float pid_i1 = 0;
float pid_d = 0;
float pid_d1 = 0;

double kp = 3.55;
double kp_1 = 3.55;
double ki = 0.005;
double ki_1 = 0.005;
double kd = 2.05; 
double kd_1 = 2.05;

double throttle = 1000;
float desired_angle = 0;

void setup()
{
  Wire.begin();
  Wire.beginTransmission(0x68);
  Wire.write(0x6B);
  Wire.write(0);
  Wire.endTransmission(true);
  Serial.begin(9600);

  pinMode(ch1, INPUT);
  pinMode(ch2, INPUT);
  pinMode(ch3, INPUT);
  pinMode(ch4, INPUT);
  
  pinMode(roll_channel, OUTPUT);
  pinMode(pitch_channel, OUTPUT);
  pinMode(throttle_channel, OUTPUT);
  pinMode(yaw_channel, OUTPUT);
  
  esc1.attach(8);
  esc2.attach(9);

  time = millis();
}

void loop()
{
  timePrev = time;
  time = millis();
  elepsedTime = (time - timePrev) / 1000;

  Wire.beginTransmission(0x68);
  Wire.write(0x3B);
  Wire.endTransmission(false);
  Wire.requestFrom(0x68,6,true);

  Acc_rawX = Wire.read()<<8|Wire.read();
  Acc_rawY = Wire.read()<<8|Wire.read();
  Acc_rawZ = Wire.read()<<8|Wire.read();
  
  Acceleration_angle[0] = atan((Acc_rawY/16384.0)/sqrt(pow((Acc_rawX/16384.0),2)+ pow((Acc_rawZ/16384.0),2)))*rad_to_deg;
  Acceleration_angle[1] = atan((Acc_rawX/16384.0)/sqrt(pow((Acc_rawY/16384.0),2)+ pow((Acc_rawZ/16384.0),2)))*rad_to_deg;

  Wire.beginTransmission(0x68);
  Wire.write(0x43);
  Wire.endTransmission(false);
  Wire.requestFrom(0x68,4,true);

  Gyr_rawX = Wire.read()<<8|Wire.read();
  Gyr_rawY = Wire.read()<<8|Wire.read();

  Gyro_angle[0] = Gyr_rawX/131.0;
  Gyro_angle[1] = Gyr_rawY/131.0;

  Total_angle[0] = 0.98 *(Total_angle[0] + Gyro_angle[0]*eleapsedTime) + 0.02*Acceleration_angle[0];
  Total_angle[1] = 0.98 *(Total_angle[1] + Gyro_angle[1]*eleapsedTime) + 0.02*Acceleration_angle[1];

  error = Total_angle[0] - desire_angle;
  error_1 = Total_angle[1] - desire_angle;

  pid_p = kp*error;
  pid_p1 = kp1*error_1;

  if (-3 < error < 3 && -3 < error_1 < 3)
  {
    pid_i = pid_i + (ki*error);
    pid_i1 = pid_i1 + (ki_1*error_1);
  }
  
  pid_d = kd*((error - previous_error)/elapsedTime);
  pid_d1 = kd_1*((error_1 - previous_error_2)/elapsedTime);

  PID = pid_p + pid_i + pid_d;
  PID_1 = pid_p1 + pid_i1 + pid_d1;

  if (PID < -1000)
  {
     PID=-1000;
}
if(PID > 1000)
{
  PID=1000;
}

pwmLeft = throttle + PID;
pwmRight = throttle - PID;

if(pwmRight < 1000)
{
  pwmRight= 1000;
}
if(pwmRight > 2000)
{
  pwmRight=2000;
}

if(pwmLeft < 1000)
{
  pwmLeft= 1000;
}
if(pwmLeft > 2000)
{
  pwmLeft=2000;
}


esc1.writeMicroseconds(pwmLeft);
esc2.writeMicroseconds(pwmRight);
previous_error = error; 
}

It looks like it is roughly correct but you would of course still need to check properly. Testing will be easiest if you have just the PID code in isolation (and you can feed it known figures and examine the output).

Some general points:
You might as well read all (14) MPU registers in one go.
This will include the temperature reading but you can discard it if you want. If there is a break in between the readings then it may update the registers which means that your accelerometer readings aren't from the same point as your gyro readings (though I don't know how much of an effect this would actually have).

It would be easier to follow the flow of loop() if you separate the different actions e.g. reading sensor, calculating angles, updating esc etc into functions. Might not be too much of a problem now but if it gets more complicated then will be worth it.

It would still be preferable to create a class I think but if not then give your variables more meaningful names. Instead of 'PID' and 'PID_1', you could have 'PID_roll' and 'PID_pitch' for example.

You are only calculating 2 ESC outputs rather than 4. Is this intentional or just because this is an example?

You can replace all the constants with constant variables or #defines.
It so happens that there's already a constant for converting radians to degrees, RAD_TO_DEG.

bms001:
It would be 'cleanest' to create a class. Then you can just create as many instances of that class as you need e.g. pitch, roll, yaw.

I doubt it. If throttle+yaw hits 100% motor power then how does pitch modify the power level to achieve it's goal. The typical quadcopter is a tightly-integrated system where each PID loop cooperates with the others.

MorganS:
The typical quadcopter is a tightly-integrated system where each PID loop cooperates with the others.

I'm not saying you're wrong, but for every example I have looked at (hobbyist stuff that people have posted online) that is not the case and the PID loops are completely separate. They calculate an output for each PID ( roll, pitch and yaw) and then these are combined when applied as adjustments to the base throttle.
It may be that commercial or other high end implementations are not done like this but I have no idea. If you know of any examples where it's done differently I would be interested to see them.
I suppose if you use a different approach like Kalman filter you might be able to couple the response for all 3 axes (maybe?).

MorganS:
If throttle+yaw hits 100% motor power then how does pitch modify the power level to achieve it's goal.

Well ultimately it can't, but you can never have maximum roll/pitch/yaw control of a quadcopter at maximum throttle anyway. Simple solutions might be:

  • Limit the PID output and throttle such that the maximum when they are combined will not be a request for more than 100% throttle
  • Handle this after the PID runs and just reduce throttle on all motors by the maximum amount that the any motor throttle is over maximum.
    e.g. if after making PID adjustments, M1 = 110%, M2 = 100%, M3 = 90%, M4 = 105% (which of course can't be delivered)
    reduce all by 10 percentage points: M1 = 100%, M2 = 90%, M3 = 80%, M4 = 95%
    (Potential problems with this would be that the PID doesn't know that the output it produced was not fully applied so you might need to adjust some of its internal values to preserve consistency)

Yup, it is intentional. I am currently working on writing the code for the X axis

bms001:
You are only calculating 2 ESC outputs rather than 4. Is this intentional or just because this is an example?

This is the new code which consists of the 4 esc. Is there anything wring with the code? Thanks

#include <Wire.h>
#include <Servo.h>

Servo esc1;
Servo esc2;
Servo esc3;
Servo esc4;

int16_t Acc_rawX, Acc_rawY, Acc_rawZ, Gyr_rawX, Gyr_rawY, Gyr_rawZ;

int ch1 = 4; 
int ch2 = 5;
int ch3 = 6;
int ch4 = 7;

int roll_channel = 8;
int pitch_channel = 9;
int throttle_channel = 10;
int yaw_channel = 11;

float Acceleration_angle[2];
float Gyro_angle[2];
float Total_angle[2];

float elapsedTime, time, timePrev;
int i;
float rad_to_deg = 180/3.141592654;

float PID_roll, PID_pitch, pwmLeft, pwmRight, pwmForward, pwmBackward, error, error_1, previous_error, previous_error_1;
float pid_p = 0;
float pid_p1 = 0;
float pid_i = 0;
float pid_i1 = 0;
float pid_d = 0;
float pid_d1 = 0;

double kp = 3.55;
double kp_1 = 3.55;
double ki = 0.005;
double ki_1 = 0.005;
double kd = 2.05; 
double kd_1 = 2.05;

double throttle = 1000;
float desired_angle = 0;

void setup()
{
  Wire.begin();
  Wire.beginTransmission(0x68);
  Wire.write(0x6B);
  Wire.write(0);
  Wire.endTransmission(true);
  Serial.begin(9600);

  pinMode(ch1, INPUT);
  pinMode(ch2, INPUT);
  pinMode(ch3, INPUT);
  pinMode(ch4, INPUT);
  
  pinMode(roll_channel, OUTPUT);
  pinMode(pitch_channel, OUTPUT);
  pinMode(throttle_channel, OUTPUT);
  pinMode(yaw_channel, OUTPUT);
  
  esc1.attach(8);
  esc2.attach(9);
  esc3.attach(10);
  esc4.attach(11);

  time = millis();
}

void loop()
{
  timePrev = time;
  time = millis();
  elapsedTime = (time - timePrev) / 1000;

  Wire.beginTransmission(0x68);
  Wire.write(0x3B);
  Wire.endTransmission(false);
  Wire.requestFrom(0x68,6,true);
  Wire.beginTransmission(0x68);
  Wire.write(0x43);
  Wire.endTransmission(false);
  Wire.requestFrom(0x68,4,true);

  Gyr_rawX = Wire.read()<<8|Wire.read();
  Gyr_rawY = Wire.read()<<8|Wire.read();
  Acc_rawX = Wire.read()<<8|Wire.read();
  Acc_rawY = Wire.read()<<8|Wire.read();
  Acc_rawZ = Wire.read()<<8|Wire.read();
  
  Acceleration_angle[0] = atan((Acc_rawY/16384.0)/sqrt(pow((Acc_rawX/16384.0),2)+ pow((Acc_rawZ/16384.0),2)))*rad_to_deg;
  Acceleration_angle[1] = atan((Acc_rawX/16384.0)/sqrt(pow((Acc_rawY/16384.0),2)+ pow((Acc_rawZ/16384.0),2)))*rad_to_deg;

  Gyro_angle[0] = Gyr_rawX/131.0;
  Gyro_angle[1] = Gyr_rawY/131.0;

  Total_angle[0] = 0.98 *(Total_angle[0] + Gyro_angle[0]*elapsedTime) + 0.02*Acceleration_angle[0];
  Total_angle[1] = 0.98 *(Total_angle[1] + Gyro_angle[1]*elapsedTime) + 0.02*Acceleration_angle[1];

  error = Total_angle[0] - desired_angle;
  error_1 = Total_angle[1] - desired_angle;

  pid_p = kp*error;
  pid_p1 = kp_1*error_1;

  if (-3 < error < 3 && -3 < error_1 < 3)
  {
    pid_i = pid_i + (ki*error);
    pid_i1 = pid_i1 + (ki_1*error_1);
  }
  
  pid_d = kd*((error - previous_error)/elapsedTime);
  pid_d1 = kd_1*((error_1 - previous_error_1)/elapsedTime);

  PID_roll= pid_p + pid_i + pid_d;
  PID_pitch = pid_p1 + pid_i1 + pid_d1;

  if (PID_roll < -1000)
  {
    PID_roll = -1000;
  }

  if (PID_roll > 1000)
  {
    PID_roll = 1000;
  }

  if (PID_pitch < -1000)
  {
    PID_pitch = -1000;
  }

  if (PID_pitch > 1000)
  {
    PID_pitch = 1000;
  }

  pwmLeft = throttle + PID_roll;
  pwmRight = throttle - PID_roll;
  pwmForward = throttle + PID_pitch;
  pwmBackward = throttle - PID_pitch;

  if (pwmRight < 1000)
  {
    pwmRight = 1000;
  }

  if (pwmRight > 2000)
  {
    pwmRight = 2000;
  }

   if (pwmLeft < 1000)
  {
    pwmLeft = 1000;
  }

  if (pwmLeft > 2000)
  {
    pwmLeft = 2000;
  }

   if (pwmForward < 1000)
  {
    pwmForward = 1000;
  }

  if (pwmForward > 2000)
  {
    pwmForward = 2000;
  }

   if (pwmBackward < 1000)
  {
    pwmRight = 1000;
  }

  if (pwmBackward > 2000)
  {
    pwmRight = 2000;
  }

  esc1.writeMicroseconds(pwmRight);
  esc2.writeMicroseconds(pwmLeft);
  esc3.writeMicroseconds(pwmForward);
  esc4.writeMicroseconds(pwmBackward);

  previous_error = error;
  previous_error_1 = error_1;
}

Based on how you have applied the PID outputs to the motors I assume you are making a '+' style quadcopter rather than 'x'?

Not directly related to PID but:

How about reading the sensor in one go? Also I think you have accel/gyro the wrong way around when you take their values from the I2C buffer.
e.g.

  Wire.beginTransmission(0x68);
  Wire.write(0x3B);
  Wire.endTransmission(false);
  Wire.requestFrom(0x68,14,true);

  Acc_rawX = Wire.read()<<8|Wire.read();
  Acc_rawY = Wire.read()<<8|Wire.read();
  Acc_rawZ = Wire.read()<<8|Wire.read();
  Temperature_raw = Wire.read()<<8|Wire.read();
  Gyr_rawX = Wire.read()<<8|Wire.read();
  Gyr_rawY = Wire.read()<<8|Wire.read();
  Gyr_rawZ = Wire.read()<<8|Wire.read();

You'll also need to wake up the MPU (write 0 to PWR_MGMT_1 register 0x6B) before it will give you any readings.

I am making a 'x' style quadcopter

bms001:
Based on how you have applied the PID outputs to the motors I assume you are making a '+' style quadcopter rather than 'x'?

Panda01:
I am making a 'x' style quadcopter

So you don't have a front/back/left/right motor, you have a front left, front right, back left and back right motor.
Which means you'll need to apply PID roll correction to all motors, and PID pitch correction to all motors.

E.g. motor X = base throttle +/- PID roll +/- PID pitch

bms001:
So you don't have a front/back/left/right motor, you have a front left, front right, back left and back right motor.
Which means you'll need to apply PID roll correction to all motors, and PID pitch correction to all motors.

E.g. motor X = base throttle +/- PID roll +/- PID pitch

Thanks for the advice. I have changed a few of the code and now I have tried to compile it together with another code that allow me to hover the quad copter at 30 cm. However, once i tried to compile it, the code can't seem to run. Is there any problem with the code? Thanks

#include <Servo.h>
#include <Wire.h>
#include <math.h>
#define PI 3.14159265

Servo esc1; //Create servo object for the 4 esc
Servo esc2;
Servo esc3;
Servo esc4;

float pid_p_gain_roll = 1.3;
float pid_i_gain_roll = 0.04;
float pid_d_gain_roll = 18.0;

float pid_p_gain_pitch = pid_p_gain_roll;
float pid_i_gain_pitch = pid_i_gain_roll;
float pid_d_gain_pitch = pid_d_gain_roll;

long accelX, accelY, accelZ;
float gForceX, gForceY, gForceZ;
double pitchAngle, rollAngle;
long gyroX, gyroY, gyroZ;
float rotX, rotY, rotZ;
float pid_error_temp;
float pid_i_mem_roll, pid_roll_setpoint, pid_output_pitch, pid_last_roll_d_error;
float pid_i_mem_pitch, pid_pitch_setpoint, pid_output_roll, pid_last_pitch_d_error;
float front_right, front_left, rear_right, rear_left;

long duration;
int distance;

int ch1 = 3;
int ch2 = 4;
int ch3 = 5;
int ch4 = 6;
int ch5 = 7;
int trigPin = A2;
int echoPin = A3;

int roll_channel = 8;
int pitch_channel = 9;
int throttle_channel = 10;
int yaw_channel = 11;
int alt_hold_switch = 12;

void setup()
{
  Serial.begin(9600);
  pinMode(ch1, INPUT); //Attach pin to the recevier input
  pinMode(ch2, INPUT);
  pinMode(ch3, INPUT);
  pinMode(ch4, INPUT);
  pinMode(ch5, INPUT);
  pinMode(echoPin, INPUT);

  pinMode(trigPin, OUTPUT);
  pinMode(roll_channel,OUTPUT); //Attach pin to the motor controlling the roll
  esc1.attach(8); //Attach pin 8 to esc 1 to control the motor
  pinMode(pitch_channel, OUTPUT);
  esc2.attach(9);
  pinMode(throttle_channel, OUTPUT);
  esc3.attach(10);
  pinMode(yaw_channel, OUTPUT);
  esc4.attach(11);
  pinMode(alt_hold_switch, OUTPUT);

  setupMPU();
}

void setupMPU()
{
  Wire.beginTransmission(0b1101000);
  Wire.write(0x6B);
  Wire.write(0b00000000);
  Wire.endTransmission();
  Wire.beginTransmission(0b1101000);
  Wire.write(0x1B);
  Wire.write(0x00000000);
  Wire.endTransmission();
  Wire.beginTransmission(0b1101000);
  Wire.write(0x1C);
  Wire.write(0b00000000);
  Wire.endTransmission();
}

void recordAccelRegisters()
{
  Wire.beginTransmission(0b1101000);
  Wire.write(0x3B);
  Wire.endTransmission();
  Wire.requestFrom(0b1101000,6);
  while(Wire.available() < 6);
  accelX = Wire.read()<<8|Wire.read();
  accelY = Wire.read()<<8|Wire.read();
  accelZ = Wire.read()<<8|Wire.read();
  processAccelData();
}

void processAccelData()
{
  gForceX = accelX/16384.0;
  gForceY = accelY/16384.0;
  gForceZ = accelZ/16384.0;
  pitchAngle = (atan2(gForceY, sqrt(sq(gForceX) + sqrt(gForceZ))))* (360/(2*PI));
  rollAngle = (atan2(gForceX, sqrt (sq(gForceY) + sqrt(gForceZ)))) * (360/(2*PI));
}

void recordGyroRegisters()
{
  Wire.beginTransmission(0b1101000);
  Wire.write(0x43);
  Wire.endTransmission();
  Wire.requestFrom(0b1101000, 6);
  while(Wire.available() < 6);
  gyroX = Wire.read()<<8|Wire.read();
  gyroY = Wire.read()<<8|Wire.read();
  gyroZ = Wire.read()<<8|Wire.read();
  processGyroData();
}

void processGyroData()
{
  rotX = gyroZ/131.0;
  rotY = gyroY/131.0;
  rotZ = gyroZ/131.0;
}

void calculate_pid()
{
  pid_error_temp = rollAngle - pid_roll_setpoint;
  pid_i_mem_roll += pid_i_gain_roll * pid_error_temp;
  pid_output_roll = pid_p_gain_roll * pid_error_temp + pid_i_mem_roll + pid_d_gain_roll * (pid_error_temp - pid_last_roll_d_error);
  pid_last_roll_d_error = pid_error_temp;

  pid_error_temp = pitchAngle - pid_roll_setpoint;
  pid_i_mem_pitch += pid_i_gain_pitch * pid_error_temp;
  pid_output_pitch = pid_p_gain_roll * pid_error_temp + pid_i_mem_pitch + pid_d_gain_pitch * (pid_error_temp - pid_last_pitch_d_error);
  pid_last_pitch_d_error = pid_error_temp;
}

void loop()
{
  recordAccelRegisters();
  recordGyroRegisters();
  delay(50);

  digitalWrite(trigPin, LOW);
  delayMicroseconds(2);
  digitalWrite(trigPin, HIGH);
  delayMicroseconds(10);
  digitalWrite(trigPin, LOW);
  duration = pulseIn(echoPin, HIGH);
  distance = duration*0.034/2;
  distance = constrain(distance, 0, 30);
  
  roll_channel = pulseIn(ch1, HIGH, 20000); //Program to start the motor and to control it
  esc1.writeMicroseconds(roll_channel);
  delay(20);
  pitch_channel = pulseIn(ch2, HIGH, 20000);
  esc2.writeMicroseconds(pitch_channel);
  delay(20);
  throttle_channel = pulseIn(ch3, HIGH, 20000);
  esc3.writeMicroseconds(throttle_channel);
  delay(20);
  yaw_channel = pulseIn(ch4, HIGH, 20000);
  esc4.writeMicroseconds(yaw_channel);
  delay(20);
  alt_hold_switch = pulseIn(ch5, HIGH, 20000);
  delay(20);
  
    if (distance == 30 && alt_hold_switch <= 1300)
  {
      esc1.writeMicroseconds(roll_channel);
      esc2.writeMicroseconds(pitch_channel);
      esc3.writeMicroseconds(2000);
      esc4.writeMicroseconds(yaw_channel);
  }

    else if (distance <= 30 && alt_hold_switch >= 1300)
  {
      esc1.writeMicroseconds(roll_channel);
      esc2.writeMicroseconds(pitch_channel);
      esc3.writeMicroseconds(throttle_channel);
      esc4.writeMicroseconds(yaw_channel);
  }

  pid_roll_setpoint = 0;
  pid_pitch_setpoint = 0;

  roll_channel = throttle_channel - pid_output_pitch + pid_output_roll;
  pitch_channel = throttle_channel + pid_output_pitch + pid_output_roll;
  throttle_channel = throttle_channel + pid_output_pitch - pid_output_roll;
  yaw_channel = throttle_channel - pid_output_pitch - pid_output_roll;


  Serial.print("Channel 1 (Roll): ");
  Serial.print(roll_channel);
  Serial.print("\t\tChannel 2 (Pitch): ");
  Serial.print(pitch_channel);
  Serial.print("\t\tChannel 3 (Throttle): ");
  Serial.print(throttle_channel);
  Serial.print("\t\tChannel 4 (Yaw): ");
  Serial.print(yaw_channel);
  Serial.print("\t\tChannel 5 (Alt): ");
  Serial.print(alt_hold_switch);
  Serial.print("\t\tDistance: ");
  Serial.print(distance);
  Serial.print("\t\tPitch Angle (deg) = ");
  Serial.print(pitchAngle);
  Serial.print("\t\tRoll Angle (deg) = ");
  Serial.println(rollAngle);
}

Panda01:
However, once i tried to compile it, the code can't seem to run.

Can you elaborate what you actually mean by this? Have you tried just printing some debugging information to the Serial monitor? e.g. receiver pulse values, angle values, distance values etc to help you understand what's happening.

However, 2 major issues that I can see.
1)

Panda01:

void recordAccelRegisters()

{
  Wire.beginTransmission(0b1101000);
  Wire.write(0x3B);
  Wire.endTransmission();
  Wire.requestFrom(0b1101000,6);
  while(Wire.available() < 6);
  accelX = Wire.read()<<8|Wire.read();
  accelY = Wire.read()<<8|Wire.read();
  accelZ = Wire.read()<<8|Wire.read();
  processAccelData();
}

You are requesting 6 bytes but only reading them from the I2C buffer if there are less than 6, so you'll never enter the while loop. If you do go into the while loop you'll never leave since once you've finished reading the I2C buffer Wire.available() < 6 will continue to be true forever.

  1. Handling of receiver input and setting of motors.
    You are setting your motors based on the receiver input which does not include the PID correction.
  pitch_channel = pulseIn(ch2, HIGH, 20000);
  esc2.writeMicroseconds(pitch_channel);
  delay(20);

Then you are setting them again depending on the distance.

    if (distance == 30 && alt_hold_switch <= 1300)
  {
      esc1.writeMicroseconds(roll_channel);
      esc2.writeMicroseconds(pitch_channel);
      esc3.writeMicroseconds(2000);
      esc4.writeMicroseconds(yaw_channel);
  }

And does this look right to you?

esc3.writeMicroseconds(throttle_channel);

You should always be using these values to set your motors:

  roll_channel = throttle_channel - pid_output_pitch + pid_output_roll;
  pitch_channel = throttle_channel + pid_output_pitch + pid_output_roll;
  throttle_channel = throttle_channel + pid_output_pitch - pid_output_roll;
  yaw_channel = throttle_channel - pid_output_pitch - pid_output_roll;

But these never get used because the next time through the loop you read another pulse and use that value instead.

  1. I don't know if this is definitely an issue or not (though I'm fairly sure it's not optimal), but you are using the pulseIn() function which is preventing anything from happening for up to 2ms per channel.
    And why the delay(20)? Regardless of how you handle receiver input and motor output, I would suggest you at least do it separately i.e. read all inputs, then set all outputs.

bms001:
Can you elaborate what you actually mean by this? Have you tried just printing some debugging information to the Serial monitor? e.g. receiver pulse values, angle values, distance values etc to help you understand what's happening.

I have managed to resolve this problem already

bms001:
You are requesting 6 bytes but only reading them from the I2C buffer if there are less than 6, so you'll never enter the while loop. If you do go into the while loop you'll never leave since once you've finished reading the I2C buffer Wire.available() < 6 will continue to be true forever

Therefore how many bytes should I request?

bms001:
Then you are setting them again depending on the distance

This is to allow the quad copter to fly to a height of 30cm when it start up

bms001:
You should always be using these values to set your motors:

How should I go about to set the motor to read these values? Thanks

Panda01:
Therefore how many bytes should I request?

It's fine to request 6 bytes, but your condition for the while loop is that there are less than 6 received. 6 is not less than 6 so you will never enter your while loop to read the data you requested from the sensor. Check some of the basic Wire examples to see how this is done.

I would expect your motor update routine to look something like this:

esc1pulse = throttle_channel - pid_output_pitch + pid_output_roll;
esc2pulse = throttle_channel + pid_output_pitch + pid_output_roll;
esc3pulse = throttle_channel + pid_output_pitch - pid_output_roll;
esc4pulse = throttle_channel - pid_output_pitch - pid_output_roll;
esc1.writeMicroseconds(esc1pulse);
esc2.writeMicroseconds(esc2pulse);
esc3.writeMicroseconds(esc3pulse);
esc4.writeMicroseconds(esc4pulse);

This is only an example, not necessarily the exact code that you should use, but so you see what this is trying to achieve?

However I noticed that you are not actually calling your calculate_pid() function at any point.

In order for your quadcopter to hold some specific altitude, it will need to be able to vary the throttle. Personally I would make sure that everything else is working before relinquishing manual control of that. And even then I would be very careful and check exhaustively before letting the quad run by itself.

void recordAccelRegisters()

{
  Wire.beginTransmission(0b1101000);
  Wire.write(0x3B);
  Wire.endTransmission();
  Wire.requestFrom(0b1101000,6);
  while(Wire.available() < 6);
  accelX = Wire.read()<<8|Wire.read();
  accelY = Wire.read()<<8|Wire.read();
  accelZ = Wire.read()<<8|Wire.read();
  processAccelData();
}

This would be more obvious if it was written as...

void recordAccelRegisters()
{
  Wire.beginTransmission(0b1101000);
  Wire.write(0x3B);
  Wire.endTransmission();
  Wire.requestFrom(0b1101000,6);
  while(Wire.available() < 6) {
    //wait for 6 bytes to arrive
  }
  accelX = Wire.read()<<8|Wire.read();
  accelY = Wire.read()<<8|Wire.read();
  accelZ = Wire.read()<<8|Wire.read();
  processAccelData();
}

Therefore, it does enter the while() loop and it does exit. It never reads anything inside the loop. However if there is some glitch and it doesn't count 6 bytes, then it will be stuck here forever. Every time I write this kind of thing, I give it a timeout for when it can give up waiting.