This is the code to implement a quadcopter. PID controller tuning is necessary.

Dependencies:   PwmIn mbed MPU6050IMU

Embed: (wiki syntax)

« Back to documentation index

Show/hide line numbers main.cpp Source File

main.cpp

00001 #include "mbed.h"
00002 #include "rtos.h"
00003 #include "MPU6050.h"
00004 #include "PwmIn.h"
00005 
00006 //Functions
00007   void PID1(void const *argument); //PID Control
00008     void PeriodicInterruptISR(void); //Periodic Timer Interrupt Serive Routine
00009     //Processes and Threads
00010     osThreadId PiControlId; // Thread ID
00011     osThreadDef(PID1, osPriorityRealtime, 1024); // Declare PiControlThread as a thread, highest priority
00012 
00013     void receiver_data();
00014 //-------------------------------------------------------------------------------------------------------------
00015 
00016 //System Definitions:
00017     #define PWM_Period   20000 //in micro-seconds
00018     #define ESC_ARM      1000 // this set the PWM signal to 50% duty cycle for a signal period of 2000 micro seconds
00019     #define dt  0.005          // periodic interrupt each 20 ms... 50 Hz refresh rate of quadcopter motor speeds
00020 
00021 
00022     // Controller Gains, and boundaries
00023     //Roll
00024     #define uRollMax    300
00025     //Pitch
00026     #define uPitchMax   300
00027     // Throttle
00028     #define MaxThrottle 1600 // 1600 micro seconds
00029     #define MinThrottle 1100 // 1100 micro seconds
00030     // Yaw
00031     #define uYawMax     200
00032 
00033 //-------------------------------------------------------------------------------------------------------------
00034 
00035 //Port Configuration:
00036     // These are PWM In's that define the system's desired points and other control commands
00037     // Note: the ports of the following PWM input pins have to be interrupt enabled (Port A and D are the only 
00038     // interrupt enabled ports for KL25z).
00039     PwmIn  channel1(PTA5);  // Roll
00040     PwmIn  channel2(PTA13); // {itch
00041     PwmIn  channel3(PTA16); // Throttle
00042     PwmIn  channel4(PTA17); // Yaw
00043 
00044     //PWM Outputs
00045     // Configuration of motors:
00046     // Motor4         Motor1
00047     //      .   ^   .
00048     //        . | . 
00049     //        . | .
00050     //      .       .
00051     // Motor3         Motor2
00052     PwmOut PWM_motor1(PTB3); //PWM Motor1
00053     PwmOut PWM_motor3(PTB1); //PWM Motor3
00054     PwmOut PWM_motor2(PTB2); //PWM Motor2
00055     PwmOut PWM_motor4(PTB0); //PWM Motor4
00056     
00057     // MPU6050 gyroscope and accelerometer
00058     // Note the library MPU6050.h must be modified to match the chosen I2C pins
00059     // defined in the hardware design
00060    MPU6050 mpu6050; 
00061 
00062     //Timer Interrupts
00063     Timer t;
00064     Ticker PeriodicInt; // Declare a timer interrupt: PeriodicInt
00065     Serial pc(USBTX, USBRX); // tx, rx
00066 //-------------------------------------------------------------------------------------------------------------
00067 
00068 //Gloabal Variables Definitions: 
00069 
00070     // PID
00071     //Throttle
00072     float desiredThrottle;
00073     //Roll
00074     float eRoll, desiredRoll, desRoll, actualRoll, eDerRoll, elastRoll, eIntRoll, uRoll;
00075     float KpRoll, KdRoll, KiRoll;
00076     //Pitch
00077     float ePitch, desiredPitch, desPitch, actualPitch, eDerPitch, elastPitch, eIntPitch, uPitch;
00078     float KpPitch, KdPitch, KiPitch;
00079     
00080     //Yaw
00081     float eYaw, desiredYaw, desYaw, actualYaw, eDerYaw, elastYaw, eIntYaw, uYaw;
00082     float KpYaw, KdYaw, KiYaw;
00083 
00084     // control speeds of the motors
00085     float v1, v2, v3, v4; 
00086 
00087     // Gyro Stuff
00088     float sum = 0;
00089     uint32_t sumCount = 0;
00090 
00091 
00092     float RollCalibration, PitchCalibration, YawCalibration;
00093     float actRoll, actPitch, actYaw;     
00094                
00095 //-------------------------------------------------------------------------------------------------------------
00096 
00097 int main() {
00098 
00099     // System setup
00100     // this sets up the periodic interrupt, the UART baud rate communication with the PC, as well as
00101     // setting up the PWM signal period of each motor.
00102     pc.baud(9600);  // Set max uart baud rate
00103     pc.printf("We have good serial communication! :D\n");
00104 
00105     //Initializing Threads for interrupts
00106     PiControlId = osThreadCreate(osThread(PID1), NULL); //Create Thread for PID1
00107     PeriodicInt.attach(&PeriodicInterruptISR, dt); //Periodic timer interrupt every dt seconds as defined above
00108 
00109     //PWM Period
00110     PWM_motor1.period_us(PWM_Period); // This sets the PWM period to [PWM_Period]
00111     PWM_motor2.period_us(PWM_Period); //
00112     PWM_motor3.period_us(PWM_Period); //
00113     PWM_motor4.period_us(PWM_Period); //
00114 
00115     // this sets all motor PWM signals to 50% to arm the esc's for operation
00116     PWM_motor1.pulsewidth_us(2000); //Set the Pulsewidth output
00117     PWM_motor2.pulsewidth_us(2000); //Set the Pulsewidth output
00118     PWM_motor3.pulsewidth_us(2000); //Set the Pulsewidth output
00119     PWM_motor4.pulsewidth_us(2000); //Set the Pulsewidth output
00120     
00121     wait(10);
00122     PWM_motor1.pulsewidth_us(ESC_ARM); //Set the Pulsewidth output
00123     PWM_motor2.pulsewidth_us(ESC_ARM); //Set the Pulsewidth output
00124     PWM_motor3.pulsewidth_us(ESC_ARM); //Set the Pulsewidth output
00125     PWM_motor4.pulsewidth_us(ESC_ARM); //Set the Pulsewidth output
00126     
00127     KpRoll = .55;
00128     KdRoll = 0;
00129     KiRoll = 0;
00130     
00131     KpPitch = KpRoll;
00132     KdPitch = KdRoll;
00133     KiPitch = KiRoll;
00134    
00135     //Set up I2C
00136     i2c.frequency(400000);  // use fast (400 kHz) I2C
00137     t.start();
00138       // Read the WHO_AM_I register, this is a good test of communication
00139     uint8_t whoami = mpu6050.readByte(MPU6050_ADDRESS, WHO_AM_I_MPU6050);  // Read WHO_AM_I register for MPU-6050
00140     pc.printf("I AM 0x%x\n\r", whoami); pc.printf("I SHOULD BE 0x68\n\r");
00141 
00142     if (whoami == 0x68){ // WHO_AM_I should always be 0x68
00143 
00144         pc.printf("MPU6050 is online...");
00145         wait(1);
00146         mpu6050.MPU6050SelfTest(SelfTest); // Start by performing self test and reporting values
00147         pc.printf("x-axis self test: acceleration trim within : ");
00148         pc.printf("%f \n\r", SelfTest[0]);
00149         pc.printf("y-axis self test: acceleration trim within : ");
00150         pc.printf("%f \n\r", SelfTest[1]);
00151         pc.printf("z-axis self test: acceleration trim within : "); 
00152         pc.printf("%f \n\r", SelfTest[2]); 
00153         pc.printf("x-axis self test: gyration trim within : "); 
00154         pc.printf("%f \n\r", SelfTest[3]); 
00155         pc.printf("y-axis self test: gyration trim within : "); 
00156         pc.printf("%f \n\r", SelfTest[4]); 
00157         pc.printf("z-axis self test: gyration trim within : "); 
00158         pc.printf("%f \n\r", SelfTest[5]); 
00159 
00160         wait(1);
00161 
00162         if(SelfTest[0] < 1.0f && SelfTest[1] < 1.0f && SelfTest[2] < 1.0f && SelfTest[3] < 1.0f && SelfTest[4] < 1.0f && SelfTest[5] < 1.0f){
00163             mpu6050.resetMPU6050(); // Reset registers to default in preparation for device calibration
00164             mpu6050.calibrateMPU6050(gyroBias, accelBias); // Calibrate gyro and accelerometers, load biases in bias registers
00165             mpu6050.initMPU6050(); pc.printf("MPU6050 initialized for active data mode....\n\r"); // Initialize device for active mode read of acclerometer, gyroscope, and temperature
00166 
00167             wait(2);
00168             }
00169         else {
00170             pc.printf("Device did not the pass self-test!\n\r");
00171           }
00172     }
00173 
00174     else{
00175         pc.printf("Could not connect to MPU6050: \n\r");
00176         pc.printf("%#x \n",  whoami);
00177         while(1) ; // Loop forever if communication doesn't happen
00178     }
00179     
00180     wait(10); // wait 10 seconds to make sure esc's are armed and ready to go
00181     
00182     // counter for mpu calibration
00183     int counter = 0;
00184     //Loop Forever
00185     while (1) {
00186         
00187 
00188               // If data ready bit set, all data registers have new data
00189         if(mpu6050.readByte(MPU6050_ADDRESS, INT_STATUS) & 0x01) {  // check if data ready interrupt
00190             mpu6050.readAccelData(accelCount);  // Read the x/y/z adc values
00191             mpu6050.getAres();
00192 
00193             // Now we'll calculate the accleration value into actual g's
00194             ax = (float)accelCount[0]*aRes - accelBias[0];  // get actual g value, this depends on scale being set
00195             ay = (float)accelCount[1]*aRes - accelBias[1];
00196             az = (float)accelCount[2]*aRes - accelBias[2];
00197 
00198             mpu6050.readGyroData(gyroCount);  // Read the x/y/z adc values
00199             mpu6050.getGres();
00200 
00201             // Calculate the gyro value into actual degrees per second
00202             gx = (float)gyroCount[0]*gRes; // - gyroBias[0];  // get actual gyro value, this depends on scale being set
00203             gy = (float)gyroCount[1]*gRes; // - gyroBias[1];
00204             gz = (float)gyroCount[2]*gRes; // - gyroBias[2];
00205 
00206             tempCount = mpu6050.readTempData();  // Read the x/y/z adc values
00207             temperature = (tempCount) / 340. + 36.53; // Temperature in degrees Centigrade
00208          }
00209 
00210           Now = t.read_us();
00211           deltat = (float)((Now - lastUpdate)/1000000.0f) ; // set integration time by time elapsed since last filter update
00212           lastUpdate = Now;
00213 
00214           sum += deltat;
00215           sumCount++;
00216 
00217           if(lastUpdate - firstUpdate > 10000000.0f) {
00218              beta = 0.04;  // decrease filter gain after stabilized
00219              zeta = 0.015; // increasey bias drift gain after stabilized
00220           }
00221 
00222          // Pass gyro rate as rad/s
00223           mpu6050.MadgwickQuaternionUpdate(ax, ay, az, gx*PI/180.0f, gy*PI/180.0f, gz*PI/180.0f);
00224 
00225 
00226         // Define output variables from updated quaternion---these are Tait-Bryan angles, commonly used in aircraft orientation.
00227         // In this coordinate system, the positive z-axis is down toward Earth.
00228         // Yaw is the angle between Sensor x-axis and Earth magnetic North (or true North if corrected for local declination, looking down on the sensor positive yaw is counterclockwise.
00229         // Pitch is angle between sensor x-axis and Earth ground plane, toward the Earth is positive, up toward the sky is negative.
00230         // Roll is angle between sensor y-axis and Earth ground plane, y-axis up is positive roll.
00231         // These arise from the definition of the homogeneous rotation matrix constructed from quaternions.
00232         // Tait-Bryan angles as well as Euler angles are non-commutative; that is, the get the correct orientation the rotations must be
00233         // applied in the correct order which for this configuration is yaw, pitch, and then roll.
00234         // For more see http://en.wikipedia.org/wiki/Conversion_between_quaternions_and_Euler_angles which has additional links.
00235           yaw   = atan2(2.0f * (q[1] * q[2] + q[0] * q[3]), q[0] * q[0] + q[1] * q[1] - q[2] * q[2] - q[3] * q[3]);
00236           pitch = -asin(2.0f * (q[1] * q[3] - q[0] * q[2]));
00237           roll  = atan2(2.0f * (q[0] * q[1] + q[2] * q[3]), q[0] * q[0] - q[1] * q[1] - q[2] * q[2] + q[3] * q[3]);
00238           actPitch  = pitch * 180.0f / PI;
00239           actYaw = yaw   * 180.0f / PI;
00240           actRoll   = roll  * 180.0f / PI;
00241           
00242           if (counter < 800){
00243               counter++;
00244               RollCalibration = actRoll;
00245               PitchCalibration= actPitch;
00246               YawCalibration = actYaw;
00247               }
00248           else{
00249               
00250               actRoll = actRoll - RollCalibration;
00251               actualRoll = actPitch - PitchCalibration;
00252               actualYaw = actYaw - YawCalibration;
00253               
00254               if(actRoll > 180){
00255                   actualPitch=-(actRoll-360);
00256                   }
00257               if(actRoll < -180){
00258                   actualPitch=-(actRoll+360);
00259                   }
00260               if ((actRoll > -180)  && actRoll < 180) {
00261                   actualPitch=-actRoll;
00262                   }
00263               
00264           
00265               // Measuring receiver values
00266               desRoll         = 1000000 * channel1.pulsewidth();  // desired point for roll
00267               desPitch        = 1000000 * channel2.pulsewidth();  // desired point for pitch
00268               desiredThrottle = 1000000 * channel3.pulsewidth();  // desired point for throttle
00269               desYaw          = 1000000 * channel4.pulsewidth();  // desired point for yaw
00270               
00271               // The desired roll, pitch and yaw has a range between 1000 to 2000 microseconds
00272               // we will map these values to limit the desired input between -45deg to +45deg for roll and pitch
00273               // As for yaw, the desired input will range between -180 to 180 deg
00274               //
00275               // The equaion below maps the PWM value to a desired deg value, then it is shifter by some amount of degree so that 
00276               // the 1000 us is equivalent to -45deg, and the 2000 us point is equivalent to 45deg.
00277               // The same logic is used for the Yaw equation
00278               desiredRoll = (90 * (abs(desRoll -1000))/1000) -  45;
00279               desiredPitch= (90 * (abs(desPitch-1000))/1000) -  45;
00280               desiredYaw  = (360* (abs(desYaw  -1000))/1000) - 180;
00281          }
00282          // pc.printf("%f             %f             %f\n\r", actualPitch, desiredPitch, ePitch);
00283        //   pc.printf("%f         %f          %f         %f\n\r ", uRoll, uPitch, eRoll, ePitch);
00284 
00285           pc.printf("%f         %f         %f         %f\n\r", v1, v2, v3, v4);
00286        //   pc.printf("%f         %f         %f         %f         %f         %f \n\r\n\r\n\r", actualRoll, desiredRoll, actualPitch, desiredPitch, actualYaw, eYaw );
00287          /* 
00288           pc.printf("%f \n\r", v1);
00289           pc.printf("%f \n\r", v2);
00290           pc.printf("%f \n\r", v3);
00291           pc.printf("%f \n\r \n\r", v4);
00292           
00293           pc.printf("error in roll %f \n\r \n\r", eRoll);
00294           pc.printf("error in pitch %f \n\r \n\r", ePitch);
00295           pc.printf("error in yaw %f \n\r \n\r", eYaw);*/
00296           /*
00297           pc.printf("Pulse width Roll value %f \n\r",desRoll);
00298           pc.printf("Pulse width Pitch value %f \n\r",desPitch);
00299           pc.printf("Pulse width Throttle value %f \n\r",desiredThrottle);
00300           pc.printf("Pulse width Yaw value %f \n\r",desYaw);
00301           */
00302           /*
00303           pc.printf("Desired Roll: %f \n\r", desiredRoll);
00304           pc.printf("Desired Pitch: %f \n\r", desiredPitch);
00305           pc.printf("Desired Throttle: %f \n\r", desiredThrottle);
00306           pc.printf("Desired Yaw: %f \n\r", desiredYaw);
00307           */
00308           myled= !myled;
00309           count = t.read_ms();
00310           sum = 0;
00311           sumCount = 0;
00312           
00313           //wait(.5);
00314           
00315           }
00316 
00317 
00318 
00319 }
00320 
00321 
00322 
00323 // Updated on: Sept 19 2019
00324 // Koceila Cherfouh
00325 // Deescription
00326 // periodic time interrupt that controls the speed of the motor based on the gyro and accelerometer readings using a PID controller. 
00327 //
00328 // Update and whats left to do:
00329 // make sure all variables are declared. define max values, and gain values for each PID controllers.
00330 // define the motor speed assignment based on the system's kinematic model
00331 // test and tune the controller gain
00332 
00333 // ******** Periodic Thread ********
00334 void PID1(void const *argument){
00335 
00336     while(true){
00337         osSignalWait(0x1, osWaitForever); // Go to sleep until signal is received.
00338 
00339         if (desiredThrottle > 1050){ // 1050 micro seconds will serve as the activation throttle 
00340             // 1- PID roll
00341             eRoll= desiredRoll - actualRoll;
00342             eDerRoll = eRoll - elastRoll;          // derivative of error
00343             uRoll = (KpRoll * eRoll) + (KdRoll * eDerRoll) + (KiRoll * eIntRoll); // computing the pitch control input
00344             // this part of the code limits the integrator windeup. an effect that takes place when
00345             if (uRoll > uRollMax){
00346                 uRoll = uRollMax;
00347             }
00348             if (uRoll < -uRollMax){
00349                 uRoll = -uRollMax;
00350             }
00351             else {
00352                 eIntRoll += eRoll;                       // Computing the integral sum
00353             }
00354             elastRoll = eRoll;
00355             //-------------------------------------------------------------------------------------------------------------
00356             // 2- PID pitch
00357             ePitch = desiredPitch - actualPitch;              // error
00358             eDerPitch = ePitch - elastPitch ;          // derivative of error
00359             uPitch = (KpPitch * ePitch) + (KdPitch * eDerPitch) + (KiPitch * eIntPitch); // computing the pitch control input
00360             // this part of the code limits the integrator windeup. an effect that takes place when
00361             if (uPitch > uPitchMax){
00362                 uPitch = uPitchMax;
00363             }
00364             if (uPitch < -uPitchMax){
00365                 uPitch = -uPitchMax;
00366             }
00367             else {
00368                 eIntPitch += ePitch;                       // Computing the integral sum
00369             }
00370             elastPitch = ePitch;
00371             //-------------------------------------------------------------------------------------------------------------
00372             // 4- PID yaw
00373            /* eYaw= desiredYaw - actualYaw;
00374             eDerYaw = eYaw - elastYaw ;          // derivative of error
00375             uYaw = (KpYaw * eYaw) + (KdYaw * eDerYaw) + (KiYaw * eIntYaw); // computing the pitch control input
00376             // this part of the code limits the integrator windeup. an effect that takes place when
00377             if (uYaw > uYawMax){
00378                 uYaw = uYawMax;
00379             }
00380             else {
00381                 eIntYaw += eYaw;                       // Computing the integral sum
00382             }
00383             elastYaw = eYaw;       */ 
00384             //-------------------------------------------------------------------------------------------------------------
00385             // Now that all pid control inputs are computed. send the PWM control input using the kinematic model to the ESC's       
00386             v1= desiredThrottle - uPitch - uRoll - uYaw; //Calculate the pulse for esc 1 (front-right - CCW)
00387             v2= desiredThrottle + uPitch - uRoll + uYaw; //Calculate the pulse for esc 2 (rear-right - CW)
00388             v3= desiredThrottle + uPitch + uRoll - uYaw; //Calculate the pulse for esc 3 (rear-left - CCW)
00389             v4= desiredThrottle - uPitch + uRoll + uYaw; //Calculate the pulse for esc 4 (front-left - CW)
00390             
00391             if ( v1 > 2000 ){
00392                 v1=2000;
00393                 } 
00394             if ( v1 < 1000){
00395                 v1=1000;
00396                 }
00397             if ( v2 > 2000 ){
00398                 v2=2000;
00399                 } 
00400             if ( v2 < 1000){
00401                 v2=1000;
00402                 }
00403             if ( v3 > 2000 ){
00404                 v3=2000;
00405                 } 
00406             if ( v3 < 1000){
00407                 v3=1000;
00408                 }
00409             if ( v4 > 2000 ){
00410                 v4=2000;
00411                 } 
00412             if ( v4 < 1000){
00413                 v4=1000;
00414                 }
00415         }
00416         else{   // In the case where desired throttle is <1050 microseconds, we want to deactivate all motors
00417             v1= desiredThrottle;
00418             v2= desiredThrottle;
00419             v3= desiredThrottle;
00420             v4= desiredThrottle;
00421             }
00422 
00423         // outputing the necessary PWM value to the motors
00424         PWM_motor1.pulsewidth_us(abs(v1)); //Set the Pulsewidth output
00425         PWM_motor2.pulsewidth_us(abs(v2)); //Set the Pulsewidth output
00426         PWM_motor3.pulsewidth_us(abs(v3)); //Set the Pulsewidth output
00427         PWM_motor4.pulsewidth_us(abs(v4)); //Set the Pulsewidth output
00428 
00429     }
00430 
00431 }
00432 
00433 
00434 // ******** Periodic Interrupt Handler 1********
00435 void PeriodicInterruptISR(void){
00436     osSignalSet(PiControlId,0x1); // Activate the signal, PiControl, with each periodic timer interrupt.
00437 }
00438