🦾 Training Platform

Clawbot +
EZ Template

Use the V5 Clawbot as a complete programming training robot — drive PID, autonomous routines, arm position control, and the built-in PID tuner — all on one safe, rebuild-friendly platform.

📚 What this guide is for A practice robot for learning EZ Template before your competition robot is built. Use this if you're new to code and the team robot isn't ready yet.
// Section 01
Why the Clawbot is a Perfect Training Robot 🦾
It has everything you need to teach every concept in this curriculum — and nothing that breaks in ways that confuse students.
🎮 Three Roles, One Robot · Phase A
Each team self-nominates students for Clawbot training. Within your crew, work is distributed by role — Driver, Engineer, Strategist. All three work the same robot through the same sections, but each brings a different lens and produces different notebook deliverables.
🎮 Driver
How does it feel? Tunes drive code, sets button map, runs practice. Deliverable: tuned config + driver-feel notes.
🔧 Engineer
How is it built? Leads sensor installs, writes code, owns the auton. Deliverable: calibration values + working auton.
🧠 Strategist
What's possible? Plans missions, observes field outcomes, builds decision tree. Deliverable: strategic observation log.
Convergence at field practice. All three roles take the sensored Clawbot to the actual Override field together — Drivers drive, Engineers debug, Strategists observe. The intelligence you generate (sensor calibration values, mounting tricks, scoring observations) goes directly to whoever's working on the V1 Hero Bot.

How to navigate this page: follow the Quick Start below in order. Each section has worked solutions you can copy and adapt. Your LMS module pages walk through the same content with role-specific lens, daily pacing, and the 5/15/30-minute “Stuck? Try this” rule: 5 min = re-read the section · 15 min = ask a vet on your team · 30 min = bring it to Coach Tansopalucks.

Mentor checkpoints exist for hardware safety — specifically the potentiometer install in Section 6 Exercise 2. Don’t skip those. Everything else, you can self-direct.
📅 Phase A Timeline · Apr 27 → Jun 4 (5.5 weeks)
Pacing aid — not a deadline
Week 1
Apr 27–May 3
Pages 0–3
Build · IMU
Week 2
May 4–10
Pages 4–6
Arm · Switches · Pot🛑
Week 3
May 11–17
Pages 7–8
Dash · Optical
Week 4
May 18–24
Page 9
Distance
Week 5
May 25–31
(Memorial Day)
Pages 10–11
Macros · Auton
Week 5.5
Jun 1–4
Pages 12–13
Field · Handoff
Solid borders = Phase A core (Pages 0–9) — build, drive, all 5 sensors. Every team finishes this.   Dashed borders = Phase A stretch / Phase B core (Pages 10–14) — macros, auton, field practice, handoff. Slips to Phase B for most teams. That’s expected, not a failure. Phase B (Jun 5–21) is where take-home practice begins.
🎯 Why this curriculum is universal
The Clawbot itself isn’t built to win Override (or any single V5RC game) — its mechanism set is generic. But the skill stack you build here transfers to every V5RC robot you’ll ever build: sensor calibration, EZ-Template + IMU drivetrain, PID arm control, sensor-conditioned auton, driver-assist macros, the failsafe pattern, the “no timing-based motion” discipline. We estimate ~85% of these patterns transfer directly to your V1 Hero Bot. The Clawbot’s job is to teach the patterns; the V1’s job is to apply them to Override scoring. Same approach works for whatever VEX reveals next April.
☑ LMS module checklist (16 pages) · tap to expand
Phase A core (must-finish):
  1. Page 0 — Welcome · 5 min read
  2. Page 1 — Build the Clawbot · 3–4 hours · mentor inspect
  3. Page 2 — Toolchain + first drive code · 1 hour
  4. Page 3 — Add the IMU · 30 min
  5. Page 4 — Arm + Claw control · 2–3 hours
  6. Page 5 — Sensor 0: Motor encoders · 1 hour · foundation
  7. Page 6 — Sensor 1: Limit switches · 1–2 hours
  8. Page 7 — Sensor 2: Pot on the arm · 2 hours · 🛑 mentor required
  9. Page 8 — Sensor 3: Brain dashboard · 1 hour
  10. Page 9 — Sensor 4: Optical on claw · 2 hours
  11. Page 10 — Sensor 5: Distance on back · 2 hours
Phase A stretch / Phase B core:
  1. Page 11 — Driver-assist macros · 3 hours · most teams: Phase B
  2. Page 12 — Sensor-driven auton mission · 3–4 hours · most teams: Phase B
  3. Page 13 — Field practice on Override · ongoing · most teams: Phase B
  4. Page 14 — Phase A wrap + handoff · 1–2 hours · basic version at Mtg 12; refined in Phase B
  5. Page 15 — Phase B preview · 5 min read
🤖
3
Motors
2 drive (left/right) + 1 arm. Plus 1 servo for the claw.
💰
$0
Extra Cost
Every team already has a Clawbot — use what you have.
📚
6
Sections
Why · IMU · EZ Template · Arm + Claw · Drills · Sensors.
🎯
5+
Concepts
Drive PID, autonomous, arm position, sensors, calibration.
📋 IF YOU READ NOTHING ELSE, READ THIS
  1. Build the Clawbot first. Follow VEX’s official PDF or 3D step-by-step assembly guide — vexrobotics.com/v5/downloads/build-instructions . Same page also hosts the Override Hero Bot, Push Back Hero Bot, and other V5 builds.
  2. Add an IMU to your Clawbot (Section 2). 5 minutes.
  3. Copy the EZ Template configuration (Section 3). It just works.
  4. Add arm position control + claw toggle (Section 4).
  5. Run the progressive drills (Section 5) one per week.
  6. By drill 5, you can write a competition-ready autonomous.
Short answer: Yes, EZ Template works on the Clawbot. It requires one addition — a V5 Inertial Sensor plugged into any spare smart port. With that, the Clawbot becomes a full EZ Template robot capable of PID drive, turns, autonomous routines, and the built-in PID tuner.

How the Clawbot Trains the Whole Team

It's not just a build exercise. One robot, four parallel training tracks — every new student runs through all four before touching the competition robot. Tap a track to see the details.

Track 1
🔧Build Training
Do: Follow the Hero Bot assembly guide using the team's parts bin. Build instructions: vexrobotics.com/v5/downloads/build-instructions (Clawbot, Override Hero Bot, plus other V5 builds).
Learn: VEX structural parts, motor mounting, wiring, screw sizing, tool use.
Produce: A working Clawbot + one build-log notebook entry.
Track 2
🏎Driver Training
Do: Pair up. Run lap drills, precise stops, and arm-claw coordination exercises.
Learn: Stopping distance, turn radius, throttle response, center-of-gravity shifts.
Produce: Timed drill results + 3 driving observations that surprised you.
Track 3
💻Programming Training
Do: Clone the EZ Template project, upload it, change one parameter, upload again.
Learn: The edit-upload-test cycle, the shape of a real codebase, PID basics.
Produce: One committed code change + a before/after driving comparison.
Track 4
📐Design + Notebook
Do: Open the Clawbot CAD in Onshape. Identify one part. Change one dimension.
Learn: CAD navigation, part IDing, how design choices get documented.
Produce: A labeled screenshot + a 3-sentence notebook entry.

Why the Clawbot Works So Well for Training

Four characteristics that make the Clawbot the right choice for teaching. Tap any card to read the detail.

🔒Safe to Crash
Low and slow. Students can let autonomous routines fail without damaging anything. Mistakes are safe and educational.
🔄Fast to Rebuild
If a student wires motors wrong, the Clawbot is easy to disassemble and reassemble. No complex custom mechanisms to break.
🎯Three Subsystems
Drive, arm, and claw — teaches tank drive, position control, and toggle/button control all in one robot.
📚Known Dimensions
4" wheels, 11.5" track width, green (200 RPM) cartridges. No measuring needed — just enter the values.

What Students Will Learn Using This Setup

  1. Configuring a real robot chassis in code (ports, wheel size, cartridge)
  2. Adding an IMU and why it matters for turning
  3. Writing and tuning drive PID and turn PID
  4. Arm position control with move_absolute()
  5. Claw toggle control with rising-edge detection
  6. Writing and running a complete autonomous routine
  7. Using EZ Template's built-in PID tuner (no re-uploading needed)
⚠️
One required addition: The stock Clawbot has no V5 Inertial Sensor. EZ Template's turning PID requires it. Adding one is simple — plug it into any free smart port and mount it flat anywhere on the robot. This is actually a great exercise: students learn why heading sensors matter.
🔬 Check for Understanding
Adding which single piece of hardware to a stock V5 Clawbot makes it capable of running every concept in this curriculum?
A second motor pair for the drivetrain
A pneumatic system
A V5 Inertial Sensor (IMU)
A custom-machined chassis
The IMU unlocks turn PID, heading-aware autons, and accurate odometry. Everything else in the curriculum (limit switches, optical, distance, pot) is part of Section 6 sensor practice; the IMU is the one piece that distinguishes a stock Clawbot from a complete EZ-Template training platform.
🎥 Community videos · build skills
Coach has curated external V5RC tutorial videos that pair well with this section. Several short ones are especially worth watching during the build: See all curated external references →
// Section 02
Adding the IMU to Your Clawbot 🧭
One sensor, one smart port — transforms the Clawbot from a basic drive robot into a full EZ Template platform.
📌 Quick Take 5-minute install. One sensor (IMU) plugs into a Smart Port. After this, your Clawbot can do real autonomous routines with drive PID and accurate turning. Without it, autonomous is guesswork.

Why the IMU Is Required

EZ Template uses the IMU (Inertial Measurement Unit) for heading-based turning. Instead of guessing how far each motor needs to spin to turn 90°, it reads the actual rotation of the robot and stops when it reaches exactly 90°. Without it, turns drift significantly — especially on foam tiles.

Where to Mount It

💡
The Clawbot typically uses ports 1 (left motor), 10 (right motor), 3 (claw), and 8 (arm). Ports 11–20 are usually free — pick any one for the IMU. Port 11 is a clean choice that keeps wiring tidy.

Calibration — The IMU Must Warm Up

When the robot turns on, the IMU needs about 3 seconds to calibrate. During this time the robot must be completely still. EZ Template handles this automatically in initialize() when you call chassis.imu_calibrate().

⚠️
Never move the robot during IMU calibration! If you pick it up or bump it in the first 3 seconds after turning on, the heading will be wrong for the entire session. The V5 Brain screen shows "Calibrating..." — wait until it disappears before driving.

Verify It's Working

After mounting, turn on the Brain and go to Devices → Inertial Sensor. You should see heading, pitch, roll, and yaw values. Rotate the robot by hand — the heading value should change. If it reads 0 and doesn't move, check the smart cable connection.

