VEXCODE V5 · C++ · SUBSYSTEM FILES · INTERNAL

Subsystem-File Architecture for VEXcode V5

Per-mechanism .cpp files so main.cpp stays as a thin orchestration layer. Same architectural principles as the PROS reference, written for the VEXcode V5 C++ toolchain.

Toolchain switch · May 16, 2026
The team has switched the primary teaching toolchain to VEXcode V5 + C++. The previous PROS + EZ-Template material remains available at /code-architectureshelved, not deleted. The architectural pattern is identical between toolchains; only the API calls and project structure differ. If you ever want the more advanced PID and auton-selector features of EZ-Template, that page is still there.

SECTION 1Why subsystem files

Once you have 3+ mechanisms, main.cpp becomes a 300-line monster: drive logic, lift logic, claw logic, intake logic all jammed into one usercontrol() loop. The fix is the same pattern professional embedded software uses: separation of concerns by subsystem. One .cpp file per mechanism. main.cpp becomes a thin orchestration layer that says "call the lift, call the intake, call the tube" in a loop — nothing more.

The 10-line usercontrol test: when your code uses the subsystem-file architecture, the usercontrol() function in main.cpp should fit in 10 lines or less, regardless of how many mechanisms the robot has. If it doesn't, mechanism logic is still in main — refactor it out.

SECTION 2The file structure

Every Spartan VEXcode project follows this layout. Files marked NEW are the ones we add beyond what VEXcode's "new project" wizard gives you.

src/ ├── main.cpp // pre_auton(), autonomous(), usercontrol(), main() — orchestration ONLY ├── autons.cpp // auton routines (one function per match auton) ├── robot-config.cpp // motor, sensor, pneumatic definitions — single source of truth ├── arm.cpp / lift.cpp // per-mechanism control logic (one file per mechanism) ├── claw.cpp // claw toggle pattern + state ├── intake.cpp // held-button intake logic ├── tube.cpp // non-blocking tube state machine (if equipped) └── pneumatics.cpp // solenoid toggle patterns (if equipped) include/ ├── vex.h // VEX framework master header (auto-generated by VEXcode) ├── robot-config.h // extern declarations for hardware ├── autons.h // auton function declarations ├── arm.h / lift.h // declares <name>_init() and <name>_control() ├── claw.h ├── intake.h ├── tube.h └── pneumatics.h
Flat layout (no nested folders): VEXcode's file browser displays files in a flat list within each folder — nested subdirectories don't render nicely. We keep src/ and include/ flat. The encapsulation still works through file-scope static, the <name>_init() naming convention, and one extern-driven robot-config file.
VEXcode header convention: headers use the .h extension, not .hpp (which PROS used). The vex.h master header is auto-generated by VEXcode and pulls in the entire VEX framework. Your own headers (arm.h, claw.h, etc.) follow the same convention.

SECTION 3The four convention rules

Stick to these rules and code from Clawbot becomes copy-pasteable to Flex, code from Flex becomes copy-pasteable to a competition robot.

Every subsystem exposes <name>_init() and <name>_control()Predictable. Anyone reading main.cpp knows what to expect: each mechanism has exactly one init call and one control call.
State variables are static at file scopeCompiler-enforces "this state belongs to this subsystem." If main.cpp tries to write to claw_open, you get a linker error — proving the encapsulation.
Power and timing constants live at the top of the subsystem fileTuning happens by opening one file, scrolling to the top. LIFT_POWER, TUBE_PULSE_TICKS, etc. are visible immediately.
robot-config.cpp is the ONLY place motor / sensor objects are definedSingle source of truth for port assignments. All other files use extern declarations via robot-config.h.

SECTION 4main.cpp + robot-config — works for every robot

This skeleton works for Clawbot, Flex, and any competition robot. The only thing that changes per-robot is which subsystem files exist and which control functions are called inside usercontrol().

The thin main.cpp

Notice what's NOT here: no mechanism motor commands, no button-press logic, no state variables for toggles. All of that lives inside the subsystem files.

FILE src/main.cpp ORCHESTRATION ONLY — no mechanism logic
#include "vex.h"
#include "robot-config.h"
#include "autons.h"
#include "arm.h"         // or "lift.h" depending on the robot
#include "claw.h"
// #include "intake.h"   // uncomment per robot
// #include "tube.h"     // uncomment per robot
// #include "pneumatics.h"  // uncomment per robot

