VEX V5 PYTHON | 04.4 ROTATION SENSORS

Guide objective

Measure a mechanism shaft or tracking wheel, distinguish angle from multi-turn position and convert calibrated rotation into useful movement targets.

01 | AN EXTERNAL ENCODER MEASURES A CHOSEN SHAFT

A V5 Rotation Sensor measures a shaft’s angle, accumulated position and angular velocity. It can observe a shaft even when that shaft is not driven directly by a motor.

A motor encoder tells you about the motor output shaft. An external sensor placed on a lift output, arm pivot or free-rolling tracking wheel can measure closer to the quantity you care about.

It still measures its own shaft, not the whole mechanism automatically. Loose couplings, backlash or a slipping wheel can separate sensor rotation from actual movement.

02 | ANGLE, POSITION AND VELOCITY

Use explicit units
API readingMeaningExample use
angle(DEGREES)Absolute angle within one revolution, 0 to below 360°Single-turn arm orientation
position(TURNS)Accumulated signed shaft turns; can be resetMulti-turn lift or wheel travel
velocity(VelocityUnits.RPM)Shaft revolutions per minuteMotion and settling checks

Angle wraps after one revolution. Position can continue above one turn or below zero. For example, +2.25 turns is +810° of accumulated change from its position reference.

The absolute angle is not reset by restarting the project or powering off. It does not preserve a multi-turn mechanism height or make a restart position reference trustworthy.

03 | MOUNT ON THE SHAFT YOU WANT TO OBSERVE

mechanism_rotation = Rotation(Ports.PORT19, False)

Port 19 is an example. Secure the sensor and shaft connection, document any gearing and keep the shaft aligned with the sensor mounting.

For a lift, a sensor on a downstream shaft may reveal movement that differs from the motor encoder after gearing or transmission play. Consider where a chain slip would occur relative to the sensor.

For a tracking wheel, connect the sensor to the free wheel’s shaft and keep the wheel reliably in contact with the surface.

04 | CHECK THE SIGN BEFORE CONTROL

Choose a positive direction: increasing lift height, increasing arm angle or robot-forward tracking travel. Then move the shaft slowly while the robot is disabled and observe the sign.

rotation_sensor = Rotation(Ports.PORT19, True)
# Or, for an existing sensor:
rotation_sensor.set_reversed(True)

Reverse the sensor interpretation if required. This does not reverse a motor. Separately verify that a positive motor command moves the mechanism in the positive sensor direction.

A sign mismatch can make a position controller drive away from its target. Check it at low output before enabling closed-loop movement.

05 | ZERO MUST MATCH A KNOWN PHYSICAL POSITION

mechanism_rotation.set_position(0, TURNS)

This changes the numerical position reference without moving the mechanism. Use it only after the mechanism is at a verified physical zero.

If the mechanism always starts at the bottom, verify that placement before resetting. A software flag cannot detect that someone started it halfway up.

Do not repeatedly zero during driver control or after a fault. That destroys the meaning of the limits. Automated homing with a limit switch is covered in 04.5.

06 | CONVERT SENSOR TURNS INTO MECHANISM TARGETS

Suppose the measured safe mechanism travel spans six sensor turns from its known lower reference. A nominal 30% target is:

target_turns = 30 / 100 × 6
# Target: 1.8 sensor turns

Six turns is an illustrative value to measure and replace. The count is sensor turns, not necessarily motor turns or arm revolutions.

A fraction of shaft travel equals a fraction of physical height only if the mechanical conversion is suitably linear. Changing spool radius or linkage geometry may require a different model.

Use targets inside the verified software-safe range
Nominal percentageSensor target for six-turn span
25%1.5 turns
30%1.8 turns
50%3.0 turns
75%4.5 turns

07 | KEEP SOFTWARE LIMITS INSIDE PHYSICAL STOPS

If a physical span is approximately 0 to 6 turns, software limits might start at 0.15 and 5.85 turns as teaching examples. Measure stopping behaviour and choose actual margins for the build.

At the upper software limit, block positive output but normally allow movement back down. At the lower limit, block negative output but normally allow movement up.

