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 */ 180 @Deprecated(forRemoval = true, since = "2025") 181 public double calculate( 182 double positionRadians, double velocityRadPerSec, double accelRadPerSecSquared) { 183 return ks * Math.signum(velocityRadPerSec) 184 + kg * Math.cos(positionRadians) 185 + kv * velocityRadPerSec 186 + ka * accelRadPerSecSquared; 187 } 188 189 /** 190 * Calculates the feedforward from the gains and velocity setpoint assuming continuous control 191 * (acceleration is assumed to be zero). 192 * 193 * @param positionRadians The position (angle) setpoint. This angle should be measured from the 194 * horizontal (i.e. if the provided angle is 0, the arm should be parallel with the floor). If 195 * your encoder does not follow this convention, an offset should be added. 196 * @param velocity The velocity setpoint. 197 * @return The computed feedforward. 198 */ 199 public double calculate(double positionRadians, double velocity) { 200 return calculate(positionRadians, velocity, 0); 201 } 202 203 /** 204 * Calculates the feedforward from the gains and setpoints assuming continuous control. 205 * 206 * @param currentAngle The current angle in radians. This angle should be measured from the 207 * horizontal (i.e. if the provided angle is 0, the arm should be parallel to the floor). If 208 * your encoder does not follow this convention, an offset should be added. 209 * @param currentVelocity The current velocity setpoint in radians per second. 210 * @param nextVelocity The next velocity setpoint in radians per second. 211 * @param dt Time between velocity setpoints in seconds. 212 * @return The computed feedforward in volts. 213 */ 214 @SuppressWarnings("removal") 215 @Deprecated(forRemoval = true, since = "2025") 216 public double calculate( 217 double currentAngle, double currentVelocity, double nextVelocity, double dt) { 218 return ArmFeedforwardJNI.calculate( 219 ks, kv, ka, kg, currentAngle, currentVelocity, nextVelocity, dt); 220 } 221 222 /** 223 * Calculates the feedforward from the gains and setpoints assuming discrete control. 224 * 225 * @param currentAngle The current angle in radians. This angle should be measured from the 226 * horizontal (i.e. if the provided angle is 0, the arm should be parallel to the floor). If 227 * your encoder does not follow this convention, an offset should be added. 228 * @param currentVelocity The current velocity setpoint in radians per second. 229 * @param nextVelocity The next velocity setpoint in radians per second. 230 * @return The computed feedforward in volts. 231 */ 232 public double calculateWithVelocities( 233 double currentAngle, double currentVelocity, double nextVelocity) { 234 return ArmFeedforwardJNI.calculate( 235 ks, kv, ka, kg, currentAngle, currentVelocity, nextVelocity, m_dt); 236 } 237 238 // Rearranging the main equation from the calculate() method yields the 239 // formulas for the methods below: 240 241 /** 242 * Calculates the maximum achievable velocity given a maximum voltage supply, a position, and an 243 * acceleration. Useful for ensuring that velocity and acceleration constraints for a trapezoidal 244 * profile are simultaneously achievable - enter the acceleration constraint, and this will give 245 * you a simultaneously-achievable velocity constraint. 246 * 247 * @param maxVoltage The maximum voltage that can be supplied to the arm. 248 * @param angle The angle of the arm, in radians. This angle should be measured from the 249 * horizontal (i.e. if the provided angle is 0, the arm should be parallel with the floor). If 250 * your encoder does not follow this convention, an offset should be added. 251 * @param acceleration The acceleration of the arm, in (rad/s²). 252 * @return The maximum possible velocity in (rad/s) at the given acceleration and angle. 253 */ 254 public double maxAchievableVelocity(double maxVoltage, double angle, double acceleration) { 255 // Assume max velocity is positive 256 return (maxVoltage - ks - Math.cos(angle) * kg - acceleration * ka) / kv; 257 } 258 259 /** 260 * Calculates the minimum achievable velocity given a maximum voltage supply, a position, and an 261 * acceleration. Useful for ensuring that velocity and acceleration constraints for a trapezoidal 262 * profile are simultaneously achievable - enter the acceleration constraint, and this will give 263 * you a simultaneously-achievable velocity constraint. 264 * 265 * @param maxVoltage The maximum voltage that can be supplied to the arm, in volts. 266 * @param angle The angle of the arm, in radians. This angle should be measured from the 267 * horizontal (i.e. if the provided angle is 0, the arm should be parallel with the floor). If 268 * your encoder does not follow this convention, an offset should be added. 269 * @param acceleration The acceleration of the arm, in (rad/s²). 270 * @return The minimum possible velocity in (rad/s) at the given acceleration and angle. 271 */ 272 public double minAchievableVelocity(double maxVoltage, double angle, double acceleration) { 273 // Assume min velocity is negative, ks flips sign 274 return (-maxVoltage + ks - Math.cos(angle) * kg - acceleration * ka) / kv; 275 } 276 277 /** 278 * Calculates the maximum achievable acceleration given a maximum voltage supply, a position, and 279 * a velocity. Useful for ensuring that velocity and acceleration constraints for a trapezoidal 280 * profile are simultaneously achievable - enter the velocity constraint, and this will give you a 281 * simultaneously-achievable acceleration constraint. 282 * 283 * @param maxVoltage The maximum voltage that can be supplied to the arm, in volts. 284 * @param angle The angle of the arm, in radians. This angle should be measured from the 285 * horizontal (i.e. if the provided angle is 0, the arm should be parallel with the floor). If 286 * your encoder does not follow this convention, an offset should be added. 287 * @param velocity The velocity of the elevator, in (rad/s) 288 * @return The maximum possible acceleration in (rad/s²) at the given velocity. 289 */ 290 public double maxAchievableAcceleration(double maxVoltage, double angle, double velocity) { 291 return (maxVoltage - ks * Math.signum(velocity) - Math.cos(angle) * kg - velocity * kv) / ka; 292 } 293 294 /** 295 * Calculates the minimum achievable acceleration given a maximum voltage supply, a position, and 296 * a velocity. Useful for ensuring that velocity and acceleration constraints for a trapezoidal 297 * profile are simultaneously achievable - enter the velocity constraint, and this will give you a 298 * simultaneously-achievable acceleration constraint. 299 * 300 * @param maxVoltage The maximum voltage that can be supplied to the arm, in volts. 301 * @param angle The angle of the arm, in radians. This angle should be measured from the 302 * horizontal (i.e. if the provided angle is 0, the arm should be parallel with the floor). If 303 * your encoder does not follow this convention, an offset should be added. 304 * @param velocity The velocity of the elevator, in (rad/s) 305 * @return The maximum possible acceleration in (rad/s²) at the given velocity. 306 */ 307 public double minAchievableAcceleration(double maxVoltage, double angle, double velocity) { 308 return maxAchievableAcceleration(-maxVoltage, angle, velocity); 309 } 310 311 /** Arm feedforward struct for serialization. */ 312 public static final ArmFeedforwardStruct struct = new ArmFeedforwardStruct(); 313 314 /** Arm feedforward protobuf for serialization. */ 315 public static final ArmFeedforwardProto proto = new ArmFeedforwardProto(); 316}