Closed-Loop Overview#

Closed-loop control typically refers to control of a motor that relies on sensor data to adjust based on error. Systems/mechanisms that rely on maintaining a certain position or velocity achieve this state using closed-loop control. This is achieved by feedback (PID) and feedforward control. Closed-loop control can be performed on the robot controller or on the individual motor controllers. The benefits of onboard closed-loop control are that there is no sensor latency, and the closed-loop controller has a 1 kHz update frequency. This can result in a more responsive output compared to running the closed-loop on the robot controller.

Since closed-loop control changes based on the dynamics of the system (velocity, mass, CoG, etc.), closed-loop relies on PID and feedforward parameters. These parameters are configured either via Tuner Configs or in code. The parameters can be determined using System Identification (such as with WPILib SysID) or through manual tuning.

Manual tuning typically follows this process:

  1. Set \(K_p\), \(K_i\) and \(K_d\) to zero.

  2. Increase \(K_p\) until the output starts to oscillate around the setpoint.

  3. Increase \(K_d\) as much as possible without introducing jittering to the response.

All closed-loop control requests follow the naming pattern {ClosedLoopMode}{ControlOutputType}. For example, the VelocityVoltage control request performs a velocity closed-loop using voltage output.

Choosing Output Type#

The choice of control output type can affect the reproducibility and stability of the closed-loop control.

DutyCycle has the benefit of being the simplest control output type, as it is unaffected by voltage and current measurements. However, because DutyCycle represents a proportion of the supply voltage, changes in battery voltage can affect the reproducibility of the control request.

Voltage control output takes into account the supply voltage to ensure its voltage output remains consistent. As a result, Voltage control often results in more stable and reproducible behavior compared to DutyCycle control, so Voltage control is often preferred.

A disadvantage with both DutyCycle and Voltage control output types is that they control acceleration indirectly and require a velocity feedforward \(K_v\) to hold a constant velocity. On the other hand, torque-based control output types, such as TorqueCurrentFOC, directly control acceleration, which has several advantages:

  • Since the torque request is directly proportional to acceleration, \(K_v\) is generally unnecessary. A torque output of 0 corresponds to a constant velocity, assuming no external forces.

  • \(K_a\) can be tuned independently of all the other closed-loop gains by comparing the measured acceleration with the requested acceleration.

  • Because the output is in units of torque, the units of the gains more closely match those of forces in the real world.

As a result, torque-based control output types offer more stable and reproducible behavior that can be easier to tune compared to the other control output types.

Gain Slots#

It may be useful to switch between presets of gains in a motor controller, so the TalonFX supports multiple gain slots. All closed-loop control requests have a member variable Slot that can be assigned an integer ID to select the set of gains used by the closed-loop. The gain slots can be configured in code using Slot*Configs (Java, C++, Python) objects.

Gravity Feedforward#

The gravity feedforward \(K_g\) is the output necessary to overcome gravity, in units of the control output type. Phoenix 6 supports the two most common use cases for \(K_g\)—elevators and arms—using the GravityType config in the gain slots.


For systems with a constant gravity component, such as an elevator, \(K_g\) adds a constant value to the closed-loop output. To find \(K_g\), determine the output necessary to hold the elevator at a constant height in open-loop control.


For systems with an angular gravity component, such as an arm, the output of \(K_g\) is dependent on the cosine of the angle between the arm and horizontal. The value of \(K_g\) can be found by determining the output necessary to hold the arm horizontally forward.

Since the arm \(K_g\) uses the angle of the arm relative to horizontal, the Talon FX often requires an absolute sensor whose position is 1:1 with the arm, and the sensor offset and ratios must be configured.

When using an absolute sensor, such as a CANcoder, the sensor offset must be configured such that a position of 0 represents the arm being held horizontally forward. From there, the RotorToSensor ratio must be configured to the ratio between the absolute sensor and the Talon FX rotor.

Converting from Meters#

In some applications, it may be useful to translate between meters and rotations. This can be done using the following equation:

\[rotations = \frac{meters}{2 \pi \cdot wheelRadius} \cdot gearRatio\]

where meters is the target in meters, wheelRadius is the radius of the wheel in meters, and gearRatio is the gear ratio between the output shaft and the wheel.

This equation also works with converting velocity from m/s to rps or acceleration from m/s² to rps/s.

Continuous Mechanism Wrap#

A continuous mechanism is a mechanism with unlimited travel in any direction, and whose rotational position can be represented with multiple unique position values. Some examples of continuous mechanisms are swerve drive steer mechanisms or turrets (without cable management).

ContinuousWrap (Java, C++, Python) is a mode of closed loop operation that enables the Talon to take the “shortest path” to a target position for a continuous mechanism. It does this by assuming that the mechanism is continuous within 1 rotation.

For example, if a Talon is currently at 2.1 rotations, it knows this is equivalent to every position that is exactly 1.0 rotations away from each other (3.1, 1.1, 0.1, -0.9, etc.). If that Talon is then commanded to a position of 0.8 rotations, instead of driving backwards 1.3 rotations or forwards 0.7 rotations, it will drive backwards 0.3 rotations to a target of 1.8 rotations.


The ContinuousWrap config only affects the closed loop operation. Other signals such as Position are unaffected by this config.

In order to use this feature, the FeedbackConfigs (Java, C++, Python) ratio configs must be configured so that the mechanism is properly described. An example is provided below, where there is a continuous mechanism with a 12.8:1 speed reduction between the rotor and mechanism.

Diagram describing how the feedback ratio configs are used