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 edu.wpi.first.math.controller; 006 007import edu.wpi.first.math.MathSharedStore; 008import edu.wpi.first.math.MathUsageId; 009import edu.wpi.first.math.MathUtil; 010import edu.wpi.first.math.trajectory.TrapezoidProfile; 011import edu.wpi.first.util.sendable.Sendable; 012import edu.wpi.first.util.sendable.SendableBuilder; 013import edu.wpi.first.util.sendable.SendableRegistry; 014 015/** 016 * Implements a PID control loop whose setpoint is constrained by a trapezoid profile. Users should 017 * call reset() when they first start running the controller to avoid unwanted behavior. 018 */ 019public class ProfiledPIDController implements Sendable { 020 private static int instances; 021 022 private PIDController m_controller; 023 private double m_minimumInput; 024 private double m_maximumInput; 025 private TrapezoidProfile.State m_goal = new TrapezoidProfile.State(); 026 private TrapezoidProfile.State m_setpoint = new TrapezoidProfile.State(); 027 private TrapezoidProfile.Constraints m_constraints; 028 029 /** 030 * Allocates a ProfiledPIDController with the given constants for Kp, Ki, and Kd. 031 * 032 * @param Kp The proportional coefficient. 033 * @param Ki The integral coefficient. 034 * @param Kd The derivative coefficient. 035 * @param constraints Velocity and acceleration constraints for goal. 036 */ 037 public ProfiledPIDController( 038 double Kp, double Ki, double Kd, TrapezoidProfile.Constraints constraints) { 039 this(Kp, Ki, Kd, constraints, 0.02); 040 } 041 042 /** 043 * Allocates a ProfiledPIDController with the given constants for Kp, Ki, and Kd. 044 * 045 * @param Kp The proportional coefficient. 046 * @param Ki The integral coefficient. 047 * @param Kd The derivative coefficient. 048 * @param constraints Velocity and acceleration constraints for goal. 049 * @param period The period between controller updates in seconds. The default is 0.02 seconds. 050 */ 051 public ProfiledPIDController( 052 double Kp, double Ki, double Kd, TrapezoidProfile.Constraints constraints, double period) { 053 m_controller = new PIDController(Kp, Ki, Kd, period); 054 m_constraints = constraints; 055 instances++; 056 057 SendableRegistry.add(this, "ProfiledPIDController", instances); 058 MathSharedStore.reportUsage(MathUsageId.kController_ProfiledPIDController, instances); 059 } 060 061 /** 062 * Sets the PID Controller gain parameters. 063 * 064 * <p>Sets the proportional, integral, and differential coefficients. 065 * 066 * @param Kp Proportional coefficient 067 * @param Ki Integral coefficient 068 * @param Kd Differential coefficient 069 */ 070 public void setPID(double Kp, double Ki, double Kd) { 071 m_controller.setPID(Kp, Ki, Kd); 072 } 073 074 /** 075 * Sets the proportional coefficient of the PID controller gain. 076 * 077 * @param Kp proportional coefficient 078 */ 079 public void setP(double Kp) { 080 m_controller.setP(Kp); 081 } 082 083 /** 084 * Sets the integral coefficient of the PID controller gain. 085 * 086 * @param Ki integral coefficient 087 */ 088 public void setI(double Ki) { 089 m_controller.setI(Ki); 090 } 091 092 /** 093 * Sets the differential coefficient of the PID controller gain. 094 * 095 * @param Kd differential coefficient 096 */ 097 public void setD(double Kd) { 098 m_controller.setD(Kd); 099 } 100 101 /** 102 * Sets the IZone range. When the absolute value of the position error is greater than IZone, the 103 * total accumulated error will reset to zero, disabling integral gain until the absolute value of 104 * the position error is less than IZone. This is used to prevent integral windup. Must be 105 * non-negative. Passing a value of zero will effectively disable integral gain. Passing a value 106 * of {@link Double#POSITIVE_INFINITY} disables IZone functionality. 107 * 108 * @param iZone Maximum magnitude of error to allow integral control. 109 */ 110 public void setIZone(double iZone) { 111 m_controller.setIZone(iZone); 112 } 113 114 /** 115 * Gets the proportional coefficient. 116 * 117 * @return proportional coefficient 118 */ 119 public double getP() { 120 return m_controller.getP(); 121 } 122 123 /** 124 * Gets the integral coefficient. 125 * 126 * @return integral coefficient 127 */ 128 public double getI() { 129 return m_controller.getI(); 130 } 131 132 /** 133 * Gets the differential coefficient. 134 * 135 * @return differential coefficient 136 */ 137 public double getD() { 138 return m_controller.getD(); 139 } 140 141 /** 142 * Get the IZone range. 143 * 144 * @return Maximum magnitude of error to allow integral control. 145 */ 146 public double getIZone() { 147 return m_controller.getIZone(); 148 } 149 150 /** 151 * Gets the period of this controller. 152 * 153 * @return The period of the controller. 154 */ 155 public double getPeriod() { 156 return m_controller.getPeriod(); 157 } 158 159 /** 160 * Returns the position tolerance of this controller. 161 * 162 * @return the position tolerance of the controller. 163 */ 164 public double getPositionTolerance() { 165 return m_controller.getPositionTolerance(); 166 } 167 168 /** 169 * Returns the velocity tolerance of this controller. 170 * 171 * @return the velocity tolerance of the controller. 172 */ 173 public double getVelocityTolerance() { 174 return m_controller.getVelocityTolerance(); 175 } 176 177 /** 178 * Sets the goal for the ProfiledPIDController. 179 * 180 * @param goal The desired goal state. 181 */ 182 public void setGoal(TrapezoidProfile.State goal) { 183 m_goal = goal; 184 } 185 186 /** 187 * Sets the goal for the ProfiledPIDController. 188 * 189 * @param goal The desired goal position. 190 */ 191 public void setGoal(double goal) { 192 m_goal = new TrapezoidProfile.State(goal, 0); 193 } 194 195 /** 196 * Gets the goal for the ProfiledPIDController. 197 * 198 * @return The goal. 199 */ 200 public TrapezoidProfile.State getGoal() { 201 return m_goal; 202 } 203 204 /** 205 * Returns true if the error is within the tolerance of the error. 206 * 207 * <p>This will return false until at least one input value has been computed. 208 * 209 * @return True if the error is within the tolerance of the error. 210 */ 211 public boolean atGoal() { 212 return atSetpoint() && m_goal.equals(m_setpoint); 213 } 214 215 /** 216 * Set velocity and acceleration constraints for goal. 217 * 218 * @param constraints Velocity and acceleration constraints for goal. 219 */ 220 public void setConstraints(TrapezoidProfile.Constraints constraints) { 221 m_constraints = constraints; 222 } 223 224 /** 225 * Get the velocity and acceleration constraints for this controller. 226 * 227 * @return Velocity and acceleration constraints. 228 */ 229 public TrapezoidProfile.Constraints getConstraints() { 230 return m_constraints; 231 } 232 233 /** 234 * Returns the current setpoint of the ProfiledPIDController. 235 * 236 * @return The current setpoint. 237 */ 238 public TrapezoidProfile.State getSetpoint() { 239 return m_setpoint; 240 } 241 242 /** 243 * Returns true if the error is within the tolerance of the error. 244 * 245 * <p>This will return false until at least one input value has been computed. 246 * 247 * @return True if the error is within the tolerance of the error. 248 */ 249 public boolean atSetpoint() { 250 return m_controller.atSetpoint(); 251 } 252 253 /** 254 * Enables continuous input. 255 * 256 * <p>Rather then using the max and min input range as constraints, it considers them to be the 257 * same point and automatically calculates the shortest route to the setpoint. 258 * 259 * @param minimumInput The minimum value expected from the input. 260 * @param maximumInput The maximum value expected from the input. 261 */ 262 public void enableContinuousInput(double minimumInput, double maximumInput) { 263 m_controller.enableContinuousInput(minimumInput, maximumInput); 264 m_minimumInput = minimumInput; 265 m_maximumInput = maximumInput; 266 } 267 268 /** Disables continuous input. */ 269 public void disableContinuousInput() { 270 m_controller.disableContinuousInput(); 271 } 272 273 /** 274 * Sets the minimum and maximum values for the integrator. 275 * 276 * <p>When the cap is reached, the integrator value is added to the controller output rather than 277 * the integrator value times the integral gain. 278 * 279 * @param minimumIntegral The minimum value of the integrator. 280 * @param maximumIntegral The maximum value of the integrator. 281 */ 282 public void setIntegratorRange(double minimumIntegral, double maximumIntegral) { 283 m_controller.setIntegratorRange(minimumIntegral, maximumIntegral); 284 } 285 286 /** 287 * Sets the error which is considered tolerable for use with atSetpoint(). 288 * 289 * @param positionTolerance Position error which is tolerable. 290 */ 291 public void setTolerance(double positionTolerance) { 292 setTolerance(positionTolerance, Double.POSITIVE_INFINITY); 293 } 294 295 /** 296 * Sets the error which is considered tolerable for use with atSetpoint(). 297 * 298 * @param positionTolerance Position error which is tolerable. 299 * @param velocityTolerance Velocity error which is tolerable. 300 */ 301 public void setTolerance(double positionTolerance, double velocityTolerance) { 302 m_controller.setTolerance(positionTolerance, velocityTolerance); 303 } 304 305 /** 306 * Returns the difference between the setpoint and the measurement. 307 * 308 * @return The error. 309 */ 310 public double getPositionError() { 311 return m_controller.getPositionError(); 312 } 313 314 /** 315 * Returns the change in error per second. 316 * 317 * @return The change in error per second. 318 */ 319 public double getVelocityError() { 320 return m_controller.getVelocityError(); 321 } 322 323 /** 324 * Returns the next output of the PID controller. 325 * 326 * @param measurement The current measurement of the process variable. 327 * @return The controller's next output. 328 */ 329 public double calculate(double measurement) { 330 if (m_controller.isContinuousInputEnabled()) { 331 // Get error which is the smallest distance between goal and measurement 332 double errorBound = (m_maximumInput - m_minimumInput) / 2.0; 333 double goalMinDistance = 334 MathUtil.inputModulus(m_goal.position - measurement, -errorBound, errorBound); 335 double setpointMinDistance = 336 MathUtil.inputModulus(m_setpoint.position - measurement, -errorBound, errorBound); 337 338 // Recompute the profile goal with the smallest error, thus giving the shortest path. The goal 339 // may be outside the input range after this operation, but that's OK because the controller 340 // will still go there and report an error of zero. In other words, the setpoint only needs to 341 // be offset from the measurement by the input range modulus; they don't need to be equal. 342 m_goal.position = goalMinDistance + measurement; 343 m_setpoint.position = setpointMinDistance + measurement; 344 } 345 346 var profile = new TrapezoidProfile(m_constraints, m_goal, m_setpoint); 347 m_setpoint = profile.calculate(getPeriod()); 348 return m_controller.calculate(measurement, m_setpoint.position); 349 } 350 351 /** 352 * Returns the next output of the PID controller. 353 * 354 * @param measurement The current measurement of the process variable. 355 * @param goal The new goal of the controller. 356 * @return The controller's next output. 357 */ 358 public double calculate(double measurement, TrapezoidProfile.State goal) { 359 setGoal(goal); 360 return calculate(measurement); 361 } 362 363 /** 364 * Returns the next output of the PIDController. 365 * 366 * @param measurement The current measurement of the process variable. 367 * @param goal The new goal of the controller. 368 * @return The controller's next output. 369 */ 370 public double calculate(double measurement, double goal) { 371 setGoal(goal); 372 return calculate(measurement); 373 } 374 375 /** 376 * Returns the next output of the PID controller. 377 * 378 * @param measurement The current measurement of the process variable. 379 * @param goal The new goal of the controller. 380 * @param constraints Velocity and acceleration constraints for goal. 381 * @return The controller's next output. 382 */ 383 public double calculate( 384 double measurement, TrapezoidProfile.State goal, TrapezoidProfile.Constraints constraints) { 385 setConstraints(constraints); 386 return calculate(measurement, goal); 387 } 388 389 /** 390 * Reset the previous error and the integral term. 391 * 392 * @param measurement The current measured State of the system. 393 */ 394 public void reset(TrapezoidProfile.State measurement) { 395 m_controller.reset(); 396 m_setpoint = measurement; 397 } 398 399 /** 400 * Reset the previous error and the integral term. 401 * 402 * @param measuredPosition The current measured position of the system. 403 * @param measuredVelocity The current measured velocity of the system. 404 */ 405 public void reset(double measuredPosition, double measuredVelocity) { 406 reset(new TrapezoidProfile.State(measuredPosition, measuredVelocity)); 407 } 408 409 /** 410 * Reset the previous error and the integral term. 411 * 412 * @param measuredPosition The current measured position of the system. The velocity is assumed to 413 * be zero. 414 */ 415 public void reset(double measuredPosition) { 416 reset(measuredPosition, 0.0); 417 } 418 419 @Override 420 public void initSendable(SendableBuilder builder) { 421 builder.setSmartDashboardType("ProfiledPIDController"); 422 builder.addDoubleProperty("p", this::getP, this::setP); 423 builder.addDoubleProperty("i", this::getI, this::setI); 424 builder.addDoubleProperty("d", this::getD, this::setD); 425 builder.addDoubleProperty("izone", this::getIZone, this::setIZone); 426 builder.addDoubleProperty("goal", () -> getGoal().position, this::setGoal); 427 } 428}