Implementing PID Controller with C++

T

Thread Starter

tvlas

Hello!

I did not find any consistent answers on C++ forums about how a PID controller might be implemented in a C++ environment.

My problem consists of transforming from continuous to discrete (integral and derivative to discrete)

Does anyone have a clue how it can be done?

Best wishes,
Tudor V.
 
B

Bruce Durdle

First you need to take samples of the value to be controlled at finite intervals - the calculation interval, DT.

You can integrate this by adding the value of (error x DT) to the output each calculation step. However, it is probably easier to use integral reset feedback - especially as it allows the more complex stuff such as bumpless transfer to be added more easily. For this, you need to implement a second input F for the feedback. The integral action is developed by passing this through a low-pass filter having a time constant equal to the Integral Action Time TI - calculation is:

new filter out = old filter out + (new F - old filter out) x DT/TI

Add this to the (error x gain) and you have a P + I controller.

The D term is, as always, a bit more complex. But to get a simple derivative you calculate the change in error from one scan to the next - so a basic D term is (new error - old error) x TD/DT. Easiest way to incorporate this is to add it to the basic error then multiply the lot by the gain and then add in the filtered feedback.

That's the theory - but to make it work there are a lot of hooks you have to work around (such as the need to handle accumulations of very small rounding errors). You also need to build in the ability to take the derivative of either error or PV only.
 
D
The following is an excerpt from my PID controller code, which uses a algorithm adapted for digital controllers outlined by Shinskey in <i>Process Control Systems: Application, Design and Tuning</i>. It has been quite effective and tunable in our systems.

The "filters" referenced in the code are simple first-order filter objects, implemented in another class. Shinskey suggests implementing both the integral and derivative terms as first-order filters, as you see here.

--Dan<pre>
/*$2 ----------------------------------------------------------------
* $R init
--------------------------------------------------------------------- */

double
PidControlPolicy :: init( double setpoint,
double current,
double feedback )
{
inputFilter_.reset( current );
biasFilter_.reset( feedback );

output_ = feedback;
testInvariant();
isInitialized_ = true;

return output_;
}

/*$2 ----------------------------------------------------------------
* $R computeNextOutput
--------------------------------------------------------------------- */

double
PidControlPolicy :: computeNextOutput( double setpoint,
double current,
double feedback )
{
assert( isInitialized_ );

// Update the bias using the feedback selected above. For the
// digital controller, the bias term is really just a
// first-order filter of the feedback value, with a time
// constant of integralTime.

biasFilter_.setTimeConstant( parameters_.iTime() );
biasFilter_.currentInput( feedback );

// Note that if Kd=0, there is no derivative action.
// Similarly, if D=0, there is no filtering (i.e., c == cf),
// and no derivative action, as Kd is multiplied by c - cf = 0,
// no matter the value of Kd.

double filteredInput = current;

if ( parameters_.dGain() > 0 )
{
inputFilter_.setTimeConstant ( static_cast< int >
( rint ( parameters_.dTime ( ) / parameters_.dGain ( ) ) ) );
inputFilter_.currentInput( current );
filteredInput = inputFilter_.output();
}

// Next, calculate the PID output:
double error = setpoint - current;
double derivative = current - filteredInput;
double bias = biasFilter_.output();

// If this is a direct-acting controller
if ( parameters_.type() == Parameters::DIRECT_ACTING )
output_ = bias + parameters_.pGain() *
( error - parameters_.dGain() * derivative );
else
output_ = bias - parameters_.pGain() *
( error - parameters_.dGain() * derivative );

testInvariant();

// Return the PID output
return output_;
}</pre>
 
D
I didn't mention that, agreeing with Mark, "it's all about timing." The <i>controlTo()</i> method of the above PID code is called at fixed intervals of approximately equal duration. In this PID algorithm, only the first-order filters are sensitive to timing.

The filters adjust to timing jitter by computing an adjusted filter time-constant <i>tau'</i> as the ratio of desired filter time-constant, and the elapsed time since last call. Here, <i>value</i> is the incoming sample, and <i>filteredValue_</i> is the member variable holding the filtered value.<pre>
double tauPrime = static_cast<double>( tau_ ) /
static_cast<double>( deltaT );

filteredValue_ += ( value - filteredValue_ ) /
( 1.0 + tauPrime );</pre>
The discrete first-order filter concept is, again, thanks to Shinskey.

--Dan
 
L
> Here is our implementation for closed loop controllers in C++:

Yeah,I concur that are truly favorable for defining controllers in C++..Here is also some of them have a look:
1.The while loop
2.The do-while loop
3.The for loop
4.The break statement

http://www.bigdatacompanies.com
 
R

Rufus V. Smith

>> Here is our implementation for closed loop controllers in C++:

> Yeah,I concur that are truly favorable for defining controllers in C++..Here is also some of them have a look:
> 1.The while loop
> 2.The do-while loop
> 3.The for loop
> 4.The break statement

> http://www.bigdatacompanies.com

I hope people understand that this must be a joke.
Closed Loop control has nothing to do with those C++ looping constructs.
 
> I hope people understand that this must be a joke.
I also hope this.<pre>
void rlController::setPID_T1 ( double _T,
double _Kpp,
double _TnP,
double _TvP,
double _Td
)

1 + TnP*s 1 + TvP*s
Transfer function: Gr(s) = Kpp * --------- * ---------
TnP*s 1 + Td*s
T = cycle time in seconds</pre>
See Google: rlcontroller
 
Top