Let's build a two-stage elevator driven by a Kraken X60 running CTRE's integrated Talon FX controller. The goal: command the elevator to named heights (stow, L2, L4) and have it slew there on a trapezoidal profile using Motion Magic, Phoenix 6's onboard motion-profiled position control.
Configure the motor
Motion Magic needs a Slot 0 PID set plus a feedforward (kS/kV/kA) and the profile limits (cruise velocity, acceleration, jerk). All of it lives in a TalonFXConfiguration:
private final TalonFX m_motor = new TalonFX(20); // CAN ID 20
private final MotionMagicVoltage m_request = new MotionMagicVoltage(0);
public Elevator() {
var cfg = new TalonFXConfiguration();
var slot0 = cfg.Slot0;
slot0.kS = 0.25; // volts to overcome static friction
slot0.kV = 0.12; // volts per rotation/sec
slot0.kA = 0.01; // volts per rotation/sec^2
slot0.kG = 0.30; // gravity feedforward (elevator type)
slot0.kP = 4.8;
slot0.kI = 0;
slot0.kD = 0.1;
slot0.GravityType = GravityTypeValue.Elevator_Static;
var mm = cfg.MotionMagic;
mm.MotionMagicCruiseVelocity = 80; // rotations per second
mm.MotionMagicAcceleration = 160; // rps per second
mm.MotionMagicJerk = 1600; // rps per second^2
m_motor.getConfigurator().apply(cfg);
}
GravityTypeValue.Elevator_Static is the correct value for a vertical lift (use Arm_Cosine instead for a rotating arm, where the gravity term scales with the cosine of the angle).
Convert rotations to inches
Your mechanism doesn't think in motor rotations -- it thinks in inches of travel. If a 24-tooth pulley with 5 mm pitch moves the carriage, and the gearbox is 9:1, one motor rotation = (24 x 5 mm) / 9 = 13.33 mm = 0.525 in. Store that constant and convert at the boundary:
private static final double kRotationsPerInch = 9.0 / (24 * 5.0 / 25.4);
public Command goToHeight(double inches) {
double targetRot = inches * kRotationsPerInch;
return run(() -> m_motor.setControl(m_request.withPosition(targetRot)))
.until(() -> Math.abs(
m_motor.getPosition().getValueAsDouble() - targetRot) < 0.5);
}
Wire it to a button
In RobotContainer, expose named heights as command factories and bind them:
m_driver.a().onTrue(m_elevator.goToHeight(0.0)); // stow
m_driver.b().onTrue(m_elevator.goToHeight(31.5)); // L2
m_driver.y().onTrue(m_elevator.goToHeight(72.0)); // L4
Tune in this order
- Set kP=kI=kD=0 and only kG until the carriage holds position against gravity.
- Add kV (from a SysId run or
12 / free-speed-rps), then kS. - Bring up kP until it tracks the profile crisply; add a little kD only if it overshoots.
Because Motion Magic generates the trajectory on the motor controller at 1 kHz, your roboRIO loop just sends a target rotation -- no 20 ms profile math on the RIO. Watch the position-vs-setpoint plot in AdvantageScope while tuning; the measured curve should hug the generated profile with no lag.
Key takeaways
- Motion Magic runs a trapezoidal profile on the Talon FX itself; the RIO only sends a target position.
- Slot 0 needs PID plus kS/kV/kA and (for elevators/arms) kG with the correct GravityType (Elevator_Static vs Arm_Cosine).
- Always convert mechanism units (inches) to motor rotations at the API boundary using a single constant.
- Tune gravity first, then velocity feedforward, then kP -- not kP first.
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
- Phoenix 6: Motion Magic Controlsv6.docs.ctr-electronics.com
- Phoenix 6 Java API: Slot0Configs (GravityType)api.ctr-electronics.com
- WPILib Command-Based: Subsystemsdocs.wpilib.org
Read next
Articles on this, for competition day
The lesson covers how it works. These cover what to do when it breaks.
- 16 min read
FRC Elevator and Arm Design: Staging, Rigging, Motors, Gravity, and Safety
A primary-source FRC guide to designing elevators and arms: cascade vs continuous rigging, staging, motor and gear-ratio sizing, gravity math, and safe holding.
Read - 7 min read
FRC Motion Profiling Explained: TrapezoidProfile and ProfiledPIDController
How WPILib's TrapezoidProfile and ProfiledPIDController smooth mechanism motion, how to pick velocity and acceleration constraints, and when a profile beats plain PID.
Read - 18 min read
CTRE Phoenix for FRC: Kraken X60/X44, TalonFX, Phoenix 6, CANcoder & Pigeon 2
A practical guide to the CTRE Phoenix ecosystem for FRC: Kraken X60/X44 motors, TalonFX, Phoenix 6 API, Pro/FOC licensing, CANcoder, Pigeon 2 & CANivore.
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.In CTRE Phoenix 6, what does Motion Magic do when you command a TalonFX to a target elevator position?
02.For an elevator, why is a gravity feedforward (kG) added to the Motion Magic output?
03.Which control request drives a TalonFX elevator to a position with Motion Magic and voltage-based output?
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