We’re moving to a new home! Our website is currently in test mode while we update and transfer our content. Some pages and resources may be temporarily unavailable. We’ll be back with all resources shortly. Thank you for your patience.

VEX V5 PYTHON | 02.2 TURNS AND HEADINGS

Guide objective

Distinguish relative turn angles from target headings, configure an inertial SmartDrive and test calibrated autonomous turns with explicit units.

01 | RELATIVE TURN OR TARGET DIRECTION?

Turn right 90 degrees means rotate another quarter-turn from the current orientation. Face heading 90 degrees means turn towards one direction in your established heading reference.

Use relative turns to describe a change in orientation. Use target headings to describe the direction you want to face, even if the starting orientation varies.

02 | TURN BY A RELATIVE ANGLE

drivetrain.set_turn_velocity(15, PERCENT)
drivetrain.turn_for(RIGHT, 90, DEGREES)
drivetrain.turn_for(LEFT, 45, DEGREES)

These turn angles describe robot rotation, not motor-shaft rotation. Each command starts from the orientation reached by the previous action.

Starting at heading 0, right 90 followed by left 45 ideally leaves the robot facing heading 45. The commands block by default, so the second begins when the first command ends.

A plain DriveTrain can estimate a relative turn from encoders and geometry. A sensor-configured SmartDrive uses orientation feedback. In either case, verify the physical outcome.

03 | TURN TO A HEADING

drivetrain.turn_to_heading(90, DEGREES)

This targets heading 90 rather than adding 90 to the current direction. If the robot already faces 90, it does not intentionally perform another quarter-turn.

The instruction matters as much as the number
Starting headingRelative RIGHT 90 targetHeading 90 target
09090
4513590
9018090
270090

Use a heading-capable sensor configuration for turn_to_heading. This guide uses a V5 Inertial Sensor and SmartDrive, rather than the sensor-free DriveTrain from 02.1.

04 | DEFINE ZERO FOR YOUR ROUTE

An inertial heading is a direction relative to an established reference. It is not automatically geographic north or a field position.

Place the robot facing the route’s chosen zero direction, complete calibration and set heading to zero once during preparation.

drivetrain.set_heading(0, DEGREES)

Setting heading changes the reported reference; it does not physically turn the robot. Repeatedly resetting zero after each turn would change the meaning of later route headings.

05 | HEADING WRAPS AROUND

Heading is reported from 0 up to just below 360 degrees. Heading 0 and a full-turn direction of 360 describe the same orientation; use 0 for the command target.

From 350 to 10, the shortest turn is 20 degrees right, not 340 degrees left. Target-heading turns normally choose the shortest direction. For an exactly opposite target, two 180-degree paths are equally short; use a relative directional turn when the direction matters.

Rotation is different: it can accumulate beyond one revolution. A 450-degree right rotation may be reported as 450 degrees while its heading is 90. Target headings do not express a required number of complete revolutions.

06 | ADD AND CALIBRATE THE INERTIAL SENSOR

Mount the sensor firmly in the correct orientation according to its installation guidance. Record its actual Smart Port. The example uses port 20.

inertial_sensor = Inertial(Ports.PORT20)
drivetrain = SmartDrive(
    left_drive, right_drive, inertial_sensor,
    WHEEL_TRAVEL_MM, TRACK_WIDTH_MM, WHEEL_BASE_MM,
    MM, EXTERNAL_GEAR_RATIO
)

Calibrate while the robot is still and disabled. Wait for is_calibrating to become false before starting a turn. The example bounds that wait and prevents movement if preparation does not finish.

Verify that the sensor is connected and its readings change appropriately before trusting calibration status. A completed calibration wait alone does not establish correct mounting or wiring.

As with motors, use either your manual sensor declaration or the Devices-panel definition, not both. Keep manual configuration below # Begin project code.

07 | COMPLETE SMARTDRIVE COMPETITION EXAMPLE

This extends the previous starter with explicit inertial configuration and calibration. It retains the driver controls from 01.6. Autonomous requests a right 90-degree turn, pauses, then faces heading 0.

Keep the generated imports and Brain setup above this user-code block, with math imported. Replace the earlier starter rather than adding a second drivetrain or competition object. Match all example ports, gear cartridges and geometry to your robot.

