Unit 8 · Lesson 2

Feedforward Control: kS, kV, kA

A DC motor is physics. It has friction to overcome, back-EMF proportional to its speed, and inertia that resists acceleration. If you can model those three properties mathematically, you can predict the exact voltage needed for any desired speed — instantly, without waiting for an error. That model is feedforward control.

By the end of this lesson, you will:

  • Explain the physical meaning of kS, kV, and kA in terms of motor forces and voltage
  • State the feedforward formula: V = kS·sgn(v) + kV·v + kA·a
  • Calculate a predicted output voltage given kS, kV, kA values and a target velocity and acceleration
  • Use the interactive calculator to build intuition for how each gain contributes to the output
  • Describe at a conceptual level how WPILib's SysId tool measures these constants from a real mechanism
  • Explain why kA is often 0 for velocity-controlled mechanisms and when it matters

Prediction Before Reaction

In Lesson 1, we established that open-loop control has no awareness of the system it's commanding. PID (which comes in Lesson 5) is aware — but it's reactive: it waits for an error to develop before applying a correction. Waiting costs time. In a 50 Hz control loop, one missed update cycle is 20 ms of uncorrected error.

Feedforward control is different. It doesn't wait for error. Instead, it asks: "Given a desired speed, what voltage does the physics of this motor require right now?" Then it applies that voltage immediately, before any error exists. The question "does it work?" is answered by whether the physics model is accurate, not by measuring error and correcting.

The outfielder analogy from the project knowledge is exactly right: feedforward is the outfielder running to the predicted landing spot as soon as the ball is hit — not waiting until the ball is overhead to decide where to go. Feedback (PID) is the correction step: looking up and adjusting the last few steps. Feedforward handles 90% of the work instantly. PID handles the remaining 10%.

The DC Motor: Three Forces, Three Constants

Every DC motor in FRC obeys the same physics. When you apply a voltage, three things happen simultaneously:

V_applied battery × % kS — Static Friction −kS (resists motion) must be overcome first kV — Back-EMF −kV × velocity grows with speed kA — Inertia −kA × acceleration needed to change speed Net torque → speed needs const. voltage voltage ∝ velocity voltage ∝ Δvelocity

The Three Gains — Click Each to Learn

kS volts
Static friction constant. The minimum voltage to get the motor moving from a standstill. Without it, the motor stalls.
kV volts per (unit/sec)
Velocity constant. Voltage required to maintain a given velocity against back-EMF and viscous friction.
kA volts per (unit/sec²)
Acceleration constant. Extra voltage needed to change the motor's speed. Larger for heavier mechanisms.

The Feedforward Formula

SimpleMotorFeedforward — the prediction equation

V_ff = kS · sgn(v) + kV · v + kA · a
kS · sgn(v) Static friction term. sgn(v) is the sign of velocity: +1 when moving forward, −1 when moving backward. This term always opposes the direction of motion, modeling static friction as a constant drag that must be overcome before any useful motion happens.
kV · v Velocity term. Scales linearly with speed. This models back-EMF — a DC motor generates a voltage opposing the supply in proportion to its rotational speed. At 2× the speed, twice the voltage is needed to maintain it. This term is always the dominant term at competition speeds.
kA · a Acceleration term. Scales with the rate of change of velocity. Models the rotational inertia of the mechanism — Newton's second law for rotation: torque = I × α, which translates to voltage = kA × α. For constant-velocity commands (a=0), this term vanishes.

Interactive Feedforward Calculator

Adjust the sliders to build intuition for how each gain contributes to the predicted output voltage. These are realistic values for a typical Falcon 500 drive module.

Feedforward voltage calculator — V = kS·sgn(v) + kV·v + kA·a
kS (V, static friction) 0.25 V
kV (V·s/m, velocity) 2.00 V·s/m
kA (V·s²/m, accel.) 0.10 V·s²/m
Target velocity (m/s) 2.0 m/s
Acceleration (m/s²) 0.0 m/s²
kS · sgn(v):
kV · v:
kA · a:
Total V_ff:
Adjust the sliders to explore. Try: set velocity to 0 with acceleration at 5 m/s² — only kA contributes. Set acceleration to 0 — only kS and kV matter. Set velocity to −2.0 — watch kS flip sign.

