Help with PID "units" in a quadcopter control system

I'm in the process of writing my own simple quadcopter controller for experimental use, and I'm having trouble getting my head around how to convert from the degrees which my PID controller demands to an appropriate 1k-2k range for PWM output. For example, take the roll axis on a '+' configured 'copter (pseudo-code):

setpoint = scaleToRange(receiver.rollValue, -30, 30); //scale the command (1000-2000) to between -30 and 30 degrees, as that's the maximum roll permitted.
demandedRoll = rollPID.calculate(setpoint, imu.currentRoll, PID_params);

/// the part I'm having trouble with



How do I take the roll demanded by my PID controller and convert it to a value useful to the motors, that is to say, where does rollPWM come from? My first instinct is to use a simple linear relationship, i.e.:

rollPWM = scaleToRange(demandedRoll, MinValue=receiver.throttle/2, MaxValue=2000-receiver.throttle);
//don't let it go beyond 50% of throttle on low end, and the ESC's max on the high end.


However this seems far too simplistic to work.

Or should I be doing more calculations before everything goes through PID control? Any help would be great.

• (And before anyone asks, I'm also designing a servo driven test stand to develop this thing on; I'm more of a hardware guy than a software guy and I'd prefer to have my code bulletproof before I hand it the keys to 10" spinning knives). Aug 2 '13 at 5:26
• You should try your idea on your test stand before dismissing it as too simple. It sounds like you may have one missing value in your calculation though, which would be the desired total vertical thrust (left motor + right motor).
– Ian
Aug 2 '13 at 15:00

Wrapping your head around unit conversions in controllers is a pretty common problem, so don't think you're alone.

If you're implementing your PID controller using floating point math, then you really don't have to worry: the gains that you assign for the proportional, integral, and derivative action will take care of the differing ranges for the inputs and outputs.

If you're implementing your PID controller using integer math or fixed-point math, then you don't have to worry for the reasons you're worrying. Instead, you have to worry about keeping the calculations in the right range so that you neither lose precision nor overflow any calculations.

As an example for a floating-point calculation, a plausible proportional gain would be 500 output counts per degree of input error. In floating point, you'd just assign 500.0 to the proportional gain.

So have fun. Don't forget the integrator anti-windup.

• Thank you! I am indeed working on a floating point-enabled ARM processor; horray for 32 bits of precision! This makes so much more sense than the way I was thinking about it; my mistake obviously thinking that a PID controller must output the same units it takes in. Aug 4 '13 at 0:45
• Uh -- beware that single-precision floating point only gives you around 24 to 25 bits of real "precision". The rest goes into an exponent that lets the answer have a huge range. Aug 4 '13 at 2:30

Tim Wescot is one of the most experienced experts ever in this forum especially in the PID controllers field.

I recommend you to read his brilliant article here.

We used discrete PID for our quadrotor control system. We are building a quadrotor right now and tested both continuous PID controller(What you are usually find in wikipedia or web searches) and discrete PID controller, and for quadrotor systems which has a digitized system, we find discrete PID far better than continuous one.

In this method, you should use 2 separate PID for each motor, but PID gains is equal except Kp of one of them should be -Kp of another one. Here is the sample code( in C# ):

    class PIDController
{
float[] _u = new float[3];
float[] _e = new float[3];
float[] _ef = new float[3];
float _Tf;

float _Kp;
float _Ti;
float _Td;
float _h; //delta_t
float MaxValue; // min value for motors output
float MinValue; // max value for motors output
float _Alfa; //mostly 0.1
public PIDController(float SamplingPeriod,float Alfa)
{
_Alfa = Alfa;
_h = SamplingPeriod;
MinValue = 0;
MaxValue = 100;

}
public void SetCoefficient(float Kp,float Ti,float Td)
{
_Kp = Kp;
_Ti = Ti;
_Td = Td;
}
public void SetRenge(float Min,float Max)
{
MinValue = Min;
MaxValue = Max;
}

public float GetOutputSignal(float y, float r)
{

_Tf = _Alfa * _Td;
float a = _h / (_Tf + _h);
_u[1] = _u[0];
_ef[2] = _ef[1];
_ef[1] = _ef[0];
_e[2] = _e[1];
_e[1] = _e[0];
_e[0] = r - y;
_ef[0] = (1 - a) * _ef[1] + a * _e[0];
if ((_Ti != 0) && (_h > 0))
_u[0] = _u[1] + _Kp * (_e[0] - _e[1]) + (_Kp * _h / _Ti) * _e[0] + ((_Kp * _Td) / _h) * (_ef[0] - 2 * _ef[1] + _ef[2]);
else
_u[0] = 0;
//System.Diagnostics.Debug.WriteLine(string.Format("y = {0} , r = {1} , e = {2} u={3}",y,r,_e[0],_u[0]));
//System.Diagnostics.Debug.WriteLine(string.Format("Ti = {0} , Td = {1} , Kp = {2}", _Ti, _Td, _Kp));
//System.Diagnostics.Debug.WriteLine("");
if ((_u[0] > MaxValue) || (_u[0] < MinValue))
{
if (_u[0] > MaxValue) { _u[0] = MaxValue; };
if (_u[0] < MinValue) { _u[0] = MinValue; };
}

return _u[0];
}

}


Test it, you will find this solution far better than continious one. We also used genetic algorithm for auto-tuning this PID. I should mention this type of PID controller won't work without an integral gain. I also should mention that, we didn't currently fully tune our quadrotor, but we got far far better results than continuous one.