Software > Software

My first little PID: please comment

**obiwanjacobi**:

Hi,

I just finished my first PID code I wrote from scratch (from the theory) in order to fully understand it. Because I am also a noob in math, I thought I would be fun to let you guys comment on how I am doing.

I did study some other implementations of PID algorithms and each had small (and not so small) variation in how the math was done. That's why I decided to give my interpretation.

This class is part of my Arduino Template Library. It is written specifically to cover one responsibility only and work with the other (template) classes I have in the library. So for instance, the PID class does not do time tracking, I have other classes in the library for that. The user of the class has to typedef its own class hierarchies for the situation the classes are used in.

--- 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 = ((previous-Error - Error) / deltaTime) * gainD

PI = P + I

PD = P + D

PID = P + I + D

*/

template<class BaseT, typename T>

class PID : public BaseT

{

public:

T P(T setPoint, T gainP)

{

T input = BaseT::getFeedback();

T error = CalcError(setPoint, input);

return CalcP(error, gainP);

}

T P_D(T setPoint, T gainP, T gainD)

{

T input = BaseT::getFeedback();

T error = CalcError(setPoint, input);

unsigned int deltaTime = BaseT::getDeltaTime();

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

}

T P_I(T setPoint, T gainP, T gainI)

{

T input = BaseT::getFeedback();

T error = CalcError(setPoint, input);

unsigned int deltaTime = BaseT::getDeltaTime();

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

}

T P_I_D(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;

}

return error;

}

inline T CalcP(T error, T gain)

{

return error * gain;

}

inline T CalcI(T error, unsigned int deltaTime, T gain)

{

_integralAcc += (error * deltaTime) * gain;

return _integralAcc;

}

inline T CalcD(T error, unsigned int deltaTime, T gain)

{

T value = ((_lastError - error) / deltaTime) * gain;

_lastError = error;

return value;

}

};

--- End code ---

So please comment on the correctness of the math especially.

Thanx!

**jkerns**:

Math looks OK.

On the Derivitive control - that tends to get jumpy due to noise on the input - one often does a little filtering of the input to the derivitive. But that complicates the tuning, too much filtering and the D does nothing. YMMV

**obiwanjacobi**:

--- Quote from: jkerns on May 12, 2013, 06:33:02 AM ---Math looks OK.

On the Derivitive control - that tends to get jumpy due to noise on the input - one often does a little filtering of the input to the derivitive. But that complicates the tuning, too much filtering and the D does nothing. YMMV

--- End quote ---

Thanx for taking the time to look at my math.

How would you do such filtering on the D input?

**jkerns**:

A simple digitial approximation of a first order exponential filter can be done by:

filtered_error = (1 - filter_constant)*filtered_error + filter_constant*error

Where the filter_constant can be a constant value between 0 and 1 (filter as a function of the number of samples) or can be calculated from an actual time constant (in seconds) by:

filter_constant = deltaTime/(deltaTime + time_constant)

You want to pick a time constant that is faster than the expected time for the physical input to be changing, but longer than a few A/D samples.

**obiwanjacobi**:

Thank you. It's all about (tweaking) constants, I see :P

In

--- Quote from: jkerns on May 12, 2013, 10:19:57 AM ---filtered_error = (1 - filter_constant)*filtered_error + filter_constant*error

--- End quote ---

Is the bold 'filtered_error' a previous filtered_error value or...?

Otherwise I don't understand how you can use something that you're in the process of calculating...

Also: is it likely you would dynamically (at runtime) switch using P, I or D terms for a single controller process? I mean do I need to take into account that all methods update the delta-time, I-accumulator and last-error variables in order to allow mixed calling of the P, PI, PD and PID methods?

Navigation

[0] Message Index

[#] Next page

Go to full version