How These Constants Are Measured: SysId

kS, kV, and kA are not guessed — they are measured from the physical mechanism through a process called system identification. WPILib's SysId tool automates this completely. It runs two tests:

  • Quasistatic test: slowly ramps voltage up from 0V. Captures the relationship between voltage and velocity at many points. The slope of the voltage-vs-velocity line is kV; the y-intercept (minimum voltage to start moving) is kS.
  • Dynamic test: applies a sudden voltage step. Measures how fast the mechanism accelerates. The ratio of voltage-to-acceleration is kA.

SysId then runs a regression analysis on the collected data and outputs kS, kV, and kA values calibrated to your specific mechanism. Unit 8 covers SysId in detail in Lesson 3 (SimpleMotorFeedforward) and the broader characterization workflow in the tuning methodology lessons. For now, know that:

  • kS typical range: 0.1–0.5V for most FRC motors and gearboxes
  • kV typical range: 1.5–4.0 V·s/m for drivetrain; higher for slower mechanisms
  • kA typical range: 0.05–0.3 V·s²/m; often negligible at constant velocity (kA=0 is a common starting point)
💡 When kA = 0 is acceptable

For mechanisms running at constant velocity — a shooter flywheel maintaining a steady RPM, a drive motor holding a constant speed — the acceleration is zero and the kA term vanishes. V_ff = kS·sgn(v) + kV·v is sufficient. kA becomes important when you're profiling motion (deliberately accelerating a mechanism through a speed ramp) or when acceleration is large enough to produce visible lag. For most FRC velocity control applications, starting with kA=0 and adding it later if needed is the correct approach.

SimpleMotorFeedforward in Code

ShooterSubsystem.java — SimpleMotorFeedforward usage
// In Constants.java
public static final double kShooterKS = 0.23; // V
public static final double kShooterKV = 0.12; // V·s/rot
public static final double kShooterKA = 0.01; // V·s²/rot (small)

// In ShooterSubsystem.java
private final SimpleMotorFeedforward m_ff =
  new SimpleMotorFeedforward(
    Constants.kShooterKS,
    Constants.kShooterKV,
    Constants.kShooterKA);

// In a command's execute() or the subsystem's setVelocity() method
public void setVelocityRPS(double targetRPS) {
  // Feedforward predicts the voltage needed
  double ffVolts = m_ff.calculate(targetRPS);

  // Command the motor with the predicted voltage
  m_motor.setVoltage(ffVolts);

  // Publish for tuning visibility
  SmartDashboard.putNumber("Shooter/FFVolts", ffVolts);
  SmartDashboard.putNumber("Shooter/ActualRPS",
    m_motor.getVelocity().getValueAsDouble());
}

// With acceleration (for profiled motion):
double ffVolts = m_ff.calculate(currentVelocity, targetVelocity,
                                0.020); // dt in seconds
← click a highlighted token
🔍 LRI Perspective: "setVoltage() vs set() — a critical distinction"

Feedforward outputs a voltage prediction. That voltage must be passed to setVoltage(), not set(). The set() method takes a percentage of battery voltage — if you pass a feedforward voltage to it, the motor receives feedforward_voltage × battery_voltage, which is wrong (and dangerous — high at low battery, dangerously high at full battery). setVoltage() normalizes the voltage against the measured battery voltage internally, ensuring the motor sees the correct physical voltage regardless of battery state. This is the most common feedforward implementation mistake in FRC code.

🔌 System Check — Validating Feedforward Before Competition