# =============================================================================
# TEAM [MY TEAM NAME / NUMBER]
# =============================================================================
# ROBOT CONFIGURATION
# - FOUR-motor drivetrain:
#       Left  = Ports 3 and 4
#       Right = Ports 1 and 2
# - Example: blue cartridges, left normal, right reversed.
#   VERIFY your actual ports, cartridges and direction settings.
# DRIVER CONTROLS
# - Left joystick vertical (axis3): forward/back
# - Right joystick horizontal (axis1): turn
# - This program MOVES in autonomous and driver-control modes.
# - Inertial sensor: Port 20 (example; check your robot)
# - Autonomous: right 90 degrees, then return to heading 0.
# - Example geometry assumes DIRECT drive (external ratio 1).
# - Add BELOW # Begin project code, outside generated configuration.
# - Do not also define these drive motors in the Devices panel.
# - Keep generated from vex import * and brain = Brain() above.

# =============================================================================
# FOUR-MOTOR DRIVETRAIN
# =============================================================================
left_motor_1 = Motor(Ports.PORT3, GearSetting.RATIO_6_1, False)
left_motor_2 = Motor(Ports.PORT4, GearSetting.RATIO_6_1, False)
right_motor_1 = Motor(Ports.PORT1, GearSetting.RATIO_6_1, True)
right_motor_2 = Motor(Ports.PORT2, GearSetting.RATIO_6_1, True)

left_drive = MotorGroup(left_motor_1, left_motor_2)
right_drive = MotorGroup(right_motor_1, right_motor_2)

# =============================================================================
# MEASURED GEOMETRY: EXAMPLES, REPLACE WITH YOUR MEASUREMENTS
# =============================================================================
WHEEL_DIAMETER_MM = 101.6
WHEEL_TRAVEL_MM = math.pi * WHEEL_DIAMETER_MM
TRACK_WIDTH_MM = 300
WHEEL_BASE_MM = 250
EXTERNAL_GEAR_RATIO = 1.0  # This example is direct drive only

inertial_sensor = Inertial(Ports.PORT20)

drivetrain = SmartDrive(
    left_drive, right_drive, inertial_sensor,
    WHEEL_TRAVEL_MM, TRACK_WIDTH_MM, WHEEL_BASE_MM,
    MM, EXTERNAL_GEAR_RATIO
)

# =============================================================================
# CONTROLLER, SETTINGS AND HELPERS
# =============================================================================
controller = Controller(PRIMARY)  # Do not duplicate a generated Controller
DEADBAND = 5
DRIVE_LIMIT_PERCENT = 30          # Start low; increase only after testing
TURN_GAIN = 0.7
LOOP_DELAY_MS = 20
AUTON_SPEED_PERCENT = 20
TEST_TURN_DEGREES = 90
TURN_SPEED_PERCENT = 15
CALIBRATION_LIMIT_MS = 5000
setup_ready = False
MOVE_TIMEOUT_SECONDS = 3
PAUSE_MS = 500

def apply_deadband(value):
    return 0 if abs(value) <= DEADBAND else value

def drive_mix(forward, turn):
    left = forward + turn
    right = forward - turn
    largest = max(100, abs(left), abs(right))
    left = left * 100 / largest
    right = right * 100 / largest
    return (left * DRIVE_LIMIT_PERCENT / 100,
            right * DRIVE_LIMIT_PERCENT / 100)

def set_side_output(side, output):
    if abs(output) < 0.01:
        side.stop(BRAKE)
    elif output > 0:
        side.spin(FORWARD, output, PERCENT)
    else:
        side.spin(REVERSE, -output, PERCENT)

def show_status(message):
    brain.screen.clear_screen()
    brain.screen.set_cursor(1, 1)
    brain.screen.print(message)

def stop_drive():
    left_drive.stop(BRAKE)
    right_drive.stop(BRAKE)

def autonomous_ready():
    return (setup_ready and competition.is_enabled()
            and competition.is_autonomous())

def turn_relative(direction, angle_degrees):
    if not autonomous_ready():
        stop_drive()
        return
    drivetrain.set_turn_velocity(TURN_SPEED_PERCENT, PERCENT)
    drivetrain.set_timeout(MOVE_TIMEOUT_SECONDS, SECONDS)
    drivetrain.turn_for(direction, angle_degrees, DEGREES)
    drivetrain.stop()

def face_heading(target_degrees):
    if not autonomous_ready():
        stop_drive()
        return
    drivetrain.set_turn_velocity(TURN_SPEED_PERCENT, PERCENT)
    drivetrain.set_timeout(MOVE_TIMEOUT_SECONDS, SECONDS)
    drivetrain.turn_to_heading(target_degrees, DEGREES)
    drivetrain.stop()

# =============================================================================
# PRE-AUTON: CALLED ONCE AT PROJECT STARTUP
# =============================================================================
def pre_autonomous():
    global setup_ready
    setup_ready = False
    stop_drive()
    show_status("Keep robot still")
    inertial_sensor.calibrate()
    elapsed_ms = 0
    while inertial_sensor.is_calibrating() and elapsed_ms < CALIBRATION_LIMIT_MS:
        wait(10, MSEC)
        elapsed_ms += 10
    if inertial_sensor.is_calibrating():
        show_status("Calibration timeout")
        return
    # Place the robot facing your chosen route-zero direction first.
    drivetrain.set_heading(0, DEGREES)
    drivetrain.set_stopping(BRAKE)
    setup_ready = True
    show_status("Setup ready")

# =============================================================================
# AUTONOMOUS: A FINITE ROUTINE
# =============================================================================
def autonomous():
    if not autonomous_ready():
        stop_drive()
        show_status("Setup not ready")
        return
    show_status("Right 90 degrees")
    turn_relative(RIGHT, TEST_TURN_DEGREES)
    if not autonomous_ready():
        stop_drive()
        return
    wait(PAUSE_MS, MSEC)
    show_status("Face heading 0")
    face_heading(0)
    stop_drive()
    show_status("Routine ended")

# =============================================================================
# DRIVER CONTROL: REPEATING UPDATES WHILE ENABLED
# =============================================================================
def driver_control():
    if not setup_ready:
        stop_drive()
        return
    show_status("Driver control")
    while competition.is_enabled() and competition.is_driver_control():
        forward = apply_deadband(controller.axis3.position())
        turn = apply_deadband(controller.axis1.position()) * TURN_GAIN
        left_output, right_output = drive_mix(forward, turn)
        set_side_output(left_drive, left_output)
        set_side_output(right_drive, right_output)
        wait(LOOP_DELAY_MS, MSEC)
    stop_drive()

# =============================================================================
# PROGRAM START: SETUP, THEN CALLBACK REGISTRATION
# =============================================================================
pre_autonomous()
competition = Competition(driver_control, autonomous)

# Keep the main task alive without running a driving loop here.
while True:
    wait(100, MSEC)

Start the project while disabled with the robot facing the intended zero direction. Keep it still until Setup ready appears. Test in a clear area with adequate clearance for the robot’s full turning envelope.

A timeout can end a turn before it reaches the target. Routine ended reports program progress, not accuracy. Inspect both the measured heading and the physical orientation.

08 | TEST REPEATABILITY AND ACCURACY

Mark an initial forward line and test relative right and left quarter-turns separately. Record the requested angle, final sensor heading and physically observed direction.

For heading tests, begin from different directions and ask the robot to face the same target. Keep the heading reference fixed. A heading command should target that direction rather than add the same amount each time.

Example turning test log
TestStartRequestRecord
Relative0RIGHT 90Actual heading and physical turn
Target45Heading 90Actual heading and physical turn
Wraparound350Heading 10Direction and angle travelled

Repeat three times before tuning. Small variation differs from a consistent systematic error. At wraparound, 359 and 1 are only two degrees apart, not 358.

09 | TROUBLESHOOT BEFORE ADDING COMPLEXITY

  • Wrong turn direction: verify drive reversal, side membership and sensor mounting.
  • Heading command fails: check that the drivetrain is a correctly sensor-configured SmartDrive.
  • Zero changes unexpectedly: look for repeated set_heading calls or project restarts.
  • Calibration times out: stop, inspect the connection and keep the robot still during a fresh calibration.
  • Overshoot: test a lower turn speed and inspect inertia, traction and mechanical play.
  • Turn stops short: investigate timeout and load before changing geometry.

CHALLENGES | RELATIVE AND TARGET TURNS

  1. Predict: compare RIGHT 90 with heading 90 from starting headings 0, 45 and 90.
  2. Configure: document the inertial sensor port and mounting alongside your motor map.
  3. Calibrate: demonstrate preparation while disabled and still.
  4. Quarter-turns: test relative left and right 90-degree commands separately.
  5. One target: approach heading 90 from several starting directions without redefining zero.
  6. Wraparound: test heading 10 from heading 350 and record the shorter path.
  7. Repeat: collect three trials before adjusting turn speed.
  8. Disable: verify that competition controls stop the robot during a turn.
Success evidence

A saved SmartDrive project, sensor configuration record and turning test log. You can explain the difference between angle change, heading target and a reset of the heading reference.

OFFICIAL REFERENCES

This is an independent learning resource; VEX and VEXcode are trademarks of their respective owners.