We can implement a simple PI controller as follows:
float error = target_rtd - rRTD;
float u = Kp * error + integral;
integral += Ki * (error + error_prev)/2 * Ts; // global variable
However, real-world physical systems have limitations that must be addressed:
In physical systems, actuators have operational limits. For our application, the control input must remain between 0 and 1, corresponding to 0% and 100% of the MOSFET duty cycle.
float u_max = 1.0;
float u_min = 0.0;
if (u < u_min )u = u_min ;
else if (u > u_max ) u = u_max ;
When the control input $u$ is clipped by saturation, the error accumulates in the integral term, resulting in massive overshoot. To address this, we separate the control input into two terms: P and I
Then we implement anti-windup measures using saturation logic. This code handles two cases:
This approach prevents integral windup while maintaining the maximum possible control authority within the actuator's physical limits.
// Upper saturation
if ((u_P + u_I) > u_max ) {
u_I = u_max - u_P;
integral = u_I
}
// Lower saturation
if ((u_P + u_I) < u_min) {
u_I = u_min-u_P;
integral = u_I
}
From the real result, you can see the control input saturates during the first 8 seconds, then decreases to equilibrium.