Software > Software

My first little PID: please comment

<< < (3/5) > >>

obiwanjacobi:

--- Quote from: jkerns on May 14, 2013, 07:47:14 AM ---I've never switched controller types in mid stream. It isn't uncommon to modify the gains as a function of operating conditions (referred to as gain scheduling) but switching between PI and PD or whatever would be unusual.

--- End quote ---

This gives me an idea. If you put a gain of zero in for a specific term you basically shut it of. I could optimize the code to keep performing the maintenance on the variables but not calculate anything when gain equals zero. That would simplify the public interface of this class considerably.



--- Code: ---/*
BaseT is used as a base class and implements:
T getFeedback()
unsigned int getDeltaTime()
T getSmallestAcceptableError()

T is the data type that hold the values. Either float or double.

Algorithm:
Error = SetPoint - Feedback
P = Error * gainP
I = Sum(previous-I's) + ((Error * deltaTime) * gainI)
D = ((Error - previous-Error) / deltaTime) * gainD

PI = P + I
PD = P + D
PID = P + I + D
*/
template<class BaseT, typename T>
class PID : public BaseT
{
public:
// pass a gain of zero to bypass a specific term.
T Process(T setPoint, T gainP, T gainI, T gainD)
{
T input = BaseT::getFeedback();
T error = CalcError(setPoint, input);
unsigned int deltaTime = BaseT::getDeltaTime();

return CalcP(error, gainP) + CalcI(error, deltaTime, gainI) + CalcD(error, deltaTime, gainD);
}

private:
T _integralAcc;
T _lastError;

inline T CalcError(T setPoint, T input)
{
T error = setPoint - input;

if ((error < BaseT::getSmallestAcceptableError() && error > 0) ||
(error > -BaseT::getSmallestAcceptableError() && error < 0))
{
error = 0;
                        _integralAcc = 0; // Good idea??
}

return error;
}

inline T CalcP(T error, T gain)
{
if (gain == 0) return 0;

return error * gain;
}

inline T CalcI(T error, unsigned int deltaTime, T gain)
{
if (gain != 0)
{
_integralAcc += (error * deltaTime) * gain;
}

return _integralAcc;
}

inline T CalcD(T error, unsigned int deltaTime, T gain)
{
T value = 0;

if (gain != 0)
{
value = ((error - _lastError) / deltaTime) * gain;
}

_lastError = error;

return value;
}
};

--- End code ---

jwatte:
newError - prevError is just -(prevError - newError) so you just flip the sign of the D gain.

High-pass and low-pass in these signals is exactly the same as for audio. The simplest way is a single accumulator variable and a time constant; more complex filters use multiple delay stages, IIR or FIR style.

In brief
low pass: value = newValue + oldValue * c
high pass: value = newValue - oldValue * c

The range of "c" is 0 .. 1, and the gain of the filter is approimately varying like 1/(1 - c).
You may be more familiar with the form:
value = (newValue * (1 - c') + oldValue * c')
This is equivalent to a gain-compensated filter with a warping of the c factor to c'.

obiwanjacobi:

--- Quote from: jwatte on May 14, 2013, 04:58:27 PM ---newError - prevError is just -(prevError - newError) so you just flip the sign of the D gain.

--- End quote ---

But does 'just flipping the sign' have a big effect on the process and the end value that is calculated? As I understood it, the D-term is small(ish) to begin with - because it susceptible to noise and you don't want noise to disrupt your regulator. But flipping the sign sounds like a meaningful change to the algorithm.

I interpreted the whole PID thing as a big summation of all the terms. So Error - lastError sound correct to me - same way (order) you calculate delta time.

jkerns:
Depending on which way you subtract (old-new or new-old) you get a different sign on the error term so you would need a different sign on the D gain to get the correction to work in the proper direction.

Let's look at an example, if the error is increasing positive, you want the output from the controller to go negative to compensate. Right?  So, if you want to use a positive number for the gain (to match the other gains), you would select old - new to make the error term negitive which will make the output of the D part of the controller to be negative.

The "algorithm" works the same either way, you just need to make sure that the output of the D term is working in the right direction. It's more of an algebra / book keeping problem than anything else.

obiwanjacobi:

--- Quote from: jkerns on May 15, 2013, 07:04:58 AM ---Let's look at an example, if the error is increasing positive, you want the output from the controller to go negative to compensate. Right?
--- End quote ---

I would think that the sign of the error only indicates if you need to 'accelerate' (pos) or 'de-accelerate' (neg). The size/amount/value of the error indicates if a small or large correction is needed.

But if the D-term purpose is to have a dampening effect, I would think the formula would be (P + I) - D (I think I saw that somewhere used in code like that). Than it would be clear(er) what the purpose of the D-term is.


--- Quote from: jkerns on May 15, 2013, 07:04:58 AM ---Depending on which way you subtract (old-new or new-old) you get a different sign on the error term so you would need a different sign on the D gain to get the correction to work in the proper direction.

--- End quote ---

I understand that you can play around with signs all day. I just want to know how the formula was meant originally and why it works that way. I think I get most of it (I hope) ;-)

The whole D-term should be positive, then?


I am sorry if I test your patience and I do really appreciate the time you guys take to answer my questions. I am just trying to get this.

Thanks a lot.

Navigation

[0] Message Index

[#] Next page

[*] Previous page

Go to full version