PID loop tuning basics: check the loop, step-test it, then tune with a trend you can trust

Most loops that "need tuning" have a sticky valve, a noisy signal, or the wrong span behind them. Here's how to check those first, run a simple step test in Manual, turn it into starting gains, and set up the loop in Studio 5000 (ladder or structured text) with before-and-after trends in Ignition.

Picture a heat exchanger loop that's been "fine" for years. Then someone changes a pump, the outlet temperature starts swinging a few degrees either side of setpoint, and the operators put it in Manual and leave it there. The usual next step is somebody nudging the gains by feel until it looks a little better, without writing down what they started with.

This tutorial is the other way to do it: check that the loop can be tuned at all, measure how the process actually responds, start from gains you can explain, and keep a trend of before and after. It uses a heat exchanger outlet temperature (TT101) controlled by a hot water valve (TV101) as the example, but the same steps work for pump pressure, flow, and most temperature loops. Tank level is a little different, and we'll point out where.

What P, I and D do, in plain words

The controller looks at the error, the gap between setpoint (SP) and process value (PV), and decides where to put the output (CV), usually a valve or a drive speed.

  • P, proportional: "how far off am I right now?" The output moves in proportion to the error. More gain means a harder push. P alone usually settles a little away from setpoint (offset), and too much P makes the loop oscillate.
  • I, integral: "how long have I been off?" As long as there's any error, integral keeps adding to the output, which is what removes the offset. Too much integral (too short an integral time) makes slow, rolling overshoot.
  • D, derivative: "how fast is it changing?" Derivative pushes against fast changes, like a brake. It can help some slow temperature loops, but it amplifies noise. Most flow, pressure and level loops run fine with D at zero, and that's where we start.

Almost every loop you'll meet on a plant floor is a PI loop. Get P and I right and you've solved most of it.

Check the loop before you touch the gains

No gain setting fixes a mechanical or signal problem; it only hides it until conditions change. Start here. Do all of this with the loop in Manual, with operations' OK and someone watching the process.

  1. Valve or actuator stiction. Make small output steps, about 0.5–1% at a time, in one direction, then reverse. If the valve position (or the PV) doesn't move until a few steps pile up, and then jumps, the valve is sticking or has deadband. In Auto, that shows up as a PV that moves in squarish steps while the output saws up and down. That's a job for the valve packing or positioner, not for tuning.
  2. Sensor noise. Hold the output still and watch the PV for a few minutes. If it jitters by more than the swing you're trying to fix, look at grounding, shielding, and the transmitter's damping before you filter it in the PLC. Filter lightly if you must: every filter adds lag, and lag is what makes loops hard to tune.
  3. Scaling and units. Confirm the PV range in the PLC matches the transmitter range (what 4 mA and 20 mA really mean), and that 0–100% output really drives the valve closed-to-open. PIDE works in percent of span, so a wrong span means every gain you calculate is off by the same factor.
  4. Direction. Decide which way the output has to move when the PV is below setpoint. For a heating valve, PV low means open more: error is SP − PV, which is PIDE's default (ControlAction = 0). A fail-open valve, or a cooling loop, may need the other setting. Getting this wrong makes the loop run away the moment you go to Auto.
  5. Update rate. Run the loop in a periodic task that's fast compared to how quickly the process responds. For most temperature, level and pressure loops, a period in the 100–500 ms range is plenty; fast flow or pressure loops may want faster.
  6. A manual bump. Finally, make one small step in the output and watch. Does the PV move the way you expect, by roughly the amount you expect, without a long wait? If it does, you're ready for the step test.

A simple step test

The step test gives you three numbers that describe the process. They come from the process, not from anybody's opinion, which is why they make a good starting point.

  1. With the loop in Manual and the PV steady, step the output by enough to clearly see the PV move above the noise, typically 5–10%. Note the time and both values.
  2. Wait until the PV settles at its new value. Note the new PV.
  3. Step back, and if you can, repeat in both directions. If the results differ a lot, the process is nonlinear (valves often are), and you should tune for the range it normally runs in.
