⚙️ EZ Template Advanced
Before this: This is full competition code. Complete VS Code + PROS Setup, First 30 Minutes, and Level Up before opening this guide.
⚙ Built for Override 2026-27 (manual v0.1)
This guide is rebuilt around Override's rules. Drivetrain is 4×11W blue cartridges (44W) per the 55W Subsystem 1 cap (rule R11a) and 88W total motor budget (rule R10a). Arm presets are keyed to v0.1 goal heights (3.25″ / 5.8″ / 8.7″). Manipulator chapter shows both single-grip and dual-grip patterns for Override's 1-pin-and-1-cup possession limit (rule SG6). For the broader drivetrain decision (4×11W vs 4×11W + 2×5.5W half motors), see the Override 55W Drivetrain Decision guide.

Build Your
Competition Robot

4-motor drivetrain, position-controlled arm, manipulator (single or dual grip), scuff controls, macros, and pneumatics — built around Override 2026-27 constraints.

// Chapter 01
4-Motor Drivetrain Setup 🚗
Configure a 4-motor tank drive in EZ Template — two motors per side, blue cartridges, geared for Override field traversal under the 55W cap.
🤖
8
Motor Budget
4 drive + 1-2 arm + 1-2 manipulator + 0-1 endgame.
⚙️
6
Subsystems
Drive, arm, manipulator, scuff/macros, pneumatics, integration.
📒
All
Code Provided
Complete opcontrol() + initialize() combining every system.
📚
6
Sections
Drive, arm, manipulator, scuff, pneumatics, integration.
🎮 Official Override Resources (2026-27)
Why 4-motor blue? Override caps Subsystem 1 (drivetrain) at 55W of motor power per rule R11a. 4 × 11W = 44W — safely under the cap with 11W of headroom for adjustments. The remaining motor budget of the 88W total cap (rule R10a) is 44W, leaving 22W for the arm + manipulator and 22W for V2 expansion. The alternative configuration (4 × 11W green + 2 × 5.5W half motors at 55W cap) gives more torque but uses every watt of Subsystem 1 budget. We recommend 4×11W blue as the canonical baseline; see the Override 55W Drivetrain Decision guide for the full tradeoff analysis.

Port Assignment — Plan Before You Code

Write down your ports before touching the code. A 4-motor tank drive looks like this:

PortMotorReversed?Notes
1Left FrontYes (-1)Left motors face opposite direction
2Left BackYes (-2)
3Right FrontNo (3)Right motors face forward
4Right BackNo (4)
10IMU (Inertial)N/AUsed for turning accuracy
⚠️
Physical test first! Before coding, plug each motor into port 1 and spin it by hand. If the V5 Brain shows the position going negative when pushing it forward, that motor needs to be reversed (negative port number).

Chassis Declaration in robot-config.cpp

📄 src/robot-config.cpp
// ─── 4-MOTOR TANK DRIVE (Override-legal: 44W of 55W cap) ─── ez::Drive chassis( // Left motors — negative reverses direction {-1, -2}, // Right motors — positive = forward {3, 4}, // IMU port 10, // Wheel diameter (inches), external gear ratio // Common sizes: 2.75, 3.25, 4.0 3.25, 1.0 );

Gear Cartridge Guide

🔴
Red — 100 RPM
Torque. Great for arms and lifts, not for drive.
🟢
Green — 200 RPM
Balanced. Use if you went 4-motor green + 2 half-motor for the higher-torque config.
🔵
Blue — 600 RPM
Speed. Recommended for the 4×11W canonical Override drivetrain. With 3.25" wheels gives a fast, simple drive.
💡
If your motors use blue (600 RPM) cartridges with 3.25" wheels and no external gearing, your chassis call is: 3.25, 1.0. If you have a 36:48 external gear ratio (common speed-up), use 3.25, (48.0/36.0).

Set Motor Brake Type

