Introduction: Gyro Controlled Quadcopter

This Instructable was created in fulfillment of the project requirement of the Makecourse at the University of South Florida (www.makecourse.com).

By Jeremiah Masseus

In this instructable, we will build a quadcopter controlled by tilting a remote. Here's what you'll need:

2 HC-12 wireless Transceivers

1 MPU 6050 3 Axis Accelerometer Gyroscope Module

1 LiPo Battery (12V)

1 12V Power Distribution Board

4 12V Electronic Speed Controllers

4 12V Brushless Motors

4 Propellers

2 Arduinos

1 Potentiometer

2 Pushbuttons

Step 1: Control System

Essentially, values will be read off of an Accelerometer/Gyroscope (MPU 6050) using an Arduino, and transmitted wirelessly to a second Arduino, which will control the rotational speed of 4 motors based on the values read from the gyroscope. There are also 2 pushbuttons to turn the quadcopter on & off, along with a button that toggles a hover mode. In addition, there is a potentiometer that will control the thrust of the quadcopter.

Step 2: Circuit Schematic: Remote Control

Here is a schematic of the Remote. I provide a power source usually with a computer, but an independent power source will work as well, as long as it is 5V.

Step 3: Remote Control Code

/* Quadcopter Remote Code. Written by Jeremiah Masseus
Description:
This portion of the code will retrieve data about the orientation of the 
remote using the gyroscope & acclerometer, then create
4 separate values (1 for each motor) based on the orientation of the remote
to send to the quadcopter. Power, hover, and throttle control also provided.
*/

#include <stdlib.h>
#include <SoftwareSerial.h>
#include <Wire.h>
#define pbutton 3
#define hbutton 2
#define thr A6
//accelerometer
long aX;
long aY;
long aZ;
float fX;
float fY;
float fZ;
//gyroscope
long gyroX;
long gyroY;
long gyroZ;
float roll;
float pitch;
float yaw;
float sum;
float psum;
float hover = 170; 
float hover1;
float hover2;
float hover3;
float hover4;
float off = 10;
float throt;
byte power;
byte hovermode;
byte throttle;
float Motor1;
float Motor2;
float Motor3;
float Motor4;
SoftwareSerial Sender(9,10);
void setup() {
  Sender.begin(9600);
  Serial.begin(9600);
  pinMode(pbutton, INPUT);
  pinMode(hbutton, INPUT);
  digitalWrite(pbutton, HIGH);
  digitalWrite(hbutton, HIGH);
  Wire.begin();
  setupMPU();
  hover1 = hover;
  hover2 = hover;
  hover3 = hover;
  hover4 = hover;
  Sender.print('p');
  
  power = LOW; 
  hovermode = HIGH;
  throttle = HIGH;
}
void setupMPU(){
  //Sets up Gyro/Accel module
  //Turning off sleep on power up
  Wire.beginTransmission(0b1101000);// I2C address of MPU. Refer to MPU-6050 datasheet.
  Wire.write(0x6B); //Accessing Register 6B - power management
  Wire.write(0b00000000); //Setting Sleep register to zero to ensure that MPU-6050 is not in sleep mode upon power up.
  Wire.endTransmission();
  //Setting Gyro Senstivity
  Wire.beginTransmission(0b1101000);// I2C address of MPU. Refer to MPU-6050 datasheet.
  Wire.write(0x1B); //Accessing Register 1B - Gyroscope Configuration
  Wire.write(0x00000000); //Setting Gyro to +/- 250 deg/sec. NOTE: Ensure that divisor in user defined fucinon 'processgyrodata' matches the sensitivity setting. Refer to datasheet
  Wire.endTransmission();
  //Setting Accelerometer Sensitivity
  Wire.beginTransmission(0b1101000);// I2C address of MPU. Refer to MPU-6050 datasheet.
  Wire.write(0x1C); //Accessing Register 1C - Accelerometer Configuration
  Wire.write(0b00000000); //Setting Accelerometer +/- 2g. NOTE: Ensure that divisor in user defined fucinon 'processacceldata' matches the sensitivity setting. Refer to datasheet
  Wire.endTransmission();
  }
void recordgyro(){
  Wire.beginTransmission(0b1101000); //I2C address
  Wire.write(0x43); //Starting Register for Gyro Readings
  Wire.endTransmission();
  Wire.requestFrom(0b1101000,6); //Requesting 6 Accel registers, specifically registers 3B - 40
  while(Wire.available() < 6);
      gyroX = Wire.read()<<8|Wire.read(); //Stores first two bytes in X
      gyroY = Wire.read()<<8|Wire.read(); //Stores middle two bytes in Y
      gyroZ = Wire.read()<<8|Wire.read(); //Stores last two bytes in Z
      processgyrodata();
  }
void processgyrodata(){
   //Convert Values to degrees/sec. NOTE: the Divisor will change if the gyro sensitivity changes
   roll = gyroX / 131; 
   pitch = gyroY / 131;
   yaw = gyroZ / 131; 
   data2ESC();
  }
