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