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