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.controller.proto.ElevatorFeedforwardProto; 008import edu.wpi.first.math.controller.struct.ElevatorFeedforwardStruct; 009import edu.wpi.first.util.protobuf.ProtobufSerializable; 010import edu.wpi.first.util.struct.StructSerializable; 011 012/** 013 * A helper class that computes feedforward outputs for a simple elevator (modeled as a motor acting 014 * against the force of gravity). 015 */ 016public class ElevatorFeedforward implements ProtobufSerializable, StructSerializable { 017 /** The static gain, in volts. */ 018 private double ks; 019 020 /** The gravity gain, in volts. */ 021 private double kg; 022 023 /** The velocity gain, in V/(m/s). */ 024 private double kv; 025 026 /** The acceleration gain, in V/(m/s²). */ 027 private double ka; 028 029 /** The period, in seconds. */ 030 private final double m_dt; 031 032 /** 033 * Creates a new ElevatorFeedforward with the specified gains and period. 034 * 035 * @param ks The static gain in volts. 036 * @param kg The gravity gain in volts. 037 * @param kv The velocity gain in V/(m/s). 038 * @param ka The acceleration gain in V/(m/s²). 039 * @param dtSeconds The period in seconds. 040 * @throws IllegalArgumentException for kv < zero. 041 * @throws IllegalArgumentException for ka < zero. 042 * @throws IllegalArgumentException for period ≤ zero. 043 */ 044 public ElevatorFeedforward(double ks, double kg, double kv, double ka, double dtSeconds) { 045 this.ks = ks; 046 this.kg = kg; 047 this.kv = kv; 048 this.ka = ka; 049 if (kv < 0.0) { 050 throw new IllegalArgumentException("kv must be a non-negative number, got " + kv + "!"); 051 } 052 if (ka < 0.0) { 053 throw new IllegalArgumentException("ka must be a non-negative number, got " + ka + "!"); 054 } 055 if (dtSeconds <= 0.0) { 056 throw new IllegalArgumentException( 057 "period must be a positive number, got " + dtSeconds + "!"); 058 } 059 m_dt = dtSeconds; 060 } 061 062 /** 063 * Creates a new ElevatorFeedforward with the specified gains. The period is defaulted to 20 ms. 064 * 065 * @param ks The static gain in volts. 066 * @param kg The gravity gain in volts. 067 * @param kv The velocity gain in V/(m/s). 068 * @param ka The acceleration gain in V/(m/s²). 069 * @throws IllegalArgumentException for kv < zero. 070 * @throws IllegalArgumentException for ka < zero. 071 */ 072 public ElevatorFeedforward(double ks, double kg, double kv, double ka) { 073 this(ks, kg, kv, ka, 0.020); 074 } 075 076 /** 077 * Creates a new ElevatorFeedforward with the specified gains. Acceleration gain is defaulted to 078 * zero. The period is defaulted to 20 ms. 079 * 080 * @param ks The static gain in volts. 081 * @param kg The gravity gain in volts. 082 * @param kv The velocity gain in V/(m/s). 083 * @throws IllegalArgumentException for kv < zero. 084 */ 085 public ElevatorFeedforward(double ks, double kg, double kv) { 086 this(ks, kg, kv, 0); 087 } 088 089 /** 090 * Sets the static gain. 091 * 092 * @param ks The static gain in volts. 093 */ 094 public void setKs(double ks) { 095 this.ks = ks; 096 } 097 098 /** 099 * Sets the gravity gain. 100 * 101 * @param kg The gravity gain in volts. 102 */ 103 public void setKg(double kg) { 104 this.kg = kg; 105 } 106 107 /** 108 * Sets the velocity gain. 109 * 110 * @param kv The velocity gain in V/(m/s). 111 */ 112 public void setKv(double kv) { 113 this.kv = kv; 114 } 115 116 /** 117 * Sets the acceleration gain. 118 * 119 * @param ka The acceleration gain in V/(m/s²). 120 */ 121 public void setKa(double ka) { 122 this.ka = ka; 123 } 124 125 /** 126 * Returns the static gain in volts. 127 * 128 * @return The static gain in volts. 129 */ 130 public double getKs() { 131 return ks; 132 } 133 134 /** 135 * Returns the gravity gain in volts. 136 * 137 * @return The gravity gain in volts. 138 */ 139 public double getKg() { 140 return kg; 141 } 142 143 /** 144 * Returns the velocity gain in V/(m/s). 145 * 146 * @return The velocity gain. 147 */ 148 public double getKv() { 149 return kv; 150 } 151 152 /** 153 * Returns the acceleration gain in V/(m/s²). 154 * 155 * @return The acceleration gain. 156 */ 157 public double getKa() { 158 return ka; 159 } 160 161 /** 162 * Returns the period in seconds. 163 * 164 * @return The period in seconds. 165 */ 166 public double getDt() { 167 return m_dt; 168 } 169 170 /** 171 * Calculates the feedforward from the gains and setpoints assuming continuous control. 172 * 173 * @param velocity The velocity setpoint. 174 * @param acceleration The acceleration setpoint. 175 * @return The computed feedforward. 176 * @deprecated Use {@link #calculateWithVelocities(double, double)}. 177 */ 178 @SuppressWarnings("removal") 179 @Deprecated(forRemoval = true, since = "2025") 180 public double calculate(double velocity, double acceleration) { 181 return ks * Math.signum(velocity) + kg + kv * velocity + ka * acceleration; 182 } 183 184 /** 185 * Calculates the feedforward from the gains and velocity setpoint assuming continuous control 186 * (acceleration is assumed to be zero). 187 * 188 * @param velocity The velocity setpoint. 189 * @return The computed feedforward. 190 */ 191 public double calculate(double velocity) { 192 return calculate(velocity, 0); 193 } 194 195 /** 196 * Calculates the feedforward from the gains and setpoints assuming discrete control. 197 * 198 * <p>Note this method is inaccurate when the velocity crosses 0. 199 * 200 * @param currentVelocity The current velocity setpoint in meters per second. 201 * @param nextVelocity The next velocity setpoint in meters per second. 202 * @return The computed feedforward in volts. 203 */ 204 public double calculateWithVelocities(double currentVelocity, double nextVelocity) { 205 // See wpimath/algorithms.md#Elevator_feedforward for derivation 206 if (ka < 1e-9) { 207 return ks * Math.signum(nextVelocity) + kg + kv * nextVelocity; 208 } else { 209 double A = -kv / ka; 210 double B = 1.0 / ka; 211 double A_d = Math.exp(A * m_dt); 212 double B_d = A > -1e-9 ? B * m_dt : 1.0 / A * (A_d - 1.0) * B; 213 return kg 214 + ks * Math.signum(currentVelocity) 215 + 1.0 / B_d * (nextVelocity - A_d * currentVelocity); 216 } 217 } 218 219 // Rearranging the main equation from the calculate() method yields the 220 // formulas for the methods below: 221 222 /** 223 * Calculates the maximum achievable velocity given a maximum voltage supply and an acceleration. 224 * Useful for ensuring that velocity and acceleration constraints for a trapezoidal profile are 225 * simultaneously achievable - enter the acceleration constraint, and this will give you a 226 * simultaneously-achievable velocity constraint. 227 * 228 * @param maxVoltage The maximum voltage that can be supplied to the elevator, in volts. 229 * @param acceleration The acceleration of the elevator, in (m/s²). 230 * @return The maximum possible velocity in (m/s) at the given acceleration. 231 */ 232 public double maxAchievableVelocity(double maxVoltage, double acceleration) { 233 // Assume max velocity is positive 234 return (maxVoltage - ks - kg - acceleration * ka) / kv; 235 } 236 237 /** 238 * Calculates the minimum achievable velocity given a maximum voltage supply and an acceleration. 239 * Useful for ensuring that velocity and acceleration constraints for a trapezoidal profile are 240 * simultaneously achievable - enter the acceleration constraint, and this will give you a 241 * simultaneously-achievable velocity constraint. 242 * 243 * @param maxVoltage The maximum voltage that can be supplied to the elevator, in volts. 244 * @param acceleration The acceleration of the elevator, in (m/s²). 245 * @return The maximum possible velocity in (m/s) at the given acceleration. 246 */ 247 public double minAchievableVelocity(double maxVoltage, double acceleration) { 248 // Assume min velocity is negative, ks flips sign 249 return (-maxVoltage + ks - kg - acceleration * ka) / kv; 250 } 251 252 /** 253 * Calculates the maximum achievable acceleration given a maximum voltage supply and a velocity. 254 * Useful for ensuring that velocity and acceleration constraints for a trapezoidal profile are 255 * simultaneously achievable - enter the velocity constraint, and this will give you a 256 * simultaneously-achievable acceleration constraint. 257 * 258 * @param maxVoltage The maximum voltage that can be supplied to the elevator, in volts. 259 * @param velocity The velocity of the elevator, in (m/s) 260 * @return The maximum possible acceleration in (m/s²) at the given velocity. 261 */ 262 public double maxAchievableAcceleration(double maxVoltage, double velocity) { 263 return (maxVoltage - ks * Math.signum(velocity) - kg - velocity * kv) / ka; 264 } 265 266 /** 267 * Calculates the minimum achievable acceleration given a maximum voltage supply and a velocity. 268 * Useful for ensuring that velocity and acceleration constraints for a trapezoidal profile are 269 * simultaneously achievable - enter the velocity constraint, and this will give you a 270 * simultaneously-achievable acceleration constraint. 271 * 272 * @param maxVoltage The maximum voltage that can be supplied to the elevator, in volts. 273 * @param velocity The velocity of the elevator, in (m/s) 274 * @return The maximum possible acceleration in (m/s²) at the given velocity. 275 */ 276 public double minAchievableAcceleration(double maxVoltage, double velocity) { 277 return maxAchievableAcceleration(-maxVoltage, velocity); 278 } 279 280 /** ElevatorFeedforward struct for serialization. */ 281 public static final ElevatorFeedforwardStruct struct = new ElevatorFeedforwardStruct(); 282 283 /** ElevatorFeedforward protobuf for serialization. */ 284 public static final ElevatorFeedforwardProto proto = new ElevatorFeedforwardProto(); 285}