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 */ 177 @SuppressWarnings("removal") 178 @Deprecated(forRemoval = true, since = "2025") 179 public double calculate(double velocity, double acceleration) { 180 return ks * Math.signum(velocity) + kg + kv * velocity + ka * acceleration; 181 } 182 183 /** 184 * Calculates the feedforward from the gains and velocity setpoint assuming continuous control 185 * (acceleration is assumed to be zero). 186 * 187 * @param velocity The velocity setpoint. 188 * @return The computed feedforward. 189 */ 190 public double calculate(double velocity) { 191 return calculate(velocity, 0); 192 } 193 194 /** 195 * Calculates the feedforward from the gains and setpoints assuming discrete control. 196 * 197 * <p>Note this method is inaccurate when the velocity crosses 0. 198 * 199 * @param currentVelocity The current velocity setpoint in meters per second. 200 * @param nextVelocity The next velocity setpoint in meters per second. 201 * @return The computed feedforward in volts. 202 */ 203 public double calculateWithVelocities(double currentVelocity, double nextVelocity) { 204 // See wpimath/algorithms.md#Elevator_feedforward for derivation 205 if (ka == 0.0) { 206 return ks * Math.signum(nextVelocity) + kg + kv * nextVelocity; 207 } else { 208 double A = -kv / ka; 209 double B = 1.0 / ka; 210 double A_d = Math.exp(A * m_dt); 211 double B_d = 1.0 / A * (A_d - 1.0) * B; 212 return kg 213 + ks * Math.signum(currentVelocity) 214 + 1.0 / B_d * (nextVelocity - A_d * currentVelocity); 215 } 216 } 217 218 // Rearranging the main equation from the calculate() method yields the 219 // formulas for the methods below: 220 221 /** 222 * Calculates the maximum achievable velocity given a maximum voltage supply and an acceleration. 223 * Useful for ensuring that velocity and acceleration constraints for a trapezoidal profile are 224 * simultaneously achievable - enter the acceleration constraint, and this will give you a 225 * simultaneously-achievable velocity constraint. 226 * 227 * @param maxVoltage The maximum voltage that can be supplied to the elevator, in volts. 228 * @param acceleration The acceleration of the elevator, in (m/s²). 229 * @return The maximum possible velocity in (m/s) at the given acceleration. 230 */ 231 public double maxAchievableVelocity(double maxVoltage, double acceleration) { 232 // Assume max velocity is positive 233 return (maxVoltage - ks - kg - acceleration * ka) / kv; 234 } 235 236 /** 237 * Calculates the minimum achievable velocity given a maximum voltage supply and an acceleration. 238 * Useful for ensuring that velocity and acceleration constraints for a trapezoidal profile are 239 * simultaneously achievable - enter the acceleration constraint, and this will give you a 240 * simultaneously-achievable velocity constraint. 241 * 242 * @param maxVoltage The maximum voltage that can be supplied to the elevator, in volts. 243 * @param acceleration The acceleration of the elevator, in (m/s²). 244 * @return The maximum possible velocity in (m/s) at the given acceleration. 245 */ 246 public double minAchievableVelocity(double maxVoltage, double acceleration) { 247 // Assume min velocity is negative, ks flips sign 248 return (-maxVoltage + ks - kg - acceleration * ka) / kv; 249 } 250 251 /** 252 * Calculates the maximum achievable acceleration given a maximum voltage supply and a velocity. 253 * Useful for ensuring that velocity and acceleration constraints for a trapezoidal profile are 254 * simultaneously achievable - enter the velocity constraint, and this will give you a 255 * simultaneously-achievable acceleration constraint. 256 * 257 * @param maxVoltage The maximum voltage that can be supplied to the elevator, in volts. 258 * @param velocity The velocity of the elevator, in (m/s) 259 * @return The maximum possible acceleration in (m/s²) at the given velocity. 260 */ 261 public double maxAchievableAcceleration(double maxVoltage, double velocity) { 262 return (maxVoltage - ks * Math.signum(velocity) - kg - velocity * kv) / ka; 263 } 264 265 /** 266 * Calculates the minimum achievable acceleration given a maximum voltage supply and a velocity. 267 * Useful for ensuring that velocity and acceleration constraints for a trapezoidal profile are 268 * simultaneously achievable - enter the velocity constraint, and this will give you a 269 * simultaneously-achievable acceleration constraint. 270 * 271 * @param maxVoltage The maximum voltage that can be supplied to the elevator, in volts. 272 * @param velocity The velocity of the elevator, in (m/s) 273 * @return The maximum possible acceleration in (m/s²) at the given velocity. 274 */ 275 public double minAchievableAcceleration(double maxVoltage, double velocity) { 276 return maxAchievableAcceleration(-maxVoltage, velocity); 277 } 278 279 /** ElevatorFeedforward struct for serialization. */ 280 public static final ElevatorFeedforwardStruct struct = new ElevatorFeedforwardStruct(); 281 282 /** ElevatorFeedforward protobuf for serialization. */ 283 public static final ElevatorFeedforwardProto proto = new ElevatorFeedforwardProto(); 284}