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.
if (u < 0)u = 0;
else if (u > 1) u = 1;
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
float error = target_rtd - rRTD;
float u_P =Kp * error; // Proportional part
integral += Ki * (error +error_prev)/2 * Ts;
float u_I = integral; //integral part
error_prev = error;
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) > 1.0f) {
u_I = 1.0f - u_P;
integral = u_I
}
// Lower saturation
if ((u_P + u_I) < 0.0f) {
u_I = -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.
The resistance reaches the desired value within 30 seconds. Even though the model doesn't exactly match, the system remains stable and shows no oscillatory behavior, as it was designed.