Six-motor drivetrain, position-controlled arm, intake toggle, scuff controls, macros, and pneumatics — all in one guide.
Write down your ports before touching the code. A 3+3 tank drive looks like this:
| Port | Motor | Reversed? | Notes |
|---|---|---|---|
| 1 | Left Front | Yes (-1) | Left motors face opposite direction |
| 2 | Left Middle | Yes (-2) | |
| 3 | Left Back | Yes (-3) | |
| 4 | Right Front | No (4) | Right motors face forward |
| 5 | Right Middle | No (5) | |
| 6 | Right Back | No (6) | |
| 10 | IMU (Inertial) | N/A | Used for turning accuracy |
3.25, 1.0. If you have a 36:48 external gear ratio (common speed up), use 3.25, (48.0/36.0).set_drive_brake() in the right places. Check the EZ Template docs for ez::as::exit_condition for advanced brake control.The most reliable arm control method for beginners uses the motor's built-in position PID. You set a target, it holds there:
arm.get_position() with printf, and replace them with your robot's real values.Sometimes you want direct joystick control instead of presets. Add a manual mode using a button hold:
The simplest intake — hold R1 to run forward, hold R2 to reverse:
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:
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!If you have multiple toggles, make a helper so you're not duplicating the pattern everywhere:
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.
Here's a proven competition layout for a robot with a 6-motor drive, arm, and intake:
| Button | Finger | Action | Why |
|---|---|---|---|
| Left Stick | Left Thumb | Left drive side | Tank drive |
| Right Stick | Right Thumb | Right drive side | Tank drive |
| R1 | Right Index | Intake forward (toggle) | Most-used action |
| R2 | Right Index | Intake reverse | Unjam quickly |
| L1 | Left Index | Arm up | Scoring motion |
| L2 | Left Index | Arm down | Reset arm |
| D-pad UP | Left Thumb (off stick) | Arm HIGH preset | Quick score height |
| D-pad DOWN | Left Thumb (off stick) | Arm DOWN preset | Quick retract |
| A | Right Thumb (off stick) | Macro: Score sequence | Automated scoring |
| B | Right Thumb (off stick) | Pneumatic toggle | Claw/release |
DIGITAL_XX constant maps to which action. Scuff is a design decision about which physical button you assign to each piece of code.A macro is an automated sequence triggered by a single button during driver control — like pressing A to raise the arm, wait, score, and retract automatically:
pros::delay() inside opcontrol, the entire robot freezes — no driving while the macro runs. Tasks let both run simultaneously.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.One solenoid on ADI port 'A'. Setting it true extends the piston; false lets the spring retract it.
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.
pros::ADIDigitalOut({{smartPort, 'A'}}) syntax.move_absolute(900, 100). What layer of abstraction does this represent?