using namespace vex;

competition Competition;

// ═══════════════════════════════════════════════════════════════
//   PRE-AUTON — runs once at program start, before any phase
// ═══════════════════════════════════════════════════════════════
void pre_auton() {
  Brain.Screen.print("Spartan Design");
  Brain.Screen.newLine();
  Brain.Screen.print("Initializing...");

  // Calibrate the inertial sensor (~3 seconds)
  Inertial.calibrate();
  while (Inertial.isCalibrating()) wait(50, msec);

  // Per-subsystem init — one call per mechanism
  arm_init();           // or lift_init() depending on robot
  claw_init();
  // intake_init();
  // tube_init();
  // pneumatics_init();

  Brain.Screen.clearScreen();
  Brain.Screen.setCursor(1, 1);
  Brain.Screen.print("Ready.");
}

// ═══════════════════════════════════════════════════════════════
//   AUTONOMOUS — runs during 15s auton period
// ═══════════════════════════════════════════════════════════════
void autonomous() {
  // For a real season, replace with selector logic (see Section 8)
  auton_drive_test();
}

// ═══════════════════════════════════════════════════════════════
//   USERCONTROL — driver-control loop
//
//   THE 10-LINE TEST: count the lines inside this while-loop. For a
//   2-mechanism robot, it's 4 lines. Adding a 3rd mechanism adds 1 line.
// ═══════════════════════════════════════════════════════════════
void usercontrol() {
  while (true) {
    // Drive — split arcade (left stick forward/back, right stick turning)
    int forward_pct = Controller1.Axis3.position();
    int turn_pct    = Controller1.Axis1.position();
    LeftDrive.spin(fwd, forward_pct + turn_pct, percent);
    RightDrive.spin(fwd, forward_pct - turn_pct, percent);

    // Mechanisms — one line per subsystem
    arm_control();
    claw_control();
    // intake_control();
    // tube_control();
    // pneumatics_control();

    wait(20, msec);
  }
}

// ═══════════════════════════════════════════════════════════════
//   MAIN — VEXcode entry point. Don't touch unless you know why.
// ═══════════════════════════════════════════════════════════════
int main() {
  Competition.autonomous(autonomous);
  Competition.drivercontrol(usercontrol);

  pre_auton();

  while (true) wait(100, msec);   // keep the main thread alive
}
The 10-line usercontrol test: the while-loop body here has 5 lines for a 2-mechanism robot (drive in 3 lines, 2 mechanism calls), 7 lines for a 4-subsystem robot. That's the goal — the whole control loop fits on one screen.

robot-config — the single source of truth for hardware

Every motor, sensor, and pneumatic on the robot is defined in robot-config.cpp and declared as extern in robot-config.h. When ports change at practice, you update one file.

FILE include/robot-config.h extern declarations — every other file sees these
#pragma once
#include "vex.h"

// ─── Brain + Controller ──────────────────────────────────────────
extern vex::brain      Brain;
extern vex::controller Controller1;

// ─── Drivetrain motors ───────────────────────────────────────────
// We control drive motors directly via spin() — simple and explicit.
// (Alternative: vex::drivetrain class for built-in auton drive routines.)
extern vex::motor LeftDriveA;
extern vex::motor RightDriveA;
// extern vex::motor LeftDriveB;   // uncomment for 4-motor drive
// extern vex::motor RightDriveB;
// extern vex::motor_group LeftDrive;
// extern vex::motor_group RightDrive;

// ─── Inertial sensor (heading) ───────────────────────────────────
extern vex::inertial Inertial;

// ─── Mechanism motors ────────────────────────────────────────────
extern vex::motor Arm;        // Clawbot — hold-to-move L1/L2
extern vex::motor Claw;       // Clawbot/Flex — rising-edge toggle R1
// extern vex::motor Lift;    // Flex — hold-to-move L1/L2

// ─── Sensors (uncomment as wired) ────────────────────────────────
// extern vex::limit  arm_top_limit;
// extern vex::limit  arm_bot_limit;
// extern vex::optical claw_optical;
// extern vex::distance back_distance;

