Driver Control Programming
Overview
During a match, your robot has two separate phases: autonomous (the robot drives itself) and driver control (a human uses the controller to drive). This guide covers how to write the driver control program — the code that reads joystick positions and button presses and turns them into motor commands.
Why does this matter?
Even the best-built robot loses matches if it is hard to drive. Good driver control code makes the robot feel smooth and responsive. Bad driver control feels jerky, drifts on its own, or does unexpected things when you barely touch the joystick.
The Driver Control Loop
Driver control runs inside a continuous loop. Every 20 milliseconds, the program reads the controller and updates the motors. This happens about 50 times per second — fast enough to feel instant to the driver.
# VEX IQ Python
def driver_control():
while True:
# Read controller and update motors here
wait(20, MSEC) # Always include this delay!// VEX V5 C++
void usercontrol() {
while (true) {
// Read controller and update motors here
wait(20, msec); // Always include this delay!
}
}The 20ms wait is important — without it, the loop runs thousands of times per second and overloads the processor.
Reading the Controller
The VEX controller has two joysticks (called axes) and several buttons.
Joystick axes return a value from -100 to +100:
- 0 = joystick centered (not touched)
- +100 = joystick pushed all the way forward/right
- -100 = joystick pushed all the way back/left
# VEX IQ Python
forward_speed = controller.axis3.position() # Left stick up/down
turn_speed = controller.axis1.position() # Right stick left/right// VEX V5 C++
int forwardSpeed = Controller1.Axis3.position();
int turnSpeed = Controller1.Axis1.position();Drive Styles
There are two common ways to map the joysticks to your drivetrain motors.
Tank Drive
Left stick controls the left motors. Right stick controls the right motors. Push both sticks forward to go straight. Push one forward and one back to turn.
# VEX IQ Python
left_speed = controller.axis3.position()
right_speed = controller.axis2.position()
left_motor.spin(FORWARD, left_speed, PERCENT)
right_motor.spin(FORWARD, right_speed, PERCENT)Pros: Simple to understand, easy to implement
Cons: Hard to drive in a straight line — both sticks need to be exactly equal
Arcade Drive
One stick controls forward/backward speed. The other stick controls turning. The program combines them to set left and right motor speeds.
# VEX IQ Python
forward = controller.axis3.position() # left stick up/down
turn = controller.axis1.position() # right stick left/right
left_speed = forward + turn
right_speed = forward - turn
left_motor.spin(FORWARD, left_speed, PERCENT)
right_motor.spin(FORWARD, right_speed, PERCENT)// VEX V5 C++
int forward = Controller1.Axis3.position();
int turn = Controller1.Axis1.position();
int leftSpeed = forward + turn;
int rightSpeed = forward - turn;
LeftMotor.spin(forward, leftSpeed, percent);
RightMotor.spin(forward, rightSpeed, percent);Pros: Easy to drive straight (just push one stick forward), more natural for most drivers
Cons: Slightly harder to implement correctly
Most competitive VEX teams use arcade drive.
Deadband Filtering
When the joystick is centered, it should return exactly 0. But in practice, worn joysticks often return small values like 3 or -5 even when nobody is touching them. This causes the robot to slowly drift forward or turn slightly even when the controller is sitting still.
Deadband filtering ignores small joystick values near zero.
# VEX IQ Python
def apply_deadband(value, threshold=10):
if abs(value) < threshold:
return 0
return value
forward = apply_deadband(controller.axis3.position())
turn = apply_deadband(controller.axis1.position())// VEX V5 C++
int applyDeadband(int value, int threshold = 10) {
if (abs(value) < threshold) return 0;
return value;
}
int forward = applyDeadband(Controller1.Axis3.position());
int turn = applyDeadband(Controller1.Axis1.position());A typical deadband threshold is 5–15. If you set it too high, the robot feels unresponsive at low speeds.
Joystick Curves
By default, the joystick is linear — pushing halfway gives exactly 50% power. This makes the robot feel very sensitive near the center and hard to make fine adjustments.
A joystick curve remaps the joystick so small inputs produce very little power (easy fine control) and large inputs produce full power (full speed when you need it).
Cubic Curve
Raising the joystick value to the power of 3 creates a smooth curve.
# VEX IQ Python
def cubic_curve(value):
return (value ** 3) / 10000 # scale back to -100 to 100 range
forward = cubic_curve(controller.axis3.position())// VEX V5 C++
double cubicCurve(int value) {
return (value * value * value) / 10000.0;
}
double forward = cubicCurve(Controller1.Axis3.position());The cubic curve makes the robot very controllable at slow speeds while still allowing full speed when the joystick is pushed all the way.
Mapping Buttons
Buttons are used to control mechanisms like lifts, intakes, and claws.
# VEX IQ Python — run intake while button is held
if controller.buttonLUp.pressing():
intake_motor.spin(FORWARD, 100, PERCENT)
elif controller.buttonLDown.pressing():
intake_motor.spin(REVERSE, 100, PERCENT)
else:
intake_motor.stop()// VEX V5 C++
if (Controller1.ButtonL1.pressing()) {
IntakeMotor.spin(forward, 100, percent);
} else if (Controller1.ButtonL2.pressing()) {
IntakeMotor.spin(reverse, 100, percent);
} else {
IntakeMotor.stop(hold);
}Toggle Buttons
Sometimes you want a button to turn something on and off rather than run only while held. This is called a toggle — like a light switch instead of a doorbell.
# VEX IQ Python — toggle intake on/off with one button
intake_on = False
last_button = False
while True:
current_button = controller.buttonLUp.pressing()
# Detect the moment the button is first pressed (rising edge)
if current_button and not last_button:
intake_on = not intake_on # flip the toggle
if intake_on:
intake_motor.spin(FORWARD, 100, PERCENT)
else:
intake_motor.stop()
last_button = current_button
wait(20, MSEC)The key is checking for the rising edge — the moment the button changes from not-pressed to pressed. Without this, the toggle would flip on and off 50 times per second while the button is held.
Putting It All Together
# VEX IQ Python — complete driver control loop
def driver_control():
while True:
# Read joysticks with deadband
forward = apply_deadband(controller.axis3.position())
turn = apply_deadband(controller.axis1.position())
# Apply cubic curve for smoother control
forward = cubic_curve(forward)
turn = cubic_curve(turn)
# Arcade drive math
left_speed = forward + turn
right_speed = forward - turn
# Drive motors
left_motor.spin(FORWARD, left_speed, PERCENT)
right_motor.spin(FORWARD, right_speed, PERCENT)
# Mechanism control
if controller.buttonLUp.pressing():
lift_motor.spin(FORWARD, 80, PERCENT)
elif controller.buttonLDown.pressing():
lift_motor.spin(REVERSE, 80, PERCENT)
else:
lift_motor.stop(BRAKE)
wait(20, MSEC)Tips for Better Driver Control
- Test with an actual driver. Have your driver practice and tell you what feels wrong.
- Adjust deadband and curve together. A high deadband with a sharp curve can make the robot feel unresponsive around center.
- Label every button on a reference card. During a match, there is no time to think — every button press must be muscle memory.
- Check for mechanical binding. If the robot drifts in a straight line even with perfect code, one motor or wheel may be stiffer than the other.