State-Space Control and Kalman Filtering
Go beyond PID with model-based control: an LQR controller, a Kalman filter observer, and a LinearSystemLoop for a flywheel.
Sign in to track progress, earn XP, and save lessons.
PID treats the mechanism as a black box. State-space control instead uses a model of the physics, which lets you place poles deliberately, fuse noisy sensors optimally, and reason about stability. WPILib ships first-class support via LinearSystem, LinearQuadraticRegulator, KalmanFilter, and LinearSystemLoop.
The three pieces
- Plant model (
LinearSystem): the math of how input voltage produces velocity. WPILib'sLinearSystemIdbuilds one from physical constants or from SysId's kV/kA viaidentifyVelocitySystem(kV, kA). - LQR (
LinearQuadraticRegulator): the controller. You tell it how much you care about state error vs. control effort, and it computes the optimal gains. For a single-state flywheel the result is mathematically just a P controller -- but a principled one. - Kalman filter (
KalmanFilter): the observer. It fuses the noisy encoder reading with the model prediction, giving a smooth state estimate with little lag -- so it rejects sensor noise while still reacting fast to real disturbances (like a game piece passing through the flywheel).
A flywheel loop
private final LinearSystem<N1, N1, N1> m_plant =
LinearSystemId.identifyVelocitySystem(kV, kA);
private final KalmanFilter<N1, N1, N1> m_observer =
new KalmanFilter<>(Nat.N1(), Nat.N1(), m_plant,
VecBuilder.fill(3.0), // model (state) std dev
VecBuilder.fill(0.01), // encoder std dev
0.020);
private final LinearQuadraticRegulator<N1, N1, N1> m_controller =
new LinearQuadraticRegulator<>(m_plant,
VecBuilder.fill(8.0), // how badly we want to hit the target rad/s
VecBuilder.fill(12.0), // max control effort (volts)
0.020);
private final LinearSystemLoop<N1, N1, N1> m_loop =
new LinearSystemLoop<>(m_plant, m_controller, m_observer, 12.0, 0.020);
Each loop you correct with the measurement, set the next reference, predict, then apply the computed voltage:
m_loop.setNextR(VecBuilder.fill(targetRadPerSec)); // desired state
m_loop.correct(VecBuilder.fill(m_encoder.getRate()));
m_loop.predict(0.020);
m_motor.setVoltage(m_loop.getU(0));
Why teams do this
A well-tuned Kalman flywheel shows little measurement lag during spin-up while still rejecting noise and recovering fast when a ball loads it -- behavior that's hard to get from hand-tuned PID. The C++ includes mirror the Java classes: <frc/estimator/KalmanFilter.h>, <frc/controller/LinearQuadraticRegulator.h>, <frc/system/LinearSystemLoop.h>.
Where to apply it
State-space shines for flywheels, drivetrains, and elevators where you have a good kV/kA model. For a first attempt, characterize with SysId, plug kV/kA into LinearSystemId, and start from the WPILib state-space flywheel example rather than from scratch. Tune the two std-dev knobs: smaller measurement std-dev trusts the encoder more (faster, noisier); larger trusts the model more (smoother, laggier).
Key takeaways
- State-space uses a physics model: LinearSystem (plant) + LinearQuadraticRegulator (controller) + KalmanFilter (observer), tied together by LinearSystemLoop.
- Build the plant from SysId's kV/kA via LinearSystemId.identifyVelocitySystem().
- Each loop: setNextR -> correct(measurement) -> predict -> setVoltage(getU(0)).
- Tune Kalman std-devs to trade encoder trust (fast/noisy) against model trust (smooth/laggy).
Go deeper
Lesson quiz
RequiredAnswer all 3 questions correctly to complete this lesson.
1.In a WPILib state-space LinearSystemLoop, what are the three core components combined to control the system?
2.What is the primary purpose of the Kalman Filter in a WPILib state-space controller?
3.When constructing WPILib's LinearQuadraticRegulator, how do you typically specify the desired behavior?
Answer every question to submit.