Good day,
I am working on an autonomous flight controller for a quadcopter ('X' configuration) using only angles as inputs for the setpoints used in a single loop PID controller running at 200Hz (PID Implementation is Here: Quadcopter PID Controller: Derivative on Measurement / Removing the Derivative Kick). For now I am trying to get the quadcopter to stabilize at a setpoint of 0 degrees. The best I was able to come up with currently is +-5 degrees which is bad for position hold. I first tried using only a PD controller but since the quadcopter is inherently front heavy due to the stereo cameras, no amount of D or P gain is enough to stabilize the system. An example is the image below which I added a very small I gain:
As you can see from the image above (at the second plot), the oscillations occur at a level below zero degrees due to the quadcopter being front heavy. This means that the quad oscillates from the level postion of 0 degrees to and from a negative angle/towards the front. To compensate for this behaviour, I discovered that I can set the DC level at which this oscillations occur using the I gain to reach the setpoint. An image is shown below with [I think] an adequate I gain applied:
I have adjusted the PID gains to reduce the jitters caused by too much P gain and D gain. These are my current settings (Which are two tests with the corresponding footage below):
Test 1: https://youtu.be/8JsraZe6xgM
Test 2: https://youtu.be/ZZTE6VqeRq0
I can't seem to tune the quadcopter to reach the setpoint with at least +-1 degrees of error. I noticed that further increasing the I-gain no longer increases the DC offset.
When do I know if the I-gain I've set is too high? How does it reflect on the plot?
EDIT: The Perr in the graphs are just the difference of the setpoint and the CF (Complementary Filter) angle. The Derr plotted is not yet divided by the deltaTime because the execution time is small ~ 0.0047s which will make the other errors P and I hard to see. The Ierr plotted is the error integrated with time.
All the errors plotted (Perr, Ierr, Derr) are not yet multiplied by the Kp, Ki, and Kd constants
The 3rd plot for each of the images is the response of the quadcopter. The values on the Y axis correspond to the value placed as the input into the gpioPWM() function of the pigpio library. I had mapped using a scope the values such that 113 to 209 pigpio integer input corresponds to 1020 to 2000ms time high of the PWM at 400Hz to the ESC's
EDIT:
Here is my current code implementation with the setpoint of 0 degrees:
cout << "Starting Quadcopter" << endl;
float baseThrottle = 155; //1510ms
float maxThrottle = 180; //This is the current set max throttle for the PITCH YAW and ROLL PID to give allowance to the altitude PWM. 205 is the maximum which is equivalent to 2000ms time high PWM
float baseCompensation = 0; //For the Altitude PID to be implemented later
delay(3000);
float startTime=(float)getTickCount();
deltaTimeInit=(float)getTickCount(); //Starting value for first pass
while(1){
//Read Sensor Data
readGyro(&gyroAngleArray);
readAccelMag(&accelmagAngleArray);
//Time Stamp
//The while loop is used to get a consistent dt for the proper integration to obtain the correct gyroscope angles. I found that with a variable dt, it is impossible to obtain correct angles from the gyroscope.
while( ( ((float)getTickCount()-deltaTimeInit) / ( ((float)getTickFrequency()) ) ) < 0.005){ //0.00209715|0.00419
deltaTime2=((float)getTickCount()-deltaTimeInit)/(((float)getTickFrequency())); //Get Time Elapsed
cout << " DT endx = " << deltaTime2 << endl;
}
//deltaTime2=((float)getTickCount()-deltaTimeInit)/(((float)getTickFrequency())); //Get Time Elapsed
deltaTimeInit=(float)getTickCount(); //Start counting time elapsed
cout << " DT end = " << deltaTime2 << endl;
//Complementary Filter
float pitchAngleCF=(alpha)*(pitchAngleCF+gyroAngleArray.Pitch*deltaTime2)+(1-alpha)*(accelmagAngleArray.Pitch);
float rollAngleCF=(alpha)*(rollAngleCF+gyroAngleArray.Roll*deltaTime2)+(1-alpha)*(accelmagAngleArray.Roll);
float yawAngleCF=(alpha)*(yawAngleCF+gyroAngleArray.Yaw*deltaTime2)+(1-alpha)*(accelmagAngleArray.Yaw);
//Calculate Orientation Error (current - target)
float pitchError = pitchAngleCF - pitchTarget;
pitchErrorSum += (pitchError*deltaTime2);
float pitchErrorDiff = pitchError - pitchPrevError;
pitchPrevError = pitchError;
float rollError = rollAngleCF - rollTarget;
rollErrorSum += (rollError*deltaTime2);
float rollErrorDiff = rollError - rollPrevError;
rollPrevError = rollError;
float yawError = yawAngleCF - yawTarget;
yawErrorSum += (yawError*deltaTime2);
float yawErrorDiff = yawError - yawPrevError;
yawPrevError = yawError;
//PID controller list
float pitchPID = pitchKp*pitchError + pitchKi*pitchErrorSum + pitchKd*pitchErrorDiff/deltaTime2;
float rollPID = rollKp*rollError + rollKi*rollErrorSum + rollKd*rollErrorDiff/deltaTime2;
float yawPID = yawKp*yawError + yawKi*yawErrorSum + yawKd*yawErrorDiff/deltaTime2;
//Motor Control - Mixing
//Motor Front Left (1)
float motorPwm1 = -pitchPID + rollPID - yawPID + baseThrottle + baseCompensation;
//Motor Front Right (2)
float motorPwm2 = -pitchPID - rollPID + yawPID + baseThrottle + baseCompensation;
//Motor Back Left (3)
float motorPwm3 = pitchPID + rollPID + yawPID + baseThrottle + baseCompensation;
//Motor Back Right (4)
float motorPwm4 = pitchPID - rollPID - yawPID + baseThrottle + baseCompensation;
//Check if PWM is Saturating - This method is used to fill then trim the outputs of the pwm that gets fed into the gpioPWM() function to avoid exceeding the earlier set maximum throttle while maintaining the ratios of the 4 motor throttles.
float motorPWM[4] = {motorPwm1, motorPwm2, motorPwm3, motorPwm4};
float minPWM = motorPWM[0];
int i;
for(i=0; i<4; i++){ // Get minimum PWM for filling
if(motorPWM[i]<minPWM){
minPWM=motorPWM[i];
}
}
cout << " MinPWM = " << minPWM << endl;
if(minPWM<baseThrottle){
float fillPwm=baseThrottle-minPWM; //Get deficiency and use this to fill all 4 motors
cout << " Fill = " << fillPwm << endl;
motorPwm1=motorPwm1+fillPwm;
motorPwm2=motorPwm2+fillPwm;
motorPwm3=motorPwm3+fillPwm;
motorPwm4=motorPwm4+fillPwm;
}
float motorPWM2[4] = {motorPwm1, motorPwm2, motorPwm3, motorPwm4};
float maxPWM = motorPWM2[0];
for(i=0; i<4; i++){ // Get max PWM for trimming
if(motorPWM2[i]>maxPWM){
maxPWM=motorPWM2[i];
}
}
cout << " MaxPWM = " << maxPWM << endl;
if(maxPWM>maxThrottle){
float trimPwm=maxPWM-maxThrottle; //Get excess and use this to trim all 4 motors
cout << " Trim = " << trimPwm << endl;
motorPwm1=motorPwm1-trimPwm;
motorPwm2=motorPwm2-trimPwm;
motorPwm3=motorPwm3-trimPwm;
motorPwm4=motorPwm4-trimPwm;
}
//PWM Output
gpioPWM(24,motorPwm1); //1
gpioPWM(17,motorPwm2); //2
gpioPWM(22,motorPwm3); //3
gpioPWM(18,motorPwm4); //4