StandardPIDController

Proportional, Integral, Derivative (PID) controller using the a common calculation.

Info

PID is the quintessential industrial control algorithm. It is a mathematical tool for efficiently affecting change in a system to get it to a particular target state, and keeping it there, harmoniously.

It’s the algorithm that keeps drones balanced in the air, your car at the right speed when cruise control is on, and ships on the right heading with variable winds. It’s also the algorithm that can efficiently heat a cup of coffee to the perfect temperature and keep it there.

StandardPIDController implements the most common version of the PID algorithm, which tends to be more generally applicable than the ideal calculation. The standard version of PID is discussed on the Wikipedia PID article.

The following block diagram describes the calculation using the standard algorithm:

In the standard form, step 3 produces a single error value that uses both the integral to account for past errors and the derivative which can predict future error based on rate of change. This single error correction is then scaled (multiplied) by the proportional error constant.

For a detailed discussion and explanation of the PID algorithm, see the Wilderness Labs PID Guide.

API

Properties

float ProportionalComponent { get; set; }

The gain value to use when calculating the proportional corrective action. A good value to start with when tuning is 1.0.

float IntegralComponent { get; set; }

In the standard PID algorithm, the derivative gain is specified in repeats per minute. A good value to start with when tuning is 0.1.

float DerivativeComponent { get; set; }

In the standard PID algorithm, the derivative gain is specified in repeats per minute. As with the ideal form, the derivative should only be used when the ActualInput value has very little noise, as the derivative uses the slope of change to calculate the corrective action. A good value to start with when tuning is 0.001.

float OutputMax { get; set; }

The maximum allowable control output value. The control output is clamped to this value after being calculated via the CalculateControlOutput method.

float OutputMin { get; set; }

The minimum allowable control output value. The control output is clamped to this value after being calculated via the CalculateControlOutput method.

bool OutputTuningInformation { get; set; }

Whether or not to print the calculation information to the output console in an comma-delimited form. Useful for pasting into a spreadsheet to graph the system control performance when tuning the PID controller corrective action gains.

Output format is:

[output descriptor],[target],[input],[proportional action],[integral action],[derivative action],[calculated control output]

float ActualInput { get; set; }

Represents the Process Variable (PV), or the actual signal reading of the system in its current state. For example, when heating a cup of coffee to 75º, if the temp sensor says the coffee is currently at 40º, the 40º is the actual input value.

float TargetInput { get; set; }

Represents the set point (SP), or the reference target signal to achieve. For example, when heating a cup of coffee to 75º, 75º is the target input value.

Methods

void ResetIntegrator()

Resets the integrator error history.

float CalculateControlOutput()

Calculates the control output based on the difference (error) between the ActualInput and TargetInput, using the supplied ProportionalComponent, IntegralComponent, and DerivativeComponent.

Example

The Food Dehydrator 3000 sample app illustrates basic standard PID controller usage. Specifically, the DehydratorController class utilizes Netduino.Foundation’s StandardPIDController to bring the dehydrator up to a specified temperature and uses an Analog Temperature Sensor to provide feedback.

Additionally, the dehydrator uses a PWM Signal to modulate the power of the heater element.

The salient usage is described below.

DehydratorController Constructor:

In the constructor, the PID controller is instantiated and configured. In the case of the dehydrator, only the ProportionalComponent and IntegralComponent are used to calculate the control output (DerivativeComponent is set to 0, effectively removing it from the control). This is because the derivative calculation is based on the rate of change, and it requires a very smooth sensor reading, but the temp sensor reading is fairly noisy. However, it doesn’t matter, as this still provides a very efficient control.

Additionally, the control output is clamped via the OutputMin and OutputMax properties between 0.0 and 1.0, which translates to 0% to 100% duty cycle of the PWM that controls the heater element. If the controller were used in an system that kept a boat on a heading, or a car between lines, then the clamp might be between something like -0.5 and 0.5, in which a negative value meant a left heading, and a positive value meant a right heading.

public DehydratorController(AnalogTemperature tempSensor, SoftPwm heater, Relay fan, SerialLCD display)
{
    _tempSensor = tempSensor;
    _heaterRelayPwm = heater;
    _fanRelay = fan;
    _display = display;

    _pidController = new StandardPidController();
    _pidController.ProportionalComponent = .5f; // proportional
    _pidController.IntegralComponent = .55f; // integral time minutes
    _pidController.DerivativeComponent = 0f; // derivative time in minutes
    _pidController.OutputMin = 0.0f; // 0% power minimum
    _pidController.OutputMax = 1.0f; // 100% power max
    _pidController.OutputTuningInformation = true;

}

TurnOn Method

When the dehydrator is turned on, the TurnOn method is called, which sets the temperature and running time, and then calls the StartRegulatingTemperatureThread method which is responsible for the bulk of the control work.

public void TurnOn(int temp, TimeSpan runningTime)
{
    // set our state vars
    TargetTemperature = (float)temp;
    this._runningTimeLeft = runningTime;
    this._running = true;

    // keeping fan off, to get temp to rise.
    this._fanRelay.IsOn = true;
            
    // TEMP - to be replaced with PID stuff
    this._heaterRelayPwm.Frequency = 1.0f / 5.0f; // 5 seconds to start (later we can slow down)
    // on start, if we're under temp, turn on the heat to start.
    float duty = (_tempSensor.Temperature < TargetTemperature) ? 1.0f : 0.0f;
    this._heaterRelayPwm.DutyCycle = duty;
    this._heaterRelayPwm.Start();

    // start our temp regulation thread. might want to change this to notify.
    StartRegulatingTemperatureThread();
}

StartRegulatingTemperatureThread Method

This method starts a new thread which is actually responsible for reading the temperature input from the sensor and calling the PID controller’s CalculateControlOutput method to determine the amount of power to provide to the heating element in order to bring the dehydrator up to the target temperature:

protected void StartRegulatingTemperatureThread()
{
    _tempControlThread = new Thread(() => {

        // reset our integral history
        _pidController.ResetIntegrator();

        while (this._running) {

            // set our input and target on the PID calculator
            _pidController.ActualInput = _tempSensor.Temperature;
            _pidController.TargetInput = this.TargetTemperature;

            // get the appropriate power level 
            var powerLevel = _pidController.CalculateControlOutput();

            // set our PWM appropriately
            this._heaterRelayPwm.DutyCycle = powerLevel;

            // sleep for a while. 
            Thread.Sleep(_powerUpdateInterval);
        }
    });
    _tempControlThread.Start();
}