Differential Mechanism Setup

The differential mechanism APIs are constructed by setting up a DifferentialMotorConstants (Java, C++, Python) object. This includes necessary information such as the leader motor controllers’ IDs, gear ratio on the differential, and alignment of the two sides of the mechanism.

Important

The differential mechanism only manages one motor controller from each gearbox. Any other motors in the gearboxes should be separately constructed and configured to follow their corresponding leader.

Defining the Motor Constants

An example configuration for a differential wrist is shown below with the following setup:

  • A 3:1 gear ratio on the average axis

  • An additional 2:1 gear ratio on the differential axis

  • The two motor directions are aligned (ignoring inverts)

  • Closed-loop control and relevant status signals run at 200 Hz

Warning

Many of these constants, including the PID gains, are specific to this example and will not work on your mechanism.

// Average axis gains typically go in Slot 0
private static final Slot0Configs averageGains = new Slot0Configs()
   .withKP(20).withKI(0).withKD(0.1)
   .withKG(0.2).withKS(0.1).withKV(0.36).withKA(0)
   .withGravityType(GravityTypeValue.Arm_Cosine);
// Difference axis gains typically go in Slot 1
private static final Slot1Configs differenceGains = new Slot1Configs()
   .withKP(30).withKI(0).withKD(0.1)
   .withKS(0.1).withKV(0.72);

private static final double kAverageGearRatio = 3.0;
private static final double kDifferenceGearRatio = 2.0;

// Initial configs for the differential leader and follower motor controllers.
// Some configs will be overwritten; check the `with*InitialConfigs()` API documentation.
private static final TalonFXConfiguration leaderInitialConfigs = new TalonFXConfiguration()
   .withMotorOutput(
      new MotorOutputConfigs()
         .withNeutralMode(NeutralModeValue.Brake)
   )
   .withCurrentLimits(
      new CurrentLimitsConfigs()
         .withStatorCurrentLimit(Amps.of(80))
         .withStatorCurrentLimitEnable(true)
   )
   .withFeedback(
      new FeedbackConfigs()
         .withSensorToMechanismRatio(kAverageGearRatio)
   )
   .withClosedLoopGeneral(
      new ClosedLoopGeneralConfigs()
         // differential wrist is continuous on the difference axis
         .withDifferentialContinuousWrap(true)
   )
   .withSlot0(averageGains)
   .withSlot1(differenceGains)
   .withMotionMagic(
      new MotionMagicConfigs()
         .withMotionMagicCruiseVelocity(80)
         .withMotionMagicAcceleration(320)
   );
private static final TalonFXConfiguration followerInitialConfigs = new TalonFXConfiguration()
   .withFeedback(
      new FeedbackConfigs()
         .withSensorToMechanismRatio(kAverageGearRatio)
   );

// CAN bus that the devices are located on;
// All mechanism devices must share the same CAN bus
private static final CANBus kCANBus = new CANBus("canivore", "./logs/example.hoot");

private static final DifferentialMotorConstants<TalonFXConfiguration> differentialConstants =
   new DifferentialMotorConstants<TalonFXConfiguration>()
      .withCANBusName(kCANBus.getName())
      .withLeaderId(0)
      .withFollowerId(1)
      .withAlignment(MotorAlignmentValue.Aligned)
      .withSensorToDifferentialRatio(kDifferenceGearRatio)
      .withClosedLoopRate(200.0)
      .withLeaderInitialConfigs(leaderInitialConfigs)
      .withFollowerInitialConfigs(followerInitialConfigs)
      .withFollowerUsesCommonLeaderConfigs(true);
// Average axis gains typically go in Slot 0
static constexpr configs::Slot0Configs averageGains = configs::Slot0Configs{}
   .WithKP(10).WithKI(0).WithKD(0.1)
   .WithKG(0.2).WithKS(0.1).WithKV(0.12).WithKA(0)
   .WithGravityType(signals::GravityTypeValue::Arm_Cosine);
// Difference axis gains typically go in Slot 1
static constexpr configs::Slot1Configs differenceGains = configs::Slot1Configs{}
   .WithKP(10).WithKI(0).WithKD(0.1)
   .WithKS(0.1).WithKV(0.12);

static constexpr units::scalar_t kAverageGearRatio = 3.0;
static constexpr units::scalar_t kDifferenceGearRatio = 2.0;

// Initial configs for the differential leader and follower motor controllers.
// Some configs will be overwritten; check the `With*InitialConfigs()` API documentation.
static constexpr configs::TalonFXConfiguration leaderInitialConfigs = configs::TalonFXConfiguration{}
   .WithMotorOutput(
      configs::MotorOutputConfigs{}
         .WithNeutralMode(signals::NeutralModeValue::Brake)
   )
   .WithCurrentLimits(
      configs::CurrentLimitsConfigs{}
         .WithStatorCurrentLimit(80_A)
         .WithStatorCurrentLimitEnable(true)
   )
   .WithFeedback(
      configs::FeedbackConfigs{}
         .WithSensorToMechanismRatio(kAverageGearRatio)
   )
   .withClosedLoopGeneral(
      configs::ClosedLoopGeneralConfigs{}
         // differential wrist is continuous on the difference axis
         .WithDifferentialContinuousWrap(true)
   )
   .WithSlot0(averageGains)
   .WithSlot1(differenceGains)
   .WithMotionMagic(
      configs::MotionMagicConfigs{}
         .WithMotionMagicCruiseVelocity(80_tps)
         .WithMotionMagicAcceleration(320_tr_per_s_sq)
   );
static constexpr configs::TalonFXConfiguration followerInitialConfigs = configs::TalonFXConfiguration{}
   .WithFeedback(
      configs::FeedbackConfigs{}
         .WithSensorToMechanismRatio(kAverageGearRatio)
   );

// CAN bus that the devices are located on;
// All mechanism devices must share the same CAN bus
static constexpr std::string_view kCANBusName = "canivore";
static inline CANBus kCANBus{kCANBusName, "./logs/example.hoot"};

static constexpr mechanisms::DifferentialMotorConstants differentialConstants =
   mechanisms::DifferentialMotorConstants<configs::TalonFXConfiguration>{}
      .WithCANBusName(kCANBusName)
      .WithLeaderId(0)
      .WithFollowerId(1)
      .WithAlignment(signals::MotorAlignmentValue::Opposed)
      .WithSensorToDifferentialRatio(kDifferenceGearRatio)
      .WithClosedLoopRate(200_Hz)
      .WithLeaderInitialConfigs(leaderInitialConfigs)
      .WithFollowerInitialConfigs(followerInitialConfigs)
      .WithFollowerUsesCommonLeaderConfigs(true);
# Average axis gains typically go in Slot 0
_average_gains = (
   configs.Slot0Configs()
   .with_k_p(10)
   .with_k_i(0)
   .with_k_d(0.1)
   .with_k_g(0.2)
   .with_k_s(0.1)
   .with_k_v(0.12)
   .with_k_a(0)
   .with_gravity_type(signals.GravityTypeValue.ARM_COSINE)
)
# Difference axis gains typically go in Slot 1
_difference_gains = (
   configs.Slot1Configs()
   .with_k_p(10)
   .with_k_i(0)
   .with_k_d(0.1)
   .with_k_s(0.1)
   .with_k_v(0.12)
)

_average_gear_ratio = 3.0
_difference_gear_ratio = 2.0

# Initial configs for the differential leader and follower motor controllers.
# Some configs will be overwritten; check the `with*InitialConfigs()` API documentation.
_leader_initial_configs = (
   configs.TalonFXConfiguration()
   .with_motor_output(
      configs.MotorOutputConfigs()
      .with_neutral_mode(signals.NeutralModeValue.BRAKE)
   )
   .with_current_limits(
      configs.CurrentLimitsConfigs()
      .with_stator_current_limit(80.0)
      .with_stator_current_limit_enable(True)
   )
   .with_feedback(
      configs.FeedbackConfigs()
      .with_sensor_to_mechanism_ratio(_average_gear_ratio)
   )
   .with_closed_loop_general(
      configs.ClosedLoopGeneralConfigs()
      # differential wrist is continuous on the difference axis
      .with_differential_continuous_wrap(True)
   )
   .with_slot0(_average_gains)
   .with_slot1(_difference_gains)
   .with_motion_magic(
      configs.MotionMagicConfigs()
      .with_motion_magic_cruise_velocity(80)
      .with_motion_magic_acceleration(320)
   )
)
_follower_initial_configs = (
   configs.TalonFXConfiguration()
   .with_feedback(
      configs.FeedbackConfigs()
      .with_sensor_to_mechanism_ratio(_average_gear_ratio)
   )
)

# CAN bus that the devices are located on;
# All mechanism devices must share the same CAN bus
self._canbus = CANBus("canivore", "./logs/example.hoot")

self._differential_constants: mechanisms.DifferentialMotorConstants[configs.TalonFXConfiguration] = (
   mechanisms.DifferentialMotorConstants()
   .with_can_bus_name(_canbus.name)
   .with_leader_id(0)
   .with_follower_id(1)
   .with_alignment(signals.MotorAlignmentValue.OPPOSED)
   .with_sensor_to_differential_ratio(_difference_gear_ratio)
   .with_closed_loop_rate(200.0)
   .with_leader_initial_configs(_leader_initial_configs)
   .with_follower_initial_configs(_follower_initial_configs)
   .with_follower_uses_common_leader_configs(True)
)

Building the Mechanism

The differential motor constants can then be used to construct the DifferentialMechanism (Java, C++, Python) or SimpleDifferentialMechanism (Java, C++, Python). The mechanisms have constructor overloads to provide a remote sensor for the Difference axis, such as the yaw of a Pigeon 2.

// Construct the mechanism, Difference axis uses half the difference between the motors
private final DifferentialMechanism<TalonFX> diffMech =
   new DifferentialMechanism<TalonFX>(TalonFX::new, differentialConstants);

// Construct the mechanism, Difference axis uses the yaw of a Pigeon 2
private final Pigeon2 pigeon2 = new Pigeon2(0, kCANBus);
private final DifferentialMechanism<TalonFX> diffMech =
   new DifferentialMechanism<TalonFX>(
      TalonFX::new, differentialConstants,
      pigeon2, DifferentialPigeon2Source.Yaw
   );
// Construct the mechanism, Difference axis uses half the difference between the motors
mechanisms::DifferentialMechanism<hardware::TalonFX> diffMech{differentialConstants};

// Construct the mechanism, Difference axis uses the yaw of a Pigeon 2
hardware::Pigeon2 pigeon2{0, kCANBus};
mechanisms::DifferentialMechanism<hardware::TalonFX> diffMech{
   differentialConstants, pigeon2,
   mechanisms::DifferentialPigeon2Source::Yaw
};
# Construct the mechanism, Difference axis uses half the difference between the motors
self._diff_mech: mechanisms.DifferentialMechanism[hardware.TalonFX] = (
   mechanisms.DifferentialMechanism(hardware.TalonFX, self._differential_constants)
)

# Construct the mechanism, Difference axis uses the yaw of a Pigeon 2
self._pigeon2 = hardware.Pigeon2(0, self._canbus)
self._diff_mech: mechanisms.DifferentialMechanism[hardware.TalonFX] = (
   mechanisms.DifferentialMechanism(
      hardware.TalonFX,
      self._differential_constants
      self._pigeon2,
      mechanisms.DifferentialPigeon2Source.YAW
   )
)
// Construct the mechanism, Difference axis uses half the difference between the motors
private final SimpleDifferentialMechanism<TalonFX> diffMech =
   new SimpleDifferentialMechanism<TalonFX>(TalonFX::new, differentialConstants);

// Construct the mechanism, Difference axis uses the yaw of a Pigeon 2
private final Pigeon2 pigeon2 = new Pigeon2(0, kCANBus);
private final SimpleDifferentialMechanism<TalonFX> diffMech =
   new SimpleDifferentialMechanism<TalonFX>(
      TalonFX::new, differentialConstants,
      pigeon2, DifferentialPigeon2Source.Yaw
   );
// Construct the mechanism, Difference axis uses half the difference between the motors
mechanisms::SimpleDifferentialMechanism<hardware::TalonFX> diffMech{differentialConstants};

// Construct the mechanism, Difference axis uses the yaw of a Pigeon 2
hardware::Pigeon2 pigeon2{0, kCANBus};
mechanisms::SimpleDifferentialMechanism<hardware::TalonFX> diffMech{
   differentialConstants, pigeon2,
   mechanisms::DifferentialPigeon2Source::Yaw
};
# Construct the mechanism, Difference axis uses half the difference between the motors
self._diff_mech: mechanisms.SimpleDifferentialMechanism[hardware.TalonFX] = (
   mechanisms.SimpleDifferentialMechanism(hardware.TalonFX, self._differential_constants)
)

# Construct the mechanism, Difference axis uses the yaw of a Pigeon 2
self._pigeon2 = hardware.Pigeon2(0, self._canbus)
self._diff_mech: mechanisms.SimpleDifferentialMechanism[hardware.TalonFX] = (
   mechanisms.SimpleDifferentialMechanism(
      hardware.TalonFX,
      self._differential_constants
      self._pigeon2,
      mechanisms.DifferentialPigeon2Source.YAW
   )
)