// ─── Pneumatics (if equipped) ───────────────────────────────────
// extern vex::digital_out tube_cinch;
// extern vex::digital_out aligner;
FILE src/robot-config.cpp DEFINITIONS — change ports here, nowhere else
#include "vex.h"
#include "robot-config.h"

using namespace vex;

// ─── Brain + Controller ──────────────────────────────────────────
brain      Brain;
controller Controller1;

// ─── Drivetrain motors ───────────────────────────────────────────
// Clawbot example — 2-motor tank. Adjust ports + gearset for your bot.
//
// Constructor: motor(port, gearset, reverse)
//   ratio6_1     = blue cartridge (600 RPM)
//   ratio18_1    = green cartridge (200 RPM)
//   ratio36_1    = red cartridge   (100 RPM)
//
motor LeftDriveA  (PORT1,  ratio18_1, true);   // reversed (mounted backwards)
motor RightDriveA (PORT10, ratio18_1, false);

// For 4-motor drive, uncomment these + the externs:
// motor LeftDriveB  (PORT2,  ratio6_1, true);
// motor RightDriveB (PORT9,  ratio6_1, false);
// motor_group LeftDrive (LeftDriveA, LeftDriveB);
// motor_group RightDrive(RightDriveA, RightDriveB);

// ─── Inertial sensor ─────────────────────────────────────────────
inertial Inertial(PORT11);

// ─── Mechanism motors ────────────────────────────────────────────
motor Arm  (PORT8, ratio36_1, false);    // 100 RPM red — torque for lifting
motor Claw (PORT3, ratio36_1, false);    // 100 RPM red — fine grip control

// ─── Sensors (uncomment as wired) ────────────────────────────────
// limit       arm_top_limit (Brain.ThreeWirePort.A);
// limit       arm_bot_limit (Brain.ThreeWirePort.B);
// optical     claw_optical  (PORT5);
// distance    back_distance (PORT13);

// ─── Pneumatics (if equipped) ───────────────────────────────────
// digital_out tube_cinch (Brain.ThreeWirePort.A);
// digital_out aligner    (Brain.ThreeWirePort.B);
VEXcode motor gearsets: the third argument to the motor constructor encodes both the cartridge AND the gear ratio. ratio6_1 = 6:1 internal gearing = blue cartridge (600 RPM). ratio18_1 = green (200 RPM). ratio36_1 = red (100 RPM). External gear reduction (e.g. a 36:48 on drive) isn't encoded here — just use the cartridge speed and let your auton code handle distance calculations.

SECTION 5Per-robot subsystem files

Clawbot → Flex → a competition robot →

Each robot's section is collapsed by default. Notice that claw.cpp is line-for-line identical between Clawbot and Flex — that's the architecture earning its keep.

CLAWBOT Clawbot — arm + claw 2 subsystems · simplest reference

Two mechanisms: arm (hold-to-move, L1/L2) and claw (rising-edge toggle, R1).

FILE include/arm.h function declarations — short and stable
#pragma once

void arm_init();
void arm_control();

// Auton API — let autons.cpp drive the arm without touching motors directly
void arm_set_power(int power);   // -100..100 (percent)
void arm_stop();
FILE src/arm.cpp hold-to-move pattern + auton API
#include "vex.h"
#include "robot-config.h"
#include "arm.h"

using namespace vex;

// ─── Tunable constants (top of file = easy to find) ───────────────
const int ARM_POWER = 80;       // 0–100 percent; reduce for finer control

// ─── Subsystem init (called once from pre_auton()) ───────────────
void arm_init() {
  Arm.setStopping(hold);        // doesn't sag under gravity
  Arm.setPosition(0, degrees);  // encoder starts at 0
}

// ─── Opcontrol — called every usercontrol loop ───────────────────
void arm_control() {
  if (Controller1.ButtonL1.pressing()) {
    Arm.spin(forward, ARM_POWER, percent);
  } else if (Controller1.ButtonL2.pressing()) {
    Arm.spin(reverse, ARM_POWER, percent);
  } else {
    Arm.stop();                 // HOLD position (set by setStopping above)
  }
}

// ─── Auton API ─────────────────────────────────────────────────────
void arm_set_power(int power) {
  if (power >= 0) Arm.spin(forward, power,  percent);
  else            Arm.spin(reverse, -power, percent);
}

void arm_stop() {
  Arm.stop();
}
FILE include/claw.h function declarations
#pragma once

