001/*----------------------------------------------------------------------------*/ 002/* Copyright (c) FIRST 2008-2012. All Rights Reserved. */ 003/* Open Source Software - may be modified and shared by FRC teams. The code */ 004/* must be accompanied by the FIRST BSD license file in the root directory of */ 005/* the project. */ 006/*----------------------------------------------------------------------------*/ 007package edu.wpi.first.wpilibj; 008 009import java.util.TimerTask; 010 011import edu.wpi.first.wpilibj.livewindow.LiveWindowSendable; 012import edu.wpi.first.wpilibj.tables.ITable; 013import edu.wpi.first.wpilibj.tables.ITableListener; 014import edu.wpi.first.wpilibj.util.BoundaryException; 015 016/** 017 * Class implements a PID Control Loop. 018 * 019 * Creates a separate thread which reads the given PIDSource and takes 020 * care of the integral calculations, as well as writing the given 021 * PIDOutput 022 */ 023public class PIDController implements LiveWindowSendable, Controller { 024 025 public static final double kDefaultPeriod = .05; 026 private static int instances = 0; 027 private double m_P; // factor for "proportional" control 028 private double m_I; // factor for "integral" control 029 private double m_D; // factor for "derivative" control 030 private double m_F; // factor for feedforward term 031 private double m_maximumOutput = 1.0; // |maximum output| 032 private double m_minimumOutput = -1.0; // |minimum output| 033 private double m_maximumInput = 0.0; // maximum input - limit setpoint to this 034 private double m_minimumInput = 0.0; // minimum input - limit setpoint to this 035 private boolean m_continuous = false; // do the endpoints wrap around? eg. Absolute encoder 036 private boolean m_enabled = false; //is the pid controller enabled 037 private double m_prevError = 0.0; // the prior sensor input (used to compute velocity) 038 private double m_totalError = 0.0; //the sum of the errors for use in the integral calc 039 private Tolerance m_tolerance; //the tolerance object used to check if on target 040 private double m_setpoint = 0.0; 041 private double m_error = 0.0; 042 private double m_result = 0.0; 043 private double m_period = kDefaultPeriod; 044 PIDSource m_pidInput; 045 PIDOutput m_pidOutput; 046 java.util.Timer m_controlLoop; 047 private boolean m_freed = false; 048 private boolean m_usingPercentTolerance; 049 050 /** 051 * Tolerance is the type of tolerance used to specify if the PID controller is on target. 052 * The various implementations of this class such as PercentageTolerance and AbsoluteTolerance 053 * specify types of tolerance specifications to use. 054 */ 055 public interface Tolerance { 056 public boolean onTarget(); 057 } 058 059 public class PercentageTolerance implements Tolerance { 060 double percentage; 061 062 PercentageTolerance(double value) { 063 percentage = value; 064 } 065 066 @Override 067 public boolean onTarget() { 068 return (Math.abs(getError()) < percentage / 100 069 * (m_maximumInput - m_minimumInput)); 070 } 071 } 072 073 public class AbsoluteTolerance implements Tolerance { 074 double value; 075 076 AbsoluteTolerance(double value) { 077 this.value = value; 078 } 079 080 @Override 081 public boolean onTarget() { 082 return Math.abs(getError()) < value; 083 } 084 } 085 086 public class NullTolerance implements Tolerance { 087 088 @Override 089 public boolean onTarget() { 090 throw new RuntimeException("No tolerance value set when using PIDController.onTarget()"); 091 } 092 } 093 094 private class PIDTask extends TimerTask { 095 096 private PIDController m_controller; 097 098 public PIDTask(PIDController controller) { 099 if (controller == null) { 100 throw new NullPointerException("Given PIDController was null"); 101 } 102 m_controller = controller; 103 } 104 105 @Override 106 public void run() { 107 m_controller.calculate(); 108 } 109 } 110 111 /** 112 * Allocate a PID object with the given constants for P, I, D, and F 113 * @param Kp the proportional coefficient 114 * @param Ki the integral coefficient 115 * @param Kd the derivative coefficient 116 * @param Kf the feed forward term 117 * @param source The PIDSource object that is used to get values 118 * @param output The PIDOutput object that is set to the output percentage 119 * @param period the loop time for doing calculations. This particularly effects calculations of the 120 * integral and differential terms. The default is 50ms. 121 */ 122 public PIDController(double Kp, double Ki, double Kd, double Kf, 123 PIDSource source, PIDOutput output, 124 double period) { 125 126 if (source == null) { 127 throw new NullPointerException("Null PIDSource was given"); 128 } 129 if (output == null) { 130 throw new NullPointerException("Null PIDOutput was given"); 131 } 132 133 m_controlLoop = new java.util.Timer(); 134 135 136 m_P = Kp; 137 m_I = Ki; 138 m_D = Kd; 139 m_F = Kf; 140 141 m_pidInput = source; 142 m_pidOutput = output; 143 m_period = period; 144 145 m_controlLoop.schedule(new PIDTask(this), 0L, (long) (m_period * 1000)); 146 147 instances++; 148 HLUsageReporting.reportPIDController(instances); 149 m_tolerance = new NullTolerance(); 150 } 151 152 /** 153 * Allocate a PID object with the given constants for P, I, D and period 154 * @param Kp the proportional coefficient 155 * @param Ki the integral coefficient 156 * @param Kd the derivative coefficient 157 * @param source the PIDSource object that is used to get values 158 * @param output the PIDOutput object that is set to the output percentage 159 * @param period the loop time for doing calculations. This particularly effects calculations of the 160 * integral and differential terms. The default is 50ms. 161 */ 162 public PIDController(double Kp, double Ki, double Kd, 163 PIDSource source, PIDOutput output, 164 double period) { 165 this(Kp, Ki, Kd, 0.0, source, output, period); 166 } 167 168 /** 169 * Allocate a PID object with the given constants for P, I, D, using a 50ms period. 170 * @param Kp the proportional coefficient 171 * @param Ki the integral coefficient 172 * @param Kd the derivative coefficient 173 * @param source The PIDSource object that is used to get values 174 * @param output The PIDOutput object that is set to the output percentage 175 */ 176 public PIDController(double Kp, double Ki, double Kd, 177 PIDSource source, PIDOutput output) { 178 this(Kp, Ki, Kd, source, output, kDefaultPeriod); 179 } 180 181 /** 182 * Allocate a PID object with the given constants for P, I, D, using a 50ms period. 183 * @param Kp the proportional coefficient 184 * @param Ki the integral coefficient 185 * @param Kd the derivative coefficient 186 * @param Kf the feed forward term 187 * @param source The PIDSource object that is used to get values 188 * @param output The PIDOutput object that is set to the output percentage 189 */ 190 public PIDController(double Kp, double Ki, double Kd, double Kf, 191 PIDSource source, PIDOutput output) { 192 this(Kp, Ki, Kd, Kf, source, output, kDefaultPeriod); 193 } 194 195 /** 196 * Free the PID object 197 */ 198 public void free() { 199 m_controlLoop.cancel(); 200 synchronized (this) { 201 m_freed = true; 202 m_pidOutput = null; 203 m_pidInput = null; 204 m_controlLoop = null; 205 } 206 if(this.table!=null) table.removeTableListener(listener); 207 } 208 209 /** 210 * Read the input, calculate the output accordingly, and write to the output. 211 * This should only be called by the PIDTask 212 * and is created during initialization. 213 */ 214 private void calculate() { 215 boolean enabled; 216 PIDSource pidInput; 217 218 synchronized (this) { 219 if (m_pidInput == null) { 220 return; 221 } 222 if (m_pidOutput == null) { 223 return; 224 } 225 enabled = m_enabled; // take snapshot of these values... 226 pidInput = m_pidInput; 227 } 228 229 if (enabled) { 230 double input; 231 double result; 232 PIDOutput pidOutput = null; 233 synchronized (this){ 234 input = pidInput.pidGet(); 235 } 236 synchronized (this) { 237 m_error = m_setpoint - input; 238 if (m_continuous) { 239 if (Math.abs(m_error) 240 > (m_maximumInput - m_minimumInput) / 2) { 241 if (m_error > 0) { 242 m_error = m_error - m_maximumInput + m_minimumInput; 243 } else { 244 m_error = m_error 245 + m_maximumInput - m_minimumInput; 246 } 247 } 248 } 249 250 if (m_I != 0) { 251 double potentialIGain = (m_totalError + m_error) * m_I; 252 if (potentialIGain < m_maximumOutput) { 253 if (potentialIGain > m_minimumOutput) { 254 m_totalError += m_error; 255 } else { 256 m_totalError = m_minimumOutput / m_I; 257 } 258 } else { 259 m_totalError = m_maximumOutput / m_I; 260 } 261 } 262 263 m_result = m_P * m_error + m_I * m_totalError + m_D * (m_error - m_prevError) + m_setpoint * m_F; 264 m_prevError = m_error; 265 266 if (m_result > m_maximumOutput) { 267 m_result = m_maximumOutput; 268 } else if (m_result < m_minimumOutput) { 269 m_result = m_minimumOutput; 270 } 271 pidOutput = m_pidOutput; 272 result = m_result; 273 } 274 275 pidOutput.pidWrite(result); 276 } 277 } 278 279 /** 280 * Set the PID Controller gain parameters. 281 * Set the proportional, integral, and differential coefficients. 282 * @param p Proportional coefficient 283 * @param i Integral coefficient 284 * @param d Differential coefficient 285 */ 286 public synchronized void setPID(double p, double i, double d) { 287 m_P = p; 288 m_I = i; 289 m_D = d; 290 291 if (table != null) { 292 table.putNumber("p", p); 293 table.putNumber("i", i); 294 table.putNumber("d", d); 295 } 296 } 297 298 /** 299 * Set the PID Controller gain parameters. 300 * Set the proportional, integral, and differential coefficients. 301 * @param p Proportional coefficient 302 * @param i Integral coefficient 303 * @param d Differential coefficient 304 * @param f Feed forward coefficient 305 */ 306 public synchronized void setPID(double p, double i, double d, double f) { 307 m_P = p; 308 m_I = i; 309 m_D = d; 310 m_F = f; 311 312 if (table != null) { 313 table.putNumber("p", p); 314 table.putNumber("i", i); 315 table.putNumber("d", d); 316 table.putNumber("f", f); 317 } 318 } 319 320 /** 321 * Get the Proportional coefficient 322 * @return proportional coefficient 323 */ 324 public synchronized double getP() { 325 return m_P; 326 } 327 328 /** 329 * Get the Integral coefficient 330 * @return integral coefficient 331 */ 332 public synchronized double getI() { 333 return m_I; 334 } 335 336 /** 337 * Get the Differential coefficient 338 * @return differential coefficient 339 */ 340 public synchronized double getD() { 341 return m_D; 342 } 343 344 /** 345 * Get the Feed forward coefficient 346 * @return feed forward coefficient 347 */ 348 public synchronized double getF() { 349 return m_F; 350 } 351 352 /** 353 * Return the current PID result 354 * This is always centered on zero and constrained the the max and min outs 355 * @return the latest calculated output 356 */ 357 public synchronized double get() { 358 return m_result; 359 } 360 361 /** 362 * Set the PID controller to consider the input to be continuous, 363 * Rather then using the max and min in as constraints, it considers them to 364 * be the same point and automatically calculates the shortest route to 365 * the setpoint. 366 * @param continuous Set to true turns on continuous, false turns off continuous 367 */ 368 public synchronized void setContinuous(boolean continuous) { 369 m_continuous = continuous; 370 } 371 372 /** 373 * Set the PID controller to consider the input to be continuous, 374 * Rather then using the max and min in as constraints, it considers them to 375 * be the same point and automatically calculates the shortest route to 376 * the setpoint. 377 */ 378 public synchronized void setContinuous() { 379 this.setContinuous(true); 380 } 381 382 /** 383 * Sets the maximum and minimum values expected from the input and setpoint. 384 * 385 * @param minimumInput the minimum value expected from the input 386 * @param maximumInput the maximum value expected from the input 387 */ 388 public synchronized void setInputRange(double minimumInput, double maximumInput) { 389 if (minimumInput > maximumInput) { 390 throw new BoundaryException("Lower bound is greater than upper bound"); 391 } 392 m_minimumInput = minimumInput; 393 m_maximumInput = maximumInput; 394 setSetpoint(m_setpoint); 395 } 396 397 /** 398 * Sets the minimum and maximum values to write. 399 * 400 * @param minimumOutput the minimum percentage to write to the output 401 * @param maximumOutput the maximum percentage to write to the output 402 */ 403 public synchronized void setOutputRange(double minimumOutput, double maximumOutput) { 404 if (minimumOutput > maximumOutput) { 405 throw new BoundaryException("Lower bound is greater than upper bound"); 406 } 407 m_minimumOutput = minimumOutput; 408 m_maximumOutput = maximumOutput; 409 } 410 411 /** 412 * Set the setpoint for the PIDController 413 * @param setpoint the desired setpoint 414 */ 415 public synchronized void setSetpoint(double setpoint) { 416 if (m_maximumInput > m_minimumInput) { 417 if (setpoint > m_maximumInput) { 418 m_setpoint = m_maximumInput; 419 } else if (setpoint < m_minimumInput) { 420 m_setpoint = m_minimumInput; 421 } else { 422 m_setpoint = setpoint; 423 } 424 } else { 425 m_setpoint = setpoint; 426 } 427 428 if (table != null) 429 table.putNumber("setpoint", m_setpoint); 430 } 431 432 /** 433 * Returns the current setpoint of the PIDController 434 * @return the current setpoint 435 */ 436 public synchronized double getSetpoint() { 437 return m_setpoint; 438 } 439 440 /** 441 * Returns the current difference of the input from the setpoint 442 * @return the current error 443 */ 444 public synchronized double getError() { 445 //return m_error; 446 return getSetpoint() - m_pidInput.pidGet(); 447 } 448 449 /** 450 * Set the percentage error which is considered tolerable for use with 451 * OnTarget. (Input of 15.0 = 15 percent) 452 * @param percent error which is tolerable 453 * @deprecated Use {@link #setPercentTolerance(double)} or {@link #setAbsoluteTolerance(double)} instead. 454 */ 455 @Deprecated 456 public synchronized void setTolerance(double percent) { 457 m_tolerance = new PercentageTolerance(percent); 458 } 459 460 /** Set the PID tolerance using a Tolerance object. 461 * Tolerance can be specified as a percentage of the range or as an absolute 462 * value. The Tolerance object encapsulates those options in an object. Use it by 463 * creating the type of tolerance that you want to use: setTolerance(new PIDController.AbsoluteTolerance(0.1)) 464 * @param tolerance a tolerance object of the right type, e.g. PercentTolerance 465 * or AbsoluteTolerance 466 */ 467 private synchronized void setTolerance(Tolerance tolerance) { 468 m_tolerance = tolerance; 469 } 470 471 /** 472 * Set the absolute error which is considered tolerable for use with 473 * OnTarget. 474 * @param absvalue absolute error which is tolerable in the units of the input object 475 */ 476 public synchronized void setAbsoluteTolerance(double absvalue) { 477 m_tolerance = new AbsoluteTolerance(absvalue); 478 } 479 480 /** 481 * Set the percentage error which is considered tolerable for use with 482 * OnTarget. (Input of 15.0 = 15 percent) 483 * @param percentage percent error which is tolerable 484 */ 485 public synchronized void setPercentTolerance(double percentage) { 486 m_tolerance = new PercentageTolerance(percentage); 487 } 488 489 /** 490 * Return true if the error is within the percentage of the total input range, 491 * determined by setTolerance. This assumes that the maximum and minimum input 492 * were set using setInput. 493 * @return true if the error is less than the tolerance 494 */ 495 public synchronized boolean onTarget() { 496 return m_tolerance.onTarget(); 497 } 498 499 /** 500 * Begin running the PIDController 501 */ 502 @Override 503 public synchronized void enable() { 504 m_enabled = true; 505 506 if (table != null) { 507 table.putBoolean("enabled", true); 508 } 509 } 510 511 /** 512 * Stop running the PIDController, this sets the output to zero before stopping. 513 */ 514 @Override 515 public synchronized void disable() { 516 m_pidOutput.pidWrite(0); 517 m_enabled = false; 518 519 if (table != null) { 520 table.putBoolean("enabled", false); 521 } 522 } 523 524 /** 525 * Return true if PIDController is enabled. 526 */ 527 public synchronized boolean isEnable() { 528 return m_enabled; 529 } 530 531 /** 532 * Reset the previous error,, the integral term, and disable the controller. 533 */ 534 public synchronized void reset() { 535 disable(); 536 m_prevError = 0; 537 m_totalError = 0; 538 m_result = 0; 539 } 540 541 @Override 542 public String getSmartDashboardType() { 543 return "PIDController"; 544 } 545 546 private final ITableListener listener = new ITableListener() { 547 @Override 548 public void valueChanged(ITable table, String key, Object value, boolean isNew) { 549 if (key.equals("p") || key.equals("i") || key.equals("d") || key.equals("f")) { 550 if (getP() != table.getNumber("p", 0.0) || getI() != table.getNumber("i", 0.0) || 551 getD() != table.getNumber("d", 0.0) || getF() != table.getNumber("f", 0.0)) 552 setPID(table.getNumber("p", 0.0), table.getNumber("i", 0.0), table.getNumber("d", 0.0), table.getNumber("f", 0.0)); 553 } else if (key.equals("setpoint")) { 554 if (getSetpoint() != ((Double) value).doubleValue()) 555 setSetpoint(((Double) value).doubleValue()); 556 } else if (key.equals("enabled")) { 557 if (isEnable() != ((Boolean) value).booleanValue()) { 558 if (((Boolean) value).booleanValue()) { 559 enable(); 560 } else { 561 disable(); 562 } 563 } 564 } 565 } 566 }; 567 private ITable table; 568 @Override 569 public void initTable(ITable table) { 570 if(this.table!=null) 571 this.table.removeTableListener(listener); 572 this.table = table; 573 if(table!=null) { 574 table.putNumber("p", getP()); 575 table.putNumber("i", getI()); 576 table.putNumber("d", getD()); 577 table.putNumber("f", getF()); 578 table.putNumber("setpoint", getSetpoint()); 579 table.putBoolean("enabled", isEnable()); 580 table.addTableListener(listener, false); 581 } 582 } 583 584 /** 585 * {@inheritDoc} 586 */ 587 @Override 588 public ITable getTable() { 589 return table; 590 } 591 592 /** 593 * {@inheritDoc} 594 */ 595 @Override 596 public void updateTable() { 597 } 598 599 /** 600 * {@inheritDoc} 601 */ 602 @Override 603 public void startLiveWindowMode() { 604 disable(); 605 } 606 607 /** 608 * {@inheritDoc} 609 */ 610 @Override 611 public void stopLiveWindowMode() { 612 } 613}