Programming an X-drive
Set up a four-wheel X-drive in VEXcode C++, then add joystick control and optional field-oriented driving.
A four-motor X-drive with omni wheels at 45°. Use the complete project below instead of the two-motor shared configuration.
On this pageWheel layout and directions
Wheel layout and directions
An X-drive uses four ordinary omni wheels angled at the corners of the chassis. It can move sideways without first turning. This guide assumes a symmetric chassis and wheel rolling directions at 45° to the front; it does not describe a mecanum-wheel build.
Names refer to corners as viewed from above. Configure each motor so a positive command rolls that corner in the forward-pointing direction shown below. The example reversal flags are starting values, not a substitute for checking your mounting and gears.
| Motor | Example Smart Port | Positive rolling direction |
|---|---|---|
| frontLeft | 1 | Forward and right |
| frontRight | 2 | Forward and left |
| backLeft | 3 | Forward and left |
| backRight | 4 | Forward and right |
References: VEX Robotics: Creating a V5 Drivetrain (opens in a new tab) · VEX Robotics: Motor and Motor Group — C++ API (opens in a new tab)
Combine forward, sideways, and turn
Use forward-positive, right-positive, and clockwise-positive commands. Each wheel receives the sum of its contribution to those three movements. These are motor-output commands, not measured chassis speeds.
For a right strafe, front-left and back-right are positive while the other two are negative. For a clockwise turn, the left motors are positive and the right motors are negative. The signs follow the wheel directions in the diagram.
When a mixed command exceeds 100%, scale all four outputs by the same factor. Clipping each motor separately changes the requested direction. For example, forward 60 plus right 60 produces 120, 0, 0, 120; normalization makes that 100, 0, 0, 100.
frontLeft = forward + right + turn frontRight = forward − right − turn backLeft = forward − right + turn backRight = forward + right − turn scale = 100 / max(100, largest absolute wheel command)
References: VEX Robotics: Motor and Motor Group — C++ API (opens in a new tab)
A complete driver-control project
Start with an empty V5 C++ project. Replace src/main.cpp with this example; do not paste it beneath the shared tank-drive program. If you configured these devices in the Devices window, remove the duplicate declarations or use your generated device names consistently.
The left stick controls forward/backward and left/right movement. The right stick controls turning. This is robot-oriented: pushing forward always moves toward the robot’s front, wherever the robot is facing. The small deadband keeps a resting stick from moving the robot.
The autonomous callback is only a 300 ms right-strafe direction test at 20% output. Start it through competition control. It does not measure distance. These examples have not been compiled with the VEX toolchain or tested on a physical X-drive.
#include "vex.h"
#include <algorithm>
#include <cmath>
using namespace vex;
motor frontLeft(PORT1, ratio18_1, false);
motor frontRight(PORT2, ratio18_1, true);
motor backLeft(PORT3, ratio18_1, false);
motor backRight(PORT4, ratio18_1, true);
controller driver(primary);
competition match;
double deadband(double input) {
return std::fabs(input) < 5 ? 0 : input;
}
void stopXDrive() {
frontLeft.stop(brake);
frontRight.stop(brake);
backLeft.stop(brake);
backRight.stop(brake);
}
void driveX(double forwardPct, double rightPct, double turnPct) {
double fl = forwardPct + rightPct + turnPct;
double fr = forwardPct - rightPct - turnPct;
double bl = forwardPct - rightPct + turnPct;
double br = forwardPct + rightPct - turnPct;
double peak = std::max({100.0, std::fabs(fl), std::fabs(fr),
std::fabs(bl), std::fabs(br)});
double scale = 100.0 / peak;
if (fl == 0 && fr == 0 && bl == 0 && br == 0) {
stopXDrive();
return;
}
frontLeft.spin(forward, fl * scale, pct);
frontRight.spin(forward, fr * scale, pct);
backLeft.spin(forward, bl * scale, pct);
backRight.spin(forward, br * scale, pct);
}
void autonomous() {
driveX(0, 20, 0);
wait(300, msec);
stopXDrive();
}
void driverControl() {
while (true) {
driveX(deadband(driver.Axis3.position(percent)),
deadband(driver.Axis4.position(percent)),
deadband(driver.Axis1.position(percent)));
wait(20, msec);
}
}
int main() {
vexcodeInit();
match.autonomous(autonomous);
match.drivercontrol(driverControl);
while (true) wait(100, msec);
}References: VEX Robotics: Motor and Motor Group — C++ API (opens in a new tab) · VEX Robotics: Controller Axis — C++ API (opens in a new tab) · VEX Robotics: Competition — C++ API (opens in a new tab) · PROS: Multitasking and synchronization (opens in a new tab)
Check each movement separately
| Command | Front left | Front right | Back left | Back right |
|---|---|---|---|---|
| Forward | + | + | + | + |
| Right | + | − | − | + |
| Clockwise | + | − | + | − |
- With the wheels clear of the floor, test each motor at low output. Confirm its name, port, and positive rolling direction against the diagram. Stop the program before changing wiring.
- On clear floor space, test forward only, then right only, then clockwise turn only. Begin with small stick inputs.
- If one wheel disagrees with the table, correct its reversed flag. If all wheels match but the robot moves incorrectly, check wheel angles, roller freedom, and ground contact.
- Try diagonal movement, then translation while turning. Confirm the outputs remain bounded and the sticks stop the drive when released.
References: VEX Robotics: Motor and Motor Group — C++ API (opens in a new tab)
Optional: field-oriented control
Field-oriented control keeps the left stick aligned with a fixed field direction while the robot turns. It needs a heading reference. Here, field forward is the direction the robot faces when calibration finishes; positive heading is clockwise, matching the default VEX inertial rotation convention.
Keep driveX(), stopXDrive(), and deadband(). Add the sensor, state, and calibration function below before driverControl(), and replace driverControl() with this version. In main(), call calibrateHeading() immediately after vexcodeInit(), before registering the competition callbacks. Keep the robot stationary during calibration. Do not also define a second imu or pi from another guide.
Face field forward when establishing zero. At a clockwise heading of 90°, pushing field forward should request robot-left travel. Check that case at low output. A bad zero or reversed heading sign rotates the controls incorrectly. The autonomous test remains robot-oriented.
inertial imu(PORT5);
bool headingReady = false;
constexpr double pi = 3.141592653589793;
void calibrateHeading() {
headingReady = false;
if (!imu.installed()) return;
imu.calibrate();
timer calibration;
while (imu.isCalibrating() && calibration.time(msec) < 5000)
wait(20, msec);
if (imu.isCalibrating()) return;
imu.setRotation(0, degrees);
headingReady = true;
}
void driverControl() {
while (true) {
if (!headingReady || !imu.installed() || imu.isCalibrating()) {
stopXDrive();
wait(20, msec);
continue;
}
double fieldForward = deadband(driver.Axis3.position(percent));
double fieldRight = deadband(driver.Axis4.position(percent));
double heading = imu.rotation(degrees) * pi / 180.0;
double robotForward = fieldForward * std::cos(heading)
+ fieldRight * std::sin(heading);
double robotRight = -fieldForward * std::sin(heading)
+ fieldRight * std::cos(heading);
driveX(robotForward, robotRight,
deadband(driver.Axis1.position(percent)));
wait(20, msec);
}
}References: VEX Robotics: Inertial Sensor — C++ API (opens in a new tab) · VEX Robotics: Controller Axis — C++ API (opens in a new tab)
Using an X-drive in autonomous
Reuse driveX() as the final motor-output step. A position controller can calculate a field-forward and field-right correction, rotate them into robot coordinates with the same transformation used above, and pass them to driveX() with a heading correction. Match the coordinate and angle conventions before combining examples.
The Simple autonomous and Driving to coordinates examples on this site send commands to a left and right tank drive. They are not drop-in X-drive controllers. Keep their ideas—feedback, output limits, settling checks, and timeouts—but replace their motor mixing and account for sideways movement.
Do not equate motor-wheel travel with straight chassis travel: the wheels roll at an angle, and their encoders also change during turning. For ideal forward-only travel at 45°, each wheel rolls chassis distance × cos(45°), so chassis distance is wheel travel × √2. Include external gearing when converting motor rotation to wheel travel. Slip still changes the result. Independent tracking wheels can measure forward and sideways motion for odometry.
For closed-loop tests, start with one axis at a time, stop all four motors on timeout or invalid sensor data, and tune on your own robot before combining motion axes.
References: VEX Robotics: Creating a V5 Drivetrain (opens in a new tab) · VEX Robotics: Motor and Motor Group — C++ API (opens in a new tab) · VEX Robotics: Inertial Sensor — C++ API (opens in a new tab) · PROS: Multitasking and synchronization (opens in a new tab)
Sources for this guide
Examples are written for VEXcode C++. PROS and LemLib references explain related concepts; their APIs are different.
- VEX RoboticsCreating a V5 Drivetrain (opens in a new tab)
Mechanical layouts and tradeoffs for standard and holonomic drivetrains. The X-drive mixer on this site is derived for the explicitly shown wheel directions.
- VEX RoboticsMotor and Motor Group — C++ API (opens in a new tab)
VEXcode motor construction, encoder position, signed spin commands, and braking.
- VEX RoboticsController Axis — C++ API (opens in a new tab)
Joystick axis numbering and signed percentage position readings.
- VEX RoboticsCompetition — C++ API (opens in a new tab)
Registering autonomous and driver-control callbacks.
- VEX RoboticsInertial Sensor — C++ API (opens in a new tab)
Calibration, continuous rotation, heading wraparound, and the default clockwise-positive convention.
- PROSMultitasking and synchronization (opens in a new tab)
Why loops need delays and why concurrent tasks need an explicit ownership and synchronization model. PROS task APIs are not VEXcode APIs.