void loop() {
  if(digitalRead(pbutton) == LOW){
    //toggles power mode
    delay(500);
      hover1 = hover;
      hover2 = hover;
      hover3 = hover;
      hover4 = hover;
      hovermode = HIGH;
      throttle = HIGH;
    if(power == HIGH){
      power = LOW;
      Serial.print("POWER OFF");
      }
     else{
      power = HIGH; 
      Serial.print("POWER ON");
      }
    }   
    
  if(digitalRead(hbutton) == LOW){
    delay(500);
    //toggles hover mode
    if(hovermode == HIGH){
      hovermode = LOW; //exits hover mode - will start to read gyro values
      Serial.print("HOVER OFF");
      hover1 = hover;
      hover2 = hover;
      hover3 = hover;
      hover4 = hover;
      sum = 0;
      }
     else{
      hovermode = HIGH; //enters hover mode. Quadcopter will remain in place
        hover1 = hover;
        hover2 = hover;
        hover3 = hover;
        hover4 = hover;
      Serial.print("HOVER ON");
      }
    }
  throt = map(analogRead(thr), 0, 1023, -50, 50); //Throttle value
 
  if(power == LOW){
    Sender.print('p'); //Sends the letter p to the receiver to indicate that the motors should be off 
    }
  else if(power == HIGH && hovermode == HIGH) {
     Sender.print('i'); 
     Sender.print(hover + throt); 
     Sender.println('E');
     Serial.println(hover + throt); 
    }
  else{ 
    //power==HIGH && hovermode==LOW. Arduino will start to control motors using the gyro
    recordgyro();
  }
 }
void recordacc(){
  //Rertrieves the accelerometer values
  Wire.beginTransmission(0b1101000); //I2C address
  Wire.write(0x3B); //Starting Register for Accel Readings
  Wire.endTransmission();
  Wire.requestFrom(0b1101000,6); //Requesting 6 Gyro registers, specifically registers 43 - 48
  while(Wire.available() < 6);
      aX = Wire.read() << 8|Wire.read(); //Stores first two bytes in X
      aY = Wire.read() << 8|Wire.read(); //Stores middle two bytes in Y
      aZ = Wire.read() << 8|Wire.read(); //Stores last two bytes in Z
      processacceldata();
  }
void processacceldata(){
      //Convert Values to gs. NOTE: the Divisor will change if the accel sensitivity changes
      fX = aX / 16384.0; 
      fY = aY / 16384.0;
      fZ = aZ / 16384.0;  
  }
void data2ESC(){
  //Uses raw data to orient each induvidual motor to replicate the orientation of the remote
  //roll
  if(abs(roll)>= 10){ //ignores minute movements due to human error
    hover1 = hover1 - roll/60;
    hover2 = hover2 + roll/60;
    hover3 = hover3 + roll/60;
    hover4 = hover4 - roll/60;
    }
  //pitch  
    if(abs(pitch)>= 20){
    hover1 = hover1 + pitch/120;
    hover2 = hover2 + pitch/120;
    hover3 = hover3 - pitch/120;
    hover4 = hover4 - pitch/120;
    }
  /*If the current value is close to the hover value, 
  the motor will adopt the hover value, to account for the 
  differnce in precision  between the gyro & a human*/
  if(abs(hover1-hover)<=0.5){
    hover1 = hover;
    }
  if(abs(hover2 - hover)<=0.5){
    hover2 = hover;
    }
  if(abs(hover3 - hover)<=0.5){
    hover3 = hover;
    }
  if(abs(hover4 - hover)<=0.5){
    hover4 = hover;
    }
  Motor1 = hover1 + throt; 
  Motor2 = hover2 + throt;
  Motor3 = hover3 + throt; 
  Motor4 = hover4 + throt; 
  //Sends the values of each motor, separated by Charaters to indicate the limits 
  Sender.write("M");
  Sender.print(Motor1);
  Sender.write("D");
  Sender.print(Motor2);
  Sender.print("T");
  Sender.print(Motor3);
  Sender.print("Q");
  Sender.print(Motor4);
  Sender.println("E");
  delay(10);
  Serial.print(Motor1);
  Serial.print(" ");
  Serial.print(Motor2);
  Serial.print(" ");
  Serial.print(Motor3);
  Serial.print(" ");
  Serial.println(Motor4);
}

Step 4: Circuit Schematic: Quadcopter Body

You will have to solder the leads of the ESCs to the power distribution board. I also soldered an XT-60 wire terminal to the board to allow for easily plugging in (and removal) of a LiPo battery. I also crimped complementary terminals to the wires of the ESCs and Motors, which allowed for easy connection and disconnection. The wires connected to the PWM ports of the Arduino of go into the white signal wires of the ESCs. This is how the Arduino sends the information to the ESCs. The red and black wires on the ESCs will provide a 5V and ground, which can be used to power peripherals or the Arduino itself. Lastly, the motors need to be alternating clockwise & counterclockwise in their rotation.

