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.
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.
Four characteristics that make the Clawbot the right choice for teaching. Tap any card to read the detail.
move_absolute()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.
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().
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.
ez::Drive chassis(...) matches the physical port (curriculum uses Smart Port 11).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.| Port | Device | Notes | Reversed? |
|---|---|---|---|
| 1 | Left Drive Motor | Green cartridge (200 RPM) | Yes — use -1 |
| 10 | Right Drive Motor | Green cartridge (200 RPM) | No — use 10 |
| 8 | Arm Motor | Red cartridge (100 RPM) | Check physically |
| 3 | Claw Motor | Red cartridge (100 RPM) | Check physically |
| 11 | IMU (Inertial Sensor) | Added — any free port | N/A |
ez::Drive chassis({-1}, {10}, 11, 4.0, 200.0); — what does the negative sign on the left motor port mean?initialize() run, and what's the consequence?{-1} not {1}.main.h is missing the EZ-Template include. Verify #include "EZ-Template/api.hpp" is in the header.HOLD instead of COAST. Set it in initialize() with chassis.drive_brake_set(MOTOR_BRAKE_COAST);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.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().
get_digital (held) instead of get_digital_new_press (rising edge)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.arm.move_absolute(ARM_HIGH, 50), the arm reaches the target. With no further command sent, what does the arm do next?move_absolute stays engaged until a new commandmove_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.get_digital instead of get_digital_new_press. Use the rising-edge function for toggles.arm.move_absolute(armTarget, 50) — the arm coasts when no button is pressed. Add the hold-at-target call in the else.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).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.
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.
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.
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):
X — the controller screen shows the tuner UI. The first constant is usually Drive PID kP.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).0.05 are noticeable; kD changes of 1.0 are noticeable).B again. Repeat until the robot stops at exactly 24 inches with no overshoot, no oscillation.Drive PID kD, then Turn PID kP, etc. Tune each.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.
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.
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.
Drive forward 12″, turn right 90°, drive forward another 12″, return to start.
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.
Drive a 24″×24″ square. Should return within 1–2″ of start if PID is well-tuned.
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″.
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().
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.
Use printf to discover the encoder values for arm presets, then update the constants.
Then drive the arm manually with L1/L2 to each useful height, watch the printf output in the terminal, and update:
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.
Drive to object, grab, raise, turn, drive to goal, lower, release. Target: under 10 seconds.
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.
pid_wait() finishes the turn?pid_wait() requires the arm to be movingarm.move_absolute() is non-blocking, so the arm raises while the turn happens — saves cycle timepid_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.pros::delay() timings — slower motors at lower voltage take longer than the timer allowspros::delay() with a sensor condition (while (arm_pot.get_angle() < POT_ARM_HIGH) ...) which is voltage-independent.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).chassis.drive_imu_reset() and chassis.drive_sensor_reset() at the start of every auton routine, not just the first one.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.Work through these in order. Each one introduces one sensor and teaches one calibration pattern. Total time: ~5–6 practice sessions.
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:
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.
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().
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:
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.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.
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.
Step-by-step workflow:
ARM_DOWN, ARM_MID, ARM_HIGH.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.
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.
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.
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.
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.
opcontrol() body with this for the exerciseWhat 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.
WHEEL_DIAMETER_IN constant is set smaller than the actual wheel sizeWHEEL_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.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.
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.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_EDR → pros::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.
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:
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.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.
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.
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.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.
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.
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.
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.
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.
main.h ReferenceBy 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:
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.
get_value(). Is the limit currently pressed?get_value() for readable codebool 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.wall_reset_back() called as the FIRST line of every auton routine?claw_optical.set_led_pwm(100) in initialize()? Without the LED on, the sensor sees ambient light only.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.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.opcontrol, so it doesn’t pause your driving. We use it for the dashboard and rumble feedback.| L1 | Smart 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 Stick | Left drivetrain (analog) |
| R1 (held) | Arm UP (soft-limit aware) |
| R2 (held) | Arm DOWN (soft-limit aware) |
| A | Pickup preset (arm DOWN + claw OPEN) |
| Y | Score preset (arm HIGH) |
| X | Travel preset (arm MID + claw CLOSED) |
| B | Sequential pickup combo (full sequence) |
| R Stick | Right drivetrain (analog) |
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.
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.
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.
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.
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.
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.
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.
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.
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.| Section | Active Controls | What changed |
|---|---|---|
| Sec 1–3 | Sticks only | Just drive |
| Sec 4 | + R1/R2 arm, L1/L2 claw (manual) | Mechanism manual control |
| Sec 5–6 Ex 2 | Same buttons, smarter behavior | Arm uses pot under the hood |
| Sec 6 Ex 3 | No new buttons | Dashboard runs in background |
| Sec 6 Ex 4 | L1 upgrades: manual close → smart grab | Optical now gates the close |
| Sec 6 Ex 5–6 | No new buttons | Distance + auton mission |
| Sec 7 | + A/B/X/Y presets, D-Pad turns, slow-mode | Full macro stack online |
| Field practice | All buttons | Driver 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.
| 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 |
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.smart_grab() do that a simple "close claw on L1" button does not?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).get_digital_new_press) for one-shot macros, not get_digital. And only ONE macro per button.When the Override Hero Bot drops, it offers a second tier of training between Clawbot and competition robot:
Once the Override Hero Bot is built, layer sensors in this order (matches the priority stack):
Each sensor adds value if and only if the prior tier is reliable. Don't skip ahead.
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.
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.