## MicroHTR (Part 3)

Part 1, 2, 3, 4, 5, 6, 7, 8, 9

Now comes the magic of PID. In the next exemple, a PI controller type is used. The I term relates to the history of errors. In this way, the constant error observed in the P only controller type produces a cumulative error which is compensated by the controller, up to the setpoint.

In its most simple expression, the PI code looks like

double PID(double processVariable) /* Compute output signal */ { /* Timing stuff */ uint32_t now = millis(); double timeInterval = (now - _lastTime) / 1000.0; /* Compute error */ double error = (_computedSetpoint - processVariable); /* Compute error integral */ _integralError += ((error + _lastError ) * timeInterval) / 2.0; /* Compute output */ double outputValue = (_Kp * error) + (_Ki * _integralError); return(outputValue); };

And here is a plot of the temperature profile using this PI controller

What strikes you is the large overshoot of temperature while the controller tries to reach the setpoint and the time needed for the controller to switch the power off. This is a well known phenomenon, nammed at windup. There are multiple ways of addressing this critical issue. The simplest consists in constraining the maximum integral value. While this solution is simple, it requires some adjustments depending upon the context. I prefered a simple, versatile and very efficient solution which consists in programming an adpative setpoint. A “computed setpoint” is set and kept close, above or below, the actual temperature value until it reaches the real setpoint. During this time, the integral value is nulled, preventing any windup!

Here are the few added lines to the PID function code:

if (_firstPass == 0x01) { _computedSetpoint = processVariable; _firstPass = 0x00; } if (_computedSetpoint != _setpoint) { /* Reset integral of errors */ _integralError = 0; if (_computedSetpoint < _setpoint) { _computedSetpoint = (processVariable + 1); if (_computedSetpoint > _setpoint) { _computedSetpoint = _setpoint; } } else if (_computedSetpoint > _setpoint) { _computedSetpoint = (processVariable - 1); if (_computedSetpoint < _setpoint) { _computedSetpoint = _setpoint; } } }

From the code, you can read that a one degree Celsius offset is maintained between the computed setpoint and the actual temperature, until the computed setpoint reaches the real setpoint.

As a result of this method, the damping effect happens faster with almost no overshoot. The same principle applies when changing the set point during normal operation.

Ultimately, the temperature control is getting better: