A shooter has to reach a consistent surface speed before it launches, or the 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. Instead you build a SparkFlexConfig and 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. That is worth doing for a one-time setup, but never call it every loop: flash writes block CAN comms.
Command a target RPM
Command the velocity through the SparkClosedLoopController with setReference(). REVLib 2026 deprecates that method in favor of an identical setSetpoint(), so use that one on the 2026 library:
private static final double kTargetRpm = 4800;
public Command spinUp() {
return run(() ->
m_pid.setReference(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 run the feeder only when the flywheel is ready. This is 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 velocityFF is set correctly, the controller jumps to near the right voltage immediately, and P only has to trim the last few percent. Teams that leave FF at zero and crank P instead 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. (REVLib is moving toward a feedForward config with explicit kS/kV terms, but 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.
Keep going
Take the quiz · +10 XP with an accountMore in Worked Examples and Mini-Projects
Sources & corrections
This lesson is AI-assisted: drafted from primary sources, then reviewed and edited by hand. Errors still get through. When one is reported we fix it and write down what changed — publicly, in the corrections log.
Sources and further reading
- REVLib: Velocity Control Modedocs.revrobotics.com
- REVLib: Configuring a SPARKdocs.revrobotics.com
- Migrating to REVLib 2025docs.revrobotics.com
Read next
Articles on this, for competition day
The lesson covers how it works. These cover what to do when it breaks.
- 17 min read
FRC Flywheel Shooter Design: Compression, Speed, Backspin, and Hooding
How to design an FRC flywheel shooter: exit velocity, compression, flywheel inertia and RPM recovery, single vs dual wheels, backspin, hooding, motors, and tuning.
Read - 20 min read
REV Robotics for FRC: NEO Motors, Spark MAX/Flex, PDH & MAXSwerve
A practical guide to the REV Robotics FRC ecosystem: NEO, NEO Vortex and NEO 550 motors, Spark MAX and Spark Flex controllers, the PDH, and MAXSwerve.
Read - 13 min read
FRC Code Structure Best Practices: Command-Based Project Architecture
How to structure an FRC command-based robot project the right way — subsystems, commands, RobotContainer, and Constants — verified against official WPILib docs.
Read
Lesson quiz
RequiredAll 3 right completes the lesson. Miss one and only that question comes back — anything you already answered correctly stays banked.
0 of 3 answered
01.When should you apply PersistMode.kPersistParameters when configuring a SPARK Flex in REVLib 2025?
02.What units does the SPARK's onboard velocity closed loop use for its setpoint by default?
03.For a flywheel shooter in REVLib, how should feedforward and PID gains be combined?
Answer every question to submit.
All 51 lessons in Programming, Controls & Sensors
- Not started:Mini-Project: A Closed-Loop Elevator with Motion Magic
- Not started:Mini-Project: A Velocity-Controlled Shooter on REVLib
- Not started:Mini-Project: A Teleop Swerve Drive Subsystem
- Not started:Mini-Project: An Autonomous Routine with PathPlanner
- Not started:Mini-Project: Vision-Aligned Scoring with Limelight
- Not started:State-Space Control and Kalman Filtering
- Not started:Log Replay Architecture with AdvantageKit
- Not started:Advanced Pose Estimation: Multi-Tag Fusion and Standard Deviations
- Not started:Robot Coordination, Alerts, and Operator Feedback
- Not started:Case Study: Hardening Software Before an Event