📄 src/robot-config.cpp — inside initialize()
// In your initialize() function: chassis.set_drive_brake(MOTOR_BRAKE_COAST); // for driver control // chassis.set_drive_brake(MOTOR_BRAKE_HOLD); // for auton (holds position)
💡
EZ Template automatically switches brake mode between auton (hold) and driver control (coast) if you call set_drive_brake() in the right places. Check the EZ Template docs for ez::as::exit_condition for advanced brake control.
Endgame note (SG12): The Override endgame is a 10-second midfield positional fight. Robots in the midfield are capped at 18″ tall and want to stay put against opponent pushing. Your drive's holding power and pushing capacity matter more in the final 10 seconds than top speed. Practice driving into the midfield, holding position, and resisting nudges — that's an 8-point swing per match if you do it right.
// Chapter 02
Motorized Arm — Position Control 💪
Hold the arm at a target angle, move to presets, and prevent it from slamming into hard stops.
📌 Quick Take Position-controlled arm = PID + presets + soft stops. PID holds it at any angle. Presets jump to known positions (low, mid, high, score). Soft stops prevent it from slamming through frame at the limits.

Declare the Arm Motor

📄 src/robot-config.cpp
// Arm motor on port 8, red cartridge (100 RPM for torque) pros::Motor arm(8, pros::E_MOTOR_GEAR_RED); // Declare in main.h so all files can use it
📄 include/main.h — add this line
extern pros::Motor arm; // makes arm available in all .cpp files

Override Goal Heights → Arm Presets

Override has goals at three distinct heights (per the v0.1 manual glossary). Plus a ground position for picking pins up off the field. That gives 4 arm presets:

PresetGoal TypeHeightD-pad Button
ARM_GROUNDPin pickup from field0″LEFT
ARM_LOWAlliance goal3.25″ (82.5mm)DOWN
ARM_MIDShort neutral goal (in quadrants)5.8″ (146.5mm)RIGHT
ARM_HIGHTall center goal (midfield)8.7″ (222.7mm)UP
📝
Encoder values below are placeholders — they depend on your arm linkage geometry, gear ratio, and starting angle. Use a limit switch (Hero Bot baseline) at the GROUND position as your zero reference, then tune each preset by physically moving the arm to each goal height and reading arm.get_position() from the V5 Brain screen.

Simple Hold-Position Control

The most reliable arm control method for beginners uses the motor's built-in position PID. You set a target, it holds there:

📄 src/main.cpp
// ─── OVERRIDE ARM PRESETS (motor encoder units — tune for your robot!) ─── const int ARM_GROUND = 0; // pin pickup from field (limit-switch zero) const int ARM_LOW = 600; // alliance goal (3.25" / 82.5mm) — tune! const int ARM_MID = 1400; // short neutral (5.8" / 146.5mm) — tune! const int ARM_HIGH = 2400; // tall center (8.7" / 222.7mm) — tune! const int ARM_SPEED = 100; // max arm velocity (0–100) int armTarget = ARM_GROUND; // current target position void arm_control() { // D-pad UP → tall center goal if (master.get_digital(DIGITAL_UP)) { armTarget = ARM_HIGH; } // D-pad RIGHT → short neutral goal else if (master.get_digital(DIGITAL_RIGHT)) { armTarget = ARM_MID; } // D-pad DOWN → alliance goal else if (master.get_digital(DIGITAL_DOWN)) { armTarget = ARM_LOW; } // D-pad LEFT → ground (pin pickup) else if (master.get_digital(DIGITAL_LEFT)) { armTarget = ARM_GROUND; } // Move arm toward its target using move_absolute arm.move_absolute(armTarget, ARM_SPEED); }
⚠️
Tune your preset values! The numbers 600 / 1400 / 2400 are placeholders. After installing a limit switch at the ARM_GROUND reference, drive the arm to each goal height physically, read arm.get_position() with printf, and replace the placeholders with your robot's real values.

Manual Override — Joystick Control

Sometimes you want direct joystick control instead of presets. Add a manual mode using a button hold:

void arm_control() { // Hold L1/L2 for manual joystick control of arm if (master.get_digital(DIGITAL_L1)) { arm.move_velocity(100); // move up manually armTarget = arm.get_position(); // update target so it holds here } else if (master.get_digital(DIGITAL_L2)) { arm.move_velocity(-100); // move down manually armTarget = arm.get_position(); } else { arm.move_absolute(armTarget, ARM_SPEED); // hold last target } }

Using the Arm in Autonomous

void my_auton() { // Drive forward toward an alliance goal while raising arm arm.move_absolute(ARM_LOW, ARM_SPEED); // start arm moving (non-blocking) chassis.pid_drive_set(24_in, 110); chassis.pid_wait(); // Wait until arm reaches position (within 50 units) while (abs(arm.get_position() - ARM_LOW) > 50) { pros::delay(10); } // Now drop the pin (see Manipulator chapter)... }
// Chapter 03
Manipulator Control — Single & Dual Grip 🔄
Pick up and place pins and cups under Override's 1-pin-and-1-cup possession limit. Two architecture patterns: single-grip (one motor handles both) or dual-grip (separate grippers for pin and cup).
📌 Quick Take Manipulator must enforce the 1+1 possession rule: if you're holding a pin, the next pickup must be a cup (or release first). Add a sensor + state machine, not a clever mechanical lockout — sensors are easier to debug.
Override possession context (rule SG6): Each robot can hold a maximum of one cup AND one pin at any time during the match. This rules out magazine accumulators that hold many objects. Your manipulator picks one of two architectural paths: a single-grip mechanism that handles pin and cup sequentially (one at a time, simpler hardware), or a dual-grip mechanism with two separate grippers (faster cycling, more motor budget). See the Override Manipulator Deep Dive for the mechanism-side decision.

Pattern A — Single-Grip

One manipulator motor that picks up either a pin or a cup, depending on what your gripper is currently approaching. This matches the simplest hardware path (one universal manipulator). Code is just "intake on/off" with a toggle.

📄 src/robot-config.cpp
// Single-grip: one motor on port 6, green cartridge for balanced speed/torque pros::Motor manipulator(6, pros::E_MOTOR_GEAR_GREEN); // In main.h: extern pros::Motor manipulator;

Toggle Control — Press Once to Latch On

A toggle means: first press turns it on, second press turns it off. You need to track both the current state and the previous button state to detect the moment of the press:

// ─── TOGGLE STATE VARIABLES ─────────────────────────── bool manipOn = false; // is manipulator currently grabbing? bool lastR1State = false; // was R1 pressed last loop? void manipulator_control() { bool r1Now = master.get_digital(DIGITAL_R1); // Detect rising edge: button just became pressed this frame if (r1Now && !lastR1State) { manipOn = !manipOn; // flip the toggle } lastR1State = r1Now; // remember for next loop // R2 always reverses (drops/releases — overrides toggle) if (master.get_digital(DIGITAL_R2)) { manipulator.move_voltage(-12000); } else if (manipOn) { manipulator.move_voltage(12000); } else { manipulator.brake(); } }
💡
Rising edge detection is the key pattern. r1Now && !lastR1State means "button is pressed NOW but was NOT pressed last loop" — that's the exact moment of a press. Without this, the toggle would flip every 20ms while you hold the button!

Reusable Toggle Helper Function

If you have multiple toggles (like the dual-grip pattern below), make a helper so you're not duplicating the pattern everywhere:

// Put this in main.h — a reusable toggle detector bool toggleOnPress(bool buttonNow, bool& lastState, bool& toggleState) { if (buttonNow && !lastState) toggleState = !toggleState; lastState = buttonNow; return toggleState; } // Usage — now any toggle is one line: bool manipOn = toggleOnPress(master.get_digital(DIGITAL_R1), lastR1, manipRunning);

Pattern B — Dual-Grip

Two separate grippers, one tuned for pins (small, narrow) and one for cups (large, hourglass). Each has its own motor and its own toggle. The robot can hold one pin AND one cup simultaneously (within the SG6 limit) and place both in one trip to the goal. Faster cycle but more motor budget — uses 22W of Subsystem 3 instead of 11W.

📄 src/robot-config.cpp
// Dual-grip: two motors, one per gripper pros::Motor pin_grip(6, pros::E_MOTOR_GEAR_GREEN); // pin gripper pros::Motor cup_grip(7, pros::E_MOTOR_GEAR_GREEN); // cup gripper // In main.h: extern pros::Motor pin_grip; extern pros::Motor cup_grip;

Two-Toggle Control

R1 toggles the pin grip, L1 toggles the cup grip. R2 / L2 reverse (drop) the corresponding gripper. Using the helper function from above keeps this clean:

// ─── DUAL-GRIP STATE ──────────────────────────────── bool pinOn = false, lastR1 = false; bool cupOn = false, lastL1 = false; void manipulator_control() { // R1 toggles pin grip; R2 reverses (drops pin) pinOn = toggleOnPress(master.get_digital(DIGITAL_R1), lastR1, pinOn); if (master.get_digital(DIGITAL_R2)) pin_grip.move_voltage(-12000); else if (pinOn) pin_grip.move_voltage(12000); else pin_grip.brake(); // L1 toggles cup grip; L2 reverses (drops cup) cupOn = toggleOnPress(master.get_digital(DIGITAL_L1), lastL1, cupOn); if (master.get_digital(DIGITAL_L2)) cup_grip.move_voltage(-12000); else if (cupOn) cup_grip.move_voltage(12000); else cup_grip.brake(); }
⚠️
Button conflict with the arm chapter. Chapter 02 used L1 / L2 for manual arm override. Pattern B above reuses L1 / L2 for cup grip. If you went dual-grip, move the manual arm override to a different button (e.g., the right joystick Y-axis as a velocity-control alternative) or accept the tradeoff of no manual arm mode. The Full Robot integration in Chapter 06 picks one path and resolves the conflict.

Choosing Between Patterns

Both patterns are Override-legal under SG6. The choice depends on your mechanism:

PatternMotor BudgetCycle TimeMechanical Complexity
A — Single-Grip11W (1 motor)2 trips per stack (pin trip + cup trip)Lowest
B — Dual-Grip22W (2 motors)1 trip per stackHigher; two grippers to tune

The Hero Bot baseline uses Pattern A. Pattern B is a worthwhile upgrade if your team has the motor budget headroom (within the 88W total cap) and the mechanism design has matured.

// Chapter 04
Scuff Controls + Macro Buttons 🎮
Remap buttons to a claw-grip style layout and add one-button sequences that run automatically.
📌 Quick Take Macro = one button press → multi-step sequence. Score a stack: lift arm, open claw, lower, close, lift. Driver hits one button instead of five. Free reliability gain on every match.

What Are Scuff Controls?

Scuff controls (named after Scuf Gaming controllers) remap actions to buttons accessible with your index fingers while keeping thumbs on the joysticks. In VRC this usually means putting high-use actions on the bumpers (L1, L2, R1, R2) so your thumbs never have to leave the sticks.

👍
Standard Layout
Thumbs on sticks to drive, then move thumb to face buttons (A/B/X/Y) for mechanisms. Slower reaction.
Scuff Layout
Thumbs stay on sticks always. Index fingers control bumpers for intake/arm. Much faster during driver control.

Example Scuff Remapping

A proven Override competition layout for the Hero Bot baseline (4-motor drive, arm, single-grip manipulator):

ButtonFingerActionWhy
Left StickLeft ThumbLeft drive sideTank drive
Right StickRight ThumbRight drive sideTank drive
R1Right IndexManipulator grab (toggle)Most-used action
R2Right IndexManipulator releaseDrop pin or cup
L1Left IndexArm up (manual)Fine-tune scoring height
L2Left IndexArm down (manual)Reset arm
D-pad UPLeft Thumb (off stick)Arm HIGH preset (tall center)Quick score height
D-pad RIGHTLeft Thumb (off stick)Arm MID preset (short neutral)Quad-goal scoring
D-pad DOWNLeft Thumb (off stick)Arm LOW preset (alliance)Alliance-goal scoring
D-pad LEFTLeft Thumb (off stick)Arm GROUND presetPin pickup
ARight Thumb (off stick)Macro: Score-stack sequenceAutomated pin + cup scoring
BRight Thumb (off stick)Cup-flip pneumatic toggleCup orientation correction
💡
The code doesn't change for scuff — you just choose which DIGITAL_XX constant maps to which action. Scuff is a design decision about which physical button you assign to each piece of code. If you went with the dual-grip manipulator pattern from Chapter 03, swap the manual arm controls (L1/L2) onto the right joystick Y-axis and use L1/L2 for cup-grip toggle/release.

Macro Buttons — One Press Runs a Sequence

A macro is an automated sequence triggered by a single button during driver control. The most useful Override macro is score-stack: drive driver-side already aligned with a goal, press A, and the robot raises the arm, drops the pin, then drops the cup — all without taking your thumbs off the sticks.

⚠️
Macros must run in a separate task, not in your main opcontrol loop. If you use pros::delay() inside opcontrol, the entire robot freezes — no driving while the macro runs. Tasks let both run simultaneously.
// ─── OVERRIDE SCORE-STACK MACRO — runs in its own task ─── // Assumes: arm is up to scoring height, manipulator holds 1 pin AND 1 cup // (i.e., dual-grip pattern, or two separate macros for single-grip). bool macroRunning = false; void score_stack_macro_task(void* param) { macroRunning = true; // Step 1: Raise arm to selected goal height (use ARM_LOW for alliance, // ARM_MID for short neutral, ARM_HIGH for tall center) int targetHeight = ARM_LOW; // or ARM_MID / ARM_HIGH arm.move_absolute(targetHeight, 100); while (abs(arm.get_position() - targetHeight) > 80) pros::delay(10); // Step 2: Drop pin (manipulator reverse for 400ms) manipulator.move_voltage(-12000); pros::delay(400); manipulator.brake(); pros::delay(200); // pause for pin to settle in goal // Step 3: For dual-grip robots: drop cup. For single-grip: this // macro ends here, driver picks up cup separately and runs again. // Uncomment if dual-grip: // cup_grip.move_voltage(-12000); // pros::delay(400); // cup_grip.brake(); // Step 4: Return arm to ground for next pickup arm.move_absolute(ARM_GROUND, 100); macroRunning = false; pros::Task::current().remove(); } // In opcontrol() — trigger the macro with button A: if (master.get_digital_new_press(DIGITAL_A) && !macroRunning) { pros::Task scoreTask(score_stack_macro_task); }
get_digital_new_press() is a built-in PROS shortcut that detects rising edge automatically — it only triggers once per press, which is perfect for macros and toggles. Use it instead of tracking lastButtonState manually when you don't need to track state across the loop.

Emergency Macro Cancel

// Let driver cancel the macro with B button if (master.get_digital_new_press(DIGITAL_B) && macroRunning) { macroRunning = false; arm.move_absolute(armTarget, 100); // stop at current position manipulator.brake(); }
// Chapter 05
Pneumatics 💨
Single-acting and double-acting pistons — wiring, code, and toggle control.
📌 Quick Take Single-acting = spring returns piston, one solenoid. Double-acting = solenoid drives both directions, takes two solenoid outputs but uses no spring force. Double-acting is more controllable and most teams use it.

Pneumatics Basics

💨
Single-Acting
One solenoid. Air pushes the piston out; a spring pulls it back. Simpler, uses less air. One state per solenoid.
💨💨
Double-Acting
Two solenoids. Air pushes out AND pulls back under power. More reliable, more consistent force in both directions.
🔌
ADI Ports
Solenoids plug into the 3-wire (ADI) ports on the V5 Brain — ports A through H — not the smart ports.
⚠️
Air management matters! You have a limited tank of air per match. Count your piston activations during testing and make sure you won't run dry by the end of driver control.

Single-Acting Piston — Override Cup-Orientation Flip

A natural Override use for a single-acting piston is a cup-orientation flipper: a small piston that rotates the cup gripper 180° between transparent-side-up and opaque-side-up. This matters for scoring because of rule SC3 — each pin scores per half, and a pin nested inside the transparent half of a cup still scores while the opaque-half pin does not. Flipping the cup before placement can swing scoring outcomes if your strategy depends on which half is exposed.

Why a piston works here: the gripper only needs two states (default rotation, flipped) and the motion is fast and binary. A spring-return single-acting piston is the lowest-cost solution — one ADI port, one solenoid, no motor budget consumed.
📄 src/robot-config.cpp
// Single-acting solenoid on ADI port A — cup-orientation flipper pros::ADIDigitalOut cup_flip('A'); // In main.h: extern pros::ADIDigitalOut cup_flip;
📄 src/main.cpp
// ─── CUP-FLIP TOGGLE ─────────────────────────────────── // false = default orientation (transparent up); true = flipped (opaque up) bool cupFlipped = false; bool lastBtnB = false; void cup_flip_control() { bool btnB = master.get_digital(DIGITAL_B); if (btnB && !lastBtnB) { cupFlipped = !cupFlipped; // flip state cup_flip.set_value(cupFlipped); // send to solenoid } lastBtnB = btnB; } // In autonomous — just set the value directly: cup_flip.set_value(true); // flip to opaque-up before placing pros::delay(300); // wait for piston to actuate cup_flip.set_value(false); // retract (spring) to transparent-up

Double-Acting Piston

Two solenoids — one to extend, one to retract. Both on ADI ports. You can't have both true at the same time — that fights against itself.

📄 src/robot-config.cpp
// Double-acting — solenoid A extends, solenoid B retracts pros::ADIDigitalOut piston_extend('A'); pros::ADIDigitalOut piston_retract('B'); // In main.h: extern pros::ADIDigitalOut piston_extend; extern pros::ADIDigitalOut piston_retract;
📄 src/main.cpp
// ─── DOUBLE-ACTING CONTROL ─────────────────────────── bool isExtended = false; void set_piston(bool extend) { isExtended = extend; if (extend) { piston_extend.set_value(true); pros::delay(50); // brief pulse is enough piston_extend.set_value(false); // cut power, piston holds } else { piston_retract.set_value(true); pros::delay(50); piston_retract.set_value(false); } } void piston_control() { if (master.get_digital_new_press(DIGITAL_B)) { set_piston(!isExtended); // toggle on each press } }
💡
Some teams wire double-acting pistons to the 3-wire expander if the Brain runs out of ADI ports. The code is identical — just use pros::ADIDigitalOut({{smartPort, 'A'}}) syntax.

Pneumatics in Autonomous

void my_auton() { // Drive to alliance goal, raise arm, flip cup before placing arm.move_absolute(ARM_LOW, 100); chassis.pid_drive_set(30_in, 110); chassis.pid_wait(); cup_flip.set_value(true); // flip to opaque-up pros::delay(250); cup_flip.set_value(false); // piston returns to default // (then drop the cup via manipulator — see Chapter 03) chassis.pid_drive_set(-12_in, 100); // back off to clear goal chassis.pid_wait(); }
🔗
Want the full deep-dive? The Pneumatics Best Practices guide covers air budgeting with an interactive estimator, build practices that prevent leaks, solenoid wiring and ADI expander setup, 5 programming patterns for efficiency, single vs double-acting decision framework, and a pre-match checklist.
// Chapter 06
Putting It All Together 🤖
The complete opcontrol() and initialize() combining every system — copy and adapt for your robot.
📌 Quick Take Full code template provided. Copy/paste, adapt port numbers and PID constants to your robot. The integration patterns (one button mapping, mutual exclusion of macros, screen output) are reusable across all your future robots.
📋
This is a complete, working starting point built around the Hero Bot baseline: 4-motor blue drive (44W), 1-motor arm with 4 Override-keyed presets, single-grip manipulator, and a cup-flip pneumatic. Replace port numbers, preset values, and button mappings to match your actual robot. Total motor draw: 4×11W drive + 1×11W arm + 1×11W manipulator = 66W of the 88W cap (rule R10a) — leaves 22W of headroom for V2 expansion (e.g. dual-grip upgrade, climber, or vision compute).

robot-config.cpp — All Declarations

📄 src/robot-config.cpp
#include "main.h" // ─── DRIVETRAIN ─────────────────────────────────────── ez::Drive chassis( {-1, -2}, // left motors (reversed) {3, 4}, // right motors 10, // IMU port 3.25, 1.0 // wheel diameter, gear ratio ); // ─── MECHANISMS ────────────────────────────────────── pros::Motor arm (5, pros::E_MOTOR_GEAR_RED); // red = torque pros::Motor manipulator(6, pros::E_MOTOR_GEAR_GREEN); // single-grip // ─── SENSORS (arm reference + position) ────────────────────────── pros::ADIDigitalIn arm_limit('C'); // limit switch at ARM_GROUND pros::ADIAnalogIn arm_pot('D'); // potentiometer (optional) // ─── PNEUMATICS ────────────────────────────────────── pros::ADIDigitalOut cup_flip('A'); // cup-orientation flipper // ─── CONTROLLER ────────────────────────────────────── pros::Controller master(pros::E_CONTROLLER_MASTER);

main.cpp — Complete opcontrol()

📄 src/main.cpp
#include "main.h" // ─── ARM PRESETS ────────────────────────────────────── // ─── OVERRIDE ARM PRESETS (keyed to v0.1 goal heights — tune!) ─── const int ARM_GROUND = 0; // pin pickup const int ARM_LOW = 600; // alliance goal (3.25") const int ARM_MID = 1400; // short neutral (5.8") const int ARM_HIGH = 2400; // tall center (8.7") int armTarget = ARM_GROUND; // ─── TOGGLE/STATE VARIABLES ─────────────────────────── bool manipOn = false, lastR1 = false; bool cupFlipped = false, lastBtnB = false; bool macroRunning = false; // ─── MECHANISM FUNCTIONS ────────────────────────────── void arm_control() { if (master.get_digital(DIGITAL_L1)) arm.move_velocity(100), armTarget = arm.get_position(); else if (master.get_digital(DIGITAL_L2)) arm.move_velocity(-100), armTarget = arm.get_position(); else if (master.get_digital(DIGITAL_UP)) armTarget = ARM_HIGH; else if (master.get_digital(DIGITAL_RIGHT)) armTarget = ARM_MID; else if (master.get_digital(DIGITAL_DOWN)) armTarget = ARM_LOW; else if (master.get_digital(DIGITAL_LEFT)) armTarget = ARM_GROUND; else arm.move_absolute(armTarget, 100); } void manipulator_control() { bool r1 = master.get_digital(DIGITAL_R1); if (r1 && !lastR1) manipOn = !manipOn; lastR1 = r1; if (master.get_digital(DIGITAL_R2)) manipulator.move_voltage(-12000); else if (manipOn) manipulator.move_voltage(12000); else manipulator.brake(); } void cup_flip_control() { bool b = master.get_digital(DIGITAL_B); if (b && !lastBtnB) { cupFlipped = !cupFlipped; cup_flip.set_value(cupFlipped); } lastBtnB = b; } // ─── SCORE MACRO TASK ───────────────────────────────── void score_stack_macro_task(void*) { macroRunning = true; arm.move_absolute(ARM_LOW, 100); // alliance goal default; tune per match while (abs(arm.get_position() - ARM_LOW) > 80) pros::delay(10); manipulator.move_voltage(-12000); pros::delay(400); manipulator.brake(); arm.move_absolute(ARM_GROUND, 100); macroRunning = false; pros::Task::current().remove(); } // ─── MAIN DRIVER CONTROL ───────────────────────────── void opcontrol() { while (true) { chassis.opcontrol_tank(); // 4-motor tank drive arm_control(); // 4 presets + manual override manipulator_control(); // toggle manipulator cup_flip_control(); // pneumatic cup-orientation // Score-stack macro — button A, non-blocking via task if (master.get_digital_new_press(DIGITAL_A) && !macroRunning) pros::Task(score_stack_macro_task); pros::delay(ez::E_TASK_DELAY); // always last — 10ms } }

Autonomous Template

📄 src/autons.cpp — starter auton using all systems
void full_auton() { // 1. Drive to alliance goal while raising arm to LOW preset arm.move_absolute(ARM_LOW, 100); chassis.pid_drive_set(24_in, 110); chassis.pid_wait(); while (abs(arm.get_position() - ARM_LOW) > 50) pros::delay(10); // 2. Drop preloaded pin (manipulator reverse) manipulator.move_voltage(-12000); pros::delay(400); manipulator.brake(); // 3. Back off goal, lower arm chassis.pid_drive_set(-12_in, 100); arm.move_absolute(ARM_GROUND, 100); chassis.pid_wait(); // 4. Turn toward neutral quadrant goal chassis.pid_turn_set(90_deg, 90); chassis.pid_wait(); chassis.pid_drive_set(18_in, 110); chassis.pid_wait(); // 5. (Optional) Set Toggle on a neutral goal — pin placement does this // 6. End off field perimeter for AWP eligibility (rule SC8) chassis.pid_drive_set(-6_in, 80); chassis.pid_wait(); }
🏆
AWP eligibility (rule SC8): Auto Win Point requires placing 7+ alliance pins, having 3+ goals with 2+ alliance pins each, neither robot contacting the field perimeter at the end of auto, and no auto violations. Step 6 above — backing off the perimeter — is what unlocks the "neither robot contacting field perimeter" condition. Coordinate with your alliance partner so both robots clear the perimeter before auto ends.
🏆
You now have a full competition robot codebase! From here, focus on tuning PID constants, refining your autonomous paths, and improving driver consistency through practice. The code is just the foundation — the reps make the difference.
📚
Next steps: Explore odometry in EZ Template for position tracking, look into motion profiling for smoother paths, and consider adding a second controller (partner) for complex mechanisms.
⚙ STEM Highlight Engineering: Abstraction Layers & System Architecture
A full competition robot is a layered system — exactly like software stacks or circuit hierarchies. The hardware layer (motors, pneumatics) is controlled by the firmware layer (V5 Brain), which is managed by your application layer (EZ Template + your code). Abstraction means each layer hides complexity from the layer above: chassis.pid_drive_set(36, 110) abstracts away encoder math, PID loops, and motor voltage — you only think in inches. This is the same principle behind every operating system ever written.
🎤 Interview line: “Our robot uses three abstraction layers. EZ Template abstracts the hardware — we command in inches and degrees, not motor ticks. Our subsystem files abstract each mechanism. Our auton functions combine these into high-level strategies. This mirrors how professional software systems are architected.”
🔬 Check for Understanding
Your arm uses move_absolute(900, 100). What layer of abstraction does this represent?
Raw hardware — you are directly setting motor voltage
Application layer — the PROS motor API abstracts encoder position control so you work in ticks, not voltage
There is no abstraction — the motor just runs
This is firmware code inside the V5 Brain
Related Guides
💾 Version Control → 📄 Organizing Code → 🔬 PID Diagnostics →
📝
← ALL GUIDES