Yeah, I've opted to use a genetic algorithm to tune it. Not convinced it's the best approach, but in the linear velocity case (no rotation pids), it seemed to consistently demonstrate convergence... May take a shot at it with gradient descent here eventually, but I need to be able to capture the response metrics a bit better than I am (rise / settle time, overshoot, decay, etc)... I haven't tried Ziegler-Nichols because it requires extra work to capture the different error metrics. I may end up giving it a shot, though, if nothing else works.
Still, I'm pretty sure this doesn't have anything to do with gain tuning - watching the output tells me the angular momentum is what's really driving the response and no amount of gain is going to overcome that. The reason is because if I use strong gains to correct more quickly, it drives the angular mometum further in the other direction, and the oscillation remains.
The PID configuration is derived from the source code here:
https://github.com/tu-darmstadt-ros...ns/src/gazebo_quadrotor_simple_controller.cpp
The code of interest is in the 'void GazeboQuadrotorSimpleController::Update()' function.
The most relevant code is as follows, it's my code that is modified somewhat from what's found in the Hector gazebo codebase:
----------------------------
mPose = mLink->GetWorldPose();
mAngularVelocity = mLink->GetWorldAngularVel();
ang_accel = mLink->GetWorldAngularAccel() - mAngAccel;
mAcceleration = (mLink->GetWorldLinearVel() - mVelocity) / dt;
mVelocity = mLink->GetWorldLinearVel();
mAngAccel = mLink->GetWorldAngularAccel();
// Get gravity
math::Vector3 gravity_body = pose.rot.RotateVector(mWorld->GetPhysicsEngine()->GetGravity());
double gravity = gravity_body.GetLength();
loadFactor = gravity * gravity / mWorld->GetPhysicsEngine()->GetGravity().Dot(gravity_body); // Get gravity
// update controllers
mForce.Set(0.0, 0.0, 0.0);
mTorque.Set(0.0, 0.0, 0.0);
roll = mPose.rot.GetRoll();
pitch = mPose.rot.GetPitch();
yaw = mPose.rot.GetYaw();
pitchCommand = .392; // -mPids[PidType::VelocityX]->update(0, mVelocity.x, mAcceleration.x, dt);
rollCommand = mPids[PidType::VelocityY]->update(0, mVelocity.y, mAcceleration.y, dt);
aa_x = mPids[PidType::Roll]->update(rollCommand, roll, mAngularVelocity.x, dt);
aa_y = mPids[PidType::Pitch]->update(pitchCommand, pitch, mAngularVelocity.y, dt);
mTorque.x = mInertia.x * aa_x;
mTorque.y = mInertia.y * aa_y;
mTorque.z = mInertia.z * mPids[PidType::Yaw]->update(ang_vel.z, mAngularVelocity.z, 0, dt);
mForce.z = mMass * (mPids[PidType::VelocityZ]->update(9, mVelocity.z, mAcceleration.z, dt) + loadFactor * gravity);
if (mMaxForce > 0.0 && mForce.z > mMaxForce) mForce.z = mMaxForce;
if (mForce.z < 0.0) mForce.z = 0.0;
mLink->AddRelativeForce(mForce);
mLink->AddRelativeTorque(mTorque - mLink->GetInertial()->GetCoG().Cross(mForce));
---------------------------
Basically, the system computes the forces / torques relative to the body frame of the drone. Linear velocity PIDs (X and Y) are stacked with angular velocity PIDs (outer / inner loop scheme), such that the linear velocity PIDs output an angle of rotation for the angular PID setpoints. X velocity drives pitch and Y velocity drives roll. The angular pids then compute an angular acceleration to compute the resultant torques according to T = I*a.
Currently, I'm using 0.392 radians as my setpoint for the pitch / roll PIDs with no horizontal velocity and a 9 m/s vertical velocity.
The pid code applies both input and output limits (a bit redundant, but I went with it anyway). Integral terms are limited by the output limit to overcome integral windup. For the X/Y velocity pids, the input limit is 18, and the integral/output limit is 0.392. Likewise, my angular pids limit input to 0.392 and output angular acceleration to 27.7 rads/s^2. max_force_ is limited to 25N.
The force and angular acceleration limits are back-calculated from the rotor performance properties that I found when I dug through the Hector quadrotor model source code (they specified a max torque / force per volt, I assumed 11.1V rotors, and worked it out from there...)
FWIW, here's the PID source code I've used (I've actually tried multiple implementations, to the same effect)... Note that I don't actually use the dx / dt parameters here because the update interval is constant:
-------------------------------------------------
double LearnerPid::update(double setPoint, double x, double dx, double dt)
{
//limit input to valid ranges
if (in_limit > 0.0 && fabs(setPoint) > in_limit)
setPoint = (setPoint < 0 ? -1.0 : 1.0) * in_limit;
double error = setPoint - x;
mIterm += (gain_i * error);
//limit integral to valid output ranges to avoid windup
if (out_limit > 0.0 && fabs(mIterm) > out_limit)
mIterm = (mIterm < 0 ? -1.0 : 1.0) * out_limit;
double dInput = (x - mLastInput);
output = gain_p * error + mIterm + gain_d * dInput;
//limit output to valid ranges
if (out_limit > 0.0 && fabs(output) > out_limit)
output = (output < 0 ? -1.0 : 1.0) * out_limit;
return output;
}
My apologies if my terminology or explanations are off... I'm not really a robotics guy and physics was never my best subject.