Basic Implementation

We can implement a simple PI controller as follows:

ordinary_pi.png

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:

1. Actuator Saturation Limits

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.

sat_pi.png

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 ; 

2. Anti-Integral Windup

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:

  1. Upper saturation: If the proportional term ($u_P$) alone exceeds 1.0, we clip it to 1.0 and reset the integral term ($u_I$) to prevent windup. If the combined control signal ($u_P + u_I$) exceeds 1.0, we limit the integral term so their sum equals exactly 1.0.
  2. Lower saturation: Similarly, if $u_P$ is less than 0.0, we clip it to 0.0 and reset $u_I$. If their combined value is negative, we adjust u_I to cancel out $u_P$, effectively making the control signal 0.0.

This approach prevents integral windup while maintaining the maximum possible control authority within the actuator's physical limits.

anti_winding_pi.png


// 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
}

Result

PI_resp.png

From the real result, you can see the control input saturates during the first 8 seconds, then decreases to equilibrium.