⚡ EZ Template · Autonomous Techniques

Drive and Operate Simultaneously

Moving forward while spinning the intake. Raising the arm during a turn. Ejecting into a goal while still rolling forward. Three clean methods for running multiple mechanisms at the same time during autonomous — from simple to powerful.

1
The Problem
2
Inline Commands
3
pid_wait_until
4
PROS Tasks
5
Full Example
// Section 01
Why Sequential Code Can’t Do Two Things at Once
C++ code runs top-to-bottom, one line at a time. When EZ Template’s pid_wait() is blocking — waiting for the drive to finish — no other code runs. That means mechanisms sit idle during every movement.
💡
The core issue: pid_wait() is a blocking call. The program stops at that line and waits until the drive PID finishes. While it waits, your intake, arm, and claw code never execute. The robot moves — but it does not do anything else while moving.

What Sequential vs Concurrent Looks Like on a Timeline

Sequential
(default)
Drive 36"
Intake
Arm up
Drive 14"
5.8s total
Concurrent
(this guide)
Drive 36"
Intake (same time)
Drive 14"
Arm up (overlaps)
3.4s total — 2.4 seconds saved

Three Methods, Different Trade-offs

Method 1 — Inline Commands
Call intake.move() or arm.move() before starting the drive. The mechanism runs while the drive PID executes. Simplest approach — works when you want a mechanism running for the entire drive duration.
Method 2 — pid_wait_until
Trigger a mechanism at a specific point mid-movement. Drive starts, and at a certain distance or angle the mechanism switches state. Precise timing, no extra complexity.
Method 3 — PROS Tasks
Run a mechanism on a separate thread entirely. The task runs independently in the background — the arm rises while the drive executes complex multi-step movements. Most powerful, slightly more complex to set up. Best for mechanisms that need their own timing logic.
// Section 02
Method 1 — Inline Commands Before the Drive
The simplest form of concurrency. Start a mechanism, then start a drive movement. Both run simultaneously until pid_wait() returns. No tasks, no extra logic.
ℹ️
Key insight: pid_drive_set() and pid_turn_set() are non-blocking — they start the movement and return immediately. Only pid_wait() blocks. So anything you call before pid_wait() happens concurrently with the drive.

Drive Forward While Spinning the Intake

src/autons.cpp — intake runs the entire drive
// Start intake BEFORE starting the drive intake.move(127); // intake on chassis.pid_drive_set(36, 110); // drive starts (non-blocking) chassis.pid_wait(); // wait for drive to finish intake.move(0); // stop intake after drive done

Raise Arm During a Turn

src/autons.cpp — arm raises while turning
// Move arm to scoring height while turning to face the goal arm.move_absolute(900, 100); // arm starts rising (non-blocking) chassis.pid_turn_set(90, 90); // turn starts (non-blocking) chassis.pid_wait(); // wait for turn to finish // At this point: robot is turned AND arm is at (or near) scoring height // Drive to goal and score chassis.pid_drive_set(12, 80); chassis.pid_wait(); intake.move(-127); // eject into goal pros::delay(400); intake.move(0);

Three Mechanisms at Once

Start as many mechanisms as you want before calling pid_wait(). All of them run while the drive executes:

src/autons.cpp — intake + arm + drive simultaneously
// All three start before the blocking wait intake.move(127); // ① intake on arm.move_absolute(600, 80); // ② arm to mid position chassis.pid_drive_set(24, 110); // ③ drive forward chassis.pid_wait(); // ← everything runs until drive finishes // Robot has driven 24 inches with intake running and arm moving the whole time
⚠️
Watch the arm timing. move_absolute() returns immediately but the arm takes time to physically reach its target. If the drive finishes before the arm reaches position, you will start scoring before the arm is ready. Either extend the drive distance slightly to give the arm time, or check arm position before scoring — shown in the Full Route Example.

When to Use Method 1

// Section 03
Method 2 — Trigger at a Specific Point
pid_wait_until(distance) pauses the autonomous at a precise point mid-movement to trigger a mechanism action, then continues. Perfect for timing: open the claw at 20 inches, start ejecting at 30 inches, raise the arm at 15 degrees into a turn.

Start Intake Mid-Drive at a Precise Distance

src/autons.cpp — intake starts at 20 inches of a 36-inch drive
// Drive 36 inches — but trigger intake at 20 inches chassis.pid_drive_set(36, 110); chassis.pid_wait_until(20); // reached 20 inches — robot still moving intake.move(127); // start intake at exactly this point chassis.pid_wait(); // complete the remaining 16 inches

Raise Arm at a Specific Turn Angle

src/autons.cpp — arm starts rising at 45° during a 90° turn
// Turn 90 degrees — start raising the arm halfway through chassis.pid_turn_set(90, 90); chassis.pid_wait_until(45); // at 45° into the turn arm.move_absolute(900, 100); // arm starts rising chassis.pid_wait(); // finish the remaining 45° // Arm has had 45° worth of turn time to start rising before the drive continues

Multiple Triggers in One Movement

Chain multiple pid_wait_until() calls in a single drive to trigger different actions at different points:

src/autons.cpp — intake on at 15", arm up at 25", eject at 35"
// One 40-inch drive with three mechanism triggers chassis.pid_drive_set(40, 110); chassis.pid_wait_until(15); // 15 inches: open intake intake.move(127); chassis.pid_wait_until(25); // 25 inches: arm starts rising arm.move_absolute(800, 100); chassis.pid_wait_until(35); // 35 inches: reverse intake to eject intake.move(-127); chassis.pid_wait(); // complete the final 5 inches intake.move(0);
💡
This is the most common pattern for competitive VRC autonomous. A single long drive with mechanism triggers at key points covers more ground, scores more game elements, and does it all within a continuous, smooth movement. A robot that drives 40 inches while operating three mechanisms is almost impossible to build with pid_wait() alone.
// Section 04
Method 3 — PROS Tasks for True Concurrency
A PROS Task runs on a separate thread. While the main autonomous thread executes drive movements, the task thread controls a mechanism completely independently — with its own timing, its own sensor checks, its own loop.
ℹ️
When to use Tasks vs Methods 1 and 2: use a Task when the mechanism needs its own logic that cannot be expressed as a simple move() call at a trigger point — for example, an arm that needs to hold position at a preset while the drive does multiple movements, or a mechanism that responds to a sensor during the autonomous sequence.

Pattern A — Run a Mechanism for a Fixed Duration

Start a task, do other movements, then kill the task when done:

src/autons.cpp — intake task runs while drive does multiple movements
// Task function — runs on its own thread void intakeTask(void* param) { intake.move(127); pros::delay(2000); // spin for 2 seconds intake.move(0); } // In your auton function: void autonLeft() { // Launch the intake task pros::Task iTask(intakeTask); // Main thread continues independently chassis.pid_drive_set(24, 110); chassis.pid_wait(); chassis.pid_turn_set(90, 90); chassis.pid_wait(); chassis.pid_drive_set(14, 80); chassis.pid_wait(); // Intake task has been running the whole time, stops automatically at 2s }

Pattern B — Arm Preset That Holds Position

A task that raises the arm to a position and holds it there, freeing the main thread to handle driving:

src/autons.cpp — arm raises and holds while drive sequences run
static bool armTaskDone = false; void armRaiseTask(void* param) { arm.move_absolute(900, 100); // start moving to scoring height // Wait until arm reaches target (within 20 ticks) while (abs((int)arm.get_position() - 900) > 20) { pros::delay(20); } armTaskDone = true; // signal main thread that arm is ready } void autonScoreHigh() { armTaskDone = false; pros::Task aTask(armRaiseTask); // arm starts rising // Drive to scoring position while arm rises intake.move(127); chassis.pid_drive_set(30, 110); chassis.pid_wait(); chassis.pid_turn_set(90, 90); chassis.pid_wait(); intake.move(0); // Wait for arm to be fully up before scoring while (!armTaskDone) pros::delay(20); // Arm is at height — score chassis.pid_drive_set(8, 60); chassis.pid_wait(); claw.move(0); // release }
⚠️
Tasks and EZ Template chassis functions are already thread-safe. Do not try to call chassis.pid_drive_set() from inside a task — only drive from the main autonomous thread. Tasks are for mechanism control (motors, pneumatics, sensors) not drive control. EZ Template’s PID runs in its own internal task and manages the drive for you.
// Section 05
A Complete Concurrent Autonomous Routine
All three methods combined in a realistic 4-action autonomous: collect a game element while driving, raise the arm during a turn, score while still moving, then return to AWP position.

The Scenario

src/autons.cpp — full concurrent routine
void autonLeftFull() { // ── PHASE 1: Drive to element with intake running ──────────── intake.move(127); // Method 1: intake on before drive chassis.pid_drive_set(30, 110); chassis.pid_wait_quick_chain(); // chain into turn without stopping // ── PHASE 2: Turn + raise arm simultaneously ────────────── arm.move_absolute(900, 100); // Method 1: arm starts before turn chassis.pid_turn_set(90, 90); chassis.pid_wait_until(60); // Method 2: at 60° into turn... intake.move(0); // ...stop intake (element secured) chassis.pid_wait_quick_chain(); // chain into drive // ── PHASE 3: Drive to goal, eject mid-approach ──────────── chassis.pid_drive_set(18, 80); chassis.pid_wait_until(10); // Method 2: at 10 inches, start eject intake.move(-127); // eject while still rolling forward chassis.pid_wait(); // full settle at goal intake.move(0); // ── PHASE 4: Arm down + swing to AWP ────────────────── arm.move_absolute(0, 100); // Method 1: lower arm while swinging chassis.pid_swing_set(ez::LEFT_SWING, -70, 90); chassis.pid_wait(); // settle at AWP line // Done: scored 1 element, AWP line touched, arm stowed }
🏆
Build this incrementally. Start with the drive sequence working correctly with no mechanisms. Add intake in Phase 1. Test. Add arm in Phase 2. Test. Add the eject trigger. Test. Each addition should take the routine from working to working-plus-one-thing. Never add two concurrent mechanisms at the same time — you will not know which one broke it.

Quick Reference — Which Method for Which Situation

Situation Method Code
Intake runs entire driveMethod 1move() then pid_drive_set()
Arm rises during a turnMethod 1move_absolute() then pid_turn_set()
Intake starts mid-driveMethod 2pid_wait_until(dist) then move()
Eject starts 5" before stoppingMethod 2pid_wait_until(dist) then move(-127)
Mechanism runs across multiple drive movesMethod 3pros::Task
Arm holds at height, then scores on signalMethod 3pros::Task with armTaskDone flag
⚙ STEM Highlight Computer Science: Concurrency, Threads & Scheduling
PROS Tasks implement concurrent execution — multiple threads running on the same CPU, interleaved by the scheduler. This is cooperative/preemptive multitasking: the RTOS time-slices CPU access between tasks. pros::delay() is a yield point — it tells the scheduler to run other tasks while this one waits. Without delay calls in a loop, a task monopolizes the CPU (starvation). Understanding that concurrency is simulated on a single core — not truly parallel — explains why race conditions and shared state bugs occur.
🎤 Interview line: “PROS Tasks implement concurrent execution through time-sliced scheduling on a single CPU core. When we call pros::delay(), we yield CPU time to other tasks. This is cooperative multitasking — the same model used in early operating systems and still used in embedded systems today.”
🔬 Check for Understanding
You launch a PROS Task that runs a loop with no pros::delay(). What happens to the rest of your robot code?
Nothing — tasks run in true parallel on separate CPU cores
The task runs normally but the control loop slows down slightly
The task monopolizes the CPU, starving the control loop and causing the robot to stop responding to controller input
PROS automatically adds delays to prevent this
Related Guides
⚙️ Finite State Machine →🚫 Stall Detection →🤖 Full Robot Code →
← ALL GUIDES