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.ArmFeedforwardProto; 008import edu.wpi.first.math.controller.struct.ArmFeedforwardStruct; 009import edu.wpi.first.math.jni.ArmFeedforwardJNI; 010import edu.wpi.first.util.protobuf.ProtobufSerializable; 011import edu.wpi.first.util.struct.StructSerializable; 012 013/** 014 * A helper class that computes feedforward outputs for a simple arm (modeled as a motor acting 015 * against the force of gravity on a beam suspended at an angle). 016 */ 017public class ArmFeedforward implements ProtobufSerializable, StructSerializable { 018 /** The static gain, in volts. */ 019 private double ks; 020 021 /** The gravity gain, in volts. */ 022 private double kg; 023 024 /** The velocity gain, in V/(rad/s). */ 025 private double kv; 026 027 /** The acceleration gain, in V/(rad/s²). */ 028 private double ka; 029 030 /** The period, in seconds. */ 031 private final double m_dt; 032 033 /** 034 * Creates a new ArmFeedforward with the specified gains and period. 035 * 036 * @param ks The static gain in volts. 037 * @param kg The gravity gain in volts. 038 * @param kv The velocity gain in V/(rad/s). 039 * @param ka The acceleration gain in V/(rad/s²). 040 * @param dtSeconds The period in seconds. 041 * @throws IllegalArgumentException for kv < zero. 042 * @throws IllegalArgumentException for ka < zero. 043 * @throws IllegalArgumentException for period ≤ zero. 044 */ 045 public ArmFeedforward(double ks, double kg, double kv, double ka, double dtSeconds) { 046 this.ks = ks; 047 this.kg = kg; 048 this.kv = kv; 049 this.ka = ka; 050 if (kv < 0.0) { 051 throw new IllegalArgumentException("kv must be a non-negative number, got " + kv + "!"); 052 } 053 if (ka < 0.0) { 054 throw new IllegalArgumentException("ka must be a non-negative number, got " + ka + "!"); 055 } 056 if (dtSeconds <= 0.0) { 057 throw new IllegalArgumentException( 058 "period must be a positive number, got " + dtSeconds + "!"); 059 } 060 m_dt = dtSeconds; 061 } 062 063 /** 064 * Creates a new ArmFeedforward with the specified gains. The period is defaulted to 20 ms. 065 * 066 * @param ks The static gain in volts. 067 * @param kg The gravity gain in volts. 068 * @param kv The velocity gain in V/(rad/s). 069 * @param ka The acceleration gain in V/(rad/s²). 070 * @throws IllegalArgumentException for kv < zero. 071 * @throws IllegalArgumentException for ka < zero. 072 */ 073 public ArmFeedforward(double ks, double kg, double kv, double ka) { 074 this(ks, kg, kv, ka, 0.020); 075 } 076 077 /** 078 * Creates a new ArmFeedforward with the specified gains. 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/(rad/s). 083 * @throws IllegalArgumentException for kv < zero. 084 */ 085 public ArmFeedforward(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/(rad/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/(rad/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/(rad/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/(rad/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. 172 * 173 * @param positionRadians The position (angle) setpoint. This angle should be measured from the 174 * horizontal (i.e. if the provided angle is 0, the arm should be parallel with the floor). If 175 * your encoder does not follow this convention, an offset should be added. 176 * @param velocityRadPerSec The velocity setpoint. 177 * @param accelRadPerSecSquared The acceleration setpoint. 178 * @return The computed feedforward. 179 * @deprecated Use {@link #calculateWithVelocities(double, double, double)} instead 180 */ 181 @Deprecated(forRemoval = true, since = "2025") 182 public double calculate( 183 double positionRadians, double velocityRadPerSec, double accelRadPerSecSquared) { 184 return ks * Math.signum(velocityRadPerSec) 185 + kg * Math.cos(positionRadians) 186 + kv * velocityRadPerSec 187 + ka * accelRadPerSecSquared; 188 } 189 190 /** 191 * Calculates the feedforward from the gains and velocity setpoint assuming continuous control 192 * (acceleration is assumed to be zero). 193 * 194 * @param positionRadians The position (angle) setpoint. This angle should be measured from the 195 * horizontal (i.e. if the provided angle is 0, the arm should be parallel with the floor). If 196 * your encoder does not follow this convention, an offset should be added. 197 * @param velocity The velocity setpoint. 198 * @return The computed feedforward. 199 */ 200 public double calculate(double positionRadians, double velocity) { 201 return calculate(positionRadians, velocity, 0); 202 } 203 204 /** 205 * Calculates the feedforward from the gains and setpoints assuming continuous control. 206 * 207 * @param currentAngle The current angle in radians. This angle should be measured from the 208 * horizontal (i.e. if the provided angle is 0, the arm should be parallel to the floor). If 209 * your encoder does not follow this convention, an offset should be added. 210 * @param currentVelocity The current velocity setpoint in radians per second. 211 * @param nextVelocity The next velocity setpoint in radians per second. 212 * @param dt Time between velocity setpoints in seconds. 213 * @return The computed feedforward in volts. 214 * @deprecated Use {@link #calculateWithVelocities(double, double, double)} instead. 215 */ 216 @SuppressWarnings("removal") 217 @Deprecated(forRemoval = true, since = "2025") 218 public double calculate( 219 double currentAngle, double currentVelocity, double nextVelocity, double dt) { 220 return ArmFeedforwardJNI.calculate( 221 ks, kv, ka, kg, currentAngle, currentVelocity, nextVelocity, dt); 222 } 223 224 /** 225 * Calculates the feedforward from the gains and setpoints assuming discrete control. 226 * 227 * @param currentAngle The current angle in radians. This angle should be measured from the 228 * horizontal (i.e. if the provided angle is 0, the arm should be parallel to the floor). If 229 * your encoder does not follow this convention, an offset should be added. 230 * @param currentVelocity The current velocity setpoint in radians per second. 231 * @param nextVelocity The next velocity setpoint in radians per second. 232 * @return The computed feedforward in volts. 233 */ 234 public double calculateWithVelocities( 235 double currentAngle, double currentVelocity, double nextVelocity) { 236 return ArmFeedforwardJNI.calculate( 237 ks, kv, ka, kg, currentAngle, currentVelocity, nextVelocity, m_dt); 238 } 239 240 // Rearranging the main equation from the calculate() method yields the 241 // formulas for the methods below: 242 243 /** 244 * Calculates the maximum achievable velocity given a maximum voltage supply, a position, and an 245 * acceleration. Useful for ensuring that velocity and acceleration constraints for a trapezoidal 246 * profile are simultaneously achievable - enter the acceleration constraint, and this will give 247 * you a simultaneously-achievable velocity constraint. 248 * 249 * @param maxVoltage The maximum voltage that can be supplied to the arm. 250 * @param angle The angle of the arm, in radians. This angle should be measured from the 251 * horizontal (i.e. if the provided angle is 0, the arm should be parallel with the floor). If 252 * your encoder does not follow this convention, an offset should be added. 253 * @param acceleration The acceleration of the arm, in (rad/s²). 254 * @return The maximum possible velocity in (rad/s) at the given acceleration and angle. 255 */ 256 public double maxAchievableVelocity(double maxVoltage, double angle, double acceleration) { 257 // Assume max velocity is positive 258 return (maxVoltage - ks - Math.cos(angle) * kg - acceleration * ka) / kv; 259 } 260 261 /** 262 * Calculates the minimum achievable velocity given a maximum voltage supply, a position, and an 263 * acceleration. Useful for ensuring that velocity and acceleration constraints for a trapezoidal 264 * profile are simultaneously achievable - enter the acceleration constraint, and this will give 265 * you a simultaneously-achievable velocity constraint. 266 * 267 * @param maxVoltage The maximum voltage that can be supplied to the arm, in volts. 268 * @param angle The angle of the arm, in radians. This angle should be measured from the 269 * horizontal (i.e. if the provided angle is 0, the arm should be parallel with the floor). If 270 * your encoder does not follow this convention, an offset should be added. 271 * @param acceleration The acceleration of the arm, in (rad/s²). 272 * @return The minimum possible velocity in (rad/s) at the given acceleration and angle. 273 */ 274 public double minAchievableVelocity(double maxVoltage, double angle, double acceleration) { 275 // Assume min velocity is negative, ks flips sign 276 return (-maxVoltage + ks - Math.cos(angle) * kg - acceleration * ka) / kv; 277 } 278 279 /** 280 * Calculates the maximum achievable acceleration given a maximum voltage supply, a position, and 281 * a velocity. Useful for ensuring that velocity and acceleration constraints for a trapezoidal 282 * profile are simultaneously achievable - enter the velocity constraint, and this will give you a 283 * simultaneously-achievable acceleration constraint. 284 * 285 * @param maxVoltage The maximum voltage that can be supplied to the arm, in volts. 286 * @param angle The angle of the arm, in radians. This angle should be measured from the 287 * horizontal (i.e. if the provided angle is 0, the arm should be parallel with the floor). If 288 * your encoder does not follow this convention, an offset should be added. 289 * @param velocity The velocity of the elevator, in (rad/s) 290 * @return The maximum possible acceleration in (rad/s²) at the given velocity. 291 */ 292 public double maxAchievableAcceleration(double maxVoltage, double angle, double velocity) { 293 return (maxVoltage - ks * Math.signum(velocity) - Math.cos(angle) * kg - velocity * kv) / ka; 294 } 295 296 /** 297 * Calculates the minimum achievable acceleration given a maximum voltage supply, a position, and 298 * a velocity. Useful for ensuring that velocity and acceleration constraints for a trapezoidal 299 * profile are simultaneously achievable - enter the velocity constraint, and this will give you a 300 * simultaneously-achievable acceleration constraint. 301 * 302 * @param maxVoltage The maximum voltage that can be supplied to the arm, in volts. 303 * @param angle The angle of the arm, in radians. This angle should be measured from the 304 * horizontal (i.e. if the provided angle is 0, the arm should be parallel with the floor). If 305 * your encoder does not follow this convention, an offset should be added. 306 * @param velocity The velocity of the elevator, in (rad/s) 307 * @return The maximum possible acceleration in (rad/s²) at the given velocity. 308 */ 309 public double minAchievableAcceleration(double maxVoltage, double angle, double velocity) { 310 return maxAchievableAcceleration(-maxVoltage, angle, velocity); 311 } 312 313 /** Arm feedforward struct for serialization. */ 314 public static final ArmFeedforwardStruct struct = new ArmFeedforwardStruct(); 315 316 /** Arm feedforward protobuf for serialization. */ 317 public static final ArmFeedforwardProto proto = new ArmFeedforwardProto(); 318}