001/*----------------------------------------------------------------------------*/ 002/* Copyright (c) FIRST 2008-2017. 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/*----------------------------------------------------------------------------*/ 007 008package edu.wpi.first.wpilibj; 009 010import java.util.ArrayDeque; 011import java.util.Queue; 012import java.util.TimerTask; 013 014import edu.wpi.first.wpilibj.livewindow.LiveWindowSendable; 015import edu.wpi.first.wpilibj.tables.ITable; 016import edu.wpi.first.wpilibj.tables.ITableListener; 017import edu.wpi.first.wpilibj.util.BoundaryException; 018 019/** 020 * Class implements a PID Control Loop. 021 * 022 * <p>Creates a separate thread which reads the given PIDSource and takes care of the integral 023 * calculations, as well as writing the given PIDOutput. 024 * 025 * <p>This feedback controller runs in discrete time, so time deltas are not used in the integral 026 * and derivative calculations. Therefore, the sample rate affects the controller's behavior for a 027 * given set of PID constants. 028 */ 029public class PIDController implements PIDInterface, LiveWindowSendable, Controller { 030 031 public static final double kDefaultPeriod = .05; 032 private static int instances = 0; 033 @SuppressWarnings("MemberName") 034 private double m_P; // factor for "proportional" control 035 @SuppressWarnings("MemberName") 036 private double m_I; // factor for "integral" control 037 @SuppressWarnings("MemberName") 038 private double m_D; // factor for "derivative" control 039 @SuppressWarnings("MemberName") 040 private double m_F; // factor for feedforward term 041 private double m_maximumOutput = 1.0; // |maximum output| 042 private double m_minimumOutput = -1.0; // |minimum output| 043 private double m_maximumInput = 0.0; // maximum input - limit setpoint to this 044 private double m_minimumInput = 0.0; // minimum input - limit setpoint to this 045 // do the endpoints wrap around? eg. Absolute encoder 046 private boolean m_continuous = false; 047 private boolean m_enabled = false; // is the pid controller enabled 048 // the prior error (used to compute velocity) 049 private double m_prevError = 0.0; 050 // the sum of the errors for use in the integral calc 051 private double m_totalError = 0.0; 052 // the tolerance object used to check if on target 053 private Tolerance m_tolerance; 054 private int m_bufLength = 1; 055 private Queue<Double> m_buf; 056 private double m_bufTotal = 0.0; 057 private double m_setpoint = 0.0; 058 private double m_prevSetpoint = 0.0; 059 private double m_error = 0.0; 060 private double m_result = 0.0; 061 private double m_period = kDefaultPeriod; 062 protected PIDSource m_pidInput; 063 protected PIDOutput m_pidOutput; 064 java.util.Timer m_controlLoop; 065 Timer m_setpointTimer; 066 private boolean m_freed = false; 067 private boolean m_usingPercentTolerance; 068 069 /** 070 * Tolerance is the type of tolerance used to specify if the PID controller is on target. 071 * 072 * <p>The various implementations of this class such as PercentageTolerance and AbsoluteTolerance 073 * specify types of tolerance specifications to use. 074 */ 075 public interface Tolerance { 076 boolean onTarget(); 077 } 078 079 /** 080 * Used internally for when Tolerance hasn't been set. 081 */ 082 public class NullTolerance implements Tolerance { 083 @Override 084 public boolean onTarget() { 085 throw new RuntimeException("No tolerance value set when calling onTarget()."); 086 } 087 } 088 089 public class PercentageTolerance implements Tolerance { 090 private final double m_percentage; 091 092 PercentageTolerance(double value) { 093 m_percentage = value; 094 } 095 096 @Override 097 public boolean onTarget() { 098 return isAvgErrorValid() && (Math.abs(getAvgError()) < m_percentage / 100 * (m_maximumInput 099 - m_minimumInput)); 100 } 101 } 102 103 public class AbsoluteTolerance implements Tolerance { 104 private final double m_value; 105 106 AbsoluteTolerance(double value) { 107 m_value = value; 108 } 109 110 @Override 111 public boolean onTarget() { 112 return isAvgErrorValid() && Math.abs(getAvgError()) < m_value; 113 } 114 } 115 116 private class PIDTask extends TimerTask { 117 118 private PIDController m_controller; 119 120 public PIDTask(PIDController controller) { 121 if (controller == null) { 122 throw new NullPointerException("Given PIDController was null"); 123 } 124 m_controller = controller; 125 } 126 127 @Override 128 public void run() { 129 m_controller.calculate(); 130 } 131 } 132 133 /** 134 * Allocate a PID object with the given constants for P, I, D, and F 135 * 136 * @param Kp the proportional coefficient 137 * @param Ki the integral coefficient 138 * @param Kd the derivative coefficient 139 * @param Kf the feed forward term 140 * @param source The PIDSource object that is used to get values 141 * @param output The PIDOutput object that is set to the output percentage 142 * @param period the loop time for doing calculations. This particularly effects calculations of 143 * the integral and differential terms. The default is 50ms. 144 */ 145 @SuppressWarnings("ParameterName") 146 public PIDController(double Kp, double Ki, double Kd, double Kf, PIDSource source, 147 PIDOutput output, double period) { 148 149 if (source == null) { 150 throw new NullPointerException("Null PIDSource was given"); 151 } 152 if (output == null) { 153 throw new NullPointerException("Null PIDOutput was given"); 154 } 155 156 m_controlLoop = new java.util.Timer(); 157 m_setpointTimer = new Timer(); 158 m_setpointTimer.start(); 159 160 m_P = Kp; 161 m_I = Ki; 162 m_D = Kd; 163 m_F = Kf; 164 165 m_pidInput = source; 166 m_pidOutput = output; 167 m_period = period; 168 169 m_controlLoop.schedule(new PIDTask(this), 0L, (long) (m_period * 1000)); 170 171 instances++; 172 HLUsageReporting.reportPIDController(instances); 173 m_tolerance = new NullTolerance(); 174 175 m_buf = new ArrayDeque<Double>(m_bufLength + 1); 176 } 177 178 /** 179 * Allocate a PID object with the given constants for P, I, D and period 180 * 181 * @param Kp the proportional coefficient 182 * @param Ki the integral coefficient 183 * @param Kd the derivative coefficient 184 * @param source the PIDSource object that is used to get values 185 * @param output the PIDOutput object that is set to the output percentage 186 * @param period the loop time for doing calculations. This particularly effects calculations of 187 * the integral and differential terms. The default is 50ms. 188 */ 189 @SuppressWarnings("ParameterName") 190 public PIDController(double Kp, double Ki, double Kd, PIDSource source, PIDOutput output, 191 double period) { 192 this(Kp, Ki, Kd, 0.0, source, output, period); 193 } 194 195 /** 196 * Allocate a PID object with the given constants for P, I, D, using a 50ms period. 197 * 198 * @param Kp the proportional coefficient 199 * @param Ki the integral coefficient 200 * @param Kd the derivative coefficient 201 * @param source The PIDSource object that is used to get values 202 * @param output The PIDOutput object that is set to the output percentage 203 */ 204 @SuppressWarnings("ParameterName") 205 public PIDController(double Kp, double Ki, double Kd, PIDSource source, PIDOutput output) { 206 this(Kp, Ki, Kd, source, output, kDefaultPeriod); 207 } 208 209 /** 210 * Allocate a PID object with the given constants for P, I, D, using a 50ms period. 211 * 212 * @param Kp the proportional coefficient 213 * @param Ki the integral coefficient 214 * @param Kd the derivative coefficient 215 * @param Kf the feed forward term 216 * @param source The PIDSource object that is used to get values 217 * @param output The PIDOutput object that is set to the output percentage 218 */ 219 @SuppressWarnings("ParameterName") 220 public PIDController(double Kp, double Ki, double Kd, double Kf, PIDSource source, 221 PIDOutput output) { 222 this(Kp, Ki, Kd, Kf, source, output, kDefaultPeriod); 223 } 224 225 /** 226 * Free the PID object. 227 */ 228 public void free() { 229 m_controlLoop.cancel(); 230 synchronized (this) { 231 m_freed = true; 232 m_pidOutput = null; 233 m_pidInput = null; 234 m_controlLoop = null; 235 } 236 if (m_table != null) { 237 m_table.removeTableListener(m_listener); 238 } 239 } 240 241 /** 242 * Read the input, calculate the output accordingly, and write to the output. This should only be 243 * called by the PIDTask and is created during initialization. 244 */ 245 protected void calculate() { 246 boolean enabled; 247 PIDSource pidInput; 248 249 synchronized (this) { 250 if (m_pidInput == null) { 251 return; 252 } 253 if (m_pidOutput == null) { 254 return; 255 } 256 enabled = m_enabled; // take snapshot of these values... 257 pidInput = m_pidInput; 258 } 259 260 if (enabled) { 261 double input; 262 double result; 263 final PIDOutput pidOutput; 264 synchronized (this) { 265 input = pidInput.pidGet(); 266 } 267 synchronized (this) { 268 m_error = getContinuousError(m_setpoint - input); 269 270 if (m_pidInput.getPIDSourceType().equals(PIDSourceType.kRate)) { 271 if (m_P != 0) { 272 double potentialPGain = (m_totalError + m_error) * m_P; 273 if (potentialPGain < m_maximumOutput) { 274 if (potentialPGain > m_minimumOutput) { 275 m_totalError += m_error; 276 } else { 277 m_totalError = m_minimumOutput / m_P; 278 } 279 } else { 280 m_totalError = m_maximumOutput / m_P; 281 } 282 283 m_result = m_P * m_totalError + m_D * m_error 284 + calculateFeedForward(); 285 } 286 } else { 287 if (m_I != 0) { 288 double potentialIGain = (m_totalError + m_error) * m_I; 289 if (potentialIGain < m_maximumOutput) { 290 if (potentialIGain > m_minimumOutput) { 291 m_totalError += m_error; 292 } else { 293 m_totalError = m_minimumOutput / m_I; 294 } 295 } else { 296 m_totalError = m_maximumOutput / m_I; 297 } 298 } 299 300 m_result = m_P * m_error + m_I * m_totalError 301 + m_D * (m_error - m_prevError) + calculateFeedForward(); 302 } 303 m_prevError = m_error; 304 305 if (m_result > m_maximumOutput) { 306 m_result = m_maximumOutput; 307 } else if (m_result < m_minimumOutput) { 308 m_result = m_minimumOutput; 309 } 310 pidOutput = m_pidOutput; 311 result = m_result; 312 313 // Update the buffer. 314 m_buf.add(m_error); 315 m_bufTotal += m_error; 316 // Remove old elements when the buffer is full. 317 if (m_buf.size() > m_bufLength) { 318 m_bufTotal -= m_buf.remove(); 319 } 320 } 321 322 pidOutput.pidWrite(result); 323 } 324 } 325 326 /** 327 * Calculate the feed forward term. 328 * 329 * <p>Both of the provided feed forward calculations are velocity feed forwards. If a different 330 * feed forward calculation is desired, the user can override this function and provide his or 331 * her own. This function does no synchronization because the PIDController class only calls it 332 * in synchronized code, so be careful if calling it oneself. 333 * 334 * <p>If a velocity PID controller is being used, the F term should be set to 1 over the maximum 335 * setpoint for the output. If a position PID controller is being used, the F term should be set 336 * to 1 over the maximum speed for the output measured in setpoint units per this controller's 337 * update period (see the default period in this class's constructor). 338 */ 339 protected double calculateFeedForward() { 340 if (m_pidInput.getPIDSourceType().equals(PIDSourceType.kRate)) { 341 return m_F * getSetpoint(); 342 } else { 343 double temp = m_F * getDeltaSetpoint(); 344 m_prevSetpoint = m_setpoint; 345 m_setpointTimer.reset(); 346 return temp; 347 } 348 } 349 350 /** 351 * Set the PID Controller gain parameters. Set the proportional, integral, and differential 352 * coefficients. 353 * 354 * @param p Proportional coefficient 355 * @param i Integral coefficient 356 * @param d Differential coefficient 357 */ 358 @SuppressWarnings("ParameterName") 359 public synchronized void setPID(double p, double i, double d) { 360 m_P = p; 361 m_I = i; 362 m_D = d; 363 364 if (m_table != null) { 365 m_table.putNumber("p", p); 366 m_table.putNumber("i", i); 367 m_table.putNumber("d", d); 368 } 369 } 370 371 /** 372 * Set the PID Controller gain parameters. Set the proportional, integral, and differential 373 * coefficients. 374 * 375 * @param p Proportional coefficient 376 * @param i Integral coefficient 377 * @param d Differential coefficient 378 * @param f Feed forward coefficient 379 */ 380 @SuppressWarnings("ParameterName") 381 public synchronized void setPID(double p, double i, double d, double f) { 382 m_P = p; 383 m_I = i; 384 m_D = d; 385 m_F = f; 386 387 if (m_table != null) { 388 m_table.putNumber("p", p); 389 m_table.putNumber("i", i); 390 m_table.putNumber("d", d); 391 m_table.putNumber("f", f); 392 } 393 } 394 395 /** 396 * Get the Proportional coefficient. 397 * 398 * @return proportional coefficient 399 */ 400 public synchronized double getP() { 401 return m_P; 402 } 403 404 /** 405 * Get the Integral coefficient. 406 * 407 * @return integral coefficient 408 */ 409 public synchronized double getI() { 410 return m_I; 411 } 412 413 /** 414 * Get the Differential coefficient. 415 * 416 * @return differential coefficient 417 */ 418 public synchronized double getD() { 419 return m_D; 420 } 421 422 /** 423 * Get the Feed forward coefficient. 424 * 425 * @return feed forward coefficient 426 */ 427 public synchronized double getF() { 428 return m_F; 429 } 430 431 /** 432 * Return the current PID result This is always centered on zero and constrained the the max and 433 * min outs. 434 * 435 * @return the latest calculated output 436 */ 437 public synchronized double get() { 438 return m_result; 439 } 440 441 /** 442 * Set the PID controller to consider the input to be continuous, Rather then using the max and 443 * min in as constraints, it considers them to be the same point and automatically calculates the 444 * shortest route to the setpoint. 445 * 446 * @param continuous Set to true turns on continuous, false turns off continuous 447 */ 448 public synchronized void setContinuous(boolean continuous) { 449 m_continuous = continuous; 450 } 451 452 /** 453 * Set the PID controller to consider the input to be continuous, Rather then using the max and 454 * min in as constraints, it considers them to be the same point and automatically calculates the 455 * shortest route to the setpoint. 456 */ 457 public synchronized void setContinuous() { 458 setContinuous(true); 459 } 460 461 /** 462 * Sets the maximum and minimum values expected from the input and setpoint. 463 * 464 * @param minimumInput the minimum value expected from the input 465 * @param maximumInput the maximum value expected from the input 466 */ 467 public synchronized void setInputRange(double minimumInput, double maximumInput) { 468 if (minimumInput > maximumInput) { 469 throw new BoundaryException("Lower bound is greater than upper bound"); 470 } 471 m_minimumInput = minimumInput; 472 m_maximumInput = maximumInput; 473 setSetpoint(m_setpoint); 474 } 475 476 /** 477 * Sets the minimum and maximum values to write. 478 * 479 * @param minimumOutput the minimum percentage to write to the output 480 * @param maximumOutput the maximum percentage to write to the output 481 */ 482 public synchronized void setOutputRange(double minimumOutput, double maximumOutput) { 483 if (minimumOutput > maximumOutput) { 484 throw new BoundaryException("Lower bound is greater than upper bound"); 485 } 486 m_minimumOutput = minimumOutput; 487 m_maximumOutput = maximumOutput; 488 } 489 490 /** 491 * Set the setpoint for the PIDController Clears the queue for GetAvgError(). 492 * 493 * @param setpoint the desired setpoint 494 */ 495 public synchronized void setSetpoint(double setpoint) { 496 if (m_maximumInput > m_minimumInput) { 497 if (setpoint > m_maximumInput) { 498 m_setpoint = m_maximumInput; 499 } else if (setpoint < m_minimumInput) { 500 m_setpoint = m_minimumInput; 501 } else { 502 m_setpoint = setpoint; 503 } 504 } else { 505 m_setpoint = setpoint; 506 } 507 508 m_buf.clear(); 509 m_bufTotal = 0; 510 511 if (m_table != null) { 512 m_table.putNumber("setpoint", m_setpoint); 513 } 514 } 515 516 /** 517 * Returns the current setpoint of the PIDController. 518 * 519 * @return the current setpoint 520 */ 521 public synchronized double getSetpoint() { 522 return m_setpoint; 523 } 524 525 /** 526 * Returns the change in setpoint over time of the PIDController. 527 * 528 * @return the change in setpoint over time 529 */ 530 public synchronized double getDeltaSetpoint() { 531 return (m_setpoint - m_prevSetpoint) / m_setpointTimer.get(); 532 } 533 534 /** 535 * Returns the current difference of the input from the setpoint. 536 * 537 * @return the current error 538 */ 539 public synchronized double getError() { 540 return getContinuousError(getSetpoint() - m_pidInput.pidGet()); 541 } 542 543 /** 544 * Sets what type of input the PID controller will use. 545 * 546 * @param pidSource the type of input 547 */ 548 void setPIDSourceType(PIDSourceType pidSource) { 549 m_pidInput.setPIDSourceType(pidSource); 550 } 551 552 /** 553 * Returns the type of input the PID controller is using. 554 * 555 * @return the PID controller input type 556 */ 557 PIDSourceType getPIDSourceType() { 558 return m_pidInput.getPIDSourceType(); 559 } 560 561 /** 562 * Returns the current difference of the error over the past few iterations. You can specify the 563 * number of iterations to average with setToleranceBuffer() (defaults to 1). getAvgError() is 564 * used for the onTarget() function. 565 * 566 * @return the current average of the error 567 */ 568 public synchronized double getAvgError() { 569 double avgError = 0; 570 // Don't divide by zero. 571 if (m_buf.size() != 0) { 572 avgError = m_bufTotal / m_buf.size(); 573 } 574 return avgError; 575 } 576 577 /** 578 * Returns whether or not any values have been collected. If no values have been collected, 579 * getAvgError is 0, which is invalid. 580 * 581 * @return True if {@link #getAvgError()} is currently valid. 582 */ 583 private synchronized boolean isAvgErrorValid() { 584 return m_buf.size() != 0; 585 } 586 587 /** 588 * Set the percentage error which is considered tolerable for use with OnTarget. (Input of 15.0 = 589 * 15 percent). 590 * 591 * @param percent error which is tolerable 592 * @deprecated Use {@link #setPercentTolerance} or {@link #setAbsoluteTolerance} instead. 593 */ 594 @Deprecated 595 public synchronized void setTolerance(double percent) { 596 m_tolerance = new PercentageTolerance(percent); 597 } 598 599 /** 600 * Set the PID tolerance using a Tolerance object. Tolerance can be specified as a percentage of 601 * the range or as an absolute value. The Tolerance object encapsulates those options in an 602 * object. Use it by creating the type of tolerance that you want to use: setTolerance(new 603 * PIDController.AbsoluteTolerance(0.1)) 604 * 605 * @param tolerance a tolerance object of the right type, e.g. PercentTolerance or 606 * AbsoluteTolerance 607 */ 608 public void setTolerance(Tolerance tolerance) { 609 m_tolerance = tolerance; 610 } 611 612 /** 613 * Set the absolute error which is considered tolerable for use with OnTarget. 614 * 615 * @param absvalue absolute error which is tolerable in the units of the input object 616 */ 617 public synchronized void setAbsoluteTolerance(double absvalue) { 618 m_tolerance = new AbsoluteTolerance(absvalue); 619 } 620 621 /** 622 * Set the percentage error which is considered tolerable for use with OnTarget. (Input of 15.0 = 623 * 15 percent) 624 * 625 * @param percentage percent error which is tolerable 626 */ 627 public synchronized void setPercentTolerance(double percentage) { 628 m_tolerance = new PercentageTolerance(percentage); 629 } 630 631 /** 632 * Set the number of previous error samples to average for tolerancing. When determining whether a 633 * mechanism is on target, the user may want to use a rolling average of previous measurements 634 * instead of a precise position or velocity. This is useful for noisy sensors which return a few 635 * erroneous measurements when the mechanism is on target. However, the mechanism will not 636 * register as on target for at least the specified bufLength cycles. 637 * 638 * @param bufLength Number of previous cycles to average. 639 */ 640 public synchronized void setToleranceBuffer(int bufLength) { 641 m_bufLength = bufLength; 642 643 // Cut the existing buffer down to size if needed. 644 while (m_buf.size() > bufLength) { 645 m_bufTotal -= m_buf.remove(); 646 } 647 } 648 649 /** 650 * Return true if the error is within the percentage of the total input range, determined by 651 * setTolerance. This assumes that the maximum and minimum input were set using setInput. 652 * 653 * @return true if the error is less than the tolerance 654 */ 655 public synchronized boolean onTarget() { 656 return m_tolerance.onTarget(); 657 } 658 659 /** 660 * Begin running the PIDController. 661 */ 662 @Override 663 public synchronized void enable() { 664 m_enabled = true; 665 666 if (m_table != null) { 667 m_table.putBoolean("enabled", true); 668 } 669 } 670 671 /** 672 * Stop running the PIDController, this sets the output to zero before stopping. 673 */ 674 @Override 675 public synchronized void disable() { 676 m_pidOutput.pidWrite(0); 677 m_enabled = false; 678 679 if (m_table != null) { 680 m_table.putBoolean("enabled", false); 681 } 682 } 683 684 /** 685 * Return true if PIDController is enabled. 686 * 687 * @deprecated Call {@link #isEnabled()} instead. 688 */ 689 @Deprecated 690 public synchronized boolean isEnable() { 691 return isEnabled(); 692 } 693 694 /** 695 * Return true if PIDController is enabled. 696 */ 697 @Override 698 public boolean isEnabled() { 699 return m_enabled; 700 } 701 702 /** 703 * Reset the previous error,, the integral term, and disable the controller. 704 */ 705 @Override 706 public synchronized void reset() { 707 disable(); 708 m_prevError = 0; 709 m_totalError = 0; 710 m_result = 0; 711 } 712 713 @Override 714 public String getSmartDashboardType() { 715 return "PIDController"; 716 } 717 718 private final ITableListener m_listener = (table, key, value, isNew) -> { 719 if (key.equals("p") || key.equals("i") || key.equals("d") || key.equals("f")) { 720 if (getP() != table.getNumber("p", 0.0) || getI() != table.getNumber("i", 0.0) 721 || getD() != table.getNumber("d", 0.0) || getF() != table.getNumber("f", 0.0)) { 722 setPID(table.getNumber("p", 0.0), table.getNumber("i", 0.0), table.getNumber("d", 0.0), 723 table.getNumber("f", 0.0)); 724 } 725 } else if (key.equals("setpoint")) { 726 if (getSetpoint() != (Double) value) { 727 setSetpoint((Double) value); 728 } 729 } else if (key.equals("enabled")) { 730 if (isEnable() != (Boolean) value) { 731 if ((Boolean) value) { 732 enable(); 733 } else { 734 disable(); 735 } 736 } 737 } 738 }; 739 private ITable m_table; 740 741 @Override 742 public void initTable(ITable table) { 743 if (this.m_table != null) { 744 m_table.removeTableListener(m_listener); 745 } 746 m_table = table; 747 if (table != null) { 748 table.putNumber("p", getP()); 749 table.putNumber("i", getI()); 750 table.putNumber("d", getD()); 751 table.putNumber("f", getF()); 752 table.putNumber("setpoint", getSetpoint()); 753 table.putBoolean("enabled", isEnable()); 754 table.addTableListener(m_listener, false); 755 } 756 } 757 758 /** 759 * Wraps error around for continuous inputs. The original error is returned if continuous mode is 760 * disabled. This is an unsynchronized function. 761 * 762 * @param error The current error of the PID controller. 763 * @return Error for continuous inputs. 764 */ 765 protected double getContinuousError(double error) { 766 if (m_continuous) { 767 if (Math.abs(error) > (m_maximumInput - m_minimumInput) / 2) { 768 if (error > 0) { 769 return error - (m_maximumInput - m_minimumInput); 770 } else { 771 return error + (m_maximumInput - m_minimumInput); 772 } 773 } 774 } 775 776 return error; 777 } 778 779 @Override 780 public ITable getTable() { 781 return m_table; 782 } 783 784 785 @Override 786 public void updateTable() { 787 } 788 789 790 @Override 791 public void startLiveWindowMode() { 792 disable(); 793 } 794 795 796 @Override 797 public void stopLiveWindowMode() { 798 } 799}