Reading a manual step test The output is stepped up in Manual. The process value does nothing for the dead time, then rises along a curve and levels off at a new value. The time constant is the time from when it starts moving until it has made 63 percent of its total change. Process gain is the change in process value divided by the change in output. PV CV θ τ 63% ΔPV ΔCV step K = ΔPV% ÷ ΔCV% · θ = dead time · τ = time constant
An illustration of a typical step response, not a recording from a real loop. Dead time is how long nothing happens; the time constant is how long it takes to make 63% of the move once it starts.

From the trend, read:

  • Process gain K: the change in PV divided by the change in output, with both in percent. Convert the PV change to percent of the PV span: a 12 °F rise on a 0–200 °F transmitter is 6%.
  • Dead time θ: the time from the step until the PV first starts to move.
  • Time constant τ: the time from when the PV starts moving until it has made 63% of its total change.

Then pick a closed-loop time constant λ (lambda): how quickly you want the loop to bring the PV back to setpoint. That's the one tuning choice you make; the rest is arithmetic. A cautious first choice is λ equal to τ, so the closed loop responds about as fast as the open loop did. Faster loops (smaller λ) are tighter but less forgiving when the process changes; don't go below θ.

For a process that settles at a new value (most temperature, pressure and flow loops), Skogestad's SIMC rules give PI settings:

  • Controller gain: Kc = τ ÷ (K × (λ + θ))
  • Integral time: Ti = the smaller of τ and 4 × (λ + θ)
  • Derivative: 0 to start

Source: S. Skogestad, "Simple analytic rules for model reduction and PID controller tuning," Journal of Process Control 13 (2003) 291–309. With Ti = τ these are the same as the classic lambda (IMC) tuning rules.

A worked example (made-up numbers, to show the arithmetic)

Transmitter range 0–200 °F. The output is stepped from 40% to 50% (ΔCV = 10%). The outlet temperature goes from 140 °F to 152 °F, a 12 °F change, which is 6% of span, so K = 6 ÷ 10 = 0.6. The PV starts moving 0.5 minutes after the step (θ = 0.5 min) and reaches 147.6 °F, 63% of the way, 2.0 minutes after that (τ = 2.0 min).

Choose λ = τ = 2.0 min. Then Kc = 2.0 ÷ (0.6 × 2.5) = 1.33, and Ti = the smaller of 2.0 and 10 = 2.0 min. In PIDE's dependent form, that's PGain = 1.33 and IGain = 2.0 (minutes per repeat).

Tank level is different. A level with a pump or valve on the outlet usually doesn't settle at a new value after a step; it keeps ramping. That's an integrating process. Measure the change in ramp rate instead (percent of span per minute, per percent of output) and call it K′. The SIMC rules for that case are Kc = 1 ÷ (K′ × (λ + θ)) and Ti = 4 × (λ + θ). Many level loops are surge tanks where a slow, smooth level is the goal, so a long λ is often the right choice.

Starting gains are a starting point. Put the loop in Auto, make a small setpoint change, and watch the trend. If it's sluggish, shorten λ a little and recalculate; if it overshoots or rolls, lengthen it. Change one thing at a time, and write each change down (the Ignition section below does that for you).

In the PLC (Studio 5000 / Logix)

The example uses the Enhanced PID instruction, PIDE, the better choice for new work on ControlLogix and CompactLogix. It has proper operator and program modes, output limits, PV fault handling, and anti-windup built in. Older code often uses the PID instruction instead; that's covered at the end of this section.

Set up the PIDE once

These are set in the PIDE tag (or its properties dialog), not every scan. The values are for the example loop; use your own ranges.

PIDE settings (tag TIC101, type PID_ENHANCED)
ParameterExampleWhy
PVEUMin / PVEUMax0.0 / 200.0The transmitter's range in °F. Gains act on percent of this span
CVEUMin / CVEUMax0.0 / 100.0What 0% and 100% output mean to the analog output (here, percent open)
SPLLimit / SPHLimityour limitsKeeps a typo on the HMI from asking for an impossible setpoint
CVLLimit / CVHLimityour limitsOutput limits in Auto. Set a minimum opening here if the process needs one
ControlAction00: error = SP − PV (heating valve). 1: error = PV − SP
DependIndepend11 = dependent form: PGain is Kc and IGain is Ti in minutes, so step-test results go straight in
PGain / IGain / DGainfrom the step testStart with DGain = 0
PVTracking1Setpoint follows PV while in Manual, so switching to Auto doesn't start with a big error
CVOverride0.0The output used while the loop is held in Override (here, valve closed). Pick the safe state for your process
TimingMode0Periodic: the PIDE uses the periodic task's period as its update time
Other tags (program scope)
NameTypePurpose
TT101_RawREALOutlet temperature in °F, from the analog input channel (already scaled by the module)
TT101_ChFaultBOOLThe input channel's fault bit
TT101_BadLo / TT101_BadHiREALReadings outside these are treated as a bad signal (a little outside the transmitter range)
FiltKREALFilter factor, 0 to 1. Roughly task period ÷ (filter time + task period). 1 = no filter
TT101_FiltREALFiltered temperature, the loop's PV
TT101_BadBOOLBad PV. Also worth an alarm
P101_Running, HX_PermitBOOLProduct pump running, and your other permissives (for example, hot water supply available)
TV101_OutREALValve command, 0–100%, to the analog output

