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.
| Starting heading | Relative RIGHT 90 target | Heading 90 target |
|---|---|---|
| 0 | 90 | 90 |
| 45 | 135 | 90 |
| 90 | 180 | 90 |
| 270 | 0 | 90 |
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.
| Test | Start | Request | Record |
|---|---|---|---|
| Relative | 0 | RIGHT 90 | Actual heading and physical turn |
| Target | 45 | Heading 90 | Actual heading and physical turn |
| Wraparound | 350 | Heading 10 | Direction 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
- Predict: compare RIGHT 90 with heading 90 from starting headings 0, 45 and 90.
- Configure: document the inertial sensor port and mounting alongside your motor map.
- Calibrate: demonstrate preparation while disabled and still.
- Quarter-turns: test relative left and right 90-degree commands separately.
- One target: approach heading 90 from several starting directions without redefining zero.
- Wraparound: test heading 10 from heading 350 and record the shorter path.
- Repeat: collect three trials before adjusting turn speed.
- 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.