After implementing feedforward, verify it before adding PID:

  • Command a single target velocity (e.g., 50 RPS for a shooter wheel). Publish both the commanded voltage (ffVolts) and the actual velocity to SmartDashboard. With just feedforward (no PID), the actual velocity should reach within 5–10% of the target. If it consistently undershoots by 20%+, kV is too small. If it overshoots, kV is too large.
  • Verify that setVoltage() is used, not set(). Add a temporary check: publish RobotController.getBatteryVoltage() alongside the commanded voltage. If ffVolts divided by battery_voltage equals what you'd pass to set(), the conversion is correct. If not, you're using the wrong method.
  • Command velocity zero (setVelocityRPS(0)). The motor should stop, not spin slowly. If it continues spinning at a small positive velocity, kS is too large — the static friction voltage at zero is still keeping it moving. Reduce kS until zero command produces zero motion.

Knowledge Check

1. A Falcon 500 driving a shooter flywheel has: kS = 0.25V, kV = 0.12 V·s/rot, kA = 0.0. The target is 80 RPS with no acceleration change. What voltage does the feedforward predict?

  • A 0.25V — only kS applies when acceleration is zero
  • B 0.25 + (0.12 × 80) + 0 = 0.25 + 9.6 + 0 = 9.85V
  • C 0.12 × 80 = 9.6V — kS only applies when starting from rest
  • D (0.25 + 0.12) × 80 = 29.6V

2. You run a shooter wheel with feedforward only (no PID). The target is 4000 RPM. SmartDashboard shows the actual RPM at 3700 RPM when holding steady. No game piece is loaded. What does this tell you about kV?

  • A kV is too large — the model is predicting more voltage than needed, causing overspeed.
  • B kV is too small — the model underpredicts the voltage needed to maintain 4000 RPM, so the actual speed falls short. Increase kV until the steady-state actual RPM matches the target.
  • C kA is the problem — increasing it will close the 300 RPM gap.
  • D kS needs to be reduced — the static friction term is consuming voltage that should drive the flywheel.

3. You implement a feedforward shooter controller with m_motor.set(ffVolts) instead of m_motor.setVoltage(ffVolts). The battery is at 10V. The feedforward predicts 7V. What voltage does the motor actually receive?

  • A 7V — setVoltage() and set() both pass voltage directly to the motor.
  • B 70V — set() treats the argument as a fraction of battery voltage. 7.0 passed to set() means 700% of 10V, which saturates at 10V (the motor's maximum). The intended 7V command becomes 10V — you've accidentally commanded full power. Always use setVoltage() for feedforward output.
  • C 7V × 10V = 70V, but the controller clamps it at 12V.
  • D 7V / 10V × 10V = 7V — the division and multiplication cancel out.
💪 Practice Prompt

Build a Feedforward Controller

  1. Choose a mechanism with a velocity target: shooter flywheel, conveyor belt, or drive motor. Start with estimated constants: kS = 0.25, kV = 0.12 (V·s/rot), kA = 0. Create a SimpleMotorFeedforward field in your subsystem. Implement setVelocityRPS(double) that calls m_ff.calculate(targetRPS) and passes the result to setVoltage().
  2. Deploy and command a target velocity. Publish the feedforward output voltage and the actual velocity. Observe: does the velocity reach the target? Is it consistently under or over? Adjust kV by ±10% and re-test until the steady-state velocity is within 5% of the target. This is manual kV tuning — SysId automates this in Lesson 3, but doing it manually first builds understanding.
  3. Now deliberately test what happens with set() instead of setVoltage(). Command a moderate target velocity and compare the actual behavior. Notice the difference in speed at different battery voltages. Then switch back to setVoltage() and confirm the velocity is more consistent. This exercise makes the setVoltage() vs set() distinction tactile, not just theoretical.
  4. Stretch goal: Use the interactive calculator in this lesson to predict what voltage your mechanism should receive at two different target velocities. Then command those velocities and measure the SmartDashboard feedforward output voltage. How close are the calculator predictions to the actual code output? Any differences would be due to differences in your kS/kV values vs. the calculator defaults.