If you have huge transient error spikes, then you need a derivative part (i.e the D in PID) to handle these.
A simple PID system is not hard to write in code, it's the tuning that's the difficult part - you need to know roughly. The integral part will handle the DC offset, the proportional part the gain, and the derivative the reaction to change.
Here's some code I wrote for a temperature controlled etching tank I made with a PIC16F and a DS18B20 temp sensor ages ago (it used some nichrome wire driven via PWM wrapped round a couple of ceramic tiles for the heating element, so it had a large thermal capacity)
The results, once tuned, were an overshoot a of less than 2 degrees on power up, and a variation of less than a degree or so thereafter. It's very old code for a one off project thrown together quickly, so it can probably be improved a fair bit.
I found this document "PID without a PHD" quite helpful. Also, Matlab, Scilab or Octave can be used to simulate and tune PID systems.
/** Control variables **/
int DEADBAND = 0; // Range where no change will be made
// PID gain values
double Kp = 10;
double Ki = 0.02;
double Kd = 5;
// Accumulator variable for Integral calculation
double I_acc = 0;
// Returns out the proportional term
double get_prop(int error)
{
double p;
p = (Kp * error);
return p;
}
// Returns the integral term
double get_int(int error)
{
double i;
I_acc += (error);
i = (double)(Ki * I_acc);
if(i > 1000) i = 1000;
if(i < 0) i = 0;
return i;
}
// Returns the derivative term
double get_deriv(int error)
{
double d;
static int prev_error;
d = (Kd * (error - prev_error));
prev_error = error;
return d;
}
double get_pid(int error)
{
double result;
double p, i, d;
p = get_prop(error);
i = get_int(error);
d = get_deriv(error);
result = p + i + d;
return result;
}
I know exactly where you're coming from. The mistake you're making is assuming that the integral term drops to zero in steady state. This is not the case, and, indeed, is highly dependent on implementation details.
First off, understand the integral term in a mathematical PID is the integral from the start of time (or, well, the system) and not "error over last few cycles". Your implementation of PID or PI should not cause the older contributors of the integral term to drop in relative weightage of the I term. Let me explain. When writing the I term's code, the first instinct is to assume that the term will diverge, crossing the variable size and overflowing, and people attempt to fix this using moving averages, degrading the weightage of the older values, and all sorts of strange gimmicks. This should not happen in a properly implement PI or PID system. Instead, you should simply calculate I as I = I + Ki*Error.
The baseline level required to maintain the system, which you mention in your question, must be provided by the I term. Since you do not know how much this is apriori, you must allow the controller to discover this value for itself. That, in fact, is the job of the I term. The Ki value should be small enough for the controller to converge before it overflows. Some thought about how this works on paper will help. Try to visualize the process, not specific boundary conditions. One thing that you should keep in mind is that the I term is not constructed from absolute value of error. It includes both positive and negative values of error.
Further, imagine the condition where the controller is just reaching the steady state. You will realize that I is not necessarily zero at this point. Indeed, I is actually the baseline control force you mention in the question. If the state actually remains stable, and if error from here on in is continuously zero (or zero averaged over time), the value of I will remain as it is.
Now, when it comes to real implementations, the problem you will face is that even with a small I, by the time your system reaches the set point, I may well have saturated. The system will then have to err in the opposite direction for a long time to rid itself of the I term it accumulated while it was still reaching the set point. In fact, I've noticed that PI and PID work best for a single set point, and degrade when you have to keep changing that point by a large value. A big contributor to this is the fact that I has high inertia. Tuning the value of I is possible to keep rhe controller functional, but when the system itself responds to stimulus slowly (say you're heating a block of metal), tuning is often difficult. Instead, what can help is to activate I only when. The system is within a certain threshold of the set point. When you change the set point by something greater than this threshold, clear I and disable it (use only P/PD control) until you reach close to the new setpoint. By doing this, you add another tunable parameter (the threshold), but it makes setting both Ki and the threshold easier than setting Ki by itself to be optimal for both situations.
Best Answer
Imagine a real system such as an oven. You want to maintain 350°F so that your cookies will be properly baked.
The input to your controller is the temperature sensor.
The output to the oven is the percentage the heaters should remain on vs. off.
In order to maintain a constant temperature, the output must be non-zero, and ideally (without you opening the door or line voltage changes etc.) will be constant.
Overshoot happens because of the closed-loop system dynamics (underdamped), and it can also happen because of integral windup, which is a nonlinear effect.