Step 5: Quadcopter Body Code

/* Quadcopter Body Code. Written by Jeremiah Masseus
Description: This portion of the code receives values from the remote control, Sorts the values for each motor into their respective areas, and writes the value to the ESCs. */ #include <stdlib.h> #include <SoftwareSerial.h> #include <Wire.h> #define ESC1 3 #define ESC2 9 #define ESC3 10 #define ESC4 11
SoftwareSerial Receiver(12,13);
char incomingByte;
char lastalpha;
char prevalpha;
String val1 = "";
String val2 = "";
String val3 = "";
String val4 = "";
float Motor1;
float Motor2;
float Motor3;
float Motor4;
float off = 10;
boolean SerialEnd = false;                      // Flag to indicate End of Serial String
boolean HC12End = false;                        // Flag to indiacte End of HC12 String
boolean commandMode = false;                    // Send AT commands
void setup() {
  Serial.begin(9600);
  Receiver.begin(9600);
  analogWrite(ESC1, off);
  analogWrite(ESC2, off);
  analogWrite(ESC3, off);
  analogWrite(ESC4, off);
  hover2 = hover;
}
void loop() {
  while (Receiver.available()){
    incomingByte = Receiver.read();
    //Serial.print( incomingByte);
      if (isAlpha(incomingByte)){//Check to see if incoming byte is a letter
        prevalpha = lastalpha;
        lastalpha = incomingByte;
        if(incomingByte =='E'){//end of one cycle of data
          Motor1 = val1.toFloat();
          val1 = "";
          Motor2 = val2.toFloat();
          val2 = "";
          Motor3 = val3.toFloat();
          val3 = "";
          Motor4 = val4.toFloat();
          val4 = "";
          if(Motor1 != 0 && Motor2 != 0 && Motor3 != 0 &&Motor4 != 0){
            //if any values are 0, then the val.toFloat() function received an error
            //as a result, skip that cycle of data if that is the case
            if(Motor1 <= 255 && Motor2 <= 255 && Motor3 <= 255 &&Motor4 <= 255){
              //ensures that the value is less than 255 for pwm outputs
              if(prevalpha == 'p'){
                  Motor1 = off;
                  Motor2 = off;
                  Motor3 = off;
                  Motor4 = off;
                }
               if(lastalpha == 'p'){
                  Motor1 = off;
                  Motor2 = off;
                  Motor3 = off;
                  Motor4 = off;
                }
              Serial.print(Motor1);
              Serial.print(" ");
              Serial.print(Motor2);
              Serial.print(" ");
              Serial.print(Motor3);
              Serial.print(" ");
              Serial.println(Motor4);
              analogWrite(ESC1, Motor1);
              analogWrite(ESC2, Motor2);
              analogWrite(ESC3, Motor3);
              analogWrite(ESC4, Motor4);
              }
          }
        }
        if(incomingByte =='i'){//indicates a new cycle of data. clear data temporary location
          val1 = "";
          val2 = "";
          val3 = "";
          val4 = "";
        }    
        if(incomingByte =='p'){
           Motor1 = off;
           Motor2 = off;
           Motor3 = off;
           Motor4 = off;
           Serial.print(Motor1);
           Serial.print(" ");
           Serial.print(Motor2);
           Serial.print(" ");
           Serial.print(Motor3);
           Serial.print(" ");
           Serial.println(Motor4);
           analogWrite(ESC1, Motor1);
           analogWrite(ESC2, Motor2);
           analogWrite(ESC3, Motor3);
           analogWrite(ESC4, Motor4);     
        }         
        }
       
  
      else{ 
        if(lastalpha == 'M'){//add motor value in byte by byte until it encounters a string terminator
         val1+= incomingByte;
        }
        else if(lastalpha == 'D'){
          val2+= incomingByte;
          }
        else if(lastalpha == 'T'){
          val3+= incomingByte;
          }
        else if(lastalpha == 'Q'){
          val4+= incomingByte;
          }
        else if (lastalpha == 'i'){ 
          val1 += incomingByte;
          val2 += incomingByte;
          val3 += incomingByte;
          val4 += incomingByte;
          }
        else if (lastalpha == 'p'){
          //Send a very low voltage to the ESC's to ensure that no motors will rotate. 
          //This value will not change while the receiver gets the char 'p'.
          val1 = 10;
          val2 = 10;
          val3 = 10;
          val4 = 10;
          }
         else{}
       }
      }
}

Step 6: Frame Creation and Assembly Tips

Using fusion 360, I created this frame to fit the components that I used for this project. I made sure to measure the components that I would be using to ensure that they would fit. Even so, I would suggest having some sandpaper handy in case something doesn't quite fit. I also drilled holes in to screw in the motors. This kept them in place while they were running. To keep the components in place in the body, I used velcro adhesive, which allowed me to repeatedly separate parts if I needed to, while keeping them snugly in place otherwise.