Swerve Calibration
The project you generated came with somebody else's numbers in TunerConstants.java. This lesson replaces the ones that matter with measurements off your own robot, then closes the drive loop. Almost no Java.
- A swerve robot you can drive, from Swerve Project Generator.
- Logging on. Two of the measurements come out of a
.wpilog. - Phoenix Tuner X, and six meters of clear carpet.
- A tape measure, a long straight edge, and a wall you may push against.
Work in order: each step measures something the steps after it depend on.
Three kinds of zeroing
Three operations in the swerve code all get called resetting or seeding. Mix them up and you get a robot that drives beautifully and has no idea where it is.
seedFieldCentric(), on the left bumper, changes which way the sticks call forward: whatever the robot faces now becomes forward. Every loop, applyOperatorPerspective()sets that same forward from alliance color: 0° on blue and 180° on red. Neither supplies an x or a y.
resetPose(Pose2d) moves the pose itself: x, y and heading in meters from the blue corner. It exists on the Phoenix 6 drivetrain, but DriveMechanism does not expose it and nothing calls it. Until Workshop 5, read Drivetrain/Pose as distance traveled since boot.
Zero the modules
Each module has a CANcoder reading which way its wheel points, and an offset saying which reading counts as straight ahead. The four k*EncoderOffsetconstants in your file came off somebody else's robot.
- Disable the robot. Nothing should be commanding a module while you set it.
- Press a straight edge flat along one side so both wheels on that side sit against it, then do the other side. By eye is not close enough: half a degree of steering error walks the robot 5 cm sideways over six meters.
- Check which way each module faces. CTRE marks this one important: every module's bevel gear has to face the vertical center of the robot. A module that is straight but flipped zeroes 180° off, and the drive verification tests fail later without saying why.
- Holding the wheels straight, run the calibration step in the Tuner X swerve generator. It reads all four CANcoders where they sit and writes the offset constants for you.
Re-enable, push the left stick forward, and sight along a carpet seam. A consistent curve to one side means a module is zeroed wrong. A wander that comes and goes is the steer gains.
Tune the steer gains
A steering motor holds an angle: a position loop, and there is no gravity to fight here. Skip kG and start at kS.
The generator gave you real numbers: kP 100, kD 0.5, kS 0.1, kV 1.91. They are often close, so adjust rather than starting from zero.
The order, from the PID page. kS is the smallest output that breaks the module loose. Raise kP until it oscillates, then back off. Add kD, as much as you can get without jitter. One number per test.
Telemetry.java publishes Drivetrain/ModuleStates and Drivetrain/ModuleTargets. Put the angle from both on one AdvantageScope plot and flick the right stick. Tuned looks like two traces on top of each other. Untuned lags, overshoots, or buzzes.
Three measurements on carpet
Measure the radius and the top speed while the drive request is still open-loop voltage: kSpeedAt12Volts means the speed at 12 volts applied.
Wheel radius
Odometry counts wheel rotations and multiplies by a radius. You want the effective one: the wheel squashed under the robot's weight and sunk into carpet. It shrinks as the tread wears, so repeat this late in the season.
- Tape the floor at the robot's front edge. Restart the code so
Drivetrain/Posereads (0, 0). - Drive straight forward, slowly, about five meters. A wheel that spins under hard acceleration counts distance the robot never travels.
- Tape the front edge again. The gap between marks is the actual distance, and
Drivetrain/Poseat the end of the run is the reported one.
Run it three times in each direction and average. Put the result in kWheelRadius, redeploy, and repeat until tape and log agree.
If it got worse, you inverted the ratio: a robot that under-reports needs a bigger radius.
Top speed
Do the radius first: this speed is wheel rotations times that radius. Find six meters of clear floor on the surface you compete on: carpet and a shop floor give different answers.
- Full stick forward. Hold until the speed stops climbing, then let go well before the wall.
- Plot
Drivetrain/TranslationSpeedMpsand read the plateau, not the spike.
That number goes in kSpeedAt12Volts. It measures the robot; the driver cap is maxSpeed in TeleopOpMode, which reads it straight back. Expect a plateau near the 4.54 the file shipped with. Wildly off means the file: check the radius, then check kDriveGearRatio against your modules.
Slip current
Stator current is proportional to torque, so a stator limit caps how hard a wheel twists. Set it where the tire loses grip. Torque above that point polishes carpet.
- Drive the robot against a wall on carpet and square it up so all four wheels point into the wall.
- In Tuner X, open one drive motor on Voltage Out and plot its velocity and its stator current.
- Ramp the voltage up slowly from zero, watching both traces.
Current climbs while velocity sits at zero. Then velocity jumps and current drops in the same instant. That is the tire letting go. Read the current at the top of the climb, just before the drop.
Stalled motors get hot fast
A drive motor pushing a wall it cannot move is a stalled motor. Keep each ramp to a couple of seconds, back off to zero between attempts, and give the motor a minute. One person on the disable, somebody else on the laptop.
That number goes in kSlipCurrent. You may be measuring the limit, not the tire. The shipped 120 A is itself a stator limit, and driveInitialConfigs sets 70 A on the supply. If the trace flattens at 120 A and the wheel never breaks loose, raise the constant temporarily and ramp again. Then redeploy and floor it from a dead stop. The robot should launch without the squeal and sideways hop of wheel spin.
Close the drive loop
A drive motor holds a speed. On a velocity loop the feedforward does nearly all of the work. The order is kV, then kS, then kP. The file starts you at kP 0.2 and kV 0.124.
Tune it on the ground, not on blocks. A wheel in the air carries no load, and gains found there will not hold a speed under a robot.
Plot the same two signals as the steer gains, speed this time. A constant gap is kV. A gap only at low speed is kS. A slow recovery after a change of direction is kP.
Switch the drive request
Up to here the stick position went straight to volts. In TeleopOpMode.java, change DriveRequestType.OpenLoopVoltage to DriveRequestType.Velocity. It comes from the same class, so the import already covers it.
No branch makes this edit for you: 1-Swerve, 2-Logging and the robot template all ship open-loop. Make it last. If the robot drives worse than it did on volts, go back to the gains.
The deadband
The same request line sets withDeadband(maxSpeed * 0.1) and withRotationalDeadband(maxAngularRate * 0.1), throwing away the bottom 10% of both sticks. At 4.54 m/s that is everything under about 0.45 m/s, which open-loop driving could not hold anyway. A tuned velocity loop can, so the deadband now discards control you paid for.
Shrink it rather than deleting it. The deadband is what keeps a worn stick's drift from creeping the robot across the field.
- Put the robot on blocks. Enable, and take your hands off the controller.
- Watch the speed component of
Drivetrain/ModuleTargets. It should be flat zero. - Halve the deadband, redeploy, repeat. When the targets start twitching, go back one value.
Set it per controller, for the worst one you will compete with.
Check your work
One run tells you whether the page landed, on the surface you compete on. Tape a start mark, restart the code so Drivetrain/Pose reads (0, 0), and drive a square: three meters forward, three left, three back, three right.
You should see
The robot back on the tape, and Drivetrain/Pose near (0, 0) after twelve meters. In the log, measured module traces sitting on the commanded ones through all four corners. Hands off the sticks, the speed component of Drivetrain/ModuleTargetsflat at zero. Turn 90° in place and press the left bumper: forward moves, x and y do not.
- Module zeros
- A module a degree off steers the robot sideways the whole way, and no radius correction fixes a heading error. Straight edge back on, re-save the zeros.
- Drive gains
Velocitywith untuned gains chases a speed the motor cannot hold. Sluggish or surging is kV. A hum at constant speed is kP too high.- Nothing is broken
- Odometry measures from where the code started, not from a point on the field. Nothing here sets one. Vision fixes it in Workshop 5.
Check yourself
TeleopOpMode.java on 1-Swerve: what does the drive request read before you edit it?
You press the left bumper, which runs seedFieldCentric(). What changed?
Tape says 5.00 m, the log says 4.80 m, and kWheelRadius is 2.167 in. What goes in the file?
Why does the wheel radius get measured before top speed?
Current climbs while velocity sits at zero, then velocity jumps and current drops. What goes in kSlipCurrent?
With the drive loop closed, why shrink the 10% deadband instead of deleting it?