Minimize An Error With A PID Controller in Unity With C#
Minimize An Error With A PID Controller in Unity With C#
using System.Collections;
//Quadcopter parameters
[Header("Internal")]
public float maxPropellerForce; //100
public float maxTorque; //1
public float throttle;
public float moveFactor; //5
//PID
public Vector3 PID_pitch_gains; //(2, 3, 2)
public Vector3 PID_roll_gains; //(2, 0.2, 0.5)
public Vector3 PID_yaw_gains; //(1, 0, 0)
//External parameters
[Header("External")]
public float windForce;
//0 -> 360
public float forceDir;
Rigidbody quadcopterRB;
//Movement factors
float moveForwardBack;
float moveLeftRight;
float yawDir;
void Start()
{
quadcopterRB = gameObject.GetComponent<Rigidbody>();
void FixedUpdate()
{
AddControls();
AddMotorForce();
AddExternalForces();
}
void AddControls()
{
//Change throttle to move up or down
if (Input.GetKey(KeyCode.UpArrow))
{
throttle += 5f;
}
if (Input.GetKey(KeyCode.DownArrow))
{
throttle -= 5f;
}
//Steering
//Move forward or reverse
moveForwardBack = 0f;
if (Input.GetKey(KeyCode.W))
{
moveForwardBack = 1f;
}
if (Input.GetKey(KeyCode.S))
{
moveForwardBack = -1f;
}
if (Input.GetKey(KeyCode.A))
{
moveLeftRight = -1f;
}
if (Input.GetKey(KeyCode.D))
{
moveLeftRight = 1f;
}
if (Input.GetKey(KeyCode.LeftArrow))
{
yawDir = -1f;
}
if (Input.GetKey(KeyCode.RightArrow))
{
yawDir = 1f;
}
}
void AddMotorForce()
{
//Calculate the errors so we can use a PID controller to stabilize
//Assume no error is if 0 degrees
//Pitch
//Returns positive if pitching forward
float pitchError = GetPitchError();
//Roll
//Returns positive if rolling left
float rollError = GetRollError() * -1f;
//Add steering
propellerForceFR -= moveForwardBack * throttle * moveFactor;
propellerForceFR -= moveLeftRight * throttle;
//FL
float propellerForceFL = throttle + (PID_pitch_output -
PID_roll_output);
//BR
float propellerForceBR = throttle + (-PID_pitch_output +
PID_roll_output);
//BL
float propellerForceBL = throttle + (-PID_pitch_output -
PID_roll_output);
//Clamp
propellerForceFR = Mathf.Clamp(propellerForceFR, 0f,
maxPropellerForce);
propellerForceFL = Mathf.Clamp(propellerForceFL, 0f,
maxPropellerForce);
propellerForceBR = Mathf.Clamp(propellerForceBR, 0f,
maxPropellerForce);
propellerForceBL = Mathf.Clamp(propellerForceBL, 0f,
maxPropellerForce);
//Yaw
//Minimize the yaw error (which is already signed):
float yawError = quadcopterRB.angularVelocity.y;
float PID_yaw_output =
PID_yaw.GetFactorFromPIDController(PID_yaw_gains, yawError);
quadcopterRB.AddForceAtPosition(propellerUp * propellerForce,
propellerPos);
//Debug
//Debug.DrawRay(propellerPos, propellerUp * 1f, Color.red);
}
return xAngle;
}
return zAngle;
}
//Rotate it
windDir = Quaternion.Euler(0, forceDir, 0) * windDir;
quadcopterRB.AddForce(windDir * windForce);
//Debug
//Is showing in which direction the wind is coming from
//center of quadcopter is where it ends and is blowing in the
direction of the line
Debug.DrawRay(transform.position, -windDir * 3f, Color.red);
}
}
Next up is the PID controller. You can change a lot of settings in a PID controller. For
example, when calculating the derivative of the error you can sometimes use an error from
two time steps back and sometimes you use an error from the last time step. When calculating
the sum of the errors, you can sometimes use a maximum value to not make the sum grow
too big.
using UnityEngine;
using System.Collections;
//PID parameters
public float gain_P = 0f;
public float gain_I = 0f;
public float gain_D = 0f;
//Sometimes you have to limit the total sum of all errors used in the I
private float error_sumMax = 20f;
return output;
}
return output;
}
//Use this when experimenting with PID parameters and the gains are
stored in a Vector3
public float GetFactorFromPIDController(Vector3 gains, float error)
{
this.gain_P = gains.x;
this.gain_I = gains.y;
this.gain_D = gains.z;
return output;
}
//P
output += gain_P * error;
//I
error_sum += Time.fixedDeltaTime * error;
//D
float d_dt_error = (error - error_old) / Time.fixedDeltaTime;
this.error_old = error;
return output;
}
}