001// Copyright (c) FIRST and other WPILib contributors. 002// Open Source Software; you can modify and/or share it under the terms of 003// the WPILib BSD license file in the root directory of this project. 004 005package org.wpilib.math.controller; 006 007import org.wpilib.math.util.MathSharedStore; 008import org.wpilib.math.util.MathUtil; 009import org.wpilib.util.sendable.Sendable; 010import org.wpilib.util.sendable.SendableBuilder; 011import org.wpilib.util.sendable.SendableRegistry; 012 013/** Implements a PID control loop. */ 014public class PIDController implements Sendable, AutoCloseable { 015 private static int instances; 016 017 // Factor for "proportional" control 018 private double m_kp; 019 020 // Factor for "integral" control 021 private double m_ki; 022 023 // Factor for "derivative" control 024 private double m_kd; 025 026 // The error range where "integral" control applies 027 private double m_iZone = Double.POSITIVE_INFINITY; 028 029 // The period (in seconds) of the loop that calls the controller 030 private final double m_period; 031 032 private double m_maximumIntegral = 1.0; 033 034 private double m_minimumIntegral = -1.0; 035 036 private double m_maximumInput; 037 038 private double m_minimumInput; 039 040 // Do the endpoints wrap around? e.g. Absolute encoder 041 private boolean m_continuous; 042 043 // The error at the time of the most recent call to calculate() 044 private double m_error; 045 private double m_errorDerivative; 046 047 // The error at the time of the second-most-recent call to calculate() (used to compute velocity) 048 private double m_prevError; 049 050 // The sum of the errors for use in the integral calc 051 private double m_totalError; 052 053 // The error that is considered at setpoint. 054 private double m_errorTolerance = 0.05; 055 private double m_errorDerivativeTolerance = Double.POSITIVE_INFINITY; 056 057 private double m_setpoint; 058 private double m_measurement; 059 060 private boolean m_haveMeasurement; 061 private boolean m_haveSetpoint; 062 063 /** 064 * Allocates a PIDController with the given constants for kp, ki, and kd and a default period of 065 * 0.02 seconds. 066 * 067 * @param kp The proportional coefficient. 068 * @param ki The integral coefficient. 069 * @param kd The derivative coefficient. 070 * @throws IllegalArgumentException if kp < 0 071 * @throws IllegalArgumentException if ki < 0 072 * @throws IllegalArgumentException if kd < 0 073 */ 074 public PIDController(double kp, double ki, double kd) { 075 this(kp, ki, kd, 0.02); 076 } 077 078 /** 079 * Allocates a PIDController with the given constants for kp, ki, and kd. 080 * 081 * @param kp The proportional coefficient. 082 * @param ki The integral coefficient. 083 * @param kd The derivative coefficient. 084 * @param period The period between controller updates in seconds. 085 * @throws IllegalArgumentException if kp < 0 086 * @throws IllegalArgumentException if ki < 0 087 * @throws IllegalArgumentException if kd < 0 088 * @throws IllegalArgumentException if period <= 0 089 */ 090 @SuppressWarnings("this-escape") 091 public PIDController(double kp, double ki, double kd, double period) { 092 m_kp = kp; 093 m_ki = ki; 094 m_kd = kd; 095 096 if (kp < 0.0) { 097 throw new IllegalArgumentException("Kp must be a non-negative number!"); 098 } 099 if (ki < 0.0) { 100 throw new IllegalArgumentException("Ki must be a non-negative number!"); 101 } 102 if (kd < 0.0) { 103 throw new IllegalArgumentException("Kd must be a non-negative number!"); 104 } 105 if (period <= 0.0) { 106 throw new IllegalArgumentException("Controller period must be a positive number!"); 107 } 108 m_period = period; 109 110 instances++; 111 SendableRegistry.add(this, "PIDController", instances); 112 113 MathSharedStore.reportUsage("PIDController", String.valueOf(instances)); 114 } 115 116 @Override 117 public void close() { 118 SendableRegistry.remove(this); 119 } 120 121 /** 122 * Sets the PID Controller gain parameters. 123 * 124 * <p>Set the proportional, integral, and differential coefficients. 125 * 126 * @param kp The proportional coefficient. 127 * @param ki The integral coefficient. 128 * @param kd The derivative coefficient. 129 */ 130 public void setPID(double kp, double ki, double kd) { 131 m_kp = kp; 132 m_ki = ki; 133 m_kd = kd; 134 } 135 136 /** 137 * Sets the Proportional coefficient of the PID controller gain. 138 * 139 * @param kp The proportional coefficient. Must be >= 0. 140 */ 141 public void setP(double kp) { 142 m_kp = kp; 143 } 144 145 /** 146 * Sets the Integral coefficient of the PID controller gain. 147 * 148 * @param ki The integral coefficient. Must be >= 0. 149 */ 150 public void setI(double ki) { 151 m_ki = ki; 152 } 153 154 /** 155 * Sets the Differential coefficient of the PID controller gain. 156 * 157 * @param kd The differential coefficient. Must be >= 0. 158 */ 159 public void setD(double kd) { 160 m_kd = kd; 161 } 162 163 /** 164 * Sets the IZone range. When the absolute value of the position error is greater than IZone, the 165 * total accumulated error will reset to zero, disabling integral gain until the absolute value of 166 * the position error is less than IZone. This is used to prevent integral windup. Must be 167 * non-negative. Passing a value of zero will effectively disable integral gain. Passing a value 168 * of {@link Double#POSITIVE_INFINITY} disables IZone functionality. 169 * 170 * @param iZone Maximum magnitude of error to allow integral control. 171 * @throws IllegalArgumentException if iZone < 0 172 */ 173 public void setIZone(double iZone) { 174 if (iZone < 0) { 175 throw new IllegalArgumentException("IZone must be a non-negative number!"); 176 } 177 m_iZone = iZone; 178 } 179 180 /** 181 * Get the Proportional coefficient. 182 * 183 * @return proportional coefficient 184 */ 185 public double getP() { 186 return m_kp; 187 } 188 189 /** 190 * Get the Integral coefficient. 191 * 192 * @return integral coefficient 193 */ 194 public double getI() { 195 return m_ki; 196 } 197 198 /** 199 * Get the Differential coefficient. 200 * 201 * @return differential coefficient 202 */ 203 public double getD() { 204 return m_kd; 205 } 206 207 /** 208 * Get the IZone range. 209 * 210 * @return Maximum magnitude of error to allow integral control. 211 */ 212 public double getIZone() { 213 return m_iZone; 214 } 215 216 /** 217 * Returns the period of this controller. 218 * 219 * @return the period of the controller. 220 */ 221 public double getPeriod() { 222 return m_period; 223 } 224 225 /** 226 * Returns the error tolerance of this controller. Defaults to 0.05. 227 * 228 * @return the error tolerance of the controller. 229 */ 230 public double getErrorTolerance() { 231 return m_errorTolerance; 232 } 233 234 /** 235 * Returns the error derivative tolerance of this controller. Defaults to ∞. 236 * 237 * @return the error derivative tolerance of the controller. 238 */ 239 public double getErrorDerivativeTolerance() { 240 return m_errorDerivativeTolerance; 241 } 242 243 /** 244 * Returns the accumulated error used in the integral calculation of this controller. 245 * 246 * @return The accumulated error of this controller. 247 */ 248 public double getAccumulatedError() { 249 return m_totalError; 250 } 251 252 /** 253 * Sets the setpoint for the PIDController. 254 * 255 * @param setpoint The desired setpoint. 256 */ 257 public void setSetpoint(double setpoint) { 258 m_setpoint = setpoint; 259 m_haveSetpoint = true; 260 261 if (m_continuous) { 262 double errorBound = (m_maximumInput - m_minimumInput) / 2.0; 263 m_error = MathUtil.inputModulus(m_setpoint - m_measurement, -errorBound, errorBound); 264 } else { 265 m_error = m_setpoint - m_measurement; 266 } 267 268 m_errorDerivative = (m_error - m_prevError) / m_period; 269 } 270 271 /** 272 * Returns the current setpoint of the PIDController. 273 * 274 * @return The current setpoint. 275 */ 276 public double getSetpoint() { 277 return m_setpoint; 278 } 279 280 /** 281 * Returns true if the error is within the tolerance of the setpoint. The error tolerance defaults 282 * to 0.05, and the error derivative tolerance defaults to ∞. 283 * 284 * <p>This will return false until at least one input value has been computed. 285 * 286 * @return Whether the error is within the acceptable bounds. 287 */ 288 public boolean atSetpoint() { 289 return m_haveMeasurement 290 && m_haveSetpoint 291 && Math.abs(m_error) < m_errorTolerance 292 && Math.abs(m_errorDerivative) < m_errorDerivativeTolerance; 293 } 294 295 /** 296 * Enables continuous input. 297 * 298 * <p>Rather then using the max and min input range as constraints, it considers them to be the 299 * same point and automatically calculates the shortest route to the setpoint. 300 * 301 * @param minimumInput The minimum value expected from the input. 302 * @param maximumInput The maximum value expected from the input. 303 */ 304 public void enableContinuousInput(double minimumInput, double maximumInput) { 305 m_continuous = true; 306 m_minimumInput = minimumInput; 307 m_maximumInput = maximumInput; 308 } 309 310 /** Disables continuous input. */ 311 public void disableContinuousInput() { 312 m_continuous = false; 313 } 314 315 /** 316 * Returns true if continuous input is enabled. 317 * 318 * @return True if continuous input is enabled. 319 */ 320 public boolean isContinuousInputEnabled() { 321 return m_continuous; 322 } 323 324 /** 325 * Sets the minimum and maximum contributions of the integral term. 326 * 327 * <p>The internal integrator is clamped so that the integral term's contribution to the output 328 * stays between minimumIntegral and maximumIntegral. This prevents integral windup. 329 * 330 * @param minimumIntegral The minimum contribution of the integral term. 331 * @param maximumIntegral The maximum contribution of the integral term. 332 */ 333 public void setIntegratorRange(double minimumIntegral, double maximumIntegral) { 334 m_minimumIntegral = minimumIntegral; 335 m_maximumIntegral = maximumIntegral; 336 } 337 338 /** 339 * Sets the error which is considered tolerable for use with atSetpoint(). 340 * 341 * @param errorTolerance Error which is tolerable. 342 */ 343 public void setTolerance(double errorTolerance) { 344 setTolerance(errorTolerance, Double.POSITIVE_INFINITY); 345 } 346 347 /** 348 * Sets the error which is considered tolerable for use with atSetpoint(). 349 * 350 * @param errorTolerance Error which is tolerable. 351 * @param errorDerivativeTolerance Error derivative which is tolerable. 352 */ 353 public void setTolerance(double errorTolerance, double errorDerivativeTolerance) { 354 m_errorTolerance = errorTolerance; 355 m_errorDerivativeTolerance = errorDerivativeTolerance; 356 } 357 358 /** 359 * Returns the difference between the setpoint and the measurement. 360 * 361 * @return The error. 362 */ 363 public double getError() { 364 return m_error; 365 } 366 367 /** 368 * Returns the error derivative. 369 * 370 * @return The error derivative. 371 */ 372 public double getErrorDerivative() { 373 return m_errorDerivative; 374 } 375 376 /** 377 * Returns the next output of the PID controller. 378 * 379 * @param measurement The current measurement of the process variable. 380 * @param setpoint The new setpoint of the controller. 381 * @return The next controller output. 382 */ 383 public double calculate(double measurement, double setpoint) { 384 m_setpoint = setpoint; 385 m_haveSetpoint = true; 386 return calculate(measurement); 387 } 388 389 /** 390 * Returns the next output of the PID controller. 391 * 392 * @param measurement The current measurement of the process variable. 393 * @return The next controller output. 394 */ 395 public double calculate(double measurement) { 396 m_measurement = measurement; 397 m_prevError = m_error; 398 m_haveMeasurement = true; 399 400 if (m_continuous) { 401 double errorBound = (m_maximumInput - m_minimumInput) / 2.0; 402 m_error = MathUtil.inputModulus(m_setpoint - m_measurement, -errorBound, errorBound); 403 } else { 404 m_error = m_setpoint - m_measurement; 405 } 406 407 m_errorDerivative = (m_error - m_prevError) / m_period; 408 409 // If the absolute value of the position error is greater than IZone, reset the total error 410 if (Math.abs(m_error) > m_iZone) { 411 m_totalError = 0; 412 } else if (m_ki != 0) { 413 m_totalError = 414 Math.clamp( 415 m_totalError + m_error * m_period, 416 m_minimumIntegral / m_ki, 417 m_maximumIntegral / m_ki); 418 } 419 420 return m_kp * m_error + m_ki * m_totalError + m_kd * m_errorDerivative; 421 } 422 423 /** Resets the previous error and the integral term. */ 424 public void reset() { 425 m_error = 0; 426 m_prevError = 0; 427 m_totalError = 0; 428 m_errorDerivative = 0; 429 m_haveMeasurement = false; 430 } 431 432 @Override 433 public void initSendable(SendableBuilder builder) { 434 builder.setSmartDashboardType("PIDController"); 435 builder.addDoubleProperty("p", this::getP, this::setP); 436 builder.addDoubleProperty("i", this::getI, this::setI); 437 builder.addDoubleProperty("d", this::getD, this::setD); 438 builder.addDoubleProperty( 439 "izone", 440 this::getIZone, 441 (double toSet) -> { 442 try { 443 setIZone(toSet); 444 } catch (IllegalArgumentException e) { 445 MathSharedStore.reportError("IZone must be a non-negative number!", e.getStackTrace()); 446 } 447 }); 448 builder.addDoubleProperty("setpoint", this::getSetpoint, this::setSetpoint); 449 builder.addDoubleProperty("measurement", () -> m_measurement, null); 450 builder.addDoubleProperty("error", this::getError, null); 451 builder.addDoubleProperty("error derivative", this::getErrorDerivative, null); 452 builder.addDoubleProperty("previous error", () -> this.m_prevError, null); 453 builder.addDoubleProperty("total error", this::getAccumulatedError, null); 454 } 455}