void claw_init();
void claw_control();

// Auton API
void claw_open_set();
void claw_close_set();
bool claw_is_open();
FILE src/claw.cpp rising-edge toggle + auton API
#include "vex.h"
#include "robot-config.h"
#include "claw.h"

using namespace vex;

// ─── Tunable constants ─────────────────────────────────────────────
const int CLAW_CLOSED_DEG = 0;
const int CLAW_OPEN_DEG   = 200;     // adjust for your claw geometry
const int CLAW_SPEED      = 50;      // 0–100 percent

// ─── File-scope state (static = invisible outside this file) ──────
static bool claw_open   = false;     // is the claw currently OPEN?
static bool last_btn_r1 = false;     // R1 state in previous loop

// ─── Subsystem init ────────────────────────────────────────────────
void claw_init() {
  Claw.setStopping(hold);
  Claw.setPosition(0, degrees);
}

// ─── Opcontrol — rising-edge detection ────────────────────────────
void claw_control() {
  bool r1 = Controller1.ButtonR1.pressing();

  // Rising-edge: NOT pressed last loop, IS pressed this loop
  if (r1 && !last_btn_r1) {
    claw_open = !claw_open;
    if (claw_open) Claw.spinToPosition(CLAW_OPEN_DEG,   degrees, CLAW_SPEED, percent, false);
    else           Claw.spinToPosition(CLAW_CLOSED_DEG, degrees, CLAW_SPEED, percent, false);
  }

  last_btn_r1 = r1;   // remember for next loop
}

// ─── Auton API ─────────────────────────────────────────────────────
void claw_open_set() {
  claw_open = true;
  Claw.spinToPosition(CLAW_OPEN_DEG, degrees, CLAW_SPEED, percent, false);
}

void claw_close_set() {
  claw_open = false;
  Claw.spinToPosition(CLAW_CLOSED_DEG, degrees, CLAW_SPEED, percent, false);
}

bool claw_is_open() { return claw_open; }
Why static matters: claw_open and last_btn_r1 are file-private. main.cpp can't accidentally read or modify them — the compiler refuses. The accessor claw_is_open() exposes a read-only view if other code (like the brain dashboard) needs to know the state.
VEXcode tip — spinToPosition() last argument: the final false means "don't wait for the move to finish before returning." This is critical in claw_control() — if it were true, the claw would block usercontrol() for the duration of the move and the driver couldn't drive while the claw was opening.
FLEX Flex — lift + claw 2 subsystems · claw.cpp identical to Clawbot

Two mechanisms: lift (hold-to-move, L1/L2) and claw (rising-edge toggle, R1). Same patterns as Clawbot — the claw.cpp file is literally identical (you copy it from the Clawbot project).

FILE include/lift.h function declarations
#pragma once

void lift_init();
void lift_control();

void lift_set_power(int power);
void lift_stop();
FILE src/lift.cpp hold-to-move — same as Clawbot's arm.cpp, renamed
#include "vex.h"
#include "robot-config.h"
#include "lift.h"

using namespace vex;

const int LIFT_POWER = 80;

void lift_init() {
  Lift.setStopping(hold);
  Lift.setPosition(0, degrees);
}

void lift_control() {
  if (Controller1.ButtonL1.pressing()) {
    Lift.spin(forward, LIFT_POWER, percent);
  } else if (Controller1.ButtonL2.pressing()) {
    Lift.spin(reverse, LIFT_POWER, percent);
  } else {
    Lift.stop();
  }
}

void lift_set_power(int power) {
  if (power >= 0) Lift.spin(forward, power,  percent);
  else            Lift.spin(reverse, -power, percent);
}

void lift_stop() {
  Lift.stop();
}
FILE src/claw.cpp IDENTICAL to Clawbot — copy-paste
#include "vex.h"
#include "robot-config.h"
#include "claw.h"

using namespace vex;

const int CLAW_CLOSED_DEG = 0;
const int CLAW_OPEN_DEG   = 200;
const int CLAW_SPEED      = 50;

static bool claw_open   = false;
static bool last_btn_r1 = false;

void claw_init() {
  Claw.setStopping(hold);
  Claw.setPosition(0, degrees);
}

void claw_control() {
  bool r1 = Controller1.ButtonR1.pressing();
  if (r1 && !last_btn_r1) {
    claw_open = !claw_open;
    if (claw_open) Claw.spinToPosition(CLAW_OPEN_DEG,   degrees, CLAW_SPEED, percent, false);
    else           Claw.spinToPosition(CLAW_CLOSED_DEG, degrees, CLAW_SPEED, percent, false);
  }
  last_btn_r1 = r1;
}

void claw_open_set()  { claw_open = true;  Claw.spinToPosition(CLAW_OPEN_DEG,   degrees, CLAW_SPEED, percent, false); }
void claw_close_set() { claw_open = false; Claw.spinToPosition(CLAW_CLOSED_DEG, degrees, CLAW_SPEED, percent, false); }
bool claw_is_open()   { return claw_open; }
The architecture's payoff: when a competition robot needs a claw, claw.cpp drops in unchanged. Only robot-config.cpp changes per-robot (different motor port). The control code never gets rewritten.
COMP BOT Competition bot — lift + intake + tube + pneumatics 4 subsystems · competition robot

Four subsystems: lift (2-motor sync, R1/R2), intake (held forward/reverse, L1/L2), tube (non-blocking timed pulses, X/Y), and pneumatics (cinch + aligner toggles, A/B).

FILE include/lift.h 2-motor lift declarations
#pragma once

void lift_init();
void lift_control();

void lift_set_power(int power);
void lift_stop();
FILE src/lift.cpp 2-motor sync — uses vex::motor_group
#include "vex.h"
#include "robot-config.h"
#include "lift.h"

using namespace vex;

const int LIFT_POWER = 90;     // 0–100 percent

void lift_init() {
  // LiftMotors is a motor_group defined in robot-config.cpp.
  // Both motors share the same brake mode and respond to one command.
  LiftMotors.setStopping(hold);
  LiftMotors.setMaxTorque(50, percent);    // ~5A total = 2.5A per motor
}

void lift_control() {
  if (Controller1.ButtonR1.pressing()) {
    LiftMotors.spin(forward, LIFT_POWER, percent);
  } else if (Controller1.ButtonR2.pressing()) {
    LiftMotors.spin(reverse, LIFT_POWER, percent);
  } else {
    LiftMotors.stop();
  }
}

void lift_set_power(int power) {
  if (power >= 0) LiftMotors.spin(forward, power,  percent);
  else            LiftMotors.spin(reverse, -power, percent);
}

void lift_stop() { LiftMotors.stop(); }
VEXcode motor_group: simpler than driving two motors manually. motor_group LiftMotors(lift_left, lift_right); — one object, one command, both motors spin together. Reversal is set on the individual motors in their constructors. This is one place VEXcode is genuinely cleaner than PROS.
FILE include/intake.h intake declarations
#pragma once

void intake_init();
void intake_control();

void intake_set_power(int power);
void intake_stop();
FILE src/intake.cpp held forward/reverse + current limit for jam protection
#include "vex.h"
#include "robot-config.h"
#include "intake.h"

using namespace vex;

void intake_init() {
  Intake.setStopping(coast);              // spins down naturally
  Intake.setMaxTorque(40, percent);       // ~2A — jam protection
}

void intake_control() {
  if      (Controller1.ButtonL1.pressing()) Intake.spin(forward, 100, percent);
  else if (Controller1.ButtonL2.pressing()) Intake.spin(reverse, 100, percent);
  else                                      Intake.stop();
}

void intake_set_power(int power) {
  if (power >= 0) Intake.spin(forward, power,  percent);
  else            Intake.spin(reverse, -power, percent);
}

void intake_stop() { Intake.stop(); }
FILE include/tube.h tube declarations
#pragma once

void tube_init();
void tube_control();

// Auton: kick off a pulse (non-blocking — returns immediately, the state
// machine in tube_control() advances the rotation over the next ~600ms).
// Only useful in auton if tube_control() is being ticked. For linear auton
// sequences, use tube_pulse_blocking() instead.
void tube_pulse(int direction);
void tube_pulse_blocking(int direction);
FILE src/tube.cpp non-blocking state machine — ALL state encapsulated
#include "vex.h"
#include "robot-config.h"
#include "tube.h"

using namespace vex;

// ─── Tunable constants ─────────────────────────────────────────────
const int TUBE_PULSE_TICKS = 30;     // 30 × 20ms = 600ms pulse
const int TUBE_POWER       = 80;     // 0–100 percent

// ─── File-scope state — invisible to main.cpp ─────────────────────
static int tube_timer     = 0;
static int tube_direction = 0;       // -1, 0, or +1
static bool prev_x        = false;
static bool prev_y        = false;

void tube_init() {
  Tube.setStopping(brake);           // snappy stop
  Tube.setMaxTorque(30, percent);    // ~1.5A — gentle
}

void tube_control() {
  // Detect button press (rising edge) — kick off the state machine
  bool x = Controller1.ButtonX.pressing();
  bool y = Controller1.ButtonY.pressing();

  if (x && !prev_x) { tube_timer = TUBE_PULSE_TICKS; tube_direction = +1; }
  if (y && !prev_y) { tube_timer = TUBE_PULSE_TICKS; tube_direction = -1; }

  prev_x = x;
  prev_y = y;

  // Run motor while counter > 0, brake when it hits 0
  if (tube_timer > 0) {
    if (tube_direction > 0) Tube.spin(forward, TUBE_POWER, percent);
    else                    Tube.spin(reverse, TUBE_POWER, percent);
    tube_timer--;
  } else {
    Tube.stop();
    tube_direction = 0;
  }
}

// Auton: non-blocking variant — only useful if tube_control() runs
void tube_pulse(int direction) {
  tube_timer = TUBE_PULSE_TICKS;
  tube_direction = direction;
}

// Auton: blocking variant — runs synchronously and returns when done
void tube_pulse_blocking(int direction) {
  if (direction > 0) Tube.spin(forward, TUBE_POWER, percent);
  else               Tube.spin(reverse, TUBE_POWER, percent);
  wait(TUBE_PULSE_TICKS * 20, msec);    // 30 ticks × 20ms = 600ms
  Tube.stop();
}
Why non-blocking: a naive implementation would do Tube.spin(...); wait(600, msec); — which freezes the WHOLE usercontrol loop for 600ms. Driver can't drive during the rotation. The state-machine pattern lets everything else (drive, lift, intake, pneumatics) keep running while the tube is pulsing.
FILE include/pneumatics.h cinch + aligner declarations
#pragma once

void pneumatics_init();
void pneumatics_control();

// Auton API — explicit setters (not toggles, so autons can request specific state)
void cinch_set(bool open);
void aligner_set(bool open);
bool cinch_is_open();
bool aligner_is_open();
FILE src/pneumatics.cpp rising-edge toggle pattern — both solenoids
#include "vex.h"
#include "robot-config.h"
#include "pneumatics.h"

using namespace vex;

// ─── File-scope state — boot defaults match physical setup ────────
static bool cinch_open   = true;     // cinch starts OPEN
static bool aligner_open = false;    // aligner starts CLOSED

static bool prev_a = false;
static bool prev_b = false;

void pneumatics_init() {
  TubeCinch.set(cinch_open);
  Aligner.set(aligner_open);
}

void pneumatics_control() {
  bool a = Controller1.ButtonA.pressing();
  bool b = Controller1.ButtonB.pressing();

  if (a && !prev_a) {
    cinch_open = !cinch_open;
    TubeCinch.set(cinch_open);
  }
  if (b && !prev_b) {
    aligner_open = !aligner_open;
    Aligner.set(aligner_open);
  }

  prev_a = a;
  prev_b = b;
}

// Auton API
void cinch_set(bool open)   { cinch_open = open;     TubeCinch.set(cinch_open); }
void aligner_set(bool open) { aligner_open = open;   Aligner.set(aligner_open); }
bool cinch_is_open()        { return cinch_open;   }
bool aligner_is_open()      { return aligner_open; }

A competition robot's usercontrol — what main.cpp looks like at the end

FILE src/main.cpp (usercontrol excerpt) 4 subsystems, 7 lines of orchestration
void usercontrol() {
  while (true) {
    // Drive — split arcade
    int forward_pct = Controller1.Axis3.position();
    int turn_pct    = Controller1.Axis1.position();
    LeftDrive.spin(forward,  forward_pct + turn_pct, percent);
    RightDrive.spin(forward, forward_pct - turn_pct, percent);

    // Mechanisms — one line per subsystem
    lift_control();
    intake_control();
    tube_control();
    pneumatics_control();

    wait(20, msec);
  }
}
One glance, full understanding. Anyone reading main.cpp immediately knows: the robot has a lift, intake, tube, and pneumatics. Each one lives in a separate file. Bug in the tube? Open tube.cpp — nothing else to scroll past.

SECTION 6Autons — using the subsystem auton API

Autons live in autons.cpp. They call subsystem auton APIs (arm_set_power(), claw_open_set(), lift_stop(), etc.) rather than touching motor objects directly. This keeps the architecture clean: each subsystem owns its hardware.

FILE include/autons.h auton function declarations
#pragma once

void auton_drive_test();
void auton_arm_cycle();
void auton_grab_and_score();
FILE src/autons.cpp drive helpers + 3 example autons
#include "vex.h"
#include "robot-config.h"
#include "autons.h"
#include "arm.h"
#include "claw.h"

using namespace vex;

// ─── Drive helpers (since we drive motors manually, not vex::drivetrain) ──
// For a more featureful drivetrain class, see VEXcode's vex::drivetrain
// — it has built-in driveFor(distance) and turnFor(angle) using the IMU.

static void drive_for_ms(int power, int duration_ms) {
  LeftDriveA.spin(forward,  power, percent);
  RightDriveA.spin(forward, power, percent);
  wait(duration_ms, msec);
  LeftDriveA.stop();
  RightDriveA.stop();
}

static void turn_for_degrees(int target_heading_deg, int power) {
  double start = Inertial.heading();
  double goal  = start + target_heading_deg;
  while (true) {
    double err = goal - Inertial.heading();
    if (std::abs(err) < 2.0) break;
    int sign = (err > 0) ? 1 : -1;
    LeftDriveA.spin(forward,  sign * power, percent);
    RightDriveA.spin(reverse, sign * power, percent);
    wait(20, msec);
  }
  LeftDriveA.stop();
  RightDriveA.stop();
}

// ═══════════════════════════════════════════════════════════════════
//   1. DRIVE TEST — short forward + back to verify motor wiring + IMU
// ═══════════════════════════════════════════════════════════════════
void auton_drive_test() {
  drive_for_ms( 60, 1200);    // forward 60% for 1.2s
  wait(300, msec);
  drive_for_ms(-60, 1200);    // back 60% for 1.2s
}

// ═══════════════════════════════════════════════════════════════════
//   2. ARM CYCLE — mechanism-in-auton validation (no driving)
// ═══════════════════════════════════════════════════════════════════
void auton_arm_cycle() {
  arm_set_power(80);
  wait(1000, msec);
  arm_stop();
  wait(500, msec);
  arm_set_power(-50);
  wait(1000, msec);
  arm_stop();
}

// ═══════════════════════════════════════════════════════════════════
//   3. GRAB AND SCORE — composite drive + claw + arm sequence
// ═══════════════════════════════════════════════════════════════════
void auton_grab_and_score() {
  claw_open_set();
  wait(400, msec);

  drive_for_ms(70, 900);      // approach (~900ms forward)

  claw_close_set();
  wait(400, msec);

  arm_set_power(80);
  wait(800, msec);
  arm_stop();

  drive_for_ms(-70, 700);     // carry away

  claw_open_set();
  wait(400, msec);

  arm_set_power(-50);
  wait(800, msec);
  arm_stop();
}
About vex::drivetrain: VEXcode provides a built-in drivetrain class with driveFor(forward, 24, inches) and turnFor(right, 90, degrees) methods that handle IMU correction automatically. It's simpler than the manual helpers above. We use manual helpers here because they're explicit about what's happening — great for learning. For competition autons, the reference projects use vex::drivetrain instead.

SECTION 7Refactoring from monolithic main.cpp

If you have working VEXcode code that puts everything in one big usercontrol() function, here's how to refactor to subsystem files. Do this only after the code already works. Refactoring broken code is a recipe for losing track of which problem you're solving.