The loop

Both versions below do the same thing in the same order, with the same tag names. Ladder is shown first. Switch to Structured Text if that's your plant standard.

Ladder

Seven rungs in a ladder routine in a periodic task. PIDE isn't available in ladder (Rockwell offers it in function block and structured text only), so rung 5 calls a one-line structured text routine that runs it. Everything around the loop stays in ladder, where your maintenance team can read it. Open "as text" under any rung to copy it into the Studio 5000 rung editor.

Drawn the way the Studio 5000 ladder editor lays it out: inputs on the left, outputs pushed to the right rail. The small numbers with arrows are where the editor shows live values; they read 0 here because nothing is online.

On a phone, swipe sideways to see the whole rung.

First scan: start the filter at the live reading, not at zero0S:FSMOVMoveSourceTT101_Raw0.0 ←DestTT101_Filt0.0 ←
Rung 0 as text
XIC(S:FS)MOV(TT101_Raw,TT101_Filt);
Light first-order filter on the temperature (FiltK 0 to 1, 1 = no filter)1CPTComputeDestTT101_Filt0.0 ←ExpressionTT101_Filt+FiltK*(TT101_Raw-TT101_Filt)
Rung 1 as text
CPT(TT101_Filt,TT101_Filt+FiltK*(TT101_Raw-TT101_Filt));
Bad PV: channel fault, or a reading outside the transmitter's range2TT101_ChFaultLESLess Than (A<B)Source ATT101_Raw0.0 ←Source BTT101_BadLo0.0 ←GRTGreater Than (A>B)Source ATT101_Raw0.0 ←Source BTT101_BadHi0.0 ←TT101_Bad
Rung 2 as text
[XIC(TT101_ChFault),LES(TT101_Raw,TT101_BadLo),GRT(TT101_Raw,TT101_BadHi)]OTE(TT101_Bad);
Process can't respond (pump stopped or permissive lost): hold the loop in Override at CVOverride3P101_RunningHX_PermitTIC101.ProgOverrideReq
Rung 3 as text
[XIO(P101_Running),XIO(HX_Permit)]OTE(TIC101.ProgOverrideReq);
Hand the PV and its health to the PIDE4MOVMoveSourceTT101_Filt0.0 ←DestTIC101.PV0.0 ←TT101_BadTIC101.PVFault
Rung 4 as text
[MOV(TT101_Filt,TIC101.PV),XIC(TT101_Bad)OTE(TIC101.PVFault)];
Run the PIDE. It isn't available in ladder, so it lives in a one-line ST routine5JSRJump To SubroutineRoutine NameTIC101_PIDE
Rung 5 as text
JSR(TIC101_PIDE,0);
Send the output to the valve (CVEU is scaled by CVEUMin / CVEUMax)6MOVMoveSourceTIC101.CVEU0.0 ←DestTV101_Out0.0 ←
Rung 6 as text
MOV(TIC101.CVEU,TV101_Out);
(End)
All 7 rungs as text
XIC(S:FS)MOV(TT101_Raw,TT101_Filt);
CPT(TT101_Filt,TT101_Filt+FiltK*(TT101_Raw-TT101_Filt));
[XIC(TT101_ChFault),LES(TT101_Raw,TT101_BadLo),GRT(TT101_Raw,TT101_BadHi)]OTE(TT101_Bad);
[XIO(P101_Running),XIO(HX_Permit)]OTE(TIC101.ProgOverrideReq);
[MOV(TT101_Filt,TIC101.PV),XIC(TT101_Bad)OTE(TIC101.PVFault)];
JSR(TIC101_PIDE,0);
MOV(TIC101.CVEU,TV101_Out);

The routine that rung 5 calls, TIC101_PIDE, is structured text with one line. A function block routine with a single PIDE block works the same way.

(* TIC101_PIDE: structured text routine called by the JSR in rung 5 *)
PIDE(TIC101);

Structured Text

The same seven steps in one structured text routine, in a periodic task. Here the PIDE call sits right in the routine.

(* TIC101_Main: structured text routine in a periodic task (for example 100 ms) *)

(* 1. First scan: start the filter at the live reading, not at zero *)
IF S:FS THEN
    TT101_Filt := TT101_Raw;
END_IF;

(* 2. Light first-order filter on the temperature (FiltK 0 to 1, 1 = no filter) *)
TT101_Filt := TT101_Filt + FiltK * (TT101_Raw - TT101_Filt);

(* 3. Bad PV: channel fault, or a reading outside the transmitter's range *)
TT101_Bad := TT101_ChFault OR (TT101_Raw < TT101_BadLo) OR (TT101_Raw > TT101_BadHi);

(* 4. Process can't respond: hold the loop in Override at CVOverride *)
TIC101.ProgOverrideReq := NOT P101_Running OR NOT HX_Permit;

(* 5. Hand the PV and its health to the PIDE *)
TIC101.PV := TT101_Filt;
TIC101.PVFault := TT101_Bad;

(* 6. Run the PIDE *)
PIDE(TIC101);

(* 7. Send the output to the valve *)
TV101_Out := TIC101.CVEU;

One routine, in a periodic task. Don't put a PID loop in the continuous task: its scan time wanders, and the loop's math assumes a steady update.

What the pieces are doing:

  • Bad PV goes to Manual. When PVFault is on, PIDE stops calculating and drops to Manual, holding the output instead of chasing a broken signal. Alarm on TT101_Bad so someone knows why.
  • Anti-windup. PIDE uses the velocity form: it calculates a change in output each update and clamps the result at CVLLimit / CVHLimit, so integral can't pile up past the limits. The windup you still have to handle is the loop staying in Auto while the process can't respond (pump off, a hand valve closed). It ramps the output to the limit, and when the process comes back the valve slams. Rung 3 holds the loop in Override at a known output instead.
  • Bumpless manual/auto. While not in operator Manual, PIDE keeps CVOper equal to the output, so going to Manual doesn't move the valve. Going back to Auto starts from the current output, and with PVTracking on, the setpoint starts where the PV is. The operator then moves the setpoint deliberately.
  • Output limits. CVLLimit and CVHLimit apply in Auto. In Manual they only apply if CVManLimiting is on; decide with operations whether an operator should be able to go outside them.

Starting gains from the step test

Tags (program scope)
NameTypePurpose
StepCVREALOutput step, in % (for example 10.0)
StepPVREALPV change, in engineering units (for example 12.0 °F)
PVSpanREALPVEUMax − PVEUMin (200.0 here)
DeadMin, TauMinREALDead time θ and time constant τ, in minutes
LambdaMinREALYour chosen λ, in minutes
Proc_Gain, Kc_Start, Ti_StartREALResults: K, Kc, and Ti (minutes)
LamDead, Ti_LimitREALWorking values: λ + θ, and the 4 × (λ + θ) limit
ApplyGainsReqBOOLApply request from an HMI button

You can do this math on a phone calculator. Putting it in the PLC means the step-test numbers, the starting gains, and the trend can sit on one Ignition screen, and nobody types a gain into the wrong box.

Ladder

Five rungs. Rungs 0 to 2 only do math; rung 3 copies the result into the loop only when someone presses the button that sets ApplyGainsReq, and rung 4 always clears that request.

Drawn the way the Studio 5000 ladder editor lays it out: inputs on the left, outputs pushed to the right rail. The small numbers with arrows are where the editor shows live values; they read 0 here because nothing is online.

On a phone, swipe sideways to see the whole rung.

Process gain K, in % of PV span per % of output (ControlAction sets the direction)0NEQNot EqualSource AStepCV0.0 ←Source B0GRTGreater Than (A>B)Source APVSpan0.0 ←Source B0CPTComputeDestProc_Gain0.0 ←ExpressionABS(StepPV*100.0/PVSpan/StepCV)
Rung 0 as text
NEQ(StepCV,0)GRT(PVSpan,0)CPT(Proc_Gain,ABS(StepPV*100.0/PVSpan/StepCV));
SIMC starting gains: Kc = Tau / (K * (Lambda + Dead)), and the 4 * (Lambda + Dead) limit1GRTGreater Than (A>B)Source AProc_Gain0.0 ←Source B0GRTGreater Than (A>B)Source ALambdaMin0.0 ←Source B0ADDAddSource ALambdaMin0.0 ←Source BDeadMin0.0 ←DestLamDead0.0 ←CPTComputeDestKc_Start0.0 ←ExpressionTauMin/(Proc_Gain*LamDead)CPTComputeDestTi_Limit0.0 ←Expression4.0*LamDead
Rung 1 as text
GRT(Proc_Gain,0)GRT(LambdaMin,0)[ADD(LambdaMin,DeadMin,LamDead),CPT(Kc_Start,TauMin/(Proc_Gain*LamDead)),CPT(Ti_Limit,4.0*LamDead)];
Ti = the smaller of Tau and 4 * (Lambda + Dead)2MOVMoveSourceTauMin0.0 ←DestTi_Start0.0 ←GRTGreater Than (A>B)Source ATi_Start0.0 ←Source BTi_Limit0.0 ←MOVMoveSourceTi_Limit0.0 ←DestTi_Start0.0 ←
Rung 2 as text
[MOV(TauMin,Ti_Start),GRT(Ti_Start,Ti_Limit)MOV(Ti_Limit,Ti_Start)];
Apply on request: dependent form only (PGain = Kc, IGain = Ti in minutes), and only once Ti is set3ApplyGainsReqTIC101.DependIndependGRTGreater Than (A>B)Source ATi_Start0.0 ←Source B0MOVMoveSourceKc_Start0.0 ←DestTIC101.PGain0.0 ←MOVMoveSourceTi_Start0.0 ←DestTIC101.IGain0.0 ←MOVMoveSource0.0DestTIC101.DGain0.0 ←
Rung 3 as text
XIC(ApplyGainsReq)XIC(TIC101.DependIndepend)GRT(Ti_Start,0)[MOV(Kc_Start,TIC101.PGain),MOV(Ti_Start,TIC101.IGain),MOV(0.0,TIC101.DGain)];
Always clear the request, so a refused apply can't fire later4ApplyGainsReqUApplyGainsReq
Rung 4 as text
XIC(ApplyGainsReq)OTU(ApplyGainsReq);
(End)
All 5 rungs as text
NEQ(StepCV,0)GRT(PVSpan,0)CPT(Proc_Gain,ABS(StepPV*100.0/PVSpan/StepCV));
GRT(Proc_Gain,0)GRT(LambdaMin,0)[ADD(LambdaMin,DeadMin,LamDead),CPT(Kc_Start,TauMin/(Proc_Gain*LamDead)),CPT(Ti_Limit,4.0*LamDead)];
[MOV(TauMin,Ti_Start),GRT(Ti_Start,Ti_Limit)MOV(Ti_Limit,Ti_Start)];
XIC(ApplyGainsReq)XIC(TIC101.DependIndepend)GRT(Ti_Start,0)[MOV(Kc_Start,TIC101.PGain),MOV(Ti_Start,TIC101.IGain),MOV(0.0,TIC101.DGain)];
XIC(ApplyGainsReq)OTU(ApplyGainsReq);

Structured Text

The same math and the same apply-on-request logic:

(* Starting PI gains from a manual step test. Run it anywhere; it only does math until asked. *)

(* 1. Process gain K, in % of PV span per % of output *)
IF (StepCV <> 0) AND (PVSpan > 0) THEN
    Proc_Gain := ABS(StepPV * 100.0 / PVSpan / StepCV);
END_IF;

(* 2. SIMC starting gains: Kc = Tau / (K * (Lambda + Dead)), and the 4 * (Lambda + Dead) limit *)
IF (Proc_Gain > 0) AND (LambdaMin > 0) THEN
    LamDead := LambdaMin + DeadMin;
    Kc_Start := TauMin / (Proc_Gain * LamDead);
    Ti_Limit := 4.0 * LamDead;
END_IF;

(* 3. Ti = the smaller of Tau and 4 * (Lambda + Dead) *)
Ti_Start := TauMin;
IF Ti_Start > Ti_Limit THEN
    Ti_Start := Ti_Limit;
END_IF;

(* 4. Apply on request: dependent form only (PGain = Kc, IGain = Ti in minutes), and only once Ti is set *)
IF ApplyGainsReq THEN
    IF TIC101.DependIndepend AND (Ti_Start > 0) THEN
        TIC101.PGain := Kc_Start;
        TIC101.IGain := Ti_Start;
        TIC101.DGain := 0.0;
    END_IF;
    ApplyGainsReq := 0;    (* always clear it, so a refused apply can't fire later *)
END_IF;

Applying only in the dependent form matters: in the independent form, IGain is in repeats per minute (Kc ÷ Ti), and copying Ti straight in would be wrong. If your plant standard is independent gains, set PGain = Kc and IGain = Kc ÷ Ti instead. PIDE's velocity form takes gain changes without a bump in the output, but have someone watching the trend when you apply them anyway.

If your code uses the older PID instruction

PID runs in ladder and structured text, and plenty of working plants still use it. It needs a periodic task, with .UPD (loop update time, in seconds) set to match the task period. Manual is software manual (.SWM) with the output set by .SO; output limits are .MAXO / .MINO, in percent; PV scaling is .MINS / .MAXS.

Both versions run the same instruction with the same operands. Tieback, master loop and inhold aren't used here, so they're 0.

Ladder

One rung in a periodic task. The box shows the seven operands you enter; the last three rows are what the editor displays while online.

Drawn the way the Studio 5000 ladder editor lays it out: inputs on the left, outputs pushed to the right rail. The small numbers with arrows are where the editor shows live values; they read 0 here because nothing is online.

On a phone, swipe sideways to see the whole rung.

Older PID instruction, in a periodic task: .UPD must equal the task period in seconds0PIDProportional Integral DerivativePIDTIC101_PIDProcess VariableTT101_FiltTieback0Control VariableTV101_OutPID Master Loop0Inhold Bit0Inhold Value0Setpoint0.0 ←Process Variable0.0 ←Output %0.0 ←
Rung 0 as text
PID(TIC101_PID,TT101_Filt,0,TV101_Out,0,0,0);
(End)
The rung as text
PID(TIC101_PID,TT101_Filt,0,TV101_Out,0,0,0);

Structured Text

The same instruction called from structured text:

(* Older PID instruction, in a periodic task. Unused operands are 0. *)
(* TIC101_PID.UPD must equal the task period in seconds, for example 0.1 for 100 ms *)
PID(TIC101_PID, TT101_Filt, 0, TV101_Out, 0, 0, 0);

Watch the units if you move gains between PID and PIDE. In PID's independent form (.PE = 0), .KI is per second and .KD is in seconds. In PIDE's independent form, IGain is per minute and DGain is in minutes. In the dependent forms both use minutes. With the worked example above (Kc 1.33, Ti 2.0 min), PID independent would be .KP = 1.33 and .KI = 1.33 ÷ 120 s ≈ 0.011, while PIDE independent would be PGain = 1.33 and IGain = 0.67. Copy the numbers straight across and the integral is off by a factor of 60.

PIDE also has a built-in autotuner, but it needs a separate activation and only works in function block routines. A manual step test teaches you more about the process anyway.

In Ignition: trend it, and log every change

Tuning without a trend is guessing. Bring the PIDE members in as OPC tags in a TIC101 folder: PV, SP, CV, Auto, Manual, PGain, IGain, DGain, plus SPOper, CVOper, OperAutoReq and OperManualReq for the faceplate. The Oper...Req bits are momentary: the PIDE clears them itself after each execution, so a button only has to write a 1.

  • Historize PV, SP and CV. Turn on history for all three, plus Auto and the three gains. For tuning work, use a periodic sample mode around 1 second, or "on change" with a small historical deadband. A large deadband or a slow sample rate flattens exactly the wiggles you're trying to see.
  • One chart, two axes. In a Perspective Power Chart (or a Vision Easy Chart), put PV and SP on one axis in °F and CV on a second axis, 0–100%. Most tuning problems are obvious once you can see the output and the PV together: a sawtooth output with a squarish PV is stiction; slow rolling waves are usually too much integral; fast chatter is noise or derivative.
  • Before and after. Save the chart's time range from before you change anything, and compare the same kind of upset (a setpoint change, or a normal load change) after. Same window length, same axes, so the comparison is fair.
  • Log every gain change. A tag event script on each gain tag writes a row whenever the value changes, from any source: the HMI, Studio 5000, or the apply button.
# Tag Editor > the PGain tag (and IGain, DGain) > Tag Events > Value Changed
def valueChanged(tag, tagPath, previousValue, currentValue, initialChange, missedEvents):
    if initialChange or previousValue is None:
        return
    if previousValue.value == currentValue.value:
        return
    system.db.runPrepUpdate(
        "INSERT INTO tuning_log (t_stamp, tag_path, old_value, new_value) VALUES (?, ?, ?, ?)",
        [system.date.now(), str(tagPath), previousValue.value, currentValue.value],
        "PlantDB")
-- One row per gain change. Adjust types for your database.
CREATE TABLE tuning_log (
    t_stamp    DATETIME,
    tag_path   VARCHAR(255),
    old_value  FLOAT,
    new_value  FLOAT
);

Change PlantDB to your database connection's name. Tag event scripts run on the Gateway, so they log changes even when no client is open. Put the tuning_log rows in a table next to the trend and the history of "who tuned this and what did they start with" stops being tribal knowledge. If you also want to know who made a change from a screen, turn on auditing for the project.

This is also how we approach tuning on our own jobs, with more data: our in-house, read-only PLC recorder captures setpoint, process value and output over real production runs, and we use that data to help tune loops instead of tweaking by feel. More on that on how we troubleshoot.

Gotchas

Tuning around a sticky valve

A valve with stiction makes the loop cycle no matter what the gains are. Detuning it just makes the cycle slower and bigger. Check the valve in Manual first, and fix it mechanically.

PID and PIDE units differ

PID independent .KI is per second; PIDE independent IGain is per minute. Dependent forms use minutes in both. Converting a loop from PID to PIDE without converting the gains is one of the most common ways to make a good loop bad.

Windup with nothing to control

A loop left in Auto with the pump off ramps its output to the limit, then slams the valve when the pump starts. Hold it in Override or Manual whenever the process can't respond, and use PVFault so a broken transmitter doesn't drive the valve.

Over-filtering

A heavy filter makes the trend look smooth and the loop act worse, because it adds lag. Keep any filter short compared to the dead time, and fix noise at the source first. Derivative on a noisy signal chatters the valve; leave DGain at 0 unless you've got a clean, slow loop.

Wrong span, wrong gains

PIDE works in percent of PVEUMin–PVEUMax. If someone re-ranges the transmitter and not the PLC (or the other way around), the loop's effective gain changes overnight. Re-ranging is a tuning change; log it like one.

Program vs. operator control

PIDE takes mode and setpoint requests from either the program or the operator. If logic holds ProgProgReq on, the HMI's OperAutoReq and SPOper writes are ignored, and operators think the screen is broken. Decide who owns the loop and stick to it.

The process changes with load

Valves and heat exchangers often respond differently at 20% open than at 70%. Step-test in the range the loop normally runs, and recheck after a pump, valve or recipe change. Tuning that was right last year may not be right now.

Continuous task, wandering update

A loop in the continuous task gets a different update time every scan. Use a periodic task, and for the older PID make sure .UPD matches the period. A periodic task that overlaps itself is a problem too; check the task's monitor for overlaps.

The short version

Check the valve, the signal, the span and the direction in Manual first. Step the output, read K, θ and τ off the trend, pick a cautious λ, and start from Kc = τ ÷ (K × (λ + θ)) and Ti = τ, with no derivative. Hold the loop in Override when the process can't respond, trend PV, SP and CV together in Ignition, and log every gain change so the next person knows where you started.

← Previous: First-out fault capture · All tutorials

Got a loop that lives in Manual?

We can check the valve, the signal and the scaling before anyone touches the gains, then tune it with a before-and-after trend in Ignition. It's also a good thing to bring up during a free Controls Health Check: onsite in the Central Valley, or as a free remote review for plants in Reno, Northern Nevada and Kansas.