Any implausible reading should stop both directions and require investigation. A plausible but frozen reading can still be wrong; range checks are not a complete sensor-failure detector.

Use the same output guard in driver and autonomous control. A driver button must not bypass the protection that autonomous uses.

08 | A BOUNDED MECHANISM INTEGRATION HELPER

This excerpt is for an existing competition project with Brain, Controller and Competition objects. It explicitly declares one example motor and sensor; replace existing definitions rather than duplicating them.

Verify sensor connection and response, physical zero, direction, gearing and safe margins before setting ZERO_VERIFIED. Keep the mechanism mechanically supported for setup and testing.

# INTEGRATION EXAMPLE: replace/adapt the sensor declaration, do not duplicate it.
mechanism_rotation = Rotation(Ports.PORT19, False)
mechanism_motor = Motor(Ports.PORT9, GearSetting.RATIO_18_1, False)
# Motor FORWARD and positive sensor position must both mean increasing travel.
FULL_TRAVEL_TURNS = 6.0        # Measure sensor turns across the safe working range
LOWER_LIMIT_TURNS = 0.15      # Software margin inside physical stops
UPPER_LIMIT_TURNS = 5.85
POSITION_TOLERANCE_TURNS = 0.05
MECHANISM_KP_PERCENT_PER_TURN = 20
MAX_MECHANISM_PERCENT = 15
MOVE_LIMIT_MS = 4000
ZERO_VERIFIED = False         # Set only through deliberate verified preparation

# In disabled setup, after placing the mechanism at its verified physical zero:
# mechanism_rotation.set_position(0, TURNS)
# ZERO_VERIFIED = True
# Do not zero it from an unknown position.

def permitted_output(position_turns, requested_percent):
    if not -0.1 <= position_turns <= FULL_TRAVEL_TURNS + 0.1:
        return 0  # Fault range: stop both directions and investigate.
    if position_turns <= LOWER_LIMIT_TURNS and requested_percent < 0:
        return 0
    if position_turns >= UPPER_LIMIT_TURNS and requested_percent > 0:
        return 0
    return max(-MAX_MECHANISM_PERCENT, min(MAX_MECHANISM_PERCENT, requested_percent))

def move_mechanism_to_percent(target_percent):
    # Assumes your existing competition and controller objects.
    if not ZERO_VERIFIED:
        mechanism_motor.stop(BRAKE)
        return "ZERO_NOT_VERIFIED"
    if not 0 <= target_percent <= 100:
        raise ValueError("Target percentage must be from 0 to 100")
    target_turns = target_percent / 100 * FULL_TRAVEL_TURNS
    if not LOWER_LIMIT_TURNS <= target_turns <= UPPER_LIMIT_TURNS:
        raise ValueError("Target is outside the software-safe range")
    start_ms = brain.timer.time(MSEC)
    try:
        while True:
            if not (competition.is_enabled() and competition.is_autonomous()):
                return "MODE_CHANGED"
            if controller.buttonB.pressing():
                return "CANCELLED"
            if brain.timer.time(MSEC) - start_ms >= MOVE_LIMIT_MS:
                return "TIMEOUT"
            position = mechanism_rotation.position(TURNS)
            if not -0.1 <= position <= FULL_TRAVEL_TURNS + 0.1:
                return "POSITION_FAULT"
            error = target_turns - position
            if abs(error) <= POSITION_TOLERANCE_TURNS:
                return "POSITION_THRESHOLD_REACHED"
            requested = MECHANISM_KP_PERCENT_PER_TURN * error
            output = permitted_output(position, requested)
            if output == 0:
                return "LIMIT_STOP"
            direction = FORWARD if output > 0 else REVERSE
            mechanism_motor.spin(direction, abs(output), PERCENT)
            wait(20, MSEC)
    finally:
        # BRAKE is an example; choose cleanup for the supported actual mechanism.
        mechanism_motor.stop(BRAKE)

Calling move_mechanism_to_percent(30) requests 1.8 sensor turns in this example. It does not request 30 seconds or 30% motor power.

The helper stops on a threshold, limit, cancellation, mode change or deadline. It does not verify a low-speed settling dwell; add that if the application needs confirmed rest. Test run-on before relying on the margins.

A loaded lift may need suitable holding behaviour or mechanical support. BRAKE is a placeholder for the supported test mechanism, not a guarantee that a raised load stays in place. Do not rely on powered HOLD during disable or power loss.

09 | TRACKING WHEELS MEASURE ROLLING TRAVEL

A free-rolling tracking wheel is not powered by the drivetrain. Its sensor measures how much it rolls as the robot moves, which can reduce reliance on driven-wheel encoders during drive slip.

distance_mm = sensor_turn_change × wheel_turns_per_sensor_turn × π × wheel_diameter_mm

For direct shaft coupling, wheel turns per sensor turn is 1. With a 50.8 mm wheel, circumference is about 159.59 mm. A change of 2.5 turns corresponds to about 398.98 mm.

With gearing, define your ratio explicitly. If one sensor turn produces half a wheel turn, use 0.5 wheel turns per sensor turn. Measure and verify the relationship rather than guessing which way to multiply.

10 | BASELINES PRESERVE SHARED REFERENCES

start_turns = tracking_rotation.position(TURNS)

# Later:
turn_change = tracking_rotation.position(TURNS) - start_turns

Use a starting reading to measure one movement without resetting a sensor used by an odometry routine.

Signed changes preserve direction. Do not take absolute values if you need to distinguish forward from reverse travel.

A tracking wheel can still slip, lift off the floor or bounce. Compare its result with physically measured travel on the actual surface.

11 | COMPLETE SENSOR-ONLY DIAGNOSTIC

Keep generated imports, import math and Brain setup above this block. Paste below # Begin project code, outside the generated region. Replace movement code for this diagnostic.

The example uses two separate Rotation Sensors: mechanism port 19 and tracking port 20. Port 20 is not also an inertial sensor in this diagnostic; assign distinct actual ports when combining devices later.

There are no motor commands. Keep the robot disabled and move the mechanism only when safely supported. Leave MECHANISM_AT_KNOWN_ZERO False unless the physical reference has been verified.

# =============================================================================
# TEAM [MY TEAM NAME / NUMBER] | ROTATION DIAGNOSTIC
# =============================================================================
# SENSOR CONFIGURATION
# - Mechanism rotation sensor: Port 19
# - Tracking-wheel rotation sensor: Port 20
# - These are two separate example shafts; change ports to match your robot.
# - NO motor commands in this diagnostic.
# Keep generated imports, math import and Brain setup above this code.

mechanism_rotation = Rotation(Ports.PORT19, False)
tracking_rotation = Rotation(Ports.PORT20, False)
MECHANISM_AT_KNOWN_ZERO = False   # True only after physical zero is verified
MECHANISM_FULL_TRAVEL_TURNS = 6.0 # Illustrative sensor turns, not universal travel
TRACKING_WHEEL_DIAMETER_MM = 50.8
WHEEL_TURNS_PER_SENSOR_TURN = 1.0 # Direct shaft coupling in this example
REPORT_MS = 200

if MECHANISM_AT_KNOWN_ZERO:
    mechanism_rotation.set_position(0, TURNS)

# Baseline tracking travel without resetting a sensor used elsewhere.
tracking_start_turns = tracking_rotation.position(TURNS)

def tracking_distance_mm(sensor_turn_change):
    wheel_turns = sensor_turn_change * WHEEL_TURNS_PER_SENSOR_TURN
    return wheel_turns * math.pi * TRACKING_WHEEL_DIAMETER_MM

while True:
    shaft_angle = mechanism_rotation.angle(DEGREES)
    mechanism_turns = mechanism_rotation.position(TURNS)
    mechanism_speed = mechanism_rotation.velocity(VelocityUnits.RPM)
    tracking_turn_change = tracking_rotation.position(TURNS) - tracking_start_turns
    travel_mm = tracking_distance_mm(tracking_turn_change)
    if MECHANISM_AT_KNOWN_ZERO:
        mechanism_percent = 100 * mechanism_turns / MECHANISM_FULL_TRAVEL_TURNS
    else:
        mechanism_percent = None

    brain.screen.clear_screen()
    brain.screen.set_cursor(1, 1)
    brain.screen.print("Angle: " + str(round(shaft_angle, 1)))
    brain.screen.set_cursor(2, 1)
    brain.screen.print("Turns: " + str(round(mechanism_turns, 3)))
    brain.screen.set_cursor(3, 1)
    brain.screen.print("Mechanism %: " + str(mechanism_percent))
    brain.screen.set_cursor(4, 1)
    brain.screen.print("Tracking mm: " + str(round(travel_mm, 1)))
    print("ROTATION", shaft_angle, mechanism_turns, mechanism_speed,
          mechanism_percent, tracking_turn_change, travel_mm)
    wait(REPORT_MS, MSEC)

The mechanism percentage is None until a known zero is declared. The displayed percentage is not clamped, so a value outside 0–100 remains visible during diagnosis.

The tracking baseline records travel from program startup without resetting the tracking sensor. The diagnostic cannot verify cable health from a plausible reading alone; inspect the connection and confirm physical response.

12 | CALIBRATE TRACKING DISTANCE

  1. Check wheel diameter, shaft coupling and gearing ratio.
  2. Move the robot through a physically measured straight distance.
  3. Compare computed distance with the measurement over repeated trials.
  4. Check for slip and loss of contact before changing the conversion.
  5. If a repeatable scale error remains, update the effective distance factor and validate at another distance.

If a known 500 mm move reports 480 mm, multiply the existing distance-per-turn factor by 500/480 ≈ 1.0417. This corrects a scale model, not random slipping.

13 | ONE WHEEL IS NOT A COMPLETE FIELD POSITION

A tracking wheel reports travel along its own rolling direction. One forward-facing wheel cannot independently measure sideways movement and robot heading.

During a turn, an offset wheel also travels because the chassis rotates. The sensor’s change is not automatically the centre’s straight-line distance.

A complete odometry model uses sensor orientation, wheel offsets and heading change. Multiple wheel measurements can resolve more motion, but require a defined coordinate system and tested geometry.

This guide establishes the shaft-to-distance conversion. Keep full position estimation as a separate model rather than labelling one encoder reading as x/y position.

14 | RECORD AND TEST BOTH APPLICATIONS

Start with diagnosis before motor control
TestWhat to record
Known-zero mechanism testReference, sensor turns and actual position
Direction testMotor command sign and sensor-change sign
Safe-range testSoftware margins and observed run-on
Tracking straight/reverseSigned sensor change and physical distance
Tracking during rotationWheel offset and observed change
Repeated trialsScale bias, variation and surface conditions

Do not deliberately drive a mechanism into a hard stop to verify a software limit. Use conservative margins, simulated readings and supported low-output tests.

If motor and external-sensor readings disagree unexpectedly, inspect the transmission and sensor coupling rather than simply widening tolerances.

CHALLENGES | CONNECT ROTATION TO REAL MOVEMENT

  1. Explain: distinguish angle, accumulated position and angular velocity.
  2. Configure: document sensor ports, shaft location and positive directions.
  3. Calculate: find the sensor target for 40% of a verified six-turn span.
  4. Guard: explain which direction should remain allowed at each software limit.
  5. Convert: calculate travel from 2.5 turns of a direct-coupled 50.8 mm wheel.
  6. Measure: compare tracking distance over three forward and reverse trials.
  7. Evaluate: explain how a disconnected or slipping shaft can defeat a numerical limit.
  8. Extend: describe the extra information needed to turn tracking readings into field position.
Check the calculations

40% of six turns is 2.4 sensor turns. Tracking travel is 2.5 × π × 50.8 ≈ 398.98 mm with direct coupling. A two-to-one sensor/wheel gearing relationship must be defined before using a different factor.

OFFICIAL REFERENCES

The conversion and mechanism helper are team-written examples. This is an independent learning resource; VEX and VEXcode are trademarks of their respective owners.