Turret Subsystem Readme
Turret subsystem
Overview
The turret is a single-axis rotating mount that turns left and right so the shooter can aim without moving the entire robot. It uses one Spark MAX motor controller driving through a gear reduction for smooth, precise positioning. The turret is the canonical example of the set-and-seek pattern — if you are building a new profiled mechanism, read this README first.
How it works
The turret follows the set-and-seek pattern. A command sets a target angle in degrees, and the subsystem plans a smooth path to that angle using a trapezoidal motion profile. Each code cycle, the profile steps forward and a profiled PID controller with feedforward computes the motor voltage needed to track the moving setpoint.
Step-by-step move
- A command calls
setTarget(degrees). - The base class clamps the target to the configured min/max limits.
- The value is converted from degrees to radians for internal math.
- Each cycle,
seekTarget()advances the trapezoidal profile by one step. - PID computes a correction from the difference between the setpoint and the measured position.
- Feedforward estimates the voltage needed for the planned motion (using kS, kV, kA).
- The combined PID + feedforward output is sent to the motor.
- The command finishes when
isProfileSettled()reports that position and velocity are within tolerance of the goal.
Trapezoidal profile (ASCII sketch)
Velocity vs. time for a trapezoidal move:
velocity
^ ____________ <- cruise at max velocity
| / \
| / \
| / \
|________/ \________ -> time
accel decel
If the target is close, the profile ramps up and back down without ever reaching cruise speed — the trapezoid becomes a triangle.
How commands drive the turret
MoveTurretToAngleCommandextendsAbstractSetAndSeekCommand.- On initialize, the command calls
setTarget()once using a supplier. - During execute, the command calls
seekTarget()every loop to advance the profile. - The command finishes when
isProfileSettled()returns true. - If the command is interrupted, the base command schedules a settle command so the turret decelerates safely.
TrackFieldTargetCommandcontinuously recalculates a field-relative target each cycle and feeds it tosetTarget(), allowing the turret to track a moving or alliance-relative position.
Positioning and units
We use degrees in the public API because it is easier for humans to read and think about. WPILib math uses radians, so we convert once when a target is set.
The turret faces the rear of the robot. The turretZeroOffsetDegrees config
field is set to 180 to account for this — when the turret is at its mechanical
zero, it points backward. The field-target tracking math subtracts this offset
from the robot-relative angle to convert into turret-relative coordinates, so
that "aim at a field position" automatically compensates for the rear-facing
mount. All setpoint limits (minimumSetpointDegrees, maximumSetpointDegrees)
and soft limits are relative to the turret's own zero, not to robot-forward.
- Commands call
setTarget(double)with a target in degrees. AbstractSetAndSeekSubsystemclamps the target between the configured minimum and maximum and then converts to radians for the controller.TurretMotorconfigures the Spark MAX encoder conversion factors so the encoder reports turret radians, not raw motor rotations.- AdvantageKit logs are recorded in degrees in the base class for clarity.
Gear setup and ratio math
The Spark MAX uses the internal relative encoder, which measures motor rotations. The turret itself moves much slower because of the gear reduction. We must scale the encoder using the full gear ratio.
We store the ratio as motor rotations per turret rotation:
motor rotations per turret rotation = gearbox ratio × (driven teeth / driver teeth)
Example: for a 5 : 1 gearbox with a 16-tooth gear driving a 152-tooth gear:
ratio = 5 × (152 / 16) = 47.5
That ratio is stored in subsystems.json as
turretSubsystem.motorConfig.motorRotationsPerMechanismRotation. The motor
wrapper converts it to radians per motor rotation so the encoder reports
true turret radians.
If the turret moves twice as far as expected, the gear ratio is usually off by a factor of two. Check every gear stage and tooth count to confirm the real ratio.
Configuration
All turret settings live in src/main/deploy/subsystems.json (or the sim/test
variants) under the turretSubsystem key. Values are
tunable — you can adjust them on the fly through
SmartDashboard without redeploying.
Key tunables
| Setting | Units | Purpose |
|---|---|---|
minimumSetpointDegrees |
degrees | Reverse rotation limit |
maximumSetpointDegrees |
degrees | Forward rotation limit |
maximumVelocityDegreesPerSecond |
deg/s | Profile cruise speed |
maximumAccelerationDegreesPerSecondSquared |
deg/s² | Profile ramp rate |
positionToleranceDegrees |
degrees | How close is "close enough" |
velocityToleranceDegreesPerSecond |
deg/s | How slow is "stopped enough" |
kP, kI, kD |
unitless | PID gains |
kS |
volts | Static feedforward |
kV |
V/(deg/s) | Velocity feedforward |
kA |
V/(deg/s²) | Acceleration feedforward |
Motor-specific settings (CAN ID, inversion, current
limits, gear ratio) live under
turretSubsystem.motorConfig.
Tuning checklist
- Confirm the gear ratio first — wrong ratios cause every other value to misbehave.
- Choose safe max speed and acceleration values before tuning gains.
- Use SysId to measure kS, kV, and kA so feedforward is close from the start.
- Increase kP until tracking is strong but not oscillating.
- Add a small kD if you see overshoot.
- Keep test moves simple and repeatable so changes are easy to compare.
Code structure
| File | Purpose |
|---|---|
TurretSubsystem.java |
Extends AbstractSetAndSeekSubsystem; builds either TurretMotor (real) or TurretSimMotor (sim) |
commands/MoveTurretToAngleCommand.java |
Profiled move command — extends AbstractSetAndSeekCommand |
commands/TrackFieldTargetCommand.java |
Continuous field-relative tracking command |
commands/TurretSubsystemCommandFactory.java |
Factory that builds commands for RobotContainer wiring |
config/TurretSubsystemConfig.java |
Extends AbstractSetAndSeekSubsystemConfig; adds turret-specific fields |
shared/config/MotorConfig.java |
Motor-level config (CAN ID, gear ratio, current limits) (inherited) |
devices/TurretMotor.java |
Real hardware motor wrapper — extends AbstractMotor |
devices/TurretSimMotor.java |
Simulation motor wrapper — extends AbstractSimMotor |
Status / TODO
Done
- Full set-and-seek profiled motion with PID + feedforward.
- SysId characterization commands via the command factory.
- Simulation support via
TurretSimMotor. - Field-target tracking command for continuous aiming.
TODO
- Revisit aiming presets and vision integration once shooter testing begins.