Skip to content
Programming, Controls & Sensors·Lesson 38 of 51

Mini-Project: A Velocity-Controlled Shooter on REVLib

Spin a flywheel to a target RPM with a NEO Vortex on a SPARK Flex using REVLib 2025's declarative closed-loop config, and only fire when it is up to speed.

Sign in to track progress, earn XP, and save lessons.

A shooter must reach a consistent surface speed before launching, or shots scatter. We'll run a NEO Vortex (free speed 6784 RPM, Kv 565) on a SPARK Flex in closed-loop velocity mode using REVLib 2025's declarative configuration API.

The 2025 config model

In REVLib 2025 you no longer call setters one at a time on the controller. You build a SparkFlexConfig, then configure() it once with explicit reset and persist modes:

private final SparkFlex m_motor = new SparkFlex(31, MotorType.kBrushless);
private final SparkClosedLoopController m_pid = m_motor.getClosedLoopController();
private final RelativeEncoder m_enc = m_motor.getEncoder();

public Shooter() {
  SparkFlexConfig config = new SparkFlexConfig();
  config.closedLoop
      .feedbackSensor(FeedbackSensor.kPrimaryEncoder)
      .pid(0.0001, 0.0, 0.0)        // velocity P,I,D
      .velocityFF(1.0 / 6784.0);    // ~1/free-speed-RPM for a NEO Vortex (6784 RPM)
  config.smartCurrentLimit(60);

  m_motor.configure(config,
      ResetMode.kResetSafeParameters,
      PersistMode.kPersistParameters);
}

PersistMode.kPersistParameters writes to flash so the config survives a brown-out reboot -- worth it for a one-time setup, but never call it every loop (flash writes block CAN comms).

Command a target RPM

In REVLib 2025, setReference() is deprecated in favor of setSetpoint() on the SparkClosedLoopController:

private static final double kTargetRpm = 4800;

public Command spinUp() {
  return run(() ->
      m_pid.setSetpoint(kTargetRpm, ControlType.kVelocity));
}

public boolean atSpeed() {
  return Math.abs(m_enc.getVelocity() - kTargetRpm) < 100; // RPM tolerance
}

Gate the feeder on "at speed"

Expose atSpeed() as a Trigger and only run the feeder when the flywheel is ready -- this is exactly the BoVLB best practice of asking yes/no questions in problem-domain language:

Trigger ready = new Trigger(m_shooter::atSpeed);
// hold to spin up
m_driver.rightTrigger().whileTrue(m_shooter.spinUp());
// feed only once the wheel has recovered to speed
m_driver.rightTrigger().and(ready).whileTrue(m_feeder.feed());

Why velocityFF matters more than P

A flywheel's steady-state voltage is almost entirely feedforward: V = kV * rpm. If you set velocityFF correctly, the controller jumps near the right voltage instantly and P only trims the last few percent. Teams that leave FF at zero and crank P get a sluggish, oscillating flywheel that dips badly when a game piece loads it. The SPARK's velocityFF multiplies the RPM setpoint to produce a duty-cycle output, so its value is roughly 1 / free-speed-RPM. Find it from a SysId run, or empirically: command a fixed duty cycle, read the steady RPM, and velocityFF = appliedOutput / rpm. (Note: REVLib is moving toward a feedForward config with explicit kS/kV terms; velocityFF() still works in 2025.)

Plot m_enc.getVelocity() against the setpoint in AdvantageScope. A good shooter recovers to within tolerance in well under half a second after each shot.

Key takeaways

  • REVLib 2025 uses declarative SparkFlexConfig/SparkMaxConfig objects applied with configure(), not per-parameter setters.
  • In REVLib 2025 use SparkClosedLoopController.setSetpoint() -- setReference() is deprecated.
  • Use PersistMode.kPersistParameters once at setup; never persist every loop -- flash writes block CAN.
  • Flywheels are feedforward-dominated: set velocityFF (~1/Kv) correctly and keep P small; gate the feeder on an atSpeed() Trigger.

Lesson quiz

Required

Answer all 3 questions correctly to complete this lesson.

1.When running velocity closed-loop control on a SPARK MAX/Flex with REVLib, which control type do you request?

2.What units does the SPARK's onboard velocity closed loop use for the setpoint by default?

3.For a flywheel shooter, what is the recommended way to combine feedforward and PID gains in REVLib?

Answer every question to submit.