POT V2 vs. ROTATION SENSOR DECISION FLOWCHART · ARM / LIFT ANGLE MEASUREMENT NEED ANGLE MEASUREMENT MECHANISM ROTATES > 333° ? YES NO NEED SUB-DEGREE PRECISION ? YES NO SMART PORT BUDGET TIGHT? OR COST-SENSITIVE? YES NO USE POT V2 276-7417 · ~$15 · ADI port 333° · absolute · 1° resolution EITHER WORKS Pot V2 simpler; Rotation higher precision USE ROTATION SENSOR 276-7705 · ~$50 · Smart port ∞ rotation · 0.01° centidegrees SPARTAN V1 HERO BOT RECOMMENDATION: POT V2 FOR ARM (SUFFICIENT PRECISION + SMART PORT BUDGET) SPARTAN DESIGN V5RC · sensors-rotation §6
Decision flowchart: Potentiometer V2 vs Rotation Sensor for V1 Hero Bot.
📐 Sensors · Engineer · Intermediate

V5 Rotation Sensor — Overview 🌀

The V5 Rotation Sensor is a hall-effect magnetic angle sensor on a Smart port. It tracks shaft rotation continuously, in 1/100-degree increments (centidegrees), with no mechanical stops. It's the canonical sensor for odometry tracking wheels and the higher-resolution alternative to the Potentiometer V2 for arm/lift angle.

🎯
What this guide is for: the Rotation Sensor as it's used on V5RC robots running PROS + EZ-Template (sometimes paired with LemLib). Hardware, mounting, code patterns, and a clear decision section comparing the Rotation Sensor against the Potentiometer V2 for arm control. Sources verified against the VEX product page, the VEX Knowledge Base, and the PROS V5 documentation.

At a Glance

Part Number276-7705 (V5 Rotation Sensor)
Connection3-pin V5 Smart Cable to any Smart Port (1–21)
Sensing MethodHall-effect magnetic angle (no mechanical wear)
Rotation RangeInfinite — tracks multi-turn position with no mechanical stops
Resolution1/100 of a degree (centidegrees) — 36,000 ticks per revolution
Default Data Rate~10 ms (configurable down to 5 ms)
Shaft Interface1/4″ square hole — press-fits standard VEX shaft inserts
Reversible in CodeYes — set_reversed(bool) flips the count direction

What Makes It Useful

Where It Fits in Spartan's Sensor Stack

Per the Sensor Onboarding Roadmap, the Rotation Sensor sits in Tier 4 (game-element-aware sensing) for arm/lift use, and Tier 5 (advanced positioning) for odometry tracking wheels. It's not a Tier 1 install — new teams should master IMU + motor encoders first — but it becomes nearly mandatory once you start tuning autons against odometry.

💡
For Override 2026-27: the Hero Bot baseline arm uses preset angles (ground, low goal 3.25″, mid 5.8″, high 8.7″). Either a Potentiometer V2 or a Rotation Sensor works. See Section 7: Rotation Sensor vs. Potentiometer for Arm Control for the decision matrix.

Related Guides

// Section 02
What the Rotation Sensor Measures 📊
The sensor reports two things continuously: an absolute position counter and an instantaneous angle within one revolution. Knowing the difference matters for both odometry and arm control.

Three Different Readings

Per the PROS V5 Rotation API, the sensor exposes three distinct readings:

get_position()Cumulative rotation count, in centidegrees (cd). 1 full revolution = 36,000 cd. Multi-turn capable: spins past 36,000 keep counting (e.g., 5 revolutions = 180,000 cd). This is the value you use for odometry tracking wheels.
get_angle()Current angle within one revolution, 0–36,000 cd. Wraps at every full turn. This is the value you use when you want absolute angle within a single rotation, like a wheel's heading or an arm whose total range is <360°.
get_velocity()Angular velocity in centidegrees/second. Useful for velocity-feedforward control or for detecting when a mechanism has stalled (velocity ≈ 0 with motor power applied).

Centidegrees, In Plain Numbers

The 1/100-degree resolution sounds abstract until you map it to physical motion:

Position vs. Angle: When Each Is Right

⚠️
Position is signed and accumulates indefinitely. If you spin the sensor 100 revolutions clockwise then 1 counter-clockwise, position is at +99 × 36,000 = +3,564,000 cd. int32_t can hold ±2.1 billion cd (~59,000 revolutions), so overflow isn't a practical concern. But you should still call reset_position() at initialize() or autonomous() start so each match begins from a known zero.
// Section 03
Mounting & Hardware 🔧
The Rotation Sensor reads a shaft. Mount it so the shaft turns cleanly, the sensor body is fixed, and the cable doesn't snag during mechanism travel. Different use cases (odom pod vs. arm) have different mounting concerns.

Physical Setup

The sensor has a 1/4″ square hole that accepts a standard VEX High Strength Shaft Insert. The shaft passes through the sensor; the sensor body bolts to the structure. As the shaft rotates, the sensor reads the angle.

For Odometry Tracking Wheels

The Rotation Sensor is the canonical tracking-wheel sensor in V5RC. Per odom-pod-build, the standard configuration is:

See odom-pod-types for the swing-arm vs. fixed-mount tradeoffs and odometry for how the cumulative position counts feed into pose estimation.

For Arm/Lift Angle

Mount the Rotation Sensor on the arm's pivot axle so the shaft is the pivot. Two common configurations:

💡
Mount once, calibrate forever. Once the sensor is bolted to the arm, the relationship between sensor counts and arm angle is fixed for that mount. If you remove and remount, you'll need to re-zero. Best practice: in initialize(), run arm_rot.reset_position() when the arm is in a known reference pose (typically all-the-way-down, resting against a hard stop or limit switch). Then arm angle = position / scale_factor.

Wiring

// Section 04
PROS Code Patterns 💻
The pros::Rotation class is small and stable. The interesting code is what you build around it: the arm position controller, the odom integration, the sensor-fusion checks. Worked examples below.

The PROS API Surface

Header#include "pros/rotation.hpp"
Classpros::Rotation
Constructorpros::Rotation rot(port); — port is 1–21
Positionint32_t get_position() — cumulative cd, can be negative
Angleint32_t get_angle() — 0–36000 cd, wraps
Velocityint32_t get_velocity() — cd/sec
Resetint32_t reset_position() — sets position to 0
Set Positionint32_t set_position(uint32_t pos) — force a specific value
Reverseint32_t set_reversed(bool reversed) — flip count direction
Data Rateint32_t set_data_rate(uint32_t rate) — ms between updates, default 10, min 5

Code Pattern: Arm Position Controller

Same P-controller pattern as the Potentiometer version in sensors-discrete § Code Pattern: Arm Position Control, but with centidegree readings:

// PROS C++ — arm to target angle using Rotation Sensor
// EN4: rewrite in your own words for your notebook. #include "pros/motor_group.hpp" #include "pros/rotation.hpp" #include <cstdlib> pros::Motor arm(14); pros::Rotation arm_rot(15); // Smart port 15 // Convert centidegrees to degrees for human-readable code constexpr double cd_per_deg = 100.0; // P-controller: drive arm to target angle (in degrees) void arm_to_angle(double target_deg) { const double kP = 0.15; // tune for your mechanism const double tolerance_deg = 0.5; while (true) { double current_deg = arm_rot.get_position() / cd_per_deg; double error = target_deg - current_deg; if (std::abs(error) < tolerance_deg) { arm.move(0); arm.brake(); return; } double power = error * kP; if (power > 127) power = 127; if (power < -127) power = -127; arm.move(power); pros::delay(10); } } // In initialize(), zero the sensor at known reference (arm fully down) void initialize() { arm_rot.reset_position(); }

Code Pattern: Override Arm Presets

The advanced-robot page uses arm preset constants. Here's the Rotation Sensor version of that pattern:

// PROS C++ — Override Hero Bot arm presets via Rotation Sensor
// EN4: rewrite in your own words for your notebook. namespace ArmPresets { // Centidegree targets for the Hero Bot arm // Adjust based on your physical mechanism + reduction constexpr int32_t GROUND = 0; // 0.00 deg — start position constexpr int32_t LOW_GOAL = 600; // 6.00 deg constexpr int32_t MID_GOAL = 1400; // 14.00 deg constexpr int32_t HIGH_GOAL = 2400; // 24.00 deg constexpr int32_t TOLERANCE = 50; // 0.50 deg } void arm_to_preset(int32_t target_cd) { const double kP = 0.15; while (true) { int32_t error = target_cd - arm_rot.get_position(); if (std::abs(error) < ArmPresets::TOLERANCE) { arm.brake(); return; } int32_t power = (int32_t)(error * kP); if (power > 127) power = 127; if (power < -127) power = -127; arm.move(power); pros::delay(10); } } // Driver control: button-based arm presets void opcontrol() { pros::Controller master(pros::E_CONTROLLER_MASTER); while (true) { if (master.get_digital_new_press(pros::E_CONTROLLER_DIGITAL_DOWN)) { arm_to_preset(ArmPresets::GROUND); } else if (master.get_digital_new_press(pros::E_CONTROLLER_DIGITAL_RIGHT)) { arm_to_preset(ArmPresets::LOW_GOAL); } else if (master.get_digital_new_press(pros::E_CONTROLLER_DIGITAL_UP)) { arm_to_preset(ArmPresets::MID_GOAL); } else if (master.get_digital_new_press(pros::E_CONTROLLER_DIGITAL_LEFT)) { arm_to_preset(ArmPresets::HIGH_GOAL); } pros::delay(10); } }

Code Pattern: Odom Tracking Wheel

The Rotation Sensor reading is the input to your pose-estimation math. The conversion is wheel-circumference dependent:

// PROS C++ — converting Rotation Sensor cd to inches of robot travel
// EN4: rewrite in your own words for your notebook. #include "pros/rotation.hpp" pros::Rotation tracking_par(16); // parallel pod pros::Rotation tracking_perp(17); // perpendicular pod // Tracking wheel constants constexpr double WHEEL_DIAMETER_IN = 2.0; constexpr double WHEEL_CIRC_IN = WHEEL_DIAMETER_IN * 3.14159265; constexpr double CD_PER_REV = 36000.0; constexpr double CD_PER_INCH = CD_PER_REV / WHEEL_CIRC_IN; // = 5732 cd/in for a 2" wheel double get_par_inches() { return tracking_par.get_position() / CD_PER_INCH; } double get_perp_inches() { return tracking_perp.get_position() / CD_PER_INCH; } // Reset both at the start of every match void initialize() { tracking_par.reset_position(); tracking_perp.reset_position(); }

Sensor Fusion: Rotation + IMU

The Rotation Sensor gives translation; the IMU gives heading. Together they give pose. Per the Sensor Onboarding Roadmap, the sensor-fusion pattern is:

  1. Read parallel and perpendicular tracking wheel positions (in inches)
  2. Read IMU heading (in radians)
  3. Compute pose deltas using your odometry math (small-angle approximation or arc-based)
  4. Update global x, y, theta

For a worked walkthrough, see odometry § Sensor Fusion. For the LemLib version of the same thing, see Section 5 below.

// Section 05
EZ-Template & LemLib Integration 🔗
EZ-Template handles the chassis with motor encoders. LemLib uses the Rotation Sensor as a first-class citizen for tracking wheels. Both can run alongside your own arm-controller task.

EZ-Template: Subsystem Pattern

EZ-Template doesn't directly consume the Rotation Sensor for chassis odometry — it uses motor encoders for distance and IMU for heading. Where the Rotation Sensor fits is in subsystem control (arm, lift, intake) running as a parallel task, just like the Potentiometer pattern in sensors-discrete § EZ-Template Integration.

// PROS task: arm position controller using Rotation Sensor (alongside EZ-Template)
// EN4: rewrite in your own words for your notebook. extern pros::Motor arm; extern pros::Rotation arm_rot; struct ArmState { pros::Mutex mutex; int32_t target_cd = 0; bool active = false; }; ArmState arm_state; void arm_task_fn(void* p) { const double kP = 0.15; while (true) { arm_state.mutex.take(); bool active = arm_state.active; int32_t target = arm_state.target_cd; arm_state.mutex.give(); if (active) { int32_t error = target - arm_rot.get_position(); int32_t power = (int32_t)(error * kP); if (power > 127) power = 127; if (power < -127) power = -127; arm.move(power); } pros::delay(10); } } void arm_set_target(int32_t target_cd) { arm_state.mutex.take(); arm_state.target_cd = target_cd; arm_state.active = true; arm_state.mutex.give(); } void initialize() { arm_rot.reset_position(); pros::Task t(arm_task_fn, nullptr, "arm"); } // Auton can now drive AND raise the arm in parallel void example_auton() { arm_set_target(1400); // arm to mid goal, runs in background chassis.pid_drive_set(36_in, DRIVE_SPEED, true); chassis.pid_wait(); // Both done by here — chassis arrived AND arm at mid goal }

LemLib: First-Class Tracking Wheel Support

LemLib is a chassis library that uses Rotation Sensor tracking wheels directly. The construction pattern wraps Rotation Sensors in lemlib::TrackingWheel objects:

// PROS C++ — LemLib chassis with Rotation Sensor tracking wheels
// EN4: rewrite in your own words for your notebook. #include "lemlib/api.hpp" #include "pros/rotation.hpp" #include "pros/imu.hpp" // Smart port assignments pros::MotorGroup left_drive({-1, -2}); pros::MotorGroup right_drive({3, 4}); pros::Rotation tracking_par(16); // parallel pod pros::Rotation tracking_perp(17); // perpendicular pod pros::Imu imu(15); // Tracking wheels (LemLib wrappers) lemlib::TrackingWheel par_wheel( &tracking_par, // pointer to Rotation Sensor lemlib::Omniwheel::NEW_2, // 2" omni -2.5 // signed distance from tracking center, inches ); lemlib::TrackingWheel perp_wheel( &tracking_perp, lemlib::Omniwheel::NEW_2, 1.5 ); lemlib::OdomSensors sensors(&par_wheel, nullptr, &perp_wheel, nullptr, &imu); // Drivetrain lemlib::Drivetrain drivetrain(&left_drive, &right_drive, 11.0, // track width lemlib::Omniwheel::NEW_325, 360, 8); // Tuning constants (placeholder — must be tuned for your robot) lemlib::ControllerSettings lateral_controller(10, 0, 30, 3, 1, 100, 3, 500, 20); lemlib::ControllerSettings angular_controller(2, 0, 10, 3, 1, 100, 3, 500, 0); lemlib::Chassis chassis(drivetrain, lateral_controller, angular_controller, sensors); void initialize() { chassis.calibrate(); // calibrates IMU and zeros tracking wheels } void autonomous() { chassis.setPose(0, 0, 0); chassis.moveToPoint(24, 24, 4000); chassis.moveToPose(48, 24, 90, 4000); }

The signed distance values (-2.5, 1.5) tell LemLib where each pod sits relative to the tracking center. Get these wrong and your odom will drift unpredictably. Measure them carefully on the physical robot.

⚠️
EZ-Template + LemLib are usually one or the other. Some teams run both (EZ-Template for the chassis PID, LemLib for path-following) but it's confusing for new programmers. Pick one chassis library per robot and stick with it. Mixing them is an advanced pattern.

Reading the Sensor in Pure Auton (No Library)

You don't need EZ-Template or LemLib to use the Rotation Sensor — PROS is enough. For odom, this is hard work but possible. For arm control, this is the most common pattern and works fine alongside any chassis library:

// PROS C++ — minimal auton using Rotation Sensor for arm angle
// EN4: rewrite in your own words for your notebook. extern pros::Rotation arm_rot; extern pros::Motor arm; void wait_until_arm_at(int32_t target_cd, int32_t tolerance_cd = 50, int timeout_ms = 3000) { int32_t start = pros::millis(); while (true) { int32_t current = arm_rot.get_position(); if (std::abs(current - target_cd) < tolerance_cd) return; if (pros::millis() - start > timeout_ms) return; // safety timeout pros::delay(10); } } // Use in auton: void simple_auton() { arm.move(80); wait_until_arm_at(1400); // wait until arm reaches mid goal arm.brake(); // ... rest of auton }
// Section 06
Override 2026-27 Use Cases 🎯
The Rotation Sensor has three main Override applications: arm angle for goal targeting (3 goal heights), odom tracking wheels for cross-field navigation, and (more niche) cup-flip mechanism state tracking.
📍
Status: Override 2026-27 confirmed per the v0.1 game manual (April 27, 2026). Goals are 3.25″ / 5.8″ / 8.7″ tall, robots have a 1+1 cup-and-pin possession limit (SG6), and the endgame requires ≤18″ height inside the midfield (SG12.1). The applications below reflect those facts.

Use Case 1: Arm Angle for Goal Targeting

Override has 3 goal heights (low 3.25″, mid 5.8″, tall 8.7″). The Hero Bot arm needs to reach all three. A Rotation Sensor on the arm pivot lets you:

This is the same use case the Potentiometer V2 covers. Section 7 (next) compares them head-to-head.

Use Case 2: Odom Tracking Wheels for Cross-Field Navigation

Override scoring is distributed: 4 alliance goals, 4 short neutral goals across quadrants, 1 tall center goal. A typical match has the robot moving across multiple goals. Encoder-only odometry (using motor encoders) drifts after long routes; tracking wheels via Rotation Sensors are much more accurate.

The Override field layout (per the v0.1 manual) is 12′ × 12′ with goals near corners and along the perimeter midpoints. Cross-field travel can exceed 8 feet in a single auton segment — the kind of distance where encoder drift becomes noticeable.

Use Case 3: Cup-Flip Mechanism State (More Niche)

Cups have one transparent side and one opaque side. Per SC3, a pin in the transparent side scores; a pin in the opaque side does not. Some teams will build a cup-flip mechanism — a small rotating cradle that flips the cup before placement to ensure transparent-side-up.

For detecting cup orientation before flipping (i.e., reading the cup itself), use the Optical Sensor — see sensors-optical § Override Use Cases.

What the Rotation Sensor Doesn't Help With

// Section 07
Rotation Sensor vs. Potentiometer V2 — Decision Matrix ⚖️
For arm and lift angle measurement, the Potentiometer V2 (276-7417) and the Rotation Sensor (276-7705) are both reasonable choices. The decision depends on rotation range, precision needs, port budget, and cost. Here's how to choose.

Side-by-Side Specs

SpecPotentiometer V2 (276-7417)Rotation Sensor (276-7705)
Connection3-wire ADI (analog port)Smart Cable (Smart port)
Sensing Range~333° usable (single rotation only)Infinite (multi-turn capable)
Resolution~1° practical (analog noise)0.01° (centidegrees, 36000/rev)
Reading TypeAbsolute (knows angle on power-up)Relative (needs zeroing each session)
Update Rate~10 ms (ADI poll)10 ms default, 5 ms minimum
Cost$30 for 2-pack ($15 each)~$50 each
PROS Classpros::adi::Potentiometerpros::Rotation
Mechanical WearCarbon track (V2 is improved over original; still has finite life)Hall-effect magnetic (no contact, no wear)
Used By LemLibNo (not a tracking-wheel option)Yes (first-class support)

The Decision, Plainly

⬢ Use a Potentiometer V2 when:
  • The mechanism rotates within a fixed range under 333° (most arms, most lifts, most cup-flip mechanisms)
  • You need absolute angle on power-up — e.g., you want to know the arm position at the start of a match without re-zeroing
  • Your Smart Port budget is tight (Pot V2 uses an ADI port, not a Smart port)
  • Cost matters — Pot V2 is half the price of the Rotation Sensor
  • You don't need sub-degree precision (1° resolution is plenty for arm presets at 6° / 14° / 24°)
🌀 Use a Rotation Sensor when:
  • You're building odom tracking wheels — this is the canonical use, no contest
  • The mechanism can rotate beyond 333° (rare for arms, but happens with chain bars or wrap-around mechanisms)
  • You need very precise angle (sub-degree) — e.g., for a fine catapult charge or a precision turret
  • You're using LemLib and want chassis tracking wheels
  • Long-term reliability matters (no mechanical wear) and you can afford the cost

For Override 2026-27, What We Recommend

Spartan's V1 Hero Bot baseline (per the kickoff brief) calls for IMU + Potentiometer for arm angle. After looking at this in detail, that recommendation holds:

Where you should switch to a Rotation Sensor: if you decide to build odom tracking wheels for skills runs (recommended for Period 4 vet teams chasing high skills scores), use Rotation Sensors for those. Don't mix — if you're investing in Rotation Sensors anyway, it can be cleaner to put one on the arm too. But for V1, Pot V2 is the right call.

What About a Motor Encoder for Arm Angle?

The Smart Motor has a built-in encoder. Why not use that?

💡
Tier order for arm sensing: motor encoder (free, built-in) → Potentiometer V2 (cheap, absolute, simple) → Rotation Sensor (expensive, precise, multi-turn). Most V1 Override Hero Bots can stop at motor encoder + a limit switch at home position. Pot V2 is the next upgrade. Rotation Sensor is the upgrade after that, and is rarely worth it for arm control alone.

Cross-References

⚙️ STEM Highlight: Why Hall-Effect Beats Carbon Track

Inside the original VEX Potentiometer is a strip of carbon-coated plastic with a metal contact (called a wiper) that slides along it as the shaft turns. The wiper's position changes the electrical resistance, and that's how the sensor knows the angle. It's the same mechanism as a guitar volume knob.

The problem: the wiper rubs the carbon every time the shaft turns. After many cycles, the carbon wears down at the spots that get the most travel. The reading at those worn spots gets jittery or wrong. Like a record needle skipping on a scratched LP.

The V5 Rotation Sensor uses a completely different idea. A small magnet rides on the shaft. The sensor body has a chip that detects the magnetic field direction without touching anything. As the magnet rotates, the field rotates, and the chip reports the angle.

Nothing slides. Nothing wears. The reading stays the same after a million revolutions. That's why competitive teams trust the Rotation Sensor for tracking wheels — a tracking wheel might spin 500+ revolutions in a single skills run, and you can't afford the reading to degrade mid-route.