🔬 Check for Understanding
Your turn PID drifts by 5–15° on every turn. PID constants seem reasonable. What's the most likely cause?
The PID constants are wrong — tune kP higher
The robot was moved during the 3-second IMU calibration window at boot
The IMU needs more current than other sensors
EZ-Template can't do precise turns on a Clawbot
Drifty turns are almost always a calibration issue, not a tuning issue. The IMU baselines its zero-rotation reference during the 3-second calibration at boot. If the robot moves during that window (someone bumps it, drives it, picks it up), the offset gets baked in wrong and every turn is off by that amount. Fix: power-cycle, hands off, wait 5 seconds before doing anything.
🔬 Check for Understanding
Why must the IMU be mounted flat (not tilted) and rigidly (not loose)?
To prevent electrical noise from the motors
To keep the wiring short
A tilted or loose IMU lets gravity couple into the rotation reading, producing errors that look like drift
It's a VRC inspection rule
The IMU measures angular velocity by detecting how the chassis frame rotates relative to gravity. If the sensor itself shifts or tilts during driving, those movements look like rotation events to the gyro and integrate into heading drift. Mount on a flat surface with screws (not double-sided tape), and somewhere the chassis flexes least.
🔧
Stuck? Try this first.
  • Drifty turns. Power-cycle the brain, do not touch the robot, wait 5 seconds. Then test. If still drifty, check the IMU is mounted flat and rigid.
  • "IMU not detected" on boot. Reseat the Smart Cable on both ends. Try a different Smart Cable. Confirm the port number in ez::Drive chassis(...) matches the physical port (curriculum uses Smart Port 11).
  • Heading reads wildly off after rough handling or a crash. The IMU recalibrates only at boot. Power-cycle the brain — don't just re-run the program.
  • Heading reads zero and never changes. Most likely the IMU is on a different port than what your code says. Verify via the brain's device-screen.
// Section 03
EZ Template Clawbot Configuration 🖥️
Exact port assignments and the chassis constructor — ready to copy and adapt.
🔧
Required toolchain: This curriculum is written and tested for PROS kernel 4.1.x and EZ-Template 3.2.x. To install EZ-Template, download EZ-Template@3.2.2.zip from the EZ-Template releases page , then in your project directory run pros c fetch EZ-Template@3.2.2.zip followed by pros c apply EZ-Template. Newer versions may work but haven't been verified against this curriculum — if they break, pin to these.
📌 Quick Take Copy these port assignments and the chassis constructor exactly. The code just works on the Clawbot — no debugging the basics. You only customize what your team actually needs to change.

Standard Clawbot Port Map

PortDeviceNotesReversed?
1Left Drive MotorGreen cartridge (200 RPM)Yes — use -1
10Right Drive MotorGreen cartridge (200 RPM)No — use 10
8Arm MotorRed cartridge (100 RPM)Check physically
3Claw MotorRed cartridge (100 RPM)Check physically
11IMU (Inertial Sensor)Added — any free portN/A
⚠️
Always verify port numbers by checking the Brain's Devices screen before coding. Some Clawbots are assembled differently. The reversal directions (negative ports) depend on how the motors are physically oriented — test each motor on the Brain dashboard first.

robot-config.cpp — Clawbot Chassis

📄 src/robot-config.cpp
#include "main.h" // ─── CLAWBOT DRIVETRAIN ─────────────────────────────── // V5 Clawbot: 4" wheels, green (200 RPM) cartridges, no external gearing ez::Drive chassis( {-1}, // Left motor — port 1, reversed {10}, // Right motor — port 10, forward 11, // IMU port — plugged into port 11 4.0, // Wheel diameter in inches (Clawbot uses 4" omni wheels) 200.0 // Wheel RPM — green cartridge = 200 RPM, no external gearing ); // ─── MECHANISMS ────────────────────────────────────── pros::Motor arm (8, pros::v5::MotorGears::red); // port 8, red (100 RPM) pros::Motor claw(3, pros::v5::MotorGears::red); // port 3, red (100 RPM) pros::Controller master(pros::E_CONTROLLER_MASTER);
📄 include/main.h — add these lines
extern ez::Drive chassis; extern pros::Motor arm; extern pros::Motor claw;

main.cpp — initialize() for the Clawbot

📄 src/main.cpp
void initialize() { ez::ez_template_print(); pros::delay(500); chassis.imu_calibrate(); // ~3 seconds — don't move robot! chassis.drive_brake_set(MOTOR_BRAKE_COAST); // Reset arm and claw positions to zero on startup arm.tare_position(); claw.tare_position(); // Arm holds position when stopped — without this, move_velocity(0) coasts arm.set_brake_mode(pros::v5::MotorBrake::hold); default_constants(); ez::as::initialize(); }

opcontrol() — Basic Drive

📄 src/main.cpp
void opcontrol() { chassis.drive_brake_set(MOTOR_BRAKE_COAST); while (true) { // PID Tuner — press X to toggle, B to run current auton if (!pros::competition::is_connected()) { if (master.get_digital_new_press(DIGITAL_X)) chassis.pid_tuner_toggle(); if (master.get_digital_new_press(DIGITAL_B)) autonomous(); chassis.pid_tuner_iterate(); } chassis.opcontrol_tank(); // Tank drive arm_control(); // Arm function (next section) claw_control(); // Claw function (next section) pros::delay(ez::util::DELAY_TIME); } }
🔬 Check for Understanding
In the line ez::Drive chassis({-1}, {10}, 11, 4.0, 200.0); — what does the negative sign on the left motor port mean?
The motor is broken and reversed in hardware
It's a placeholder; the real port is positive
It tells EZ-Template that this motor mounts in mirror-image orientation, so commands should be inverted in software
It points to ADI port 1 instead of Smart Port 1
On the Clawbot, both drive motors face inward (mirror-image mounting). If they both spin the same direction physically, the wheels would push against each other and the robot wouldn't move. The minus sign is how you tell EZ-Template "invert the commands you send to this motor." This is a software fix for a mechanical fact — you'll see it on most V5RC robots.
🔬 Check for Understanding
When does initialize() run, and what's the consequence?
Every time the program loop iterates — runs ~50 times per second
Once when the brain boots — anything you set up here exists for the rest of program execution
Once at the start of each match — runs again when auton starts
Only when you press the brain's power button while connected to a laptop
initialize() is the boot-time setup hook. Calibration (IMU), brake mode, encoder zeroing, sensor LED enables, and any one-time configuration goes here. It does NOT run again at the start of auton or opcontrol — those have their own functions. If you want something to repeat during a match, it goes in opcontrol() or a dedicated task, not here.
🔧
Stuck? Try this first.
  • Robot spins or wheels fight when both sticks pushed forward. The minus sign on a motor port is missing or in the wrong place. Verify the left motor is {-1} not {1}.
  • One side of the drive moves much faster than the other. Motors are on different cartridge colors. Both drive motors need to match (curriculum uses green 200 RPM).
  • "ez::Drive does not name a type" build error. main.h is missing the EZ-Template include. Verify #include "EZ-Template/api.hpp" is in the header.
  • Drive feels "sticky" when you let go of the sticks. Brake mode is set to HOLD instead of COAST. Set it in initialize() with chassis.drive_brake_set(MOTOR_BRAKE_COAST);
// Section 04
Arm Position Control + Claw Toggle 🦾
Complete code for both mechanisms — with EZ Template PID for the arm and toggle control for the claw.
📌 Quick Take Two mechanisms, two patterns. Arm = position control (drive to a known angle and stop). Claw = toggle (button press flips open/closed). Both have copy-paste code below.

Arm Control — Buttons + Position Presets

📄 src/main.cpp
// ─── ARM PRESETS (tune these values for your Clawbot) ─ const int ARM_DOWN = 0; // resting position const int ARM_MID = 800; // mid height — tune with printf! const int ARM_HIGH = 1600; // full height — tune with printf! int armTarget = ARM_DOWN; void arm_control() { // L1/L2 = manual control (also updates the hold target) if (master.get_digital(DIGITAL_L1)) { arm.move_velocity( 50); armTarget = arm.get_position(); } else if (master.get_digital(DIGITAL_L2)) { arm.move_velocity(-50); armTarget = arm.get_position(); } // D-pad UP = high preset, D-pad DOWN = low preset else if (master.get_digital_new_press(DIGITAL_UP)) armTarget = ARM_HIGH; else if (master.get_digital_new_press(DIGITAL_DOWN)) armTarget = ARM_DOWN; // Hold at target — motor does the work else arm.move_absolute(armTarget, 50); }
💡
Use velocity 50 for the Clawbot arm — it's slow and controlled. At 100 the arm swings fast and can overshoot or jolt. Tune ARM_MID and ARM_HIGH by running the arm manually, checking arm.get_position() with printf, and noting where it is at each useful height.

Add Arm to EZ Template PID Tuner (Optional but Educational)

Two pieces, two locations. The PID object itself goes at file scope — if you declare it inside initialize() it goes out of scope when the function returns and the tuner ends up holding a dangling pointer to its constants. Only the push_back registration belongs in initialize().

📄 src/main.cpp — at file scope (alongside the other globals)
// Dedicated PID for the arm you can tune live. Lives at file scope so the // pointer stored in chassis.pid_tuner_pids stays valid for the program lifetime. ez::PID armPID(0.45, 0.001, 2.0, 0, "Arm");
📄 src/main.cpp — inside initialize()
// Add the arm PID to EZ Template's tuner so you can adjust it from the controller chassis.pid_tuner_pids.push_back({"Arm PID", &armPID.constants});

Claw Control — Toggle

📄 src/main.cpp
// ─── CLAW TOGGLE ───────────────────────────────────── bool clawOpen = false; // is claw currently open? bool lastBtnR1 = false; // previous R1 state for rising edge void claw_control() { bool r1 = master.get_digital(DIGITAL_R1); // Rising edge: toggle claw on each press if (r1 && !lastBtnR1) { clawOpen = !clawOpen; if (clawOpen) claw.move_absolute(200, 50); // open position else claw.move_absolute(0, 50); // closed position } lastBtnR1 = r1; }

First Autonomous Routine for the Clawbot

📄 src/autons.cpp
void clawbot_auton() { // Drive forward 24 inches while opening claw claw.move_absolute(200, 50); // open claw (non-blocking) chassis.pid_drive_set(24_in, 80); chassis.pid_wait(); // Close claw (grab something) claw.move_absolute(0, 50); pros::delay(600); // wait for claw to close // Raise arm arm.move_absolute(ARM_HIGH, 50); while (abs(arm.get_position() - ARM_HIGH) > 100) pros::delay(10); // Back up and turn chassis.pid_drive_set(-12_in, 60); chassis.pid_wait(); chassis.pid_turn_set(90_deg, 60); chassis.pid_wait(); }
🔬 Check for Understanding
A student presses R1 to toggle the claw. The claw rapidly opens and closes many times per press. What's most likely wrong?
The claw motor is overheating
The code uses get_digital (held) instead of get_digital_new_press (rising edge)
The claw motor cartridge is the wrong color
The PID constants are too aggressive
get_digital() returns true on every loop iteration the button is held — so a half-second press fires the toggle 25+ times. get_digital_new_press() only returns true on the FIRST iteration of a press (the rising edge), which is what you want for toggles. This is the most common bug in Section 4 — pick the right tool for the job.
🔬 Check for Understanding
After running arm.move_absolute(ARM_HIGH, 50), the arm reaches the target. With no further command sent, what does the arm do next?
Falls back down under gravity
Coasts freely, no torque applied
Actively holds at ARM_HIGH — move_absolute stays engaged until a new command
Returns to ARM_DOWN automatically after one second
Hold-at-target is free with move_absolute — the motor keeps applying corrective torque to stay at the commanded position until you give it a different command. No driver effort, no extra code. This is why position-based arm control (with presets) feels so much smoother than trigger-velocity-based control.
🔧
Stuck? Try this first.
  • Claw flickers rapidly when R1 is pressed. Used get_digital instead of get_digital_new_press. Use the rising-edge function for toggles.
  • Arm jerks down when buttons are released. The else branch isn't calling arm.move_absolute(armTarget, 50) — the arm coasts when no button is pressed. Add the hold-at-target call in the else.
  • ARM_HIGH stops short on day 2 of practice. Encoder drift between sessions. Workaround: power-cycle the brain before each session and don't move the arm until initialize() finishes. Permanent fix: Section 6 Exercise 1 (limit switch with auto-zero).
  • "armTarget was not declared in this scope" build error. The variable is missing from file scope. Add int armTarget = ARM_DOWN; outside any function (right after the includes is fine).
→ Coming up: in Section 7 these manual buttons evolve into driver-assist macros — press one button to coordinate arm + claw + drivetrain using the sensors you’re about to install. Sneak peek of the final map: R1/R2 = arm (with soft limits) · L1 = smart grab · A/Y/X = position presets · D-Pad ←/→ = precise turns · B = full pickup combo. Jump to Section 7 →
// Section 05
Progressive Training Exercises 📋
Work through these in order — each one builds on the last and covers a key EZ Template concept.
📌 Quick Take Six progressive drills. Drill 1 = drive forward N inches. Drill 6 = full match-style autonomous with multiple subsystems. Don't skip ahead. Each drill teaches a concept the next one needs.
Exercise 1 · Beginner
Drive and Turn — Get the Clawbot Moving

Write an autonomous that drives forward 12 inches, turns right 90°, drives forward another 12 inches, then returns to the starting position.

Goal: Understand pid_drive_set(), pid_turn_set(), and pid_wait(). After running it, measure how close it got to the starting point. If it's off by more than 2 inches, the PID needs tuning.

Exercise 2 · Beginner
Square Routine — Consistency Test

Drive a 24" × 24" square — four sides, four 90° turns. Mark the starting position with tape and see if the robot returns exactly to it after the full square.

Goal: Expose PID tuning needs. A well-tuned robot will return within 1–2 inches. A poorly-tuned one will spiral outward. Record the final position error in your engineering notebook.

Exercise 3 · Intermediate
Tune Your PIDs Live — The Built-In PID Tuner

EZ-Template includes a built-in PID tuner that runs on the V5 controller — no laptop, no re-uploading, no print statements. Press X to enter the tuner, use the controller D-pad to navigate constants, the joystick to adjust values, and B to test-run an autonomous routine with the new constants. When the robot behaves the way you want, write the constants down and update default_constants() in autons.cpp.

🎯
This tunes more than just drive PID. The tuner adjusts every PID you've registered with it. By default that's drive (forward/backward) and turn. To also tune the arm, the curriculum's Section 04 already adds armPID to the tuner via chassis.pid_tuner_pids.push_back({"Arm PID", &armPID.constants}) — so when you press X, you'll see "Arm PID" as one of the selectable constants. Same workflow: D-pad to navigate, joystick to adjust, B to test. The same pattern works for any PID you write — lift, intake, turret. Register it once, tune it from the controller forever.

Step-by-step (drive PID):

  1. Plug in the controller, power-cycle the robot, wait for IMU calibration to finish.
  2. Press X — the controller screen shows the tuner UI. The first constant is usually Drive PID kP.
  3. Press B to run the default auton (this runs whatever's set as the default in autonomous() — usually a 24-inch drive forward or a square).
  4. Watch the robot. Overshoots? Lower kP. Sluggish? Raise kP. Oscillates at the end? Add some kD.
  5. Adjust the constant with the joystick (small increments — kP changes of 0.05 are noticeable; kD changes of 1.0 are noticeable).
  6. Press B again. Repeat until the robot stops at exactly 24 inches with no overshoot, no oscillation.
  7. Use D-pad to navigate to Drive PID kD, then Turn PID kP, etc. Tune each.
  8. Write down every constant you ended up with, then update default_constants() in autons.cpp and re-upload. The tuner is for finding constants, not running them in matches.

Step-by-step (arm PID): Same flow. After pressing X, navigate with D-pad until the constant name reads Arm PID kP. Press B to run the default auton (which moves the arm). Adjust until the arm reaches each preset cleanly without overshoot or oscillation. Then update the corresponding constants in your code.

Goal: Understand what each PID term does by experiencing its effect directly on the robot.

🎯
Want a deeper dive? The dedicated PID Tuning with EZ Template → guide walks through the controller UI screen-by-screen, explains kP/kI/kD with visual examples, and covers swing PID and tracking-wheel-based odom PID for teams using those features. The PID Diagnostics guide tells you what each symptom (overshoot, oscillation, sluggishness, steady-state error) means and which constant to adjust.
Exercise 4 · Intermediate
Arm Preset Calibration

Use printf to print arm.get_position() in real time. Manually drive the arm to three useful heights (down, mid, high). Record the encoder values at each position and update ARM_DOWN, ARM_MID, and ARM_HIGH constants.

Goal: Practice sensor-based constant calibration — the same workflow used for every mechanism on a competition robot.

Exercise 5 · Advanced
Full Mission Autonomous

Set up a simple object on the floor in front of the Clawbot. Write an autonomous that: drives to the object, closes the claw to grab it, raises the arm, turns 90°, drives to a "goal" zone, lowers the arm, opens the claw to release.

Goal: Combine all three subsystems (drive, arm, claw) in one routine. Time it and try to complete the mission in under 10 seconds through tuning and optimization.

Worked Solutions 📖

EN4 reminder. These solutions are reference material for understanding EZ-Template patterns. Do not paste them into your team's actual codebase or notebook. RECF rule EN4 prohibits AI-assisted code in engineering notebooks. Read these to understand the API surface, then write your own implementation in your own words. The goal is for the student to be able to write similar code from scratch after working through these.

Exercise 1 — Drive and Turn

Drive forward 12″, turn right 90°, drive forward another 12″, return to start.

📄 src/autons.cpp
void exercise1_drive_and_turn() { // Leg 1: forward 12" chassis.pid_drive_set(12_in, 80); chassis.pid_wait(); // Right 90°. Positive degrees = clockwise (right) in EZ-Template default. chassis.pid_turn_set(90_deg, 70); chassis.pid_wait(); // Leg 2: forward 12" chassis.pid_drive_set(12_in, 80); chassis.pid_wait(); // Return to start: 180° turn, then drive 12√2 ≈ 17" diagonally back, then face start. // Simpler approach for beginner: reverse the path explicitly. chassis.pid_turn_set(-90_deg, 70); // face back along leg 2 chassis.pid_wait(); chassis.pid_drive_set(-12_in, 80); // reverse leg 2 chassis.pid_wait(); chassis.pid_turn_set(-90_deg, 70); // face back along leg 1 chassis.pid_wait(); chassis.pid_drive_set(-12_in, 80); // reverse leg 1, back to start chassis.pid_wait(); }

What to notice: every motion is followed by chassis.pid_wait(). Without it, the next command queues immediately and the robot won't finish the previous move. This is the most common beginner bug. Speeds (80 for drive, 70 for turn) are standard EZ-Template starting points — tune for your robot.

Exercise 2 — Square Routine

Drive a 24″×24″ square. Should return within 1–2″ of start if PID is well-tuned.

📄 src/autons.cpp
void exercise2_square() { // Loop the same 4-step pattern. Each side = 24" forward + 90° right turn. for (int i = 0; i < 4; i++) { chassis.pid_drive_set(24_in, 80); chassis.pid_wait(); chassis.pid_turn_set(90_deg, 70); chassis.pid_wait(); } }

What to notice: a tight loop reveals PID error. If each turn under-rotates by 1°, four turns accumulate to 4° of heading error and the square spirals outward. The end-position miss tells you whether your turn PID is biased. Tune kP/kD in the turn PID until the error spread across 5 runs is under 2″.

Exercise 3 — Use the Built-In PID Tuner

This exercise is mostly UI — press X on the controller to enable the tuner, press B to run the default auton, use joystick / buttons to adjust constants live. The code work is wiring the tuner into opcontrol().

📄 src/main.cpp — opcontrol() snippet
void opcontrol() { chassis.drive_brake_set(MOTOR_BRAKE_COAST); while (true) { // PID tuner is a development tool only — never active during a real match. if (!pros::competition::is_connected()) { // X toggles the PID tuner UI on/off if (master.get_digital_new_press(DIGITAL_X)) { chassis.pid_tuner_toggle(); } // B runs the default auton routine for live testing if (master.get_digital_new_press(DIGITAL_B)) { autonomous(); // runs whatever auton is selected } } if (chassis.pid_tuner_enabled()) { chassis.pid_tuner_iterate(); // updates UI / handles input } else { chassis.opcontrol_tank(); // normal driver control arm_control(); claw_control(); } pros::delay(ez::util::DELAY_TIME); } }

What to notice: the tuner exists in EZ-Template precisely to let you tune at the field with the controller, no laptop required. Once you find good constants, copy them into your chassis.pid_drive_constants_set(...) calls in initialize() so they're the new default.

Exercise 4 — Arm Preset Calibration

Use printf to discover the encoder values for arm presets, then update the constants.

📄 src/main.cpp — add to opcontrol() loop
// Inside the opcontrol() while(true) loop, add a printf every 250ms static int printCounter = 0; if (++printCounter >= 12) { // 12 * ~20ms = ~250ms printCounter = 0; printf("Arm position: %.0f\n", arm.get_position()); }

Then drive the arm manually with L1/L2 to each useful height, watch the printf output in the terminal, and update:

📄 src/main.cpp — replace the placeholder constants
// Update these values to match what you observed via printf const int ARM_DOWN = 0; // arm resting on chassis const int ARM_MID = 920; // example value — YOUR Clawbot will differ const int ARM_HIGH = 1750; // example value — YOUR Clawbot will differ

What to notice: each Clawbot reads slightly different encoder values at the same physical angle — depends on how the arm motor was zeroed at startup, slop in the gear train, and minor build differences. The printf-then-tune workflow is the same one you'll use for every mechanism on the competition robot, just with different sensors. This is the calibration habit that makes auton reliable.

Exercise 5 — Full Mission Auton

Drive to object, grab, raise, turn, drive to goal, lower, release. Target: under 10 seconds.

📄 src/autons.cpp
void exercise5_full_mission() { // Phase 1: prepare to acquire arm.move_absolute(ARM_DOWN, 80); // arm down claw.move_absolute(200, 80); // claw open (200 = open position) // Phase 2: drive to object chassis.pid_drive_set(18_in, 90); // approach speed chassis.pid_wait_quick(); // wait until close, not exact chassis.pid_drive_set(6_in, 40); // final approach: slower chassis.pid_wait(); // Phase 3: grab claw.move_absolute(0, 80); // close claw pros::delay(400); // wait for claw to grip // Phase 4: raise + turn (these can overlap to save time) arm.move_absolute(ARM_HIGH, 100); // raise arm (non-blocking) chassis.pid_turn_set(90_deg, 80); // turn while arm rises chassis.pid_wait(); // Make sure arm finished — turn often completes first while (abs(arm.get_position() - ARM_HIGH) > 100) pros::delay(10); // Phase 5: drive to goal chassis.pid_drive_set(18_in, 90); chassis.pid_wait(); // Phase 6: deposit arm.move_absolute(ARM_MID, 80); // lower to deposit height while (abs(arm.get_position() - ARM_MID) > 100) pros::delay(10); claw.move_absolute(200, 80); // release pros::delay(200); // Phase 7: back away chassis.pid_drive_set(-8_in, 80); chassis.pid_wait(); }

What to notice: three patterns make this auton fast and reliable. (1) pid_wait_quick() on the approach lets the next command start before the previous one is fully settled — saves 200–400 ms per long drive. (2) The arm motor command is non-blocking, so we issue it before the chassis turn and let both happen in parallel. (3) Critical waits (claw grip, arm at deposit height) use explicit delay or position-check loops to make sure the action completed before moving on. Without these, the timing breaks under different battery voltages.

🏆
When to move to the competition robot: When a student can consistently write and tune a Clawbot autonomous routine within a single practice session, they're ready to apply those skills to the competition robot. The Clawbot is training wheels — a safe place to make all the mistakes before they count.
⚙ STEM Highlight Engineering: Iterative Prototyping & Mechatronics
The Clawbot is a mechatronic system — the integration of mechanical design, electronics, and software. Studying it teaches system decomposition: break the robot into subsystems (drive, arm, claw), understand each independently, then integrate. This is how professional engineers tackle complex products. The IMU (Inertial Measurement Unit) in the Clawbot combines an accelerometer and gyroscope — using sensor fusion to produce heading measurements more stable than either sensor alone.
🎤 Interview line: “We use the Clawbot as a controlled mechatronic system for learning. Because the design is fixed, we can isolate software variables. The IMU integrates angular velocity over time to track heading — this is numerical integration, the same math as odometry.”
🔬 Check for Understanding
The IMU reports heading by integrating angular velocity over time. What type of error does this introduce over a long run?
No error — integration is mathematically exact
Drift — small measurement errors in angular velocity accumulate into growing heading error over time
Latency — the heading reading is always one frame behind
Quantization error — the IMU can only report whole-degree values
🔬 Check for Understanding
In the Section 5 full mission, why is the arm-raise command issued before pid_wait() finishes the turn?
pid_wait() requires the arm to be moving
arm.move_absolute() is non-blocking, so the arm raises while the turn happens — saves cycle time
The turn requires arm movement to balance the chassis
It's a syntax requirement of EZ-Template
Overlapping motions saves time. Drives are blocking via pid_wait(); move_absolute is not. Pick which is which deliberately. The arm raise (non-blocking) and the turn (blocking) run simultaneously, completing both ~1 second faster than running them sequentially. Multiply that across 5–6 phases of an auton and you've added 5 seconds to your match.
🔬 Check for Understanding
An auton routine works on Tuesday with a freshly-charged battery, but on Wednesday with a half-depleted battery it falls apart. What's the most likely cause?
Random hardware failure
The battery itself is broken — replace it
The routine relies on pros::delay() timings — slower motors at lower voltage take longer than the timer allows
Static electricity erased the program from flash memory
Time-based autons are fragile. At 12.8 V, a "raise the arm for 1.2 seconds" command might lift it 90°. At 11.0 V (depleted) the same 1.2 seconds might only lift it 70°. The auton works on day 1 and breaks on day 2 from voltage sag alone. Section 6 Exercise 6 replaces every pros::delay() with a sensor condition (while (arm_pot.get_angle() < POT_ARM_HIGH) ...) which is voltage-independent.
🔧
Stuck? Try this first.
  • PID tuner won't activate when X is pressed. Did you call chassis.pid_tuner_initialize_set(true) in initialize()? Also confirm the competition gate !pros::competition::is_connected() is letting it through (it should during dev).
  • Turn PID drifts off heading. Most likely IMU calibration (see Section 1 troubleshooter) before it's a tuning issue. Verify with the dashboard from Section 6 Exercise 3.
  • Auton works once but fails on the second run in the same power cycle. You need chassis.drive_imu_reset() and chassis.drive_sensor_reset() at the start of every auton routine, not just the first one.
  • The arm raises faster than the turn finishes — how do I sync them? Use while (abs(arm.get_position() - ARM_HIGH) > 100) pros::delay(10); after the turn's pid_wait(). This blocks until the arm is within 100 ticks of target.
// Section 06
Sensor Practice on the Clawbot & Hero Bot 🔮
The Clawbot is the perfect testbed for sensor onboarding before competition pressure. Same applies to the Override Hero Bot once it ships. Here's how to use both for sensor learning.
📌 Quick Take Test new sensors on the Clawbot first, in a quiet lab, before you wire them onto the competition robot. Limit switches, distance, optical, vision — all of them. Half the sensor problems at competition are issues you would have caught here.
Short answer to "should we use the Clawbot for sensor practice?" — absolutely yes. The Clawbot is fixed, well-documented, and disposable. Mistakes don't cost you a competition. Mounting failures don't break a real auton. Use it.

Why the Clawbot is the Right Sensor Testbed

Seven Clawbot Sensor Exercises

Work through these in order. Each one introduces one sensor and teaches one calibration pattern. Total time: ~5–6 practice sessions.

Sensor Exercise 0 · Beginner
Motor Encoders — The Sensor You've Been Using All Along

Every V5 Smart Motor has a built-in optical encoder. EZ-Template's drive PID, your tare_position() calls in arm code, and every PROS function that returns a motor's degrees or RPM all read from this same sensor. You've been using it since Section 2 — you just haven't seen it directly. This exercise pulls back the curtain.

Three concrete tasks:

  1. Read raw position. Print the four drive motors' encoder positions to the V5 Brain screen while driving manually. Watch the numbers change. Notice that left and right motors don't always match — that's drift, scrub, and slip showing up in real time.
  2. Convert degrees to inches. Given wheel diameter and gear ratio, calculate how far the robot has actually traveled. Drive 24″ by tape measure. Compare what the encoder says. If it's off, your constants are wrong.
  3. Detect a stall. Read encoder velocity. When velocity stays at zero while voltage is applied, the motor is stalled (jammed, overloaded, or burned out). Print a warning. This is the foundation for the failsafe pattern you'll use in Section 6.

Goal: Build the mental model EZ-Template needs to do its job. When PID misbehaves, encoder data is the first thing you check — almost every drive PID failure is one of: wrong wheel diameter, wrong gear ratio, or wheel slip. All three are visible in this dashboard. See sensors-rotation for the external Rotation Sensor (V1.1 upgrade for higher precision) and motor-troubleshooting for stall diagnostic patterns.

Important framing: EZ-Template handles the drive PID for you using these encoders. You're learning to read encoders so you can debug and integrate, not so you have to write your own PID. Don't replace EZ-Template's drive PID with your own — that's a different, much harder exercise.

Sensor Exercise 1 · Beginner
Add Limit Switches to the Arm

Mount one limit switch at the bottom of the arm's travel and one at the top. The Limit Switch (VEX 276-2174) is the right part for mechanism endpoints — its bendable spring-steel lever is designed for light, repeated contact. Don't use a Bumper Switch here — that's the harder plunger-actuated part, designed for collision detection (you'll use one as a front bumper in Exercise 6). In code, use the limit switches to: (a) prevent the arm motor from running past the limits during driver control, (b) zero the arm encoder by driving the arm down until the bottom switch triggers, then calling tare_position().

Where to mount each limit switch

Bottom limit (ADI port B). Mount it near the front of the chassis, low, where the arm c-channel rests when the arm is at its lowest position. Two reasonable spots on the standard V5 Clawbot:

  • Option 1 (preferred): on the front cross-channel of the chassis, lever pointing up. The arm c-channel rests on top of the lever when the arm is fully down. This auto-presses the switch at startup, which means the very first thing your code can do in initialize() is read arm_bot_limit.get_value(), confirm it's 0 (pressed), and call arm.tare_position() to zero the encoder. No homing routine needed.
  • Option 2: on the inside face of the arm pivot tower, near the bottom, with the lever sticking out horizontally into the arm's swing path. The arm presses the lever as it rotates down to its lowest position.

Top limit (ADI port A). Mount it on the inside face of the arm pivot tower, roughly 2–3 inches below the upper hard stop. The lever should be horizontal, pointing inward into the arm's swing path. As the arm rotates up to its highest commanded position, the arm c-channel presses the lever just before hitting the hard stop — that's the soft-limit you want, so the motor cuts off cleanly without slamming the hard stop.

Verify before wiring. Manually rotate the arm through its full range and confirm: (1) the bottom switch lever depresses smoothly when the arm is at the bottom, (2) the top switch lever depresses smoothly when the arm is at the top, (3) the arm c-channel doesn't bind on either lever during normal travel, and (4) neither switch is pressed when the arm is in its mid-range working zone. If you hear a click as the lever bottoms out and the arm keeps moving past, your switch is correctly positioned. If the arm gets stuck or the lever bends past its travel limit, reposition the switch.

Wiring. Three-wire cable from each switch to the V5 Brain's ADI ports. Color convention is red/black/white — either orientation works for digital switches because they're symmetric, but the standard is white-stripe-up. Top switch goes to ADI port A, bottom to ADI port B. The code below assumes this mapping.

Goal: Understand digital sensors. Learn the !get_value() negation gotcha (returns 0 when pressed). See the switches guide for full PROS API and the VEX KB article on Limit Switch installation for the official mounting guide.

Sensor Exercise 2 · Beginner
Add a Potentiometer to the Arm

Spartan Design's team uses the older red 3-Wire Potentiometer — 250° electrical range, fragile internal stops at each end. Do not rotate it past 265°: VEX’s install guide warns “the sensor’s internal stops can be broken allowing the hub to free rotate” if forced. Once that happens, the pot is recyclable trash.

Older red 3-Wire Potentiometer — red rounded housing with two arc-shaped mounting slots
Confirm yours looks like this — red rounded housing with two arc-shaped mounting slots and “POTENTIOMETER” embossed on the side. If yours is black and rectangular with round holes (the newer V2), you have a different pot — the code below works but you change one constant. See the pot identifier guide.
⚠️
STOP — before powering the arm with the new pot installed: Show your installation to Coach Tansopalucks, a vet, or a team mentor. Have them verify (a) the pot is mounted on the arm pivot shaft (not on a geared shaft that could over-rotate), (b) the arm's full range stays inside the pot's 250° usable arc, and (c) the mounting arc slots are positioned to keep the resting arm position near one end of the pot's travel, not the middle. Don't skip this — a destroyed pot is a $25 lesson the team would prefer you not learn.

Step-by-step workflow:

  1. Install per the VEX KB install guide — D-hole over the arm pivot shaft, two screws through the mounting arc slots, attached to a structural piece.
  2. Power on the V5 Brain with the pot wired to a 3-Wire ADI port (the team uses port C). Tap Devices on the brain’s touchscreen, then tap the potentiometer’s port. The screen will show the live raw value (0–4095) or angle.
  3. Manually rotate the arm by hand through its full intended range (down position → up position). Confirm the value changes smoothly and continuously — no sudden jumps to 0, no plateaus, no weird gaps. Note the values at ARM_DOWN, ARM_MID, ARM_HIGH.
  4. Show the mentor your range. Confirm with them that the values stay within the pot’s 0–250° safe arc and don’t approach the hard stops.
  5. Only after mentor approval: wire to the Brain ADI port and power up the motors. Now you can run the code below.

Goal: Understand absolute position sensing. Learn that pot readings persist across power-cycles (encoder readings don't). Replace your previous encoder-based arm presets with potentiometer-based ones. See the potentiometer guide.

Sensor Exercise 3 · Intermediate
Brain Screen Calibration Dashboard

Build a PROS task that prints the live values of every sensor on your Clawbot to the V5 Brain screen continuously. Lines should include: IMU heading, IMU rotation, arm pot angle, both limit switch states, battery percentage. Use this as your debugging surface for everything that follows.

Goal: Learn the pros::lcd API. Develop the habit of always having sensor data visible on the screen. See V5 screen calibration for the pattern.

Sensor Exercise 4 · Intermediate
Add an Optical Sensor to the Claw

Mount an Optical Sensor inside or just behind the Clawbot's claw, pointing at where a held object would sit. Use color to detect whether something is held, and what color it is. Build a driver-assist macro: when the claw closes on an object, print the detected color to the brain screen.

How to mount it: VEX’s Mounting and Wiring the V5 Vision Sensor article documents the gusset-and-bracket technique for the Clawbot claw — and explicitly states “other sensors can use the same mounting method.” Use a 90° Flat Gusset on top of the claw + an Angle Gusset to the back of the sensor, with two #8-32 × 3/8″ screws for each. The Optical Sensor is smaller than the Vision Sensor, so the same mounting has more clearance. See also the STEM Lab walkthrough with photos.

Goal: Practice short-range sensor mounting (the < 100mm rule). Calibrate hue ranges per object color. Learn proximity-gating (don't trust hue when proximity is low). See the Optical Sensor guide.

Sensor Exercise 5 · Intermediate
Add a Distance Sensor on the Back (Wall Reset)

Mount a V5 Distance Sensor on the back of the Clawbot — not the front. The claw sits in front and would either block the sensor or get measured itself. Back-mounted, the sensor measures distance to the field perimeter wall, which is the standard V5RC technique for autonomous start-position correction.

Build a routine that drives the Clawbot backwards until the sensor reads a target distance (e.g. 100 mm from the wall), then stops. Confirm the position is repeatable: run the routine 5 times and verify the final distance is within ±10 mm each time.

Goal: Eliminate auton drift from start-position error, battery voltage, and floor friction. Same technique top V5RC teams use. See distance-sensor-auton for the full PROS implementation pattern (wall correction during autonomous, drift elimination).

Field practice intelligence for qualifier squad: the perimeter wall’s reflectivity affects laser distance readings. Calibrate your values on the actual Override field, not just the lab. Note any deltas in your notebook — qualifier squad needs your numbers when they wire up V1.

Sensor Exercise 6 · Advanced
Sensor-Driven Auton Mission

Re-do Exercise 5 from the previous section (Full Mission Autonomous), but replace every timed action with a sensor-triggered one: drive forward until limit switch hits the wall, raise arm until pot reaches ARM_HIGH, close claw until proximity drops (object grabbed), turn until IMU heading is target, drive backwards until distance sensor reads target distance from rear wall. Time-based motions should disappear entirely.

Goal: See the difference between time-based auton (brittle, drifts) and sensor-based auton (robust, repeatable). The same skill transfers directly to your competition robot. The Clawbot is teaching you the discipline that wins matches.

Sensor Worked Solutions 📖

EN4 reminder. Same rules as the previous section. These are reference patterns. Read, understand, then write your own.

Sensor Exercise 0 — Motor Encoder Dashboard

📄 src/main.cpp — replace your opcontrol() body with this for the exercise
// Wheel + gear ratio constants for the standard Clawbot // 4" omni wheels, direct drive (1:1), Green 200 RPM cartridge const double WHEEL_DIAMETER_IN = 4.0; const double GEAR_RATIO = 1.0; // motor : wheel const double IN_PER_DEG = (WHEEL_DIAMETER_IN * M_PI) / 360.0 / GEAR_RATIO; void opcontrol() { pros::lcd::initialize(); pros::lcd::set_text(0, "MOTOR ENCODER DASHBOARD"); // Zero the encoders at start so distances read from "here" chassis.left_motors[0].tare_position(); chassis.right_motors[0].tare_position(); while (true) { // --- Drive code stays the same as before --- chassis.opcontrol_tank(); // or your team's drive function // --- Task 1: Read raw encoder positions (degrees) --- double pos_l = chassis.left_motors[0].get_position(); double pos_r = chassis.right_motors[0].get_position(); // --- Task 2: Convert to inches traveled --- double in_l = pos_l * IN_PER_DEG; double in_r = pos_r * IN_PER_DEG; // --- Task 3: Stall detection (voltage applied but velocity ~0) --- double vel_l = chassis.left_motors[0].get_actual_velocity(); int volt_l = chassis.left_motors[0].get_voltage(); bool stalled_l = (abs(volt_l) > 1000) && (fabs(vel_l) < 5.0); // --- Print to the V5 Brain screen --- pros::lcd::print(2, "L: %.1f deg = %.2f in", pos_l, in_l); pros::lcd::print(3, "R: %.1f deg = %.2f in", pos_r, in_r); pros::lcd::print(4, "L vel: %.0f RPM", vel_l); pros::lcd::print(5, "L stalled: %s", stalled_l ? "YES" : "no"); pros::delay(20); } }

What you're seeing. Each motor encoder counts in motor degrees — 360° per motor revolution, NOT per wheel revolution. That's why GEAR_RATIO matters in the conversion. The Clawbot is direct-drive (1:1) so motor degrees = wheel degrees. On the Hero Bot's 5:3 reduced drivetrain, you'd set GEAR_RATIO = 1.667 (or its inverse, depending on direction of math) to get accurate inches.

Why this matters for PID debugging. When EZ-Template's drive PID overshoots a target distance, the issue is almost always one of three things: (a) WHEEL_DIAMETER constant is wrong, (b) GEAR_RATIO is wrong, (c) wheels are slipping (encoder counts don't match actual ground travel). All three are visible in this dashboard. Try this: run the dashboard, drive the robot 24 inches by tape measure, check what the encoder reports. If it says 23 inches, your conversion is off by ~4%. If it says 24 inches but the robot only moved 18, you're slipping — the wheels spun but didn't translate.

The stall detector uses the same logic motor protection libraries use internally: voltage applied (>1V) plus velocity near zero (<5 RPM) means something is jammed. Print a warning, optionally cut motor voltage to prevent overheat. This is the foundation for the failsafe pattern you'll see in Section 6 — once you can detect a stall, you can recover from it instead of letting your robot overheat or your auton drift forever waiting for a target it'll never reach.

⚙️
Don't replace EZ-Template's drive PID. Reading encoder values is for debugging and integration. EZ-Template already has a battle-tested drive PID that uses these same encoders — and writing your own that works as well is significantly harder than it looks. Use this dashboard to verify EZ-Template is reading the encoders correctly, not to bypass it.
🔬 Check for Understanding
Your auton command is "drive forward 24 inches." The robot consistently overshoots the tall goal by ~6 inches. You run the encoder dashboard during auton and confirm it reports exactly 24.0 inches when EZ-Template stops the drive. What's the most likely cause?
The PID kP is too high — tune it down
The WHEEL_DIAMETER_IN constant is set smaller than the actual wheel size
The wheels are slipping during acceleration
The IMU is drifting during the drive
The encoder reads motor degrees and you convert to inches using WHEEL_DIAMETER * π / 360 / GEAR_RATIO. If the constant is set smaller than reality (say 3.5″ when wheels are actually 4″), each motor degree maps to fewer claimed inches than the actual ground travel. EZ-Template stops when it thinks 24 inches were reached, but the actual travel is larger. That's an overshoot. Slip causes the OPPOSITE problem: motor turns more than ground travel, encoder reports more inches than actual, EZ-Template stops early — UNDERshoot, not overshoot. The IMU governs heading, not distance. PID tuning could cause overshoot but the encoder reading would also overshoot (it'd say 26", not exactly 24"). The fact that the dashboard reads exactly 24" while the robot traveled 30" is the signature of a wheel-diameter constant error.

Sensor Exercise 1 — Limit Switches on the Arm

📄 src/robot-config.cpp — add to bottom
// ADI port A = top of arm travel, port B = bottom of arm travel pros::adi::DigitalIn arm_top_limit ('A'); pros::adi::DigitalIn arm_bot_limit ('B');
📄 include/main.h — add
extern pros::adi::DigitalIn arm_top_limit; extern pros::adi::DigitalIn arm_bot_limit;
📄 src/main.cpp — replace arm_control() with this
void arm_control() { // Limit Switch returns 0 when PRESSED. Negate for readable code. bool at_top = !arm_top_limit.get_value(); bool at_bot = !arm_bot_limit.get_value(); if (master.get_digital(DIGITAL_L1) && !at_top) { arm.move_velocity(50); // up — blocked at top armTarget = arm.get_position(); } else if (master.get_digital(DIGITAL_L2) && !at_bot) { arm.move_velocity(-50); // down — blocked at bottom armTarget = arm.get_position(); } else if (master.get_digital_new_press(DIGITAL_UP)) armTarget = ARM_HIGH; else if (master.get_digital_new_press(DIGITAL_DOWN)) armTarget = ARM_DOWN; else arm.move_absolute(armTarget, 50); // Auto-zero encoder when bottom switch is pressed if (at_bot) arm.tare_position(); }

Why !get_value(): the Limit Switch — like the Bumper Switch and most pull-up-style ADI digital inputs — returns 0 (false) when pressed and 1 (true) when released. Negate to get readable variable names like at_top. Forgetting this is the most common limit-switch bug. The auto-zero pattern at the bottom is the magic — every time the arm bottoms out, the encoder resets to zero, which keeps your ARM_MID/ARM_HIGH presets accurate even if the arm motor is power-cycled mid-match.

Sensor Exercise 2 — Potentiometer on the Arm

📍
Where this goes in EZ Template: the constructor block (file src/robot-config.cpp) goes at file scope — outside any function, after #include, alongside the other pros::Motor and pros::Imu declarations EZ Template already has. The control function (file src/main.cpp) goes alongside your other helper functions and is called from opcontrol(). Copy and paste at the indicated locations.
📄 src/robot-config.cpp — add at file scope, with the other hardware
// ADI port C = older red potentiometer on arm pivot // Use E_ADI_POT_EDR for the older red pot (250° range, hard stops) // Use E_ADI_POT_V2 for the newer black V2 pot (333° range, no hard stops) pros::adi::Potentiometer arm_pot('C', pros::E_ADI_POT_EDR);
📄 include/main.h — add alongside the existing externs
extern pros::adi::Potentiometer arm_pot;
📄 src/main.cpp — pot-based arm control (call from opcontrol)
// Replace encoder values with pot angles measured by hand on YOUR Clawbot. // Older red pot returns degrees in [0, 250). Calibrate yours; these are examples. const int POT_ARM_DOWN = 15; // degrees at resting position const int POT_ARM_MID = 90; const int POT_ARM_HIGH = 160; ez::PID arm_pot_pid(2.5, 0, 8.0, 0, "ArmPot"); void arm_to_angle(int target_deg) { arm_pot_pid.target_set(target_deg); int output = arm_pot_pid.compute(arm_pot.get_angle()); arm.move_velocity(output); }

Why pot beats encoder for arms: the pot reads absolute angle. Power-cycle the robot, the angle is still right. Encoder zero drifts unless you pair it with a limit switch (Exercise 1) or a homing routine. For competition robots with arms, pot is the standard. Tune arm_pot_pid kP/kD until the arm holds position smoothly without oscillation. The older red pot has a 250° usable range with hard stops at each end — the team’s mentor-check workflow above exists to verify your arm’s full travel stays inside that range. If your team has the newer black V2 pot, change one line: pros::E_ADI_POT_EDRpros::E_ADI_POT_V2. The V2 has 333° of travel with a 27° deadband at the ends and no hard stops — mount the pot so the arm’s full range stays inside that.

🎯
Tune arm_pot_pid live with the X-button tuner. Same pattern as drive PID and the encoder-based armPID from Section 04. Add this one line to initialize() after the arm_pot_pid declaration:
chassis.pid_tuner_pids.push_back({"ArmPot PID", &arm_pot_pid.constants});
Now press X on the controller, navigate with D-pad until you see "ArmPot PID", and adjust kP/kD with the joystick while pressing B to test. See Section 5 Exercise 3 for the full tuner walkthrough — the workflow is identical for every PID you register.

Sensor Exercise 3 — Brain Screen Calibration Dashboard

📄 src/main.cpp — add as a new function
void dashboard_fn() { pros::lcd::initialize(); while (true) { pros::lcd::print(0, "=== Clawbot Dashboard ==="); pros::lcd::print(1, "IMU heading: %.1f", chassis.drive_imu_get()); pros::lcd::print(2, "Arm encoder: %.0f", arm.get_position()); pros::lcd::print(3, "Arm pot deg: %d", arm_pot.get_angle()); pros::lcd::print(4, "Top limit: %d", !arm_top_limit.get_value()); pros::lcd::print(5, "Bot limit: %d", !arm_bot_limit.get_value()); pros::lcd::print(6, "Battery: %.0f%%", pros::battery::get_capacity()); pros::delay(100); } } // In initialize(): pros::Task dashboard_task(dashboard_fn);

Why a separate task: the LCD update runs in the background at 10 Hz without blocking your control loop. pros::Task creates a free-running thread that calls dashboard_fn() forever. The control loop in opcontrol() stays at 50 Hz; the dashboard updates 5× slower. The fast loop doesn't need to know about the dashboard. This task-based separation is exactly how you'll structure competition code.

Sensor Exercise 4 — Optical Sensor on the Claw

📄 src/robot-config.cpp
// Smart Port 5 = optical sensor pointed at where the held object sits pros::Optical claw_optical(5);
📄 src/main.cpp — in initialize()
claw_optical.set_led_pwm(100); // turn on internal LED for consistent lighting
📄 src/main.cpp — detection function
// Hue ranges — calibrate to YOUR objects under YOUR lighting! enum class HeldColor { NONE, RED, GREEN, BLUE }; HeldColor read_held_color() { // Optical sensor only valid below ~100mm. Gate on proximity first. if (claw_optical.get_proximity() < 100) return HeldColor::NONE; double hue = claw_optical.get_hue(); if (hue < 15 || hue > 345) return HeldColor::RED; else if (hue > 90 && hue < 150) return HeldColor::GREEN; else if (hue > 200 && hue < 260) return HeldColor::BLUE; return HeldColor::NONE; }

Why proximity-gate before hue: when nothing is held, the sensor sees ambient light and returns garbage hue values that fall randomly into one of your color ranges. Filtering on get_proximity() < 100 (under 100mm) means "something is close enough to read reliably." Without this gate, your auton would think it grabbed a red object every time the claw closed on air. Hue ranges shown are nominal — calibrate at your venue with the actual objects under the actual lighting.

Sensor Exercise 5 — Distance Sensor on the Back (Wall Reset)

📍
Where this goes in EZ Template: the constructor goes at file scope in src/robot-config.cpp alongside other Smart Port devices (motors, IMU). The extern goes in include/main.h. The wall-reset function goes in src/main.cpp alongside other helper functions and is called from autonomous() as the first line of every auton routine.
📄 src/robot-config.cpp — add at file scope
// Smart port 13 = back-mounted V5 Distance Sensor. // Pick any free Smart Port that's not used by the drive motors (1, 10) or the IMU (11). pros::Distance back_distance(13);
📄 include/main.h — add
extern pros::Distance back_distance;
📄 src/main.cpp — wall-reset routine (call from autonomous() before any motion)
// Drive backwards until back of robot is target_mm from the wall. // Use this as the FIRST line of every auton routine to eliminate // start-position error. void wall_reset_back(int target_mm) { const int SLOW_VOLTAGE = -30; // negative = backwards const int TIMEOUT_MS = 3000; int elapsed = 0; while (back_distance.get() > target_mm && elapsed < TIMEOUT_MS) { chassis.drive_with_voltage(SLOW_VOLTAGE, SLOW_VOLTAGE); pros::delay(20); elapsed += 20; } chassis.drive_with_voltage(0, 0); // Confirmation: log the actual final distance for debugging pros::lcd::print(7, "Wall reset: %d mm", back_distance.get()); }

Why this beats encoder-based starting position: motor encoders count from where they were last zeroed. If your robot is placed even 10mm off in the start box, every encoder-based motion compounds that error through the auton. The wall is a fixed reference. Drive to it once at the start and you’ve eliminated start-position error from the entire match. Calibrate target_mm on the actual Override field — the perimeter wall’s laser reflectivity differs from lab walls. The team observed a ~5mm delta between lab and field; expect the same. See distance-sensor-auton for the full technique including mid-auton wall corrections.

Sensor Exercise 6 — Sensor-Driven Auton Mission

Replace every timed action from Exercise 5 (in the previous section) with a sensor-triggered one. This exercise uses one new piece of hardware — a bumper switch on the front of the chassis that detects when the robot has reached the goal wall.

📍
Use a Bumper Switch (VEX 276-4858 v2 or 276-2159 original), not a Limit Switch. Both parts are 3-wire ADI digital inputs and behave identically in code (pros::adi::DigitalIn, returning 0 when pressed). The difference is physical: the Bumper Switch has a spring-loaded plunger built for repeated wall contact; the Limit Switch has a thin spring-steel lever built for light mechanism endpoint contact. Bumper Switch v2 product page · VEX KB: Bumper vs Limit . Use a bumper switch here for durability — the lever on a limit switch will bend and eventually break under repeated wall slams.

Where to mount the front bumper: on the front face of the chassis, low — around 2–3" off the ground, below the arm pivot tower. Mount it via the two slotted holes on either side of the bumper housing (use 8-32 screws with the screws not too tight or the plastic cracks). Orient the bumper so the red plunger points forward — it gets compressed when the chassis hits the wall.

⚠️
The arm MUST be at POT_ARM_HIGH when the wall-bumping drive runs. When the arm is down or at mid, the claw extends forward of the chassis at low height. If you drive into the wall in those poses, the claw hits first and the bumper never triggers — the robot will hang in the while loop until the timeout expires (or forever, in the buggy old version). The mission below raises the arm to HIGH in Phase 4 specifically so Phase 6's wall drive works. Don't reorder these phases.

If you build a competition robot whose game-piece manipulator stays low even when scoring (e.g., an intake roller instead of a claw on an arm), you'll mount the front bumper differently — high enough that the manipulator clears it. The principle is the same: the bumper has to be the first part to touch the wall when you drive forward, regardless of mechanism state.

📄 src/robot-config.cpp — add at file scope
// ADI port D = front bumper switch (used by sensor_driven_mission below) pros::adi::DigitalIn front_bumper('D');
📄 include/main.h — add
extern pros::adi::DigitalIn front_bumper;
📄 src/autons.cpp
void sensor_driven_mission() { // Phase 1: prepare arm.move_absolute(ARM_DOWN, 80); claw.move_absolute(200, 80); // Phase 2: drive forward until optical sensor sees an object close enough chassis.pid_drive_set(36_in, 90); int elapsed = 0; while (claw_optical.get_proximity() < 200 && elapsed < 3000) { pros::delay(10); elapsed += 10; } chassis.pid_targets_reset(); // stop the active PID motion chassis.drive_set(0, 0); // command motors to stop // Phase 3: close claw, then verify object held by color claw.move_absolute(0, 80); pros::delay(300); if (read_held_color() == HeldColor::NONE) { // Grab failed — abort or retry. For now, just bail. return; } // Phase 4: raise arm using pot, not encoder arm.move_velocity(100); elapsed = 0; while (arm_pot.get_angle() < POT_ARM_HIGH && elapsed < 2500) { pros::delay(10); elapsed += 10; } arm.move_velocity(0); // holds because brake mode is HOLD (set in initialize) // Phase 5: turn 90° (heading-based, IMU-driven — this is already sensor-based) chassis.pid_turn_set(90_deg, 80); chassis.pid_wait(); // Phase 6: drive until front bumper hits the goal wall // Arm MUST be at HIGH here — otherwise claw hits wall first, bumper never triggers chassis.pid_drive_set(36_in, 70); elapsed = 0; while (front_bumper.get_value() == 1 && elapsed < 3000) { // 1 = NOT pressed pros::delay(10); elapsed += 10; } chassis.pid_targets_reset(); chassis.drive_set(0, 0); // Phase 7: lower arm to deposit height (pot-based) arm.move_velocity(-80); elapsed = 0; while (arm_pot.get_angle() > POT_ARM_MID && elapsed < 2500) { pros::delay(10); elapsed += 10; } arm.move_velocity(0); // Phase 8: release claw.move_absolute(200, 80); pros::delay(200); // Phase 9: back away chassis.pid_drive_set(-8_in, 80); chassis.pid_wait(); }

What changed from Exercise 5 in the previous section: every timing-based wait is now a sensor-based condition with a timeout safety. The optical sensor tells you when an object is close enough to grab. The pot tells you when the arm is at the right height. The bumper tells you when you've reached the wall. The IMU (already used internally by EZ-Template's pid_turn_set) tells you when you've completed the turn. Every while loop has a millisecond cap so a sensor failure never hangs the auton. To stop a PID-driven motion partway through, the canonical EZ-Template pattern is chassis.pid_targets_reset() followed by chassis.drive_set(0, 0) — the first cancels the active PID target, the second commands the motors to zero. The mission completes correctly across battery voltages, friction variations, and minor robot wear that would break a time-based version. This is the discipline that wins matches.

Final main.h Reference

By this point the curriculum has incrementally added a lot of hardware and helpers. If you start hitting linker errors when calling sensor functions or shared constants from autons.cpp, that's because every cross-file symbol needs an extern (for variables) or a prototype (for functions) in main.h. Here's the complete header your project should have by the end of Section 6:

📄 include/main.h — complete header reference
#pragma once #include "api.h" #include "EZ-Template/api.hpp" // ─── Hardware (defined in robot-config.cpp) ──────────────────────── extern ez::Drive chassis; extern pros::Motor arm; extern pros::Motor claw; extern pros::Controller master; extern pros::adi::DigitalIn arm_top_limit; extern pros::adi::DigitalIn arm_bot_limit; extern pros::adi::DigitalIn front_bumper; extern pros::adi::Potentiometer arm_pot; extern pros::Optical claw_optical; extern pros::Distance back_distance; // ─── Shared constants (defined in main.cpp) ──────────────────────── extern const int ARM_DOWN, ARM_MID, ARM_HIGH; extern const int POT_ARM_DOWN, POT_ARM_MID, POT_ARM_HIGH; // ─── Shared types and prototypes ─────────────────────────────────── enum class HeldColor { NONE, RED, GREEN, BLUE }; HeldColor read_held_color(); void wall_reset_back(int target_mm); // ─── Auton routines (defined in autons.cpp) ──────────────────────── void default_constants(); void clawbot_auton(); void sensor_driven_mission();

Why this matters. The progressive blocks in this section each show only the incremental change — "add this line to robot-config.cpp," "add this extern to main.h." That works perfectly while you're working in one file, but as soon as autons.cpp calls something defined in main.cpp (like the constants or read_held_color()) the linker needs to know where to find them. extern for variables, prototypes for functions — everything in main.h, included by every .cpp file. Add to this header as you add new hardware in your own work.

🔬 Check for Understanding
A bumper switch limit returns 1 from get_value(). Is the limit currently pressed?
Yes — 1 means active, like most digital signals
No — Bumper Switch v2 returns 0 when pressed and 1 when released; negate get_value() for readable code
Maybe — the value depends on the brain firmware version
Always 1 unless the switch is physically damaged
VEX's Limit Switch and Bumper Switch v2 (and most pull-up-style ADI digital inputs) are both “active low” — pressed = 0, released = 1. Forgetting this is the single most common limit-switch bug. Standard fix: write bool at_top = !arm_top_limit.get_value(); — the negation makes the variable name and the value match human intuition, and the rest of the code reads cleanly.
🔬 Check for Understanding
Why is wall_reset_back() called as the FIRST line of every auton routine?
To wake up the distance sensor from sleep mode
To eliminate start-position error — encoder drift compounds, but a fixed wall is an absolute reference
It's a VRC inspection rule
To reset the IMU heading to zero
Encoder counts start from where the encoder was last zeroed. If the robot is placed even 10mm off in the start box, every encoder-based motion compounds that error through the auton. The wall is a fixed reference. Drive to it once at the start, eliminate the start-position error, and the rest of your auton becomes voltage- and position-independent. This pattern (sensor as absolute reference) appears in every elite V5RC auton.
🔧
Stuck? Try this first.
  • Limit switch always reads 0 (pressed). Either the ADI port in code doesn't match the physical wiring, or the switch is depressed by something at rest (cable pressing on it, mounting bracket too tight). Verify on the brain's device-screen.
  • Optical sensor reads 0 proximity always. Did you call claw_optical.set_led_pwm(100) in initialize()? Without the LED on, the sensor sees ambient light only.
  • Distance sensor reads jumpy values. Reflective surface (chassis aluminum, claw structure) too close to the sensor's beam path. Reposition or angle slightly so the beam clears the robot's own structure.
  • "undefined reference to read_held_color" build error. main.h is missing the prototype. The "Final main.h Reference" block at the end of this section shows the complete header with every extern and prototype.
  • Sensor-driven mission gets stuck in a while-loop. Did the sensor condition ever actually trigger? Add a pros::lcd::print() inside the loop to verify. Also: every while (sensor) needs a max-iterations or elapsed-ms cap so a sensor failure doesn't freeze the auton.
// Section 07
Driver-Assist Macros 🎮
Buttons that combine sensors with mechanism actions. The bridge between manual control (Sec 4) and full autonomous (Sec 6 Ex 6). Driver presses one button; sensors decide the right thing to happen. This is what wins matches in V5RC.
📖 First time meeting these words?
  • Macro: a pre-programmed sequence that runs when you press one button. Like a keyboard shortcut for your robot — instead of typing “arm down, then claw open,” you press one key and both happen.
  • opcontrol(): the function in your code that runs the entire driver-control period (1:45 in matches). It’s a loop that reads the controller and tells motors what to do, ~50 times per second.
  • Background task: code that runs in parallel with opcontrol, so it doesn’t pause your driving. We use it for the dashboard and rumble feedback.
  • Failsafe: a safety pattern. If a macro misbehaves, you should always be able to cancel it. Our failsafe: move the joystick more than 30%, the running macro stops.
  • Proximity-gating: trusting a sensor reading only when a related sensor confirms the situation is right. Example: trust the optical sensor’s color reading only when its proximity reading says something is actually close enough to look at.
  • D-Pad / face buttons: the small directional buttons on the left thumb (D-Pad: up/down/left/right) and the four lettered buttons on the right thumb (A/B/X/Y). On a stock V5 controller, you can’t use these and the joysticks at the same time.

Controller Map — Fully-Instrumented Clawbot

📍 Left Hand (Claw + Modes)
L1Smart grab (auto-close if optical confirms)
L2 (held)Claw open — manual override
D-Pad ↑Slow-mode toggle (50% drivetrain)
D-Pad ←Precise 90° left turn (IMU)
D-Pad →Precise 90° right turn (IMU)
D-Pad ↓(reserved for Phase B)
L StickLeft drivetrain (analog)
📍 Right Hand (Arm + Presets)
R1 (held)Arm UP (soft-limit aware)
R2 (held)Arm DOWN (soft-limit aware)
APickup preset (arm DOWN + claw OPEN)
YScore preset (arm HIGH)
XTravel preset (arm MID + claw CLOSED)
BSequential pickup combo (full sequence)
R StickRight drivetrain (analog)
Failsafe: Big stick deflection (>30%) during a running macro cancels it. You’re never trapped in a misbehaving routine.

The map below evolves with the curriculum — sticks first, then mechanism shoulders, then face-button presets and D-pad macros come online as you complete each section.

The Seven Macros

Macro 1 · Foundation
Combined Position Presets (A / Y / X buttons)

Sensors used: Pot (for arm angle), no others.

Why it matters: One button = a coordinated mechanism state. Driver thinks “pickup mode,” not “press arm-down then press claw-open.” Cuts driver cognitive load by ~40% in field practice.

Driver feel: Press A for pickup, Y for score, X for travel. Each takes ~0.5s to execute. Faster and more reliable than manual sequencing.

Macro 2 · Foundation
Smart Grab (L1 button)

Sensors used: Optical (proximity-gated).

Why it matters: Driver presses L1; claw closes only if the optical sensor confirms something is actually within range. Saves motor wear on empty-grabs, prevents accidental closes during defensive matches, and prints the detected color to the dashboard for confirmation.

Driver feel: “Grab” becomes a one-button action that just works. L2 stays as manual override (held = open) so you’re never trapped if optical fails.

Macro 3 · Foundation
Soft Arm Limits (R1/R2 enhanced)

Sensors used: Pot (continuous angle reading).

Why it matters: Sec 6 Ex 1 already added reactive limit switches that stop the arm when it hits the limit. This adds proactive deceleration as the arm approaches its safe range. Difference: the robot survives 8 matches instead of breaking at lunch.

Driver feel: R1/R2 work normally in the safe zone. Near the limits, the arm slows to a graceful stop instead of slamming. Driver doesn’t notice it most of the time — that’s the point.

Macro 4 · Critical
Single-Button Precise Turn (D-Pad ← / →)

Sensors used: IMU (via EZ-Template’s pid_turn_set).

Why it matters: The biggest pure driver-experience win in any V5RC robot. Humans are terrible at 90° turns under match pressure. The IMU + EZ-Template makes them perfect. Drivers go from “guessing” to “exactly 90° every time.”

Driver feel: D-pad left or right, robot rotates exactly 90°, brief rumble confirms completion. Used constantly during matches for intersection navigation and goal alignment.

Macro 5 · Critical
Slow-Mode Toggle (D-Pad ↑)

Sensors used: none.

Why it matters: 50% drivetrain speed for precision scoring. Toggle off for travel between scoring zones. Without slow-mode, fine positioning is impossible at competition speeds. With it, drivers can place a cup on a goal post at full controller deflection without overshoot.

Driver feel: Tap D-pad up to enter slow-mode (rumble pattern confirms). Tap again to exit. The drivetrain feels “heavy” in slow-mode — intentional.

Macro 6 · Critical
Controller Rumble Feedback (background)

Sensors used: Optical (object detected), IMU (turn complete), Pot (arm at preset).

Why it matters: Driver knows what’s happening without looking at the brain screen. Subtle but huge. During a tense match, eye contact with the field is everything — rumble lets sensors talk to the driver through their hands.

Driver feel: Brief buzz on grab, double-tap on macro completion, long buzz on collision. Drivers tune their map based on what feels useful vs noisy.

Macro 7 · Critical
Sequential Pickup Combo (B button)

Sensors used: All of them — Pot, Optical, Distance, IMU.

Why it matters: The bridge from macros to auton. Press B → robot drives forward, lowers arm to pickup, opens claw, drives until distance/optical detects target, closes claw if optical confirms, raises to travel position. A 4-second sequence in one button. The same logic that powers your auton, but driver-triggered.

Driver feel: Position roughly in front of a target, press B, robot does the rest. You can override with the stick at any time (failsafe). Aggressive teams use this constantly; it’s the difference between scoring 3 cups and 5 cups in a match.

Worked PROS Solutions for the Macros 📖

📍
Where this all goes in EZ Template: the helper functions go at file scope in src/main.cpp (alongside arm_to_angle from Sec 6 Ex 2). The button-press checks go inside opcontrol()’s while loop. The background tasks (haptic, soft limits) get started once at the top of opcontrol() via pros::Task task_name(task_fn);. Order matters: drivetrain first, one-shot macros next, manual mechanisms last.

Helpers — Position Presets (Macro 1)

📄 src/main.cpp — add at file scope
// Coordinated mechanism states. Each preset is a "mode" not just a position. void pickup_preset() { arm_to_angle(POT_ARM_DOWN); // from Sec 6 Ex 2 claw.move_velocity(-100); // open pros::delay(400); claw.brake(); } void score_preset() { arm_to_angle(POT_ARM_HIGH); // claw state preserved } void travel_preset() { arm_to_angle(POT_ARM_MID); claw.move_velocity(100); // close for compact travel pros::delay(400); claw.brake(); }

Smart Grab (Macro 2)

📄 src/main.cpp — add at file scope
void smart_grab() { // Only close claw if optical confirms something is there if (claw_optical.get_proximity() > 100) { claw.move_velocity(100); // close pros::delay(500); claw.brake(); int hue = claw_optical.get_hue(); pros::lcd::print(6, "Grabbed: hue=%d", hue); master.rumble("."); // haptic confirm } else { pros::lcd::print(6, "Nothing to grab"); } }

Soft Arm Limits (Macro 3)

📄 src/main.cpp — add at file scope
// Scale arm motor power based on proximity to safe-range limits. // Use this anywhere driver presses R1/R2 for manual arm control. double get_safe_arm_power(double driver_input) { double angle = arm_pot.get_angle(); if (angle > 230 && driver_input > 0) // approaching upper limit return driver_input * 0.3; // 30% power if (angle < 20 && driver_input < 0) // approaching lower limit return driver_input * 0.3; return driver_input; // full power in safe zone }

Precise Turn (Macro 4)

📄 src/main.cpp — add at file scope
// Wraps EZ-Template's pid_turn_set with a haptic confirm. // Pass -90 for left, +90 for right. void precise_turn(int degrees) { chassis.pid_turn_set(degrees, TURN_SPEED, true); chassis.pid_wait(); master.rumble("."); }

Rumble Feedback Background Task (Macro 6)

📄 src/main.cpp — add at file scope
// Background task: rumble briefly when optical sees something new. // Runs in parallel with opcontrol; never blocks driving. void haptic_task_fn() { bool was_present = false; while (true) { bool present = claw_optical.get_proximity() > 100; if (present && !was_present) master.rumble("."); // new object detected was_present = present; pros::delay(50); } }

Sequential Pickup Combo (Macro 7)

📄 src/main.cpp — add at file scope
// Press B → drive forward, lower arm, open claw, detect, grab, raise. // Stick deflection at any point cancels (failsafe pattern). void pickup_combo() { // Phase 1: arm down, claw open in parallel with drive arm_to_angle(POT_ARM_DOWN); claw.move_velocity(-100); chassis.drive_with_voltage(40, 40); // Phase 2: drive until optical detects target or 2-second timeout int elapsed = 0; while (claw_optical.get_proximity() < 100 && elapsed < 2000) { // Failsafe: stick deflection cancels macro if (abs(master.get_analog(ANALOG_LEFT_Y)) > 30) return; pros::delay(20); elapsed += 20; } chassis.drive_with_voltage(0, 0); // Phase 3: smart-grab + raise to travel smart_grab(); arm_to_angle(POT_ARM_MID); master.rumble("-."); // macro complete }

Putting It All Together — opcontrol() Structure

📄 src/main.cpp — opcontrol() with all macros wired up
bool slow_mode = false; const int TURN_SPEED = 100; void opcontrol() { pros::Task haptic_task(haptic_task_fn); // start background rumble pros::Task dashboard_task(dashboard_fn); // from Sec 6 Ex 3 while (true) { // 1. DRIVETRAIN (always-running). In slow mode, half-speed manually for fine control. if (slow_mode) { int left_v = master.get_analog(ANALOG_LEFT_Y) * 0.5; int right_v = master.get_analog(ANALOG_RIGHT_Y) * 0.5; chassis.drive_with_voltage(left_v, right_v); } else { chassis.opcontrol_tank(); // EZ-Template tank, full speed } // 2. ONE-SHOT MACROS (face buttons + D-pad) if (master.get_digital_new_press(DIGITAL_A)) pickup_preset(); if (master.get_digital_new_press(DIGITAL_Y)) score_preset(); if (master.get_digital_new_press(DIGITAL_X)) travel_preset(); if (master.get_digital_new_press(DIGITAL_B)) pickup_combo(); if (master.get_digital_new_press(DIGITAL_LEFT)) precise_turn(-90); if (master.get_digital_new_press(DIGITAL_RIGHT)) precise_turn(90); if (master.get_digital_new_press(DIGITAL_UP)) { slow_mode = !slow_mode; master.rumble(slow_mode ? ".." : "."); } // 3. SMART GRAB (L1 with sensor gate) if (master.get_digital_new_press(DIGITAL_L1)) smart_grab(); // 4. MANUAL MECHANISMS (held shoulder buttons, with soft limits) if (master.get_digital(DIGITAL_R1)) arm.move_velocity(get_safe_arm_power(100)); else if (master.get_digital(DIGITAL_R2)) arm.move_velocity(get_safe_arm_power(-100)); else arm.brake(); if (master.get_digital(DIGITAL_L2)) claw.move_velocity(-100); else claw.brake(); pros::delay(20); } }

How Button Assignments Evolve

Section Active Controls What changed
Sec 1–3Sticks onlyJust drive
Sec 4+ R1/R2 arm, L1/L2 claw (manual)Mechanism manual control
Sec 5–6 Ex 2Same buttons, smarter behaviorArm uses pot under the hood
Sec 6 Ex 3No new buttonsDashboard runs in background
Sec 6 Ex 4L1 upgrades: manual close → smart grabOptical now gates the close
Sec 6 Ex 5–6No new buttonsDistance + auton mission
Sec 7+ A/B/X/Y presets, D-Pad turns, slow-modeFull macro stack online
Field practiceAll buttonsDriver muscle memory

The shoulders are consistent end-to-end. Face buttons activate as macros come online. Drivers learn the map progressively, not all at once.

Role Focus — Driver / Engineer / Strategist

🎮 Driver
Test each macro in field practice. Tune timing values (delays, voltages). Decide if rumble feedback is helpful or noisy. Document final button preferences in a button-map sheet.
🔧 Engineer
Implement the seven macros. Tune kP/kD on PID for arm presets. Verify failsafes work (stick deflection cancels macros). Catch macro overlap bugs (two macros running simultaneously). Document the final code structure.
🧠 Strategist
Decide which macros are competition-essential vs nice-to-have. Watch matches with this Clawbot setup; document which macros drivers actually use. Recommend changes for V1 Hero Bot's button map based on what worked.
🚀 What transfers to V1 Hero Bot
Everything you build in this section transfers to your V1 Hero Bot — with one important caveat: calibration values are starting points, not final answers. Re-tune for V1’s geometry.
What Transfer
All 7 macro patterns (the code structure)100%
opcontrol() structure (drivetrain → macros → manual → tasks)100%
Failsafe pattern (stick deflection cancels)100%
Sensor calibration approach (how to tune)100%
Button assignments~80% — adjust for V1 mechanisms
Specific calibration values (pot ranges, hue thresholds, distance targets)Starting points only
The Clawbot teaches the patterns. The V1 applies them to Override scoring. Same patterns will work for whatever VEX reveals next April.
🎮 Scuff controller overlay (Phase B+ territory)
Drivers who outgrow the stock map can 3D-print an “EZ scuff” add-on — the team can produce these from ssejrog’s EZ Wide Body or EZ Thin Body on Printables. Scuffs add bottom paddles that map to existing buttons (typically D-Pad turns), letting drivers trigger macros without lifting thumbs from the sticks. Same code — just different physical input paths. Not essential for Phase A. Add scuffs in Phase B+ once a driver has established their preferred map and identifies the “I wish I could do X while driving” bottleneck.
🏠
Practicing at home in Phase B? Sensor values you tune in the lab will need re-tuning when the robot moves to a different environment. Optical hue thresholds shift under different lighting (gym fluorescents vs LED bulbs at home). Distance sensor reads vary by wall surface and color. Pot calibration usually transfers fine; everything else gets re-checked in the new space. Document deltas in your notebook so the team has a calibration record across environments.
🔬 Check for Understanding
Why does every driver-assist macro check for stick deflection inside its loop?
To smooth the joystick input signal
To save battery by detecting when the driver is idle
Failsafe — if the macro is taking too long or the driver wants to abort, stick movement immediately returns control
It's a VRC tournament safety rule
Any macro that takes control of the drivetrain must yield to driver input. The pattern is: inside the loop, check if (abs(master.get_analog(ANALOG_LEFT_Y)) > 30) return; — this lets the driver "punch out" of a misfiring macro by just moving a stick. Without this, a stuck sensor or an unexpected field condition can trap the robot in an unwanted state for the rest of the match.
🔬 Check for Understanding
What does smart_grab() do that a simple "close claw on L1" button does not?
It moves the claw faster
It only closes the claw if the optical sensor confirms an object is present, preventing wasted attempts in empty air
It uses pneumatics instead of a motor
It works without a battery
Sensor-gating turns macros into smart actions. A driver who hammers L1 in empty air gets nothing — exactly what we want. No timer-based "close claw for 500ms" guesswork; the optical sensor either confirms an object or the macro short-circuits silently. This is the bridge between manual control and full autonomous: one button, smart decision.
🔧
Stuck? Try this first.
  • Macro doesn't yield to driver input. The failsafe check if (abs(master.get_analog(ANALOG_LEFT_Y)) > 30) return; is missing from inside the loop. Add it.
  • smart_grab closes on empty air. The optical-proximity threshold is too low. Try 200 instead of 100. Also verify the optical's LED is on (set_led_pwm(100) in initialize).
  • Combo macro never advances past phase 2. The sensor condition for "object detected" is never becoming true. Print the live sensor value to the dashboard (Section 6 Exercise 3) and watch what's happening as you trigger the macro.
  • Precise turn (D-pad) overshoots. Turn PID isn't tuned, OR the IMU is loose / tilted (see Section 1). PID tune via the EZ-Template tuner with X (during dev only).
  • Multiple macros bound to overlapping buttons fire weird combos. Use rising-edge detection (get_digital_new_press) for one-shot macros, not get_digital. And only ONE macro per button.
// Section 08
Hero Bot for Override — When It Ships 🤖
Each season, VEX releases a Hero Bot designed to play the new game with parts from the V5 Competition Starter Kit. Past examples: Disco (Spin Up), Axel (High Stakes), Dex (Push Back). The Override Hero Bot has not been published yet — it typically becomes available in the days or weeks after game reveal.

When the Override Hero Bot drops, it offers a second tier of training between Clawbot and competition robot:

🎯
Recommended team workflow:
  1. Clawbot first — learn drive, arm, claw, sensors. Get every team member comfortable with PROS and EZ-Template here.
  2. Hero Bot second — build it, drive it on the actual game field. Discover what the game requires. Add sensors based on what you learn.
  3. Custom competition robot last — design your own based on what the Hero Bot taught you. Apply sensor lessons learned on simpler platforms.

Sensor Onboarding Order on Hero Bot

Once the Override Hero Bot is built, layer sensors in this order (matches the priority stack):

  1. IMU for heading. EZ-Template needs it. The Hero Bot likely won't come with one wired up by default; add it.
  2. Limit switches on every motorized mechanism. Cheap insurance.
  3. Potentiometer on whichever Override mechanism rotates within a fixed range (likely the arm or scoring mechanism).
  4. Optical Sensor if Override uses alliance-colored game elements. Mount in the intake throat.
  5. AI Vision & GPS only if you have Tiers 1–4 working reliably and the game rewards their use.

Each sensor adds value if and only if the prior tier is reliable. Don't skip ahead.

⚠️
About the Override Hero Bot: the v0.1 game manual was published April 27, 2026. The official VEX Hero Bot build instructions typically follow within days — we'll update specific Override Hero Bot sensor recommendations once VEX publishes the build. In the meantime, see override-manual-summary for confirmed Override rules and mechanism-claw for the canonical pin-and-cup manipulator architectures the team is working from.

Engineering Notebook on Sensor Work

The sensor work you do on Clawbot and Hero Bot is just as notebookable as the work on your competition robot. Calibration tables, mounting decisions, debugging logs — all of it. See the sensor notebook template guide for templates designed specifically around sensor entries.

🔬 Check for Understanding
When you transition from the Clawbot to your team's competition robot, roughly how much of the curriculum's code patterns transfer directly?
~25% — most needs to be rewritten for the new robot
~50% — about half is transferable
~85% — drivetrain, arm control, sensor patterns, macros, EZ-Template config all transfer; mainly the port map and tuned constants change
~100% — the code is identical between any two V5 robots
The Clawbot is a teaching robot specifically because the code patterns generalize. The structure of opcontrol(), the sensor wiring patterns, the macro architecture, the auton phase-by-phase pattern, the dashboard task — all of it transfers. What changes per robot is mostly: robot-config.cpp (port map, motor count), tuned constants (ARM_HIGH ticks, PID values), and any mechanism-specific code (a launcher, a flywheel, a 4-bar linkage). The 85% number is approximate but the takeaway is real: time invested in this curriculum pays off on the actual competition robot.
Related Guides
🚀 First 30 Minutes in VS Code → 🔬 PID Diagnostics → 🏎 Driver Practice → 🎮 Driver Control Tuning → 🔍 Robot Pre-Check →
← ALL GUIDES