Quadrotor

A Quadrotor's Creation

<- Return to Portfolio

Over the past five years, I have been building a quadrotor from "scratch." Using an Arduino, Xbee radios, Processing, and Python, I hope to create a fully functioning drone!

This record of that journey is split into three phases. Each phase either signifies a large gap of time or change of focus.

  • Phase I: Initial creation, flight tests, and code concepts for the drone
  • Phase II: Updating of electronics and vibration absorption
  • Phase III: Final tuning of code and sensors and first flight.

Phase I

The Beginning

Parts

During the summer of 2013 I started to piece the drone together. The initial parts list consisted of:

These are just the general parts. Other things, like the frame materials, are not listed. If you want to know where a particular thing came from, please contact me.

Frame

Using a u-shaped steel beam available at the time, I put together a frame. It is not an ideal material, and I plan on designing a better one in the future; however, the motors were more than strong enough to lift the frame.

After zip-tying the ESCs onto the frame and attaching the motors, I had to figure out how to best wire the power to the ESCs (the red and black wires in the picture below). I used low-gauge wire because each ESC can pull up to 30 amps.[^They likely never get close to this, but I have burnt through enough wires during testing I wasn't going to take the chance. ]

Frame Version 1.0

Electronics

At first, I used an Arduino Uno with an XBee shield as computer and communicator. Next to the Uno, I used a breadboard to connect the accelerometer, gyroscope, GPS, and ESCs. The wiring was straight forward for the accelerometer and gyroscope thanks to Bildr's tutorials and wiring diagrams for the accelerometer and gyroscope. The GPS and ESCs were slightly more challenging to interface with.

Old Electroincs

GPS

Like the gyroscope, the GPS communicated over a serial interface (SPI?). Using Arduino's [SoftwareSerial]() library, I was able to use two pins to get GPS data at 4800 baud. An example script is below.

    #include <SoftwareSerial.h> //include lib
    SoftwareSerial gps(11, 10 ); // RX, TX
    void setup()  {
      Serial.begin(9600); //start the serial connections
      gps.begin(4800);
    } 

    void loop() {
      if (gps.available()){
        Serial.write(gps.read()); //if data is avalible, print it
      } 
    }

I was surprised how much information the GPS receiver recorded. The script above pulls the NMEA-RMC data from the GPS which contains location (duh), time, altitude, ground speed, track angle, and more.

Programming

From my view, there are two points of focus when programming a flight controller.

Calculating the Angle

The first challenge is calculating the angle the drone is relative to the rest of the world. To do this efficiently one combines gyroscope and accelerometer data to form an IMU or Inertial Measurement Unit.

Why do you have to combine the gyroscope and accelerometer?

An accelerometer measures linear acceleration, or in simpler terms how fast the speed of an object is changing. Gravity applies a constant (relative) downwards acceleration of 9.81 meters per second per second here on earth which causes objects to fall when they're allowed. Because we know (or assume) that the drone is on earth, we know that the sum of our measured Z and Y acceleration vectors will add up to 9.81 as shown in the diagram below.

Drone Vectors

Perfect, we have an angle! Not quite. Unfortunately, every time the drone accelerates (as they tend to), the angle measurements will go out of wack when they're needed the most.

What about the gyroscope?

The gyroscope measures angular velocity, or how fast the sensor is rotation on the X, Y, and Z axes. If we take the speed (in radians/sec) at which the drone is rotating and multiply it by the time between measurements, we can keep track of the angle the drone is relative to the rest of the world. The problem with using this running sum method to keep track of the angle is that it drifts over time as errors in these measurements accumulate.

Enter the IMU! Taking the best from both worlds, an IMU uses the accelerometer to correct the gyro drift and the gyro to override the accelerometer during acceleration. How does it do this?

struct Angles compute_angles() { //return a structure of angles (x and y)

  imu.readAccel(); //read the accel values
  imu.readGyro(); //read the gyro values values
  float gyx = imu.calcGyro(imu.gx); //convert gyro values to deg/sec
  float gyy = imu.calcGyro(imu.gy);
  float rollA = atan2(imu.ay, imu.az) * 180 / 3.14; //find the angle with the accel as shown in the figure above
  float pitchA = atan2(-imu.ax, sqrt(imu.ay * imu.ay + imu.az * imu.az)) * 180 / 3.14; //find the angle with the accel as shown in the figure above
  delay(10); //wait 10 ms
  t2 = millis(); //capture time since start
  time1 = t2 - start_time; //find delta t

  struct Angles result;//delcare result thing

  if (gyx > 0 || gyx < -0.5) { //threshold for noise
    pitchG = pitch + (gyx * (time1 + 2.5) / 1000); //update the gyro calculated angle with 
  }
  if (gyy > 0 || gyy < -0.5) { //same thing for Y axis
    rollG = roll + (gyy * (time1 + 2.5) / 1000);
  }

  pitch = 0.95 * pitchA + 0.05 * pitchG; //THIS IS WHERE THE MAGIC HAPPENS
  roll = 0.95 * rollA + 0.05 * rollG; //The pitch and roll are calculated with 5% of the accelerometer and 95% of the gyroscope. Because the gyroscope values are calculated with this result, the accelerometer will completely control the value in the long run, but in the short run the gyro has preference

  if (pitch - last_pitch < 45) { //if the delta isnt some bad value
    last_pitch=pitch; //update
    sumPitch -= LMPitch[indexPitch]; //Next 7 lines combine the calculated values into a running average 
    LMPitch[indexPitch] = pitch;
    sumPitch += LMPitch[indexPitch];
    indexPitch++;
    indexPitch = indexPitch % RM_SIZE;
    if (countPitch < RM_SIZE) countPitch++;
    result.y = -sumPitch / countPitch; //update the output
  } else { // if it is one shit value skip it }
  if (roll - last_roll < 45) { //if the delta isnt some bad value
    last_roll=roll; //update
    sumRoll -= LMRoll[indexRoll];//Next 7 lines combine the calculated values into a running average
    LMRoll[indexRoll] = roll;
    sumRoll += LMRoll[indexRoll];
    indexRoll++;
    indexRoll = indexRoll % RM_SIZE;
    if (countRoll < RM_SIZE) countRoll++;
    result.x = -sumRoll / countRoll;//update the output
  } else { // if it is one shit value skip it }
  return result;
}

Calculating the Correction Forces - Phase 1

My first approach to calculating the corrective forces was very straight forward. I took the difference between the target angle and the measured angle, squared it, and scaled it to fit the PWM input values for the motor controllers (1100-2000). This resulted in the video below.

As you can see, the drone quickly begins to overshoot its corrections which causes a violent back-and-forth oscillation. These use the error with the calculated derivative and integral of the error to account for the present, future, and past error respectively. These values are tuned using the coefficients Kp, Ki, and Kd as shown in Wikipedia's diagram below.

PID diagram

By Arturo Urquizo CC BY-SA 3.0, via Wikimedia Commons

I will explain this in more detail in Phase III

Phase II Coming Soon

-->