The 5-step refactor

  1. Make sure your code works. All mechanisms respond to buttons correctly. The refactor preserves behavior — test before and after, the robot should behave identically.
  2. Create the empty subsystem files. For each mechanism, create src/<name>.cpp and include/<name>.h. Start with function signatures and empty bodies.
  3. Move one mechanism at a time. Cut the initialization code from pre_auton() in main.cpp and paste into <name>_init(). Cut the button-handling block from usercontrol() and paste into <name>_control(). Update main.cpp to #include "<name>.h".
  4. Move state variables. Any bool claw_open, int tube_timer, etc. that lived at file scope in main.cpp moves to the subsystem file. Add static to make it file-private.
  5. Build and test. One subsystem at a time. Run after each move — the robot should behave exactly as before. If something breaks, you know which mechanism's refactor introduced the bug.
What stays in main.cpp: only the pre_auton() orchestration (calling each _init()), the usercontrol() loop (calling each _control()), the autonomous() dispatch, and main(). Everything mechanism-specific moves out.
Common refactor bug: forgetting to add extern for motor objects in robot-config.h. If you get a linker error like "undefined reference to `Arm\'", that's the cause — the subsystem file can't see the motor object yet.

SECTION 8Auton selector — DIY (since VEXcode doesn't ship one)

Unlike EZ-Template, VEXcode doesn't include a built-in auton selector. Build your own. The pattern below uses controller buttons (UP/DOWN to cycle, A to confirm) and the brain screen for display — ~30 lines of code and a great teaching moment about state and event handling.

FILE src/main.cpp (selector additions) add to pre_auton + autonomous
// ─── Auton selector — add near the top of main.cpp ────────────
// Each entry: name + function pointer
struct AutonEntry {
  const char* name;
  void (*fn)();
};

AutonEntry autons[] = {
  {"1. Drive Test",       auton_drive_test},
  {"2. Arm Cycle",        auton_arm_cycle},
  {"3. Grab and Score",   auton_grab_and_score},
};
const int n_autons = sizeof(autons) / sizeof(autons[0]);
int selected_auton = 0;

void render_selector() {
  Brain.Screen.clearScreen();
  Brain.Screen.setCursor(1, 1);
  Brain.Screen.print("=== Auton Selector ===");
  Brain.Screen.newLine();
  for (int i = 0; i < n_autons; i++) {
    Brain.Screen.print("  %s %s", (i == selected_auton) ? ">" : " ", autons[i].name);
    Brain.Screen.newLine();
  }
  Brain.Screen.newLine();
  Brain.Screen.print("Up/Down to cycle  A to confirm");
}

// Append to pre_auton(), after subsystem inits:
void run_selector() {
  render_selector();
  while (!Controller1.ButtonA.pressing()) {
    if (Controller1.ButtonUp.pressing()) {
      selected_auton = (selected_auton + n_autons - 1) % n_autons;
      render_selector();
      wait(250, msec);   // debounce
    }
    if (Controller1.ButtonDown.pressing()) {
      selected_auton = (selected_auton + 1) % n_autons;
      render_selector();
      wait(250, msec);
    }
    wait(50, msec);
  }
  Brain.Screen.clearScreen();
  Brain.Screen.print("Selected: %s", autons[selected_auton].name);
}

// Replace autonomous() with:
void autonomous() {
  autons[selected_auton].fn();
}
Where to call run_selector(): add it at the end of pre_auton(), after all _init() calls. The selector blocks until the driver presses A — by design. The reference projects include this selector pre-wired.

SECTION 9Reference VEXcode projects

Working VEXcode V5 C++ projects pre-configured in the subsystem-file architecture. Two editions ship side-by-side:

Single-file edition (recommended) — for the VEXcode V5 standalone IDE (Blocks + Text). Architecture preserved as banner-commented sections inside one main.cpp. Setup is 5 minutes: paste one file, build, download.

See the pattern in a full project: the Clawbot and Flex reference builds in the curriculum show this architecture working end-to-end. Open one in VEXcode and compare its main.cpp to yours — you’ll see immediately what the refactor produces.

EN4 reminder: reference projects are for studying the architecture, not for turn-in. Your team must write its own version of every subsystem file before competition — the references show the structure, not ready-made answers.

Companion: PROS + EZ-Template reference (shelved): the team's previous toolchain material is preserved at /code-architecture with three matching EZ-Template projects. If you ever decide to upgrade to PROS's more advanced PID and built-in auton selector, that path is still there.

This site is an informational reference. RECF EN4 prohibits AI-generated content in engineering notebooks and programming code. Rewrite everything in your own words.