Dr. Dobb's is part of the Informa Tech Division of Informa PLC

This site is operated by a business or businesses owned by Informa PLC and all copyright resides with them. Informa PLC's registered office is 5 Howick Place, London SW1P 1WG. Registered in England and Wales. Number 8860726.


Channels ▼
RSS

Embedded C++


February 1997/A Reusable PID Control Class

Lots of control loops behave like damped harmonic oscillators or should. Here's a reusable class to implement that model in a variety of situations.


Introduction

Closed-loop process control is used in applications ranging from air conditioning thermostats to advanced guidance systems. What makes a controller "closed-loop" is the controller's use of feedback. The feedback is supplied from an output sensor of some sort, and feeds an input of the controller to tell the controller how far the output is from its commanded value or set-point. The controller uses this information to correct the output error.

One of the most widely used algorithms for closed-loop control is the three-term control, better known as the Proportional-Integral-Differential (PID) control loop. The objective in using PID is to control the output along a smooth curve (vs. time) towards the set-point while minimizing overshoot. A PID control algorithm includes a term which is proportional to the output error, a term proportional to the integral of the error, and a term proportional to the rate of change (derivative) of the error. The PID algorithm calculates a weighted sum of these three terms, which is applied as a gain control to a device such as a motor or a heater. (See the sidebar, "The PID Algorithm. ")

PID control loops had their beginnings with analog computers. The coefficients of the three PID terms were tuned using potentiometers. Even in today's digital control systems, the PID algorithm is the most prevalent. I use PID control quite often in my applications — in many cases with more than one PID loop in the same application. With this in mind, it only made sense to develop an abstract PID control class which could be inherited by another class to handle a specific type of control. The PidControl class is what I came up with. This class contains all the device-independent details needed for PID control. To create a controller for a specific device, all you need to do is inherit from PidControl and to provide input, output, and scaling.

PidControl Class Definition

Listing 1 shows the definition of class PidControl. Class PidControl contains several private data members which affect the operation of the PID loop. In addition to the three PID coefficients (KProportional, KIntegral, and KDifferential), class PidControl defines two other variables that hold constant values during operation: an integral limit and a derivative cycle count. The integral limit establishes positive and negative limits for the integral term, calculated within member function CalculateGain. CalculateGain does not allow the absolute value of this term to exceed the integral limit. The derivative cycle count tells the controller how many cycles to wait before calculating a new derivative term.

The class constructor takes arguments to initialize the three term coefficients as well as the integral limit and derivative cycle. The constructor also initializes the gain settings and errors to zero. The class provides SetXXX member functions to allow changing the controller constants on-the-fly. The SetSetpoint member function zeros out the ErrorSum and sets theSetpoint data member. Normally, you will override SetSetpoint to provide scaling of the signals. I illustrate this in the next section.

The CalculateGain member function (Listing 2) is the real meat of the class. CalculateGain takes the current position as its only argument and calculates the gain for each of the three PID terms. It then saves these gains and returns their sum. CalculateGain saves each of the gain terms separately so that they can be examined later when the programmer is tuning the controller.

CalculateGain is meant to produce a time-varying sequence of gain control values; thus, it must be called on a periodic cycle. The period of the cycle depends solely upon the responsiveness needed from the controller.

Deriving from PidControl

I present here a stripped-down version of the PipingSet class (Listing 3) to show how to derive from the PidControl class to form a device-specific PID control class. PipingSet performs pressure control using a servo valve. The servo valve works on a -10 to +10 volt scale, with -10 volts allowing gas to flow into the test port at the maximum rate (which increases pressure), and +10 volts allowing gas to flow out of the test port at the maximum rate (which decreases pressure). The basic functionality of this class could be applied in many different types of closed-loop controls, such as temperature or position controls. This class makes calls to Jean J. Labroses's uCOS real-time kernel for message passing and timing [1].

The PipingSet class constructor (Listing 3) calls the PidControl constructor, creates a mailbox for message passing, and then starts a control task running. The control task, when active, periodically reads a pressure sensor, calls the CalculateGain member function with the scaled pressure reading, and applies the gain to the servo valve. Various member functions pass messages to the control task to control whether or not it is in an active state.

The overridden function SetSetpoint (Listing 3) passes a message to the control task telling it that the set-point is about to change so that the control task will pause momentarily. SetSetpoint then scales the set-point to a percent of full-scale and calls the parent function. Before exiting, SetSetpoint sends another message to the control task telling it that it is okay to start controlling again.

Tuning the PID Controller

This particular PID algorithm requires manual tuning. It is possible to make a controller self-tuning (which will be the next iteration of the PidControl class). The easiest way to manually tune the controller is to start with the proportional term coefficient, leaving the other coefficients at a value of zero. A good starting point for the proportional coefficient, I've found, is a value which will cause the controller to max out when it has a 50% full-scale error. Depending on the system, the controller, when properly tuned, may not completely reach the set-point with only the proportional coefficient non-zero. Once you have the controller working decently with this term, go on to the integral term. Adjust the integral so that the response speed of the controller is acceptable, the set-point is always reached, overshoot is minimal, and there is no oscillation. You can use the integral limit to control overshoot. Once these terms are working well, start adjusting the derivative term and cycle to work out any overshoot.

Conclusion

Although the PidControl class is fairly simple, I have found it to be very effective. By using the inheritance capabilities of C++, I have been able to minimize the efforts needed when writing a new controller. Also, I can now add components such as dynamic self-tuning and oscillation and overshoot detection almost transparently to the rest of the application code.

Reference

[1] Jean J. Labrosse. uC/OS, The Real Time Kernel (R&D Books, 1992). ISBN 0-87930-444-8.

Steve Hartmann has been designing and writing software professionally since 1989. He spent seven years with Rockwell Space Operations working on the Shuttle Avionics Integration Laboratory. He now works for Ruska Instrument Corporation in Houston, Texas, designing and writing code for high-precision secondary pressure instrumentation. He also writes code for primary pressure instrumentation, PVT chemical analysis, and various other instrumentation systems. He may be reached at [email protected].


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.