001/* 002 * Copyright (C) Cross The Road Electronics. All rights reserved. 003 * License information can be found in CTRE_LICENSE.txt 004 * For support and suggestions contact support@ctr-electronics.com or file 005 * an issue tracker at https://github.com/CrossTheRoadElec/Phoenix-Releases 006 */ 007package com.ctre.phoenix6.swerve.utility; 008 009import edu.wpi.first.math.MathSharedStore; 010import edu.wpi.first.math.MathUsageId; 011import edu.wpi.first.math.MathUtil; 012import edu.wpi.first.util.sendable.Sendable; 013import edu.wpi.first.util.sendable.SendableBuilder; 014import edu.wpi.first.util.sendable.SendableRegistry; 015 016/** 017 * Phoenix-centric PID controller taken from WPI's {@link edu.wpi.first.math.controller.PIDController} class. 018 * <p> 019 * This class differs from the WPI implementation by using explicit timestamps for 020 * integral/derivative calculations. Ideally, these timestamps come from the {@link com.ctre.phoenix6.StatusSignal}. 021 */ 022public class PhoenixPIDController implements Sendable, AutoCloseable { 023 private static int instances; 024 025 // Factor for "proportional" control 026 private double m_kp; 027 028 // Factor for "integral" control 029 private double m_ki; 030 031 // Factor for "derivative" control 032 private double m_kd; 033 034 // The error range where "integral" control applies 035 private double m_iZone = Double.POSITIVE_INFINITY; 036 037 private double m_maximumIntegral = 1.0; 038 039 private double m_minimumIntegral = -1.0; 040 041 private double m_maximumInput; 042 043 private double m_minimumInput; 044 045 // Do the endpoints wrap around? e.g. Absolute encoder 046 private boolean m_continuous; 047 048 // The error at the time of the most recent call to calculate() 049 private double m_positionError; 050 private double m_velocityError; 051 052 // The error at the time of the second-most-recent call to calculate() (used to compute velocity) 053 private double m_prevError; 054 055 // The sum of the errors for use in the integral calc 056 private double m_totalError; 057 058 // The error that is considered at setpoint. 059 private double m_positionTolerance = 0.05; 060 private double m_velocityTolerance = Double.POSITIVE_INFINITY; 061 062 private double m_setpoint; 063 private double m_measurement; 064 065 private boolean m_haveMeasurement; 066 private boolean m_haveSetpoint; 067 068 private double m_lastAppliedOutput; 069 070 // The last timestamp acquired when performing a calculation 071 private double m_lastTimestamp; 072 073 /** 074 * Allocates a PIDController with the given constants for kp, ki, and kd. 075 * 076 * @param kp The proportional coefficient. 077 * @param ki The integral coefficient. 078 * @param kd The derivative coefficient. 079 */ 080 public PhoenixPIDController(double kp, double ki, double kd) { 081 m_kp = kp; 082 m_ki = ki; 083 m_kd = kd; 084 085 instances++; 086 SendableRegistry.addLW(this, "PIDController", instances); 087 088 MathSharedStore.reportUsage(MathUsageId.kController_PIDController2, instances); 089 } 090 091 @Override 092 public void close() { 093 SendableRegistry.remove(this); 094 } 095 096 /** 097 * Sets the PID Controller gain parameters. 098 * 099 * <p>Set the proportional, integral, and differential coefficients. 100 * 101 * @param kp The proportional coefficient. 102 * @param ki The integral coefficient. 103 * @param kd The derivative coefficient. 104 */ 105 public void setPID(double kp, double ki, double kd) { 106 m_kp = kp; 107 m_ki = ki; 108 m_kd = kd; 109 } 110 111 /** 112 * Sets the Proportional coefficient of the PID controller gain. 113 * 114 * @param kp proportional coefficient 115 */ 116 public void setP(double kp) { 117 m_kp = kp; 118 } 119 120 /** 121 * Sets the Integral coefficient of the PID controller gain. 122 * 123 * @param ki integral coefficient 124 */ 125 public void setI(double ki) { 126 m_ki = ki; 127 } 128 129 /** 130 * Sets the Differential coefficient of the PID controller gain. 131 * 132 * @param kd differential coefficient 133 */ 134 public void setD(double kd) { 135 m_kd = kd; 136 } 137 138 /** 139 * Sets the IZone range. When the absolute value of the position error is greater than IZone, the 140 * total accumulated error will reset to zero, disabling integral gain until the absolute value of 141 * the position error is less than IZone. This is used to prevent integral windup. Must be 142 * non-negative. Passing a value of zero will effectively disable integral gain. Passing a value 143 * of {@link Double#POSITIVE_INFINITY} disables IZone functionality. 144 * 145 * @param iZone Maximum magnitude of error to allow integral control. 146 * @throws IllegalArgumentException if iZone < 0 147 */ 148 public void setIZone(double iZone) { 149 if (iZone < 0) { 150 throw new IllegalArgumentException("IZone must be a non-negative number!"); 151 } 152 m_iZone = iZone; 153 } 154 155 /** 156 * Get the Proportional coefficient. 157 * 158 * @return proportional coefficient 159 */ 160 public double getP() { 161 return m_kp; 162 } 163 164 /** 165 * Get the Integral coefficient. 166 * 167 * @return integral coefficient 168 */ 169 public double getI() { 170 return m_ki; 171 } 172 173 /** 174 * Get the Differential coefficient. 175 * 176 * @return differential coefficient 177 */ 178 public double getD() { 179 return m_kd; 180 } 181 182 /** 183 * Get the IZone range. 184 * 185 * @return Maximum magnitude of error to allow integral control. 186 */ 187 public double getIZone() { 188 return m_iZone; 189 } 190 191 /** 192 * Returns the position tolerance of this controller. 193 * 194 * @return the position tolerance of the controller. 195 */ 196 public double getPositionTolerance() { 197 return m_positionTolerance; 198 } 199 200 /** 201 * Returns the velocity tolerance of this controller. 202 * 203 * @return the velocity tolerance of the controller. 204 */ 205 public double getVelocityTolerance() { 206 return m_velocityTolerance; 207 } 208 209 /** 210 * Returns the current setpoint of the PIDController. 211 * 212 * @return The current setpoint. 213 */ 214 public double getSetpoint() { 215 return m_setpoint; 216 } 217 218 /** 219 * Returns true if the error is within the tolerance of the setpoint. 220 * 221 * <p>This will return false until at least one input value has been computed. 222 * 223 * @return Whether the error is within the acceptable bounds. 224 */ 225 public boolean atSetpoint() { 226 return m_haveMeasurement 227 && m_haveSetpoint 228 && Math.abs(m_positionError) < m_positionTolerance 229 && Math.abs(m_velocityError) < m_velocityTolerance; 230 } 231 232 /** 233 * Enables continuous input. 234 * 235 * <p>Rather then using the max and min input range as constraints, it considers them to be the 236 * same point and automatically calculates the shortest route to the setpoint. 237 * 238 * @param minimumInput The minimum value expected from the input. 239 * @param maximumInput The maximum value expected from the input. 240 */ 241 public void enableContinuousInput(double minimumInput, double maximumInput) { 242 m_continuous = true; 243 m_minimumInput = minimumInput; 244 m_maximumInput = maximumInput; 245 } 246 247 /** Disables continuous input. */ 248 public void disableContinuousInput() { 249 m_continuous = false; 250 } 251 252 /** 253 * Returns true if continuous input is enabled. 254 * 255 * @return True if continuous input is enabled. 256 */ 257 public boolean isContinuousInputEnabled() { 258 return m_continuous; 259 } 260 261 /** 262 * Sets the minimum and maximum values for the integrator. 263 * 264 * <p>When the cap is reached, the integrator value is added to the controller output rather than 265 * the integrator value times the integral gain. 266 * 267 * @param minimumIntegral The minimum value of the integrator. 268 * @param maximumIntegral The maximum value of the integrator. 269 */ 270 public void setIntegratorRange(double minimumIntegral, double maximumIntegral) { 271 m_minimumIntegral = minimumIntegral; 272 m_maximumIntegral = maximumIntegral; 273 } 274 275 /** 276 * Sets the error which is considered tolerable for use with atSetpoint(). 277 * 278 * @param positionTolerance Position error which is tolerable. 279 */ 280 public void setTolerance(double positionTolerance) { 281 setTolerance(positionTolerance, Double.POSITIVE_INFINITY); 282 } 283 284 /** 285 * Sets the error which is considered tolerable for use with atSetpoint(). 286 * 287 * @param positionTolerance Position error which is tolerable. 288 * @param velocityTolerance Velocity error which is tolerable. 289 */ 290 public void setTolerance(double positionTolerance, double velocityTolerance) { 291 m_positionTolerance = positionTolerance; 292 m_velocityTolerance = velocityTolerance; 293 } 294 295 /** 296 * Returns the difference between the setpoint and the measurement. 297 * 298 * @return The error. 299 */ 300 public double getPositionError() { 301 return m_positionError; 302 } 303 304 /** 305 * Returns the velocity error. 306 * 307 * @return The velocity error. 308 */ 309 public double getVelocityError() { 310 return m_velocityError; 311 } 312 313 /** 314 * Returns the next output of the PID controller. 315 * 316 * @param measurement The current measurement of the process variable. 317 * @param setpoint The setpoint to target 318 * @param currentTimestamp The current timestamp to use for calculating integral/derivative error 319 * @return The next controller output. 320 */ 321 public double calculate(double measurement, double setpoint, double currentTimestamp) { 322 m_setpoint = setpoint; 323 m_haveSetpoint = true; 324 m_measurement = measurement; 325 m_haveMeasurement = true; 326 m_prevError = m_positionError; 327 328 double thisPeriod = currentTimestamp - m_lastTimestamp; 329 m_lastTimestamp = currentTimestamp; 330 331 if (m_continuous) { 332 double errorBound = (m_maximumInput - m_minimumInput) / 2.0; 333 m_positionError = MathUtil.inputModulus(m_setpoint - m_measurement, -errorBound, errorBound); 334 } else { 335 m_positionError = m_setpoint - m_measurement; 336 } 337 338 m_velocityError = (m_positionError - m_prevError) / thisPeriod; 339 340 // If the absolute value of the position error is greater than IZone, reset the total error 341 if (Math.abs(m_positionError) > m_iZone) { 342 m_totalError = 0; 343 } else if (m_ki != 0) { 344 m_totalError = 345 MathUtil.clamp( 346 m_totalError + m_positionError * thisPeriod, 347 m_minimumIntegral / m_ki, 348 m_maximumIntegral / m_ki); 349 } 350 351 m_lastAppliedOutput = m_kp * m_positionError + m_ki * m_totalError + m_kd * m_velocityError; 352 return m_lastAppliedOutput; 353 } 354 355 /** Returns the last applied output from this PID controller. */ 356 public double getLastAppliedOutput() { 357 return m_lastAppliedOutput; 358 } 359 360 /** Resets the previous error and the integral term. */ 361 public void reset() { 362 m_positionError = 0; 363 m_prevError = 0; 364 m_totalError = 0; 365 m_velocityError = 0; 366 m_haveMeasurement = false; 367 } 368 369 @Override 370 public void initSendable(SendableBuilder builder) { 371 builder.setSmartDashboardType("PIDController"); 372 builder.addDoubleProperty("p", this::getP, this::setP); 373 builder.addDoubleProperty("i", this::getI, this::setI); 374 builder.addDoubleProperty("d", this::getD, this::setD); 375 builder.addDoubleProperty("setpoint", this::getSetpoint, null); 376 } 377}