#pragma once class PidController { public: PidController(float kp, float ki, float kd, float outputMin, float outputMax) : kp_(kp), ki_(ki), kd_(kd), outputMin_(outputMin), outputMax_(outputMax) {} void setTunings(float kp, float ki, float kd) { kp_ = kp; ki_ = ki; kd_ = kd; } void setSetpoint(float setpoint) { setpoint_ = setpoint; } void reset() { integral_ = 0.0f; prevInput_ = 0.0f; firstSample_ = true; } float compute(float input, uint32_t nowMs) { if (firstSample_) { prevInput_ = input; prevTimeMs_ = nowMs; firstSample_ = false; return outputMin_; } const float dt = static_cast(nowMs - prevTimeMs_) / 1000.0f; if (dt <= 0.0f) { return lastOutput_; } const float error = setpoint_ - input; integral_ += error * dt; // Anti-windup: clamp integral so output cannot exceed limits const float integralMax = (outputMax_ - outputMin_) / (ki_ > 0.0f ? ki_ : 1.0f); if (integral_ > integralMax) { integral_ = integralMax; } else if (integral_ < 0.0f) { integral_ = 0.0f; } const float derivative = (input - prevInput_) / dt; float output = kp_ * error + ki_ * integral_ - kd_ * derivative; if (output < outputMin_) { output = outputMin_; } else if (output > outputMax_) { output = outputMax_; } prevInput_ = input; prevTimeMs_ = nowMs; lastOutput_ = output; return output; } float lastOutput() const { return lastOutput_; } float kp() const { return kp_; } float ki() const { return ki_; } float kd() const { return kd_; } private: float kp_; float ki_; float kd_; float outputMin_; float outputMax_; float setpoint_ = 0.0f; float integral_ = 0.0f; float prevInput_ = 0.0f; float lastOutput_ = 0.0f; uint32_t prevTimeMs_ = 0; bool firstSample_ = true; };