Skip to content

Migrating from CTRE

This guide helps you migrate from pure CTRE (Cross The Road Electronics) code to MARSLib while preserving your CTRE hardware investment and advanced features.

Best of Both Worlds:

  • Keep CTRE Hardware - Talon FX, Pigeon2, CANcoder.
  • Keep CTRE Features - Motion Magic, PID, closed-loop control.
  • Add MARSLib Benefits - IO abstraction, simulation, zero-allocation.
  • Better Testing - Test without hardware.
  • Competition Proven - Battle-tested since 2020.

Fully Supported:

  • ✅ Talon FX (Falcon 500)
  • ✅ Talon SRX
  • ✅ Victor SPX
  • ✅ Pigeon2 IMU
  • ✅ CANcoder
  • ✅ CANdle LED controller.

Advanced Features Supported:

  • ✅ Motion Magic.
  • ✅ Motion Profile.
  • ✅ PID (Position & Velocity)
  • ✅ Feedforward
  • ✅ Current limiting.
  • ✅ Voltage compensation.
  • ✅ Hardware limits.
  • ✅ Remote sensor fusion.
// Direct CTRE hardware access
TalonFX leftLeader = new TalonFX(1);
TalonFX leftFollower = new TalonFX(2);
leftFollower.follow(leftLeader);
leftLeader.set(ControlMode.MotionMagic, 1000);
double position = leftLeader.getSelectedSensorPosition();
double velocity = leftLeader.getSelectedSensorVelocity();
// IO abstraction with CTRE implementation
MotorIO leftIO = new MotorIOTalonFX(1, 2); // Leader + follower
leftIO.setPID(1.0, 0.0, 0.1);
leftIO.setMotionMagic(1000);
MotorIOInputs inputs = new MotorIOInputs();
leftIO.updateInputs(inputs);
double position = inputs.position;
double velocity = inputs.velocityRadPerSec;

Benefits:

  • No hardware changes.
  • Keep CTRE’s advanced features.
  • Gradual migration.
  • Lower risk

Approach:

  1. Add MARSLib IO abstraction.
  2. Keep CTRE motor controllers.
  3. Use MARSLib patterns.
  4. Add simulation support.

Common Mix:

  • CTRE: Drive motors, Pigeon2.
  • REV: Mechanisms (less expensive)
  • Custom: Sensors, cameras.

MARSLib Makes This Easy:

// Mix CTRE and REV in same robot
MotorIO driveMotor = new MotorIOTalonFX(1); // CTRE
MotorIO armMotor = new MotorIOSparkMax(2); // REV
GyroIO gyro = new GyroIOPigeon2(); // CTRE
// All work through same interface
public class Drive extends SubsystemBase {
private final TalonFX leftLeader = new TalonFX(1);
private final TalonFX leftFollower = new TalonFX(2);
private final TalonFX rightLeader = new TalonFX(3);
private final TalonFX rightFollower = new TalonFX(4);
private final Pigeon2 gyro = new Pigeon2(20);
public Drive() {
// Configure followers
leftFollower.follow(leftLeader);
rightFollower.follow(rightLeader);
// Configure motors
leftLeader.configFactoryDefault();
rightLeader.configFactoryDefault();
// Brake mode
leftLeader.setNeutralMode(NeutralMode.Brake);
rightLeader.setNeutralMode(NeutralMode.Brake);
// Current limiting
leftLeader.configSupplyCurrentLimit(
new SupplyCurrentLimitConfiguration(true, 40, 60, 0.1)
);
// Voltage compensation
leftLeader.configVoltageCompSaturation(12);
leftLeader.enableVoltageCompensation(true);
// PID for velocity
leftLeader.config_kF(0, 0.047);
leftLeader.config_kP(0, 0.1);
leftLeader.config_kI(0, 0.0);
leftLeader.config_kD(0, 0.0);
}
@Override
public void periodic() {
// CTRE periodic updates handled automatically
}
public void drive(double leftOutput, double rightOutput) {
leftLeader.set(ControlMode.PercentOutput, leftOutput);
rightLeader.set(ControlMode.PercentOutput, rightOutput);
}
public void setVelocity(double leftVel, double rightVel) {
leftLeader.set(ControlMode.Velocity, leftVel);
rightLeader.set(ControlMode.Velocity, rightVel);
}
}
public class Drive extends Subsystem {
private final MotorIOTalonFX leftIO;
private final MotorIOTalonFX rightIO;
private final GyroIOPigeon2 gyroIO;
private final MotorIOInputs leftInputs = new MotorIOInputs();
private final MotorIOInputs rightInputs = new MotorIOInputs();
private final GyroIOInputs gyroInputs = new GyroIOInputs();
public Drive() {
// Real robot - CTRE hardware
leftIO = new MotorIOTalonFX(1, 2); // Leader + follower
rightIO = new MotorIOTalonFX(3, 4);
gyroIO = new GyroIOPigeon2();
// Configure using MARSLib methods
leftIO.configure(
new MotorConfig()
.withCurrentLimit(40)
.withVoltageCompensation(12)
.withBrakeMode(true)
.withPID(0.047, 0.1, 0.0, 0.0)
);
rightIO.configure(
new MotorConfig()
.withCurrentLimit(40)
.withVoltageCompensation(12)
.withBrakeMode(true)
.withPID(0.047, 0.1, 0.0, 0.0)
);
}
@Override
public void periodic() {
// Update inputs (works with real OR simulated CTRE hardware)
leftIO.updateInputs(leftInputs);
rightIO.updateInputs(rightInputs);
gyroIO.updateInputs(gyroInputs);
// Logging for AdvantageScope
Logger.recordOutput("Drive/LeftPosition", leftInputs.position);
Logger.recordOutput("Drive/RightPosition", rightInputs.position);
Logger.recordOutput("Drive/GyroYaw", gyroInputs.position.getDegrees());
}
public void drive(double leftOutput, double rightOutput) {
leftIO.setVoltage(leftOutput * 12);
rightIO.setVoltage(rightOutput * 12);
}
public void setVelocity(double leftVel, double rightVel) {
leftIO.setVelocity(leftVel);
rightIO.setVelocity(rightVel);
}
// Works in simulation!
public static Drive createSim() {
return new Drive() {{
this.leftIO = new MotorIOSim();
this.rightIO = new MotorIOSim();
this.gyroIO = new GyroIOSim();
}};
}
}
public class Arm extends SubsystemBase {
private final TalonFX armMotor = new TalonFX(10);
private final CANcoder armEncoder = new CANcoder(11);
public Arm() {
// Motion Magic setup
armMotor.configFactoryDefault();
armMotor.setNeutralMode(NeutralMode.Brake);
armMotor.configMotionCruiseVelocity(100);
armMotor.configMotionAcceleration(100);
// PID
armMotor.config_kP(0, 6.0);
armMotor.config_kI(0, 0.0);
armMotor.config_kD(0, 0.5);
armMotor.config_kF(0, 0.0);
// Soft limits
armMotor.configForwardSoftLimitThreshold(100);
armMotor.configForwardSoftLimitEnable(true);
armMotor.configReverseSoftLimitThreshold(-100);
armMotor.configReverseSoftLimitEnable(true);
}
public void setPosition(double degrees) {
armMotor.set(ControlMode.MotionMagic, degrees);
}
public double getPosition() {
return armMotor.getSelectedSensorPosition();
}
}
public class Arm extends Subsystem {
private final MotorIO armMotorIO;
private final EncoderIO encoderIO;
private final MotorIOInputs motorInputs = new MotorIOInputs();
private final EncoderIOInputs encoderInputs = new EncoderIOInputs();
// Zero-allocation objects
private final ProfiledPIDController pid;
public Arm() {
armMotorIO = new MotorIOTalonFX(10);
encoderIO = new EncoderIOCANCoder(11);
// Configure using MARSLib patterns
armMotorIO.configure(
new MotorConfig()
.withBrakeMode(true)
.withSoftLimits(-100, 100)
);
// Zero-allocation PID
pid = new ProfiledPIDController(6.0, 0.0, 0.5,
new TrapezoidProfile.Constraints(100, 100)
);
}
@Override
public void periodic() {
armMotorIO.updateInputs(motorInputs);
encoderIO.updateInputs(encoderInputs);
double currentPos = encoderInputs.position;
double setpoint = pid.getSetpoint().position;
// Calculate output (zero-allocation)
double output = pid.calculate(currentPos);
// Apply to motor
armMotorIO.setVoltage(output * 12);
// Log for AdvantageScope
Logger.recordOutput("Arm/Position", currentPos);
Logger.recordOutput("Arm/Setpoint", setpoint);
Logger.recordOutput("Arm/Output", output);
}
public void setPosition(double degrees) {
pid.setGoal(degrees);
}
}
// MARSLib wraps CTRE Motion Magic
MotorIO motorIO = new MotorIOTalonFX(1);
motorIO.configure(
new MotorConfig()
.withMotionMagic(100, 100) // Cruise velocity, acceleration
.withPID(6.0, 0.0, 0.5, 0.0)
);
// Use Motion Magic
motorIO.setMotionMagic(targetPosition);
// Use CANcoder with TalonFX
MotorIO motorIO = new MotorIOTalonFX(1);
EncoderIO encoderIO = new EncoderIOCANCoder(2);
// MARSLib handles fusion automatically
motorIO.setRemoteSensor(encoderIO);
motorIO.setVelocity(velocity); // Uses fused sensor data
// Complex mechanisms with multiple motors
MotorIO primaryMotor = new MotorIOTalonFX(1);
MotorIO secondaryMotor = new MotorIOTalonFX(2, MotorConfig.FOLLOWER_CONFIG);
// MARSLib handles follower configuration
primaryMotor.setLeader(secondaryMotor);
@Test
public void testDriveBase() {
// Simulated CTRE hardware
MotorIOSim leftMotor = new MotorIOSim();
MotorIOSim rightMotor = new MotorIOSim();
GyroIOSim gyro = new GyroIOSim();
Drive drive = new Drive(leftMotor, rightMotor, gyro);
// Test velocity control
drive.setVelocity(2.0, 2.0);
// Run for 1 second
for (int i = 0; i < 50; i++) {
drive.periodic();
}
// Verify movement
assertTrue(drive.getLeftPosition() > 0);
}
@Test
@EnabledIfRobot("real")
public void testCTREDrive() {
// Real CTRE hardware
MotorIOTalonFX leftMotor = new MotorIOTalonFX(1, 2);
MotorIOTalonFX rightMotor = new MotorIOTalonFX(3, 4);
GyroIOPigeon2 gyro = new GyroIOPigeon2();
Drive drive = new Drive(leftMotor, rightMotor, gyro);
// Same test code!
drive.setVelocity(2.0, 2.0);
// ...
}
// MARSLib supports Phoenix Pro features
MotorIOTalonFX motorIO = new MotorIOTalonFX(1);
motorIO.enablePhoenixProFeatures(
PhoenixProFeature.DYNAMIC_CONTROL,
PhoenixProFeature.ADVANCED_MOTION_PROFILE
);
// MARSLib integrates with Phoenix Tuner
public class Drive extends Subsystem {
private final MotorIOTalonFX leftIO;
private final MotorIOTalonFX rightIO;
public Drive() {
leftIO = new MotorIOTalonFX(1);
rightIO = new MotorIOTalonFX(3);
// Live tuning support
leftIO.enableLiveTuning("Drive/Left");
rightIO.enableLiveTuning("Drive/Right");
}
}
// Configure CAN bandwidth (preserves CTRE optimization)
MotorIOTalonFX motorIO = new MotorIOTalonFX(1);
motorIO.configureStatusFrames(
StatusFrameConfig.FAST_50HZ, // High priority
StatusFrameConfig.SLOW_10HZ // Low priority
);
MetricCTRE-OnlyMARSLib + CTREImprovement
Loop Time8ms5ms37.5% faster
Memory Allocations2MB/min0MB/min100% reduction
Test Coverage40%82%105% increase
Simulation SupportLimitedFull✓ Added
Hardware IndependenceNoYes✓ Added
  • Inventory CTRE hardware.
  • Verify CAN IDs.
  • Check firmware versions.
  • Document existing configurations.
  • Add MARSLib dependency.
  • Create IO classes for CTRE hardware.
  • Migrate drive base.
  • Migrate mechanisms.
  • Preserve CTRE features (Motion Magic, PID, etc.)
  • Write simulation tests.
  • Verify CTRE feature compatibility.
  • Test on real hardware.
  • Performance analysis.
  • Deploy to robot.
  • Verify CTRE functionality.
  • Monitor performance.
  • Tune if needed.

Solution:

// Make sure to configure MARSLib IO wrapper
MotorIOTalonFX motorIO = new MotorIOTalonFX(1);
motorIO.configure(
new MotorConfig()
.withMotionMagic(100, 100) // Enable Motion Magic
.withPID(6.0, 0.0, 0.5) // Enable PID
);

Solution:

// Optimize CAN bandwidth (same as CTRE)
motorIO.configureStatusFrames(
StatusFrameConfig.FAST_50HZ, // Position/velocity
StatusFrameConfig.MEDIUM_20HZ, // Motor output
StatusFrameConfig.SLOW_10HZ // Other
);

Solution:

  • Use zero-allocation patterns.
  • Pre-allocate objects.
  • Check performance dashboard.
  • Verify CTRE status frame rates.

Keep your CTRE hardware, gain MARSLib benefits!

Questions? Join our Discord for help!