fork download
  1. #include <stdio.h>
  2.  
  3. typedef struct {
  4. // gains
  5. float Kp, Ki, Kd;
  6. // output limits
  7. float umin, umax;
  8. // derivative low-pass filter (alpha in (0,1], 1 = no filtering)
  9. float d_alpha;
  10.  
  11. // state
  12. float integ; // integral term state
  13. float prev_err; // for derivative
  14. float d_filt; // filtered derivative
  15. int initialized; // init flag
  16. } PID;
  17.  
  18. void pid_init(PID *pid, float Kp, float Ki, float Kd,
  19. float umin, float umax, float d_alpha) {
  20. pid->Kp = Kp; pid->Ki = Ki; pid->Kd = Kd;
  21. pid->umin = umin; pid->umax = umax;
  22. pid->d_alpha = (d_alpha <= 0.0f) ? 1.0f : (d_alpha > 1.0f ? 1.0f : d_alpha);
  23. pid->integ = 0.0f;
  24. pid->prev_err = 0.0f;
  25. pid->d_filt = 0.0f;
  26. pid->initialized = 0;
  27. }
  28.  
  29. static inline float clampf(float x, float lo, float hi) {
  30. if (x < lo) return lo;
  31. if (x > hi) return hi;
  32. return x;
  33. }
  34.  
  35. float pid_update(PID *pid, float setpoint, float measurement, float dt) {
  36. // basic dt guard
  37. if (dt <= 1e-6f) dt = 1e-3f;
  38.  
  39. float err = setpoint - measurement;
  40.  
  41. // init derivative on first call to avoid spike
  42. if (!pid->initialized) {
  43. pid->prev_err = err;
  44. pid->d_filt = 0.0f;
  45. pid->initialized = 1;
  46. }
  47.  
  48. // proportional
  49. float P = pid->Kp * err;
  50.  
  51. // derivative (backward diff) then low-pass filter
  52. float d_raw = (err - pid->prev_err) / dt;
  53. pid->d_filt = pid->d_alpha * pid->d_filt + (1.0f - pid->d_alpha) * d_raw;
  54. float D = pid->Kd * pid->d_filt;
  55.  
  56. // integral with anti-windup (clamped via conditional integration)
  57. float u_unsat_P_D = P + D;
  58. float integ_candidate = pid->integ + err * dt;
  59.  
  60. // tentative control with updated integral
  61. float I_tent = pid->Ki * integ_candidate;
  62. float u_tent = u_unsat_P_D + I_tent;
  63.  
  64. // apply saturation
  65. float u = clampf(u_tent, pid->umin, pid->umax);
  66.  
  67. // anti-windup: only accept integral growth if not saturating against error direction
  68. int saturating_high = (u_tent > pid->umax) && (err > 0.0f);
  69. int saturating_low = (u_tent < pid->umin) && (err < 0.0f);
  70. if (!(saturating_high || saturating_low)) {
  71. pid->integ = integ_candidate; // accept integral update
  72. }
  73. // else reject integral update this step (freezes at previous value)
  74.  
  75. // update memory
  76. pid->prev_err = err;
  77.  
  78. return u;
  79. }
  80.  
  81. // Example usage
  82. int main(void) {
  83. PID pid;
  84. pid_init(&pid, 1.0f, 2.0f, 0.05f, -10.0f, 10.0f, 0.8f);
  85.  
  86. float setpoint = 100.0f; // rpm
  87. float y = 0.0f; // measurement
  88. float dt = 0.01f; // 10 ms
  89.  
  90. for (int k = 0; k < 1000; ++k) {
  91. float u = pid_update(&pid, setpoint, y, dt);
  92. // toy plant: y[k+1] = y[k] + 0.1*u - 0.02*y
  93. y = y + 0.1f*u - 0.02f*y;
  94. if (k % 100 == 0) {
  95. printf("t=%.2f u=%.3f y=%.3f\n", k*dt, u, y);
  96. }
  97. }
  98. return 0;
  99. }
Success #stdin #stdout 0.03s 25768KB
stdin
Standard input is empty
stdout
#include <stdio.h>

typedef struct {
    // gains
    float Kp, Ki, Kd;
    // output limits
    float umin, umax;
    // derivative low-pass filter (alpha in (0,1], 1 = no filtering)
    float d_alpha;

    // state
    float integ;       // integral term state
    float prev_err;    // for derivative
    float d_filt;      // filtered derivative
    int   initialized; // init flag
} PID;

void pid_init(PID *pid, float Kp, float Ki, float Kd,
              float umin, float umax, float d_alpha) {
    pid->Kp = Kp; pid->Ki = Ki; pid->Kd = Kd;
    pid->umin = umin; pid->umax = umax;
    pid->d_alpha = (d_alpha <= 0.0f) ? 1.0f : (d_alpha > 1.0f ? 1.0f : d_alpha);
    pid->integ = 0.0f;
    pid->prev_err = 0.0f;
    pid->d_filt = 0.0f;
    pid->initialized = 0;
}

static inline float clampf(float x, float lo, float hi) {
    if (x < lo) return lo;
    if (x > hi) return hi;
    return x;
}

float pid_update(PID *pid, float setpoint, float measurement, float dt) {
    // basic dt guard
    if (dt <= 1e-6f) dt = 1e-3f;

    float err = setpoint - measurement;

    // init derivative on first call to avoid spike
    if (!pid->initialized) {
        pid->prev_err = err;
        pid->d_filt = 0.0f;
        pid->initialized = 1;
    }

    // proportional
    float P = pid->Kp * err;

    // derivative (backward diff) then low-pass filter
    float d_raw = (err - pid->prev_err) / dt;
    pid->d_filt = pid->d_alpha * pid->d_filt + (1.0f - pid->d_alpha) * d_raw;
    float D = pid->Kd * pid->d_filt;

    // integral with anti-windup (clamped via conditional integration)
    float u_unsat_P_D = P + D;
    float integ_candidate = pid->integ + err * dt;

    // tentative control with updated integral
    float I_tent = pid->Ki * integ_candidate;
    float u_tent = u_unsat_P_D + I_tent;

    // apply saturation
    float u = clampf(u_tent, pid->umin, pid->umax);

    // anti-windup: only accept integral growth if not saturating against error direction
    int saturating_high = (u_tent > pid->umax) && (err > 0.0f);
    int saturating_low  = (u_tent < pid->umin) && (err < 0.0f);
    if (!(saturating_high || saturating_low)) {
        pid->integ = integ_candidate; // accept integral update
    }
    // else reject integral update this step (freezes at previous value)

    // update memory
    pid->prev_err = err;

    return u;
}

// Example usage
int main(void) {
    PID pid;
    pid_init(&pid, 1.0f, 2.0f, 0.05f, -10.0f, 10.0f, 0.8f);

    float setpoint = 100.0f; // rpm
    float y = 0.0f;          // measurement
    float dt = 0.01f;        // 10 ms

    for (int k = 0; k < 1000; ++k) {
        float u = pid_update(&pid, setpoint, y, dt);
        // toy plant: y[k+1] = y[k] + 0.1*u - 0.02*y
        y = y + 0.1f*u - 0.02f*y;
        if (k % 100 == 0) {
            printf("t=%.2f  u=%.3f  y=%.3f\n", k*dt, u, y);
        }
    }
    return 0;
}