This is the code to implement a quadcopter. PID controller tuning is necessary.
Dependencies: PwmIn mbed MPU6050IMU
main.cpp
- Committer:
- kcherfou
- Date:
- 2019-10-08
- Revision:
- 1:2021161b8fd2
- Parent:
- 0:6038ca525e44
File content as of revision 1:2021161b8fd2:
#include "mbed.h" #include "rtos.h" #include "MPU6050.h" #include "PwmIn.h" //Functions void PID1(void const *argument); //PID Control void PeriodicInterruptISR(void); //Periodic Timer Interrupt Serive Routine //Processes and Threads osThreadId PiControlId; // Thread ID osThreadDef(PID1, osPriorityRealtime, 1024); // Declare PiControlThread as a thread, highest priority void receiver_data(); //------------------------------------------------------------------------------------------------------------- //System Definitions: #define PWM_Period 20000 //in micro-seconds #define ESC_ARM 1000 // this set the PWM signal to 50% duty cycle for a signal period of 2000 micro seconds #define dt 0.005 // periodic interrupt each 20 ms... 50 Hz refresh rate of quadcopter motor speeds // Controller Gains, and boundaries //Roll #define uRollMax 300 //Pitch #define uPitchMax 300 // Throttle #define MaxThrottle 1600 // 1600 micro seconds #define MinThrottle 1100 // 1100 micro seconds // Yaw #define uYawMax 200 //------------------------------------------------------------------------------------------------------------- //Port Configuration: // These are PWM In's that define the system's desired points and other control commands // Note: the ports of the following PWM input pins have to be interrupt enabled (Port A and D are the only // interrupt enabled ports for KL25z). PwmIn channel1(PTA5); // Roll PwmIn channel2(PTA13); // {itch PwmIn channel3(PTA16); // Throttle PwmIn channel4(PTA17); // Yaw //PWM Outputs // Configuration of motors: // Motor4 Motor1 // . ^ . // . | . // . | . // . . // Motor3 Motor2 PwmOut PWM_motor1(PTB3); //PWM Motor1 PwmOut PWM_motor3(PTB1); //PWM Motor3 PwmOut PWM_motor2(PTB2); //PWM Motor2 PwmOut PWM_motor4(PTB0); //PWM Motor4 // MPU6050 gyroscope and accelerometer // Note the library MPU6050.h must be modified to match the chosen I2C pins // defined in the hardware design MPU6050 mpu6050; //Timer Interrupts Timer t; Ticker PeriodicInt; // Declare a timer interrupt: PeriodicInt Serial pc(USBTX, USBRX); // tx, rx //------------------------------------------------------------------------------------------------------------- //Gloabal Variables Definitions: // PID //Throttle float desiredThrottle; //Roll float eRoll, desiredRoll, desRoll, actualRoll, eDerRoll, elastRoll, eIntRoll, uRoll; float KpRoll, KdRoll, KiRoll; //Pitch float ePitch, desiredPitch, desPitch, actualPitch, eDerPitch, elastPitch, eIntPitch, uPitch; float KpPitch, KdPitch, KiPitch; //Yaw float eYaw, desiredYaw, desYaw, actualYaw, eDerYaw, elastYaw, eIntYaw, uYaw; float KpYaw, KdYaw, KiYaw; // control speeds of the motors float v1, v2, v3, v4; // Gyro Stuff float sum = 0; uint32_t sumCount = 0; float RollCalibration, PitchCalibration, YawCalibration; float actRoll, actPitch, actYaw; //------------------------------------------------------------------------------------------------------------- int main() { // System setup // this sets up the periodic interrupt, the UART baud rate communication with the PC, as well as // setting up the PWM signal period of each motor. pc.baud(9600); // Set max uart baud rate pc.printf("We have good serial communication! :D\n"); //Initializing Threads for interrupts PiControlId = osThreadCreate(osThread(PID1), NULL); //Create Thread for PID1 PeriodicInt.attach(&PeriodicInterruptISR, dt); //Periodic timer interrupt every dt seconds as defined above //PWM Period PWM_motor1.period_us(PWM_Period); // This sets the PWM period to [PWM_Period] PWM_motor2.period_us(PWM_Period); // PWM_motor3.period_us(PWM_Period); // PWM_motor4.period_us(PWM_Period); // // this sets all motor PWM signals to 50% to arm the esc's for operation PWM_motor1.pulsewidth_us(2000); //Set the Pulsewidth output PWM_motor2.pulsewidth_us(2000); //Set the Pulsewidth output PWM_motor3.pulsewidth_us(2000); //Set the Pulsewidth output PWM_motor4.pulsewidth_us(2000); //Set the Pulsewidth output wait(10); PWM_motor1.pulsewidth_us(ESC_ARM); //Set the Pulsewidth output PWM_motor2.pulsewidth_us(ESC_ARM); //Set the Pulsewidth output PWM_motor3.pulsewidth_us(ESC_ARM); //Set the Pulsewidth output PWM_motor4.pulsewidth_us(ESC_ARM); //Set the Pulsewidth output KpRoll = .55; KdRoll = 0; KiRoll = 0; KpPitch = KpRoll; KdPitch = KdRoll; KiPitch = KiRoll; //Set up I2C i2c.frequency(400000); // use fast (400 kHz) I2C t.start(); // Read the WHO_AM_I register, this is a good test of communication uint8_t whoami = mpu6050.readByte(MPU6050_ADDRESS, WHO_AM_I_MPU6050); // Read WHO_AM_I register for MPU-6050 pc.printf("I AM 0x%x\n\r", whoami); pc.printf("I SHOULD BE 0x68\n\r"); if (whoami == 0x68){ // WHO_AM_I should always be 0x68 pc.printf("MPU6050 is online..."); wait(1); mpu6050.MPU6050SelfTest(SelfTest); // Start by performing self test and reporting values pc.printf("x-axis self test: acceleration trim within : "); pc.printf("%f \n\r", SelfTest[0]); pc.printf("y-axis self test: acceleration trim within : "); pc.printf("%f \n\r", SelfTest[1]); pc.printf("z-axis self test: acceleration trim within : "); pc.printf("%f \n\r", SelfTest[2]); pc.printf("x-axis self test: gyration trim within : "); pc.printf("%f \n\r", SelfTest[3]); pc.printf("y-axis self test: gyration trim within : "); pc.printf("%f \n\r", SelfTest[4]); pc.printf("z-axis self test: gyration trim within : "); pc.printf("%f \n\r", SelfTest[5]); wait(1); 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){ mpu6050.resetMPU6050(); // Reset registers to default in preparation for device calibration mpu6050.calibrateMPU6050(gyroBias, accelBias); // Calibrate gyro and accelerometers, load biases in bias registers mpu6050.initMPU6050(); pc.printf("MPU6050 initialized for active data mode....\n\r"); // Initialize device for active mode read of acclerometer, gyroscope, and temperature wait(2); } else { pc.printf("Device did not the pass self-test!\n\r"); } } else{ pc.printf("Could not connect to MPU6050: \n\r"); pc.printf("%#x \n", whoami); while(1) ; // Loop forever if communication doesn't happen } wait(10); // wait 10 seconds to make sure esc's are armed and ready to go // counter for mpu calibration int counter = 0; //Loop Forever while (1) { // If data ready bit set, all data registers have new data if(mpu6050.readByte(MPU6050_ADDRESS, INT_STATUS) & 0x01) { // check if data ready interrupt mpu6050.readAccelData(accelCount); // Read the x/y/z adc values mpu6050.getAres(); // Now we'll calculate the accleration value into actual g's ax = (float)accelCount[0]*aRes - accelBias[0]; // get actual g value, this depends on scale being set ay = (float)accelCount[1]*aRes - accelBias[1]; az = (float)accelCount[2]*aRes - accelBias[2]; mpu6050.readGyroData(gyroCount); // Read the x/y/z adc values mpu6050.getGres(); // Calculate the gyro value into actual degrees per second gx = (float)gyroCount[0]*gRes; // - gyroBias[0]; // get actual gyro value, this depends on scale being set gy = (float)gyroCount[1]*gRes; // - gyroBias[1]; gz = (float)gyroCount[2]*gRes; // - gyroBias[2]; tempCount = mpu6050.readTempData(); // Read the x/y/z adc values temperature = (tempCount) / 340. + 36.53; // Temperature in degrees Centigrade } Now = t.read_us(); deltat = (float)((Now - lastUpdate)/1000000.0f) ; // set integration time by time elapsed since last filter update lastUpdate = Now; sum += deltat; sumCount++; if(lastUpdate - firstUpdate > 10000000.0f) { beta = 0.04; // decrease filter gain after stabilized zeta = 0.015; // increasey bias drift gain after stabilized } // Pass gyro rate as rad/s mpu6050.MadgwickQuaternionUpdate(ax, ay, az, gx*PI/180.0f, gy*PI/180.0f, gz*PI/180.0f); // Define output variables from updated quaternion---these are Tait-Bryan angles, commonly used in aircraft orientation. // In this coordinate system, the positive z-axis is down toward Earth. // 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. // Pitch is angle between sensor x-axis and Earth ground plane, toward the Earth is positive, up toward the sky is negative. // Roll is angle between sensor y-axis and Earth ground plane, y-axis up is positive roll. // These arise from the definition of the homogeneous rotation matrix constructed from quaternions. // Tait-Bryan angles as well as Euler angles are non-commutative; that is, the get the correct orientation the rotations must be // applied in the correct order which for this configuration is yaw, pitch, and then roll. // For more see http://en.wikipedia.org/wiki/Conversion_between_quaternions_and_Euler_angles which has additional links. 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]); pitch = -asin(2.0f * (q[1] * q[3] - q[0] * q[2])); 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]); actPitch = pitch * 180.0f / PI; actYaw = yaw * 180.0f / PI; actRoll = roll * 180.0f / PI; if (counter < 800){ counter++; RollCalibration = actRoll; PitchCalibration= actPitch; YawCalibration = actYaw; } else{ actRoll = actRoll - RollCalibration; actualRoll = actPitch - PitchCalibration; actualYaw = actYaw - YawCalibration; if(actRoll > 180){ actualPitch=-(actRoll-360); } if(actRoll < -180){ actualPitch=-(actRoll+360); } if ((actRoll > -180) && actRoll < 180) { actualPitch=-actRoll; } // Measuring receiver values desRoll = 1000000 * channel1.pulsewidth(); // desired point for roll desPitch = 1000000 * channel2.pulsewidth(); // desired point for pitch desiredThrottle = 1000000 * channel3.pulsewidth(); // desired point for throttle desYaw = 1000000 * channel4.pulsewidth(); // desired point for yaw // The desired roll, pitch and yaw has a range between 1000 to 2000 microseconds // we will map these values to limit the desired input between -45deg to +45deg for roll and pitch // As for yaw, the desired input will range between -180 to 180 deg // // The equaion below maps the PWM value to a desired deg value, then it is shifter by some amount of degree so that // the 1000 us is equivalent to -45deg, and the 2000 us point is equivalent to 45deg. // The same logic is used for the Yaw equation desiredRoll = (90 * (abs(desRoll -1000))/1000) - 45; desiredPitch= (90 * (abs(desPitch-1000))/1000) - 45; desiredYaw = (360* (abs(desYaw -1000))/1000) - 180; } // pc.printf("%f %f %f\n\r", actualPitch, desiredPitch, ePitch); // pc.printf("%f %f %f %f\n\r ", uRoll, uPitch, eRoll, ePitch); pc.printf("%f %f %f %f\n\r", v1, v2, v3, v4); // pc.printf("%f %f %f %f %f %f \n\r\n\r\n\r", actualRoll, desiredRoll, actualPitch, desiredPitch, actualYaw, eYaw ); /* pc.printf("%f \n\r", v1); pc.printf("%f \n\r", v2); pc.printf("%f \n\r", v3); pc.printf("%f \n\r \n\r", v4); pc.printf("error in roll %f \n\r \n\r", eRoll); pc.printf("error in pitch %f \n\r \n\r", ePitch); pc.printf("error in yaw %f \n\r \n\r", eYaw);*/ /* pc.printf("Pulse width Roll value %f \n\r",desRoll); pc.printf("Pulse width Pitch value %f \n\r",desPitch); pc.printf("Pulse width Throttle value %f \n\r",desiredThrottle); pc.printf("Pulse width Yaw value %f \n\r",desYaw); */ /* pc.printf("Desired Roll: %f \n\r", desiredRoll); pc.printf("Desired Pitch: %f \n\r", desiredPitch); pc.printf("Desired Throttle: %f \n\r", desiredThrottle); pc.printf("Desired Yaw: %f \n\r", desiredYaw); */ myled= !myled; count = t.read_ms(); sum = 0; sumCount = 0; //wait(.5); } } // Updated on: Sept 19 2019 // Koceila Cherfouh // Deescription // periodic time interrupt that controls the speed of the motor based on the gyro and accelerometer readings using a PID controller. // // Update and whats left to do: // make sure all variables are declared. define max values, and gain values for each PID controllers. // define the motor speed assignment based on the system's kinematic model // test and tune the controller gain // ******** Periodic Thread ******** void PID1(void const *argument){ while(true){ osSignalWait(0x1, osWaitForever); // Go to sleep until signal is received. if (desiredThrottle > 1050){ // 1050 micro seconds will serve as the activation throttle // 1- PID roll eRoll= desiredRoll - actualRoll; eDerRoll = eRoll - elastRoll; // derivative of error uRoll = (KpRoll * eRoll) + (KdRoll * eDerRoll) + (KiRoll * eIntRoll); // computing the pitch control input // this part of the code limits the integrator windeup. an effect that takes place when if (uRoll > uRollMax){ uRoll = uRollMax; } if (uRoll < -uRollMax){ uRoll = -uRollMax; } else { eIntRoll += eRoll; // Computing the integral sum } elastRoll = eRoll; //------------------------------------------------------------------------------------------------------------- // 2- PID pitch ePitch = desiredPitch - actualPitch; // error eDerPitch = ePitch - elastPitch ; // derivative of error uPitch = (KpPitch * ePitch) + (KdPitch * eDerPitch) + (KiPitch * eIntPitch); // computing the pitch control input // this part of the code limits the integrator windeup. an effect that takes place when if (uPitch > uPitchMax){ uPitch = uPitchMax; } if (uPitch < -uPitchMax){ uPitch = -uPitchMax; } else { eIntPitch += ePitch; // Computing the integral sum } elastPitch = ePitch; //------------------------------------------------------------------------------------------------------------- // 4- PID yaw /* eYaw= desiredYaw - actualYaw; eDerYaw = eYaw - elastYaw ; // derivative of error uYaw = (KpYaw * eYaw) + (KdYaw * eDerYaw) + (KiYaw * eIntYaw); // computing the pitch control input // this part of the code limits the integrator windeup. an effect that takes place when if (uYaw > uYawMax){ uYaw = uYawMax; } else { eIntYaw += eYaw; // Computing the integral sum } elastYaw = eYaw; */ //------------------------------------------------------------------------------------------------------------- // Now that all pid control inputs are computed. send the PWM control input using the kinematic model to the ESC's v1= desiredThrottle - uPitch - uRoll - uYaw; //Calculate the pulse for esc 1 (front-right - CCW) v2= desiredThrottle + uPitch - uRoll + uYaw; //Calculate the pulse for esc 2 (rear-right - CW) v3= desiredThrottle + uPitch + uRoll - uYaw; //Calculate the pulse for esc 3 (rear-left - CCW) v4= desiredThrottle - uPitch + uRoll + uYaw; //Calculate the pulse for esc 4 (front-left - CW) if ( v1 > 2000 ){ v1=2000; } if ( v1 < 1000){ v1=1000; } if ( v2 > 2000 ){ v2=2000; } if ( v2 < 1000){ v2=1000; } if ( v3 > 2000 ){ v3=2000; } if ( v3 < 1000){ v3=1000; } if ( v4 > 2000 ){ v4=2000; } if ( v4 < 1000){ v4=1000; } } else{ // In the case where desired throttle is <1050 microseconds, we want to deactivate all motors v1= desiredThrottle; v2= desiredThrottle; v3= desiredThrottle; v4= desiredThrottle; } // outputing the necessary PWM value to the motors PWM_motor1.pulsewidth_us(abs(v1)); //Set the Pulsewidth output PWM_motor2.pulsewidth_us(abs(v2)); //Set the Pulsewidth output PWM_motor3.pulsewidth_us(abs(v3)); //Set the Pulsewidth output PWM_motor4.pulsewidth_us(abs(v4)); //Set the Pulsewidth output } } // ******** Periodic Interrupt Handler 1******** void PeriodicInterruptISR(void){ osSignalSet(PiControlId,0x1); // Activate the signal, PiControl, with each periodic timer interrupt. }