The Pattern: Declare Once, Use Everywhere

PROS hardware objects (motors, sensors, etc.) are just C++ objects. The convention used in this codebase is:

This avoids multiple-definition linker errors and keeps everything organised.

Drivetrain Motors

This robot uses a 6-motor tank drivetrain (three per side) with blue (600 RPM) cartridges geared down externally. Motors are grouped into pros::MotorGroup objects. Negative port numbers reverse the motor direction in software.

src/robot.cpp// Negative port  =  motor is physically reversed
pros::MotorGroup leftMotorGroup({-9, -19, -20}, pros::v5::MotorGears::blue);
pros::MotorGroup rightMotorGroup({16, 12, 11},  pros::v5::MotorGears::blue);
ℹ️
Port numbering. VEX V5 has 21 smart ports (1–21). You can find port assignments in VEXcode or by checking which port lights up when you plug in a cable. Use negative numbers to reverse direction instead of physically swapping motor connectors.

Intake & Scoring Mechanisms

The Push Back robot has a two-stage intake and several pneumatic mechanisms:

VariableTypePurpose
firstStageIntakepros::Motor (green, port 18)Picks blocks up from the floor / loaders (flex wheels)
secondStageIntakepros::Motor (blue, port −5)Feeds blocks up into the basket or scores into goals
slideMechanismLeft/Rightpros::adi::DigitalOut H / GDeploys a slide that channels blocks from loaders to the intake
descoreMechanismpros::adi::DigitalOut EPops blocks out of opponent long goals
alignerpros::adi::DigitalOut FTriangle-shaped wedge for aligning with long goals
src/robot.cpppros::adi::DigitalOut slideMechanismLeft('H');
pros::adi::DigitalOut slideMechanismRight('G');

// Helper: fire both solenoids together
void slideMechanism(bool value) {
    slideMechanismLeft.set_value(value);
    slideMechanismRight.set_value(value);
}

pros::adi::DigitalOut descoreMechanism('E');
pros::adi::DigitalOut aligner('F');

pros::Motor firstStageIntake(18,  pros::MotorGears::green);
pros::Motor secondStageIntake(-5, pros::MotorGears::blue);

Sensors

Sensors are the eyes of the robot. Here is every sensor on this robot and why it exists:

VariableType / PortRole
inertialIMU, port 10Tracks absolute heading; primary input for odometry
verticalEncoderRotation sensor, port −14Vertical tracking wheel for forward/backward odometry
distanceRightDistance sensor, port 15Measures distance to the right field wall → resets X or Y pose
distanceBackDistance sensor, port 2Measures distance to the back field wall → resets X or Y pose
opticalOptical sensor, port 7Detects block hue; used for color-sorting / anti-jam
gpsGPS sensor, port 13Global position via field tape (backup for long skills runs)
src/robot.cpppros::Imu     inertial(10);
pros::Gps     gps(13);
pros::Optical optical(7);
pros::Distance distanceRight(15);
pros::Distance distanceBack(2);

// Rotation sensor on the vertical tracking wheel
pros::Rotation verticalEncoder(-14); // negative = reversed
⚠️
IMU placement matters. Mount the IMU as close to the robot's center of rotation as possible, and on a flat surface. Vibrations from motors/pneumatics can introduce heading drift. Calling chassis.calibrate() at startup zeros it out for that run, but mid-run drift is why distance resets exist.

LemLib Drivetrain Configuration

LemLib needs a lemlib::Drivetrain struct that describes the physical geometry. These values must match your real robot—they affect how far the robot thinks it moves.

src/robot.cpplemlib::Drivetrain drivetrain(
    &leftMotorGroup,
    &rightMotorGroup,
    11.5,                       // track width (inches, center-to-center)
    lemlib::Omniwheel::NEW_325, // 3.25" omni wheels
    450,                        // RPM at the wheel
    2                           // horizontal drift factor (2 for omni/drift, 8 for traction)
);

Tracking Wheel

A tracking wheel is a free-spinning wheel with an encoder. It is not driven—it only measures movement—so it has no wheel slip from motor torque. This robot uses one vertical tracker:

src/robot.cpp// Tracking wheel: 1.97" diameter, 0" offset from center
lemlib::TrackingWheel verticalTracker(&verticalEncoder, 1.97, 0);

lemlib::OdomSensors sensors(
    &verticalTracker, // vertical tracking wheel #1
    nullptr,          // vertical #2 (not used)
    nullptr,          // horizontal tracking wheel (not used)
    nullptr,          // horizontal #2
    &inertial          // IMU for heading
);
Why use a tracking wheel instead of drive encoders? Drive wheels slip when accelerating, braking, or pushing/being pushed. A passive tracking wheel is isolated from motor torque, giving much cleaner position data—especially important for long skills runs.

Putting It Together: Chassis

All the pieces above are assembled into a single Chassis object. This object is used everywhere in the codebase for movement.

src/robot.cppChassis chassis(
    drivetrain,
    lateral_controller,  // see PID chapter
    angular_controller,  // see PID chapter
    sensors,
    &throttle_curve,      // expo curve for driver control
    &steer_curve
);