New Microsoft Word Document
New Microsoft Word Document
New Microsoft Word Document
///////////////////////////////////////////////////////////////////////////////////////
//Quadcopter
///////////////////////////////////////////////////////////////////////////////////////
#include <Wire.h> //Include the Wire.h library so we can communicate with the gyro.
#include <EEPROM.h> //Include the EEPROM.h library so we can store information onto the
EEPROM
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////
float pid_p_gain_yaw = 4.0; //Gain setting for the pitch P-controller. //4.0
float pid_i_gain_yaw = 0.02; //Gain setting for the pitch I-controller. //0.02
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////
byte eeprom_data[36];
int receiver_input[5];
int temperature;
double gyro_axis_cal[4];
float pid_error_temp;
boolean gyro_angles_set;
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////
//Setup routine
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////
void setup(){
//Serial.begin(57600);
//Arduino (Atmega) pins default to inputs, so they don't need to be explicitly declared as inputs.
//Check the EEPROM signature to make sure that the setup program is executed.
//If setup is completed without MPU-6050 stop the flight controller program
for (cal_int = 0; cal_int < 1250 ; cal_int ++){ //Wait 5 seconds before continuing.
//Let's take multiple gyro data samples so we can determine the average gyro offset (calibration).
for (cal_int = 0; cal_int < 2000 ; cal_int ++){ //Take 2000 readings for calibration.
//We don't want the esc's to be beeping annoyingly. So let's give them a 1000us puls while calibrating
the gyro.
}
//Now that we have 2000 measures, we need to devide by 2000 to get the average gyro offset.
//Wait until the receiver is active and the throtle is set to the lower position.
//We don't want the esc's to be beeping annoyingly. So let's give them a 1000us puls while waiting for
the receiver inputs.
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////
void loop(){
//65.5 = 1 deg/sec (check the datasheet of the MPU-6050 for more information).
gyro_roll_input = (gyro_roll_input * 0.7) + ((gyro_roll / 65.5) * 0.3); //Gyro pid input is deg/sec.
gyro_yaw_input = (gyro_yaw_input * 0.7) + ((gyro_yaw / 65.5) * 0.3); //Gyro pid input is deg/sec.
////////////////////////////////////////////////////////////////////////////////////////////////////
//https://youtu.be/4BoIE8YQwM8
//https://youtu.be/j-kE0AMEWy4
////////////////////////////////////////////////////////////////////////////////////////////////////
angle_pitch += gyro_pitch * 0.0000611; //Calculate the traveled pitch angle and add
this to the angle_pitch variable.
angle_roll += gyro_roll * 0.0000611; //Calculate the traveled roll angle and add
this to the angle_roll variable.
angle_pitch -= angle_roll * sin(gyro_yaw * 0.000001066); //If the IMU has yawed transfer the
roll angle to the pitch angel.
angle_roll += angle_pitch * sin(gyro_yaw * 0.000001066); //If the IMU has yawed transfer the
pitch angle to the roll angel.
}
//Place the MPU-6050 spirit level and note the values in the following two lines for calibration.
angle_pitch = angle_pitch * 0.9996 + angle_pitch_acc * 0.0004; //Correct the drift of the gyro
pitch angle with the accelerometer pitch angle.
angle_roll = angle_roll * 0.9996 + angle_roll_acc * 0.0004; //Correct the drift of the gyro roll
angle with the accelerometer roll angle.
//For starting the motors: throttle low and yaw left (step 1).
//When yaw stick is back in the center position start the motors (step 2).
start = 2;
pid_i_mem_roll = 0;
pid_last_roll_d_error = 0;
pid_i_mem_pitch = 0;
pid_last_pitch_d_error = 0;
pid_i_mem_yaw = 0;
pid_last_yaw_d_error = 0;
//The PID set point in degrees per second is determined by the roll receiver input.
//In the case of deviding by 3 the max roll rate is aprox 164 degrees per second ( (500-8)/3 = 164d/s ).
pid_roll_setpoint = 0;
pid_roll_setpoint /= 3.0; //Divide the setpoint for the PID roll controller by 3
to get angles in degrees.
//The PID set point in degrees per second is determined by the pitch receiver input.
//In the case of deviding by 3 the max pitch rate is aprox 164 degrees per second ( (500-8)/3 = 164d/s ).
pid_pitch_setpoint = 0;
pid_pitch_setpoint /= 3.0; //Divide the setpoint for the PID pitch controller
by 3 to get angles in degrees.
//The PID set point in degrees per second is determined by the yaw receiver input.
//In the case of deviding by 3 the max yaw rate is aprox 164 degrees per second ( (500-8)/3 = 164d/s ).
pid_yaw_setpoint = 0;
if(receiver_input_channel_3 > 1050){ //Do not yaw when turning off the motors.
if (throttle > 1800) throttle = 1800; //We need some room to keep full control at
full throttle.
esc_1 = throttle - pid_output_pitch + pid_output_roll - pid_output_yaw; //Calculate the pulse for esc
1 (front-right - CCW)
esc_2 = throttle + pid_output_pitch + pid_output_roll + pid_output_yaw; //Calculate the pulse for esc
2 (rear-right - CW)
esc_3 = throttle + pid_output_pitch - pid_output_roll - pid_output_yaw; //Calculate the pulse for esc
3 (rear-left - CCW)
esc_4 = throttle - pid_output_pitch - pid_output_roll + pid_output_yaw; //Calculate the pulse for esc
4 (front-left - CW)
if (battery_voltage < 1240 && battery_voltage > 800){ //Is the battery connected?
else{
esc_1 = 1000; //If start is not 2 keep a 1000us pulse for ess-1.
esc_2 = 1000; //If start is not 2 keep a 1000us pulse for ess-2.
esc_3 = 1000; //If start is not 2 keep a 1000us pulse for ess-3.
esc_4 = 1000; //If start is not 2 keep a 1000us pulse for ess-4.
////////////////////////////////////////////////////////////////////////////////////////////////////
//https://youtu.be/fqEkVcqxtU8
////////////////////////////////////////////////////////////////////////////////////////////////////
//! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! !
//Because of the angle calculation the loop time is getting very important. If the loop time is
//longer or shorter than 4000us the angle calculation is off. If you modify the code make sure
//that the loop time is still 4000us and no longer! More information can be found on
//! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! !
if(micros() - loop_timer > 4050)digitalWrite(12, HIGH); //Turn on the LED if the loop time
exceeds 4050us.
//The refresh rate is 250Hz. That means the esc's need there pulse every 4ms.
while(micros() - loop_timer < 4000); //We wait until 4000us are passed.
//There is always 1000us of spare time. So let's do something usefull that is very time consuming.
//Get the current gyro and receiver data and scale it to degrees per second for the pid calculations.
gyro_signalen();
while(PORTD >= 16){ //Stay in this loop until output 4,5,6 and 7 are low.
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////
//This routine is called every time input 8, 9, 10 or 11 changed state. This is used to read the receiver
signals.
//https://youtu.be/bENjl1KQbvo
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////
ISR(PCINT0_vect){
current_time = micros();
//Channel 1=========================================
//Channel 2=========================================
//Channel 3=========================================
//Channel 4=========================================
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////
void gyro_signalen(){
if(eeprom_data[31] == 1){
acc_axis[1] = Wire.read()<<8|Wire.read(); //Add the low and high byte to the acc_x
variable.
acc_axis[2] = Wire.read()<<8|Wire.read(); //Add the low and high byte to the acc_y
variable.
acc_axis[3] = Wire.read()<<8|Wire.read(); //Add the low and high byte to the acc_z
variable.
if(cal_int == 2000){
acc_x = acc_axis[eeprom_data[29] & 0b00000011]; //Set acc_x to the correct axis that
was stored in the EEPROM.
acc_y = acc_axis[eeprom_data[28] & 0b00000011]; //Set acc_y to the correct axis that
was stored in the EEPROM.
acc_z = acc_axis[eeprom_data[30] & 0b00000011]; //Set acc_z to the correct axis that
was stored in the EEPROM.
if(eeprom_data[30] & 0b10000000)acc_z *= -1; //Invert acc_z if the MSB of EEPROM
bit 30 is set.
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////
//The PID controllers are explained in part 5 of the YMFC-3D video session:
//https://youtu.be/JBvnB0279-Q
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////
void calculate_pid(){
//Roll calculations
pid_last_roll_d_error = pid_error_temp;
//Pitch calculations
pid_last_pitch_d_error = pid_error_temp;
//Yaw calculations
pid_last_yaw_d_error = pid_error_temp;
//This part converts the actual receiver signals to a standardized 1000 – 1500 – 2000 microsecond value.
else reverse = 0; //If the most significant is not set there is no reverse
low = (eeprom_data[channel * 2 + 15] << 8) | eeprom_data[channel * 2 + 14]; //Store the low value
for the specific receiver input channel
high = (eeprom_data[channel * 2 + 7] << 8) | eeprom_data[channel * 2 + 6]; //Store the high value for
the specific receiver input channel
if(actual < center){ //The actual receiver value is lower than the center
value
if(actual < low)actual = low; //Limit the lowest value to the value that was
detected during setup
difference = ((long)(center - actual) * (long)500) / (center - low); //Calculate and scale the actual
value to a 1000 - 2000us value
if(actual > high)actual = high; //Limit the lowest value to the value that was
detected during setup
difference = ((long)(actual - center) * (long)500) / (high - center); //Calculate and scale the actual
value to a 1000 - 2000us value
void set_gyro_registers(){
if(eeprom_data[31] == 1){