(This is the last modification in a larger series on writing a solid PID algorithm)
The Problem
The processes the PID will be connected to fall into two groups: direct acting and reverse acting. All the examples I’ve shown so far have been direct acting. That is, an increase in the output causes an increase in the input. For reverse acting processes the opposite is true. In a refrigerator for example, an increase in cooling causes the temperature to go down. To make the beginner PID work with a reverse process, the signs of kp, ki, and kd all must be negative.
This isn’t a problem per se, but the user must choose the correct sign, and make sure that all the parameters have the same sign.
The Solution
To make the process a little simpler, I require that kp, ki, and kd all be >=0. If the user is connected to a reverse process, they specify that separately using the SetControllerDirection function. this ensures that the parameters all have the same sign, and hopefully makes things more intuitive.
The Code
/*working variables*/
unsigned long lastTime;
double Input, Output, Setpoint;
double ITerm, lastInput;
double kp, ki, kd;
int SampleTime = 1000; //1 sec
double outMin, outMax;
bool inAuto = false;
#define MANUAL 0
#define AUTOMATIC 1
#define DIRECT 0
#define REVERSE 1
int controllerDirection = DIRECT;
void Compute()
{
if(!inAuto) return;
unsigned long now = millis();
int timeChange = (now - lastTime);
if(timeChange>=SampleTime)
{
/*Compute all the working error variables*/
double error = Setpoint - Input;
ITerm+= (ki * error);
if(ITerm > outMax) ITerm= outMax;
else if(ITerm < outMin) ITerm= outMin;
double dInput = (Input - lastInput);
/*Compute PID Output*/
Output = kp * error + ITerm- kd * dInput;
if(Output > outMax) Output = outMax;
else if(Output < outMin) Output = outMin;
/*Remember some variables for next time*/
lastInput = Input;
lastTime = now;
}
}
void SetTunings(double Kp, double Ki, double Kd)
{
if (Kp<0 || Ki<0|| Kd<0) return;
double SampleTimeInSec = ((double)SampleTime)/1000;
kp = Kp;
ki = Ki * SampleTimeInSec;
kd = Kd / SampleTimeInSec;
if(controllerDirection ==REVERSE)
{
kp = (0 - kp);
ki = (0 - ki);
kd = (0 - kd);
}
}
void SetSampleTime(int NewSampleTime)
{
if (NewSampleTime > 0)
{
double ratio = (double)NewSampleTime
/ (double)SampleTime;
ki *= ratio;
kd /= ratio;
SampleTime = (unsigned long)NewSampleTime;
}
}
void SetOutputLimits(double Min, double Max)
{
if(Min > Max) return;
outMin = Min;
outMax = Max;
if(Output > outMax) Output = outMax;
else if(Output < outMin) Output = outMin;
if(ITerm > outMax) ITerm= outMax;
else if(ITerm < outMin) ITerm= outMin;
}
void SetMode(int Mode)
{
bool newAuto = (Mode == AUTOMATIC);
if(newAuto == !inAuto)
{ /*we just went from manual to auto*/
Initialize();
}
inAuto = newAuto;
}
void Initialize()
{
lastInput = Input;
ITerm = Output;
if(ITerm > outMax) ITerm= outMax;
else if(ITerm < outMin) ITerm= outMin;
}
void SetControllerDirection(int Direction)
{
controllerDirection = Direction;
}
PID COMPLETE
And that about wraps it up. We’ve turned “The Beginner’s PID” into the most robust controller I know how to make at this time. For those readers that were looking for a detailed explanation of the PID Library, I hope you got what you came for. For those of you writing your own PID, I hope you were able to glean a few ideas that save you some cycles down the road.
Two Final Notes:
- If something in this series looks wrong please let me know. I may have missed something, or might just need to be clearer in my explanation. Either way I’d like to know.
- This is just a basic PID. There are many other issues that I intentionally left out in the name of simplicity. Off the top of my head: feed forward, reset tiebacks, integer math, different pid forms, using velocity instead of position. If there’s interest in having me explore these topics please let me know.
Tags: Arduino, Beginner's PID, PID


Nice job ! Keep the good work up ! Have you thought of doing different controll approaches like IMC or predictive control ?
I have written an IMC controller (for fun) in the past, but I would consider that a “beginner” IMC; not something I would want to give people as an example.
This is really incredible. I wish I would have found it a long time ago. I’m grateful to have finally stumbled upon some code and an explanation I can wrap my head around! Great job!
Wouldn’t it be MUCH simpler to invert output in one place instead of fiddling with constants all over the program?
And on related note, every time SetSampleTime gets called you introduce an error (however small) to constant values. Why not keep values set by user and calculate “running” constants as you do in SetTunings.
Also, it would be nice to see time overflow problem fixed on these pages. For those who do not read responses
@wwwoholic thanks for the comments.
I’m not sure what you mean by inverting the output. Make it negative? This would work if I was using the velocity form, but just making the output -25 instead of 25 would not produce the desired effect. At the very least this would require some modifications to the initialization code. Another common way of doing the direction thing is to define error as PV-SP instead of SP-PV. Maybe that’s what you meant? Also, I’m not sure what you mean by “all over the program.” I make them negative in one place. other than that all the code is the same.
Your SampleTime point is well taken. I used placeholders for the tuning parameters because I foresaw a situation (adaptive tuning) where that function would be called repeatedly. With the SampleTime, I just couldn’t see a time were that would be called over and over, so I decided the error risk was minor, and not worth the extra (albeit small) overhead addition.
It’s on my list to go through the series and update both the overflow and reset windup code. With a day job and the ospid I haven’t been able to find the time.
It has been long time since I’ve learned how PID works, so I am more than fuzzy on velocity or ideal or any other forms. However discrete form that you implementing quite easy to comprehend. Mathematically speaking negating all k constants is exactly the same as negating output
Output = – (kp * error + ki * errSum – kd * dInput)
Unless, of course, you meant something else when you wrote kp = (0 – kp);
However since you did not change the output limiting (lines 33, 34) and it will not work if you negate output then I guess you assumed Input, Output and Setpoint always be positive or zero.
In this case changing direction is usually done by:
Output = outMax – Output
and not by changing sign of constants.
@wwwoholic. I get what you’re saying now. That would certainly be mathematically equivalent. I guess my style was to leave the Compute function as uncluttered as possible, and sort of compartmentalize the direction code.
You could cerainly throw an if structure into the compute function to decide if you need to multiply by -1. Whatever you’re comfortable with.
The ultimate goal of this series was to get people familiar with the intenals of the library to the point where they could say “well I’m going to do it THIS way.” It looks like in your case I succeeded!
This is a terrific explanation. Thanks a lot for putting so much time and care into this! I learned a lot.
Brett,
I’ve been reading your (Great job by the way!!!!) blog and I’m trying to understand how the PID with a relay works. I want to fire a relay with a digital output at a set interval until the analog input reaches the setpoint at which time it goes into standby mode waiting for the analog signal to drop at which times this process repeats itself.
Do you have any ideas if your PID would work for this application? And if so do you have a moment to write a short code example?
Thank you
Dave
dpina@jgmaclellan.com
Brett,
Great explanation and code. I want to use PID for pH control in a hydroponics setup. However, there are two reagents involved, both of which have their own solenoid to dispense. I’m not sure how to relate the output to these two devices. As well, they will liekly hae a different effect on the process variable for the same on time interval. Very difficult to normalize this. Your assistance would be very much appreciated in terms of understanding how to apply the code.
@peter this a very involved (and highly interesting) topic. I recommend re-posting on the diy-pid-control google group where it can be discussed as its own thread, rather than interspersed in these comments
Awesomeness redefined !!! the best tutorial that I have seen for a long time .. Lots of respect..
@peter, the problem with ph control is that it is exponential ,you can’t use a solenoid valve for that. You must get real control valves with the right curve of the plug and seat,In industries we use a split range for the 2 control valves, the valve for acid uses 4..12 mA (0..50%) and the valve for alkaline is using 12..20mA(50..100%) this means by pH7 both valves are closed.
google for micropak control valves
Hopefully this gives you some ideas
@jazz I’m not sure I agree with you on that. 4-20mA control valves are definitely more precise, but you can approximate an analog behavior by controlling how long the solenoid pulses over a given widow (similar to PWM, but slower)
we’ve been having a great conversation on the diy-pid-control group. I’d love to hear your thoughts.
Just to say, yes the advanced topics are of interest!