← back to hollowmist.me
🤖

FRC 2021, Infinite Recharge @ Home

Robot autonomous code for Team 4537 RoboRoos' 2021 FRC season, the COVID-era "@ Home" challenges where teams ran their robots through scored autonomous tasks on home practice fields. The robot ran on a working swerve drive: the so-called holy grail of FRC drivetrains, and I wrote the autonomous side on top: the path-following commands, sensor integration, and the path library that drove our Galactic Search, AutoNav, and barrel-racing entries.

FRC 2021 Team 4537 Java / WPILib Swerve + Autonomous
About the season

Infinite Recharge At Home was the FRC 2021 challenge format, instead of head-to-head matches, teams scored their robots solo on a set of autonomous tasks: Galactic Search (read four Power Cells from one of four hidden A/B Red/Blue layouts and collect them in order), AutoNav (Barrel Racing / Slalom / Bounce timed-route trials), and a Power Port shooting challenge. All judged from video.

I worked on this with a small team of programmers, my contribution was the autonomous side: the path-following commands, the path library, and the integration with our custom CAN sensor pods.

Between a working swerve drive (rare) and a tightly tuned autonomous suite running on top of it, our results blew most of the competition out of the water. We had a very good season.

The drivetrain, swerve, working

The thing this whole project rode on, mechanically and programmatically, was that we had a working swerve drive. In FRC this is a big deal, it's widely treated as the holy grail of drivetrains. Every wheel is independently steerable and independently driven, so the chassis can translate in any direction, rotate on the spot, or do both simultaneously without changing which way it's "facing." It's the closest you can get to ideal omnidirectional motion on a four-wheeled robot.

It's also why most teams never run it. Each module is two motors, an absolute angle sensor, and a stack of synchronisation logic; the math (forward/inverse kinematics, field-relative chassis speeds, per-module azimuth offsets) needs to be right or the robot does something unpredictable instead of just being slower than a tank drive. The vast majority of FRC teams stick with tank or mecanum because swerve is mechanically and functionally complex enough that it's easy to spend a season fighting the drivetrain instead of competing with it.

Ours actually worked, four independently steerable, independently driven wheel modules sat underneath a field-relative kinematics layer with a gyro for heading. The mechanical and low-level driver work was the team's; what I built on top assumed it.

The drivebase exposed a single "go this fast in X, this fast in Y, this fast in rotation" entry point, and that's what every autonomous command below talks to. positionX, DriveByTime, the DriveToDestination family, the trajectory follower, they all only really work because that input means the robot can do exactly that, without first having to face the direction it wants to move.

Why it mattered for autonomous: a waypoint walker over a curving cone path would be brutal on a tank drive, every turn becomes a stop-rotate-go cycle. With swerve, the robot can crab sideways through the path while keeping its intake pointed at the next ball. That's the difference between scoring and not scoring on a Galactic Search run.
Autonomous runs (recorded)

Three short clips of the robot running an autonomous routine on our practice field.

Run 1, early tuning pass
An earlier autonomous run at reduced speed, getting the routine settled before opening it up. Different task to Runs 2 and 3.
Run 2, full-speed task
A different scored autonomous task, run at full speed.
Run 3, full-speed task
Another scored autonomous task again, vertical phone capture, full speed.
The autonomous command library

Everything built on WPILib's command-based framework. Each autonomous command is a standalone CommandBase subclass that drives the swerve drivebase using one of our sensors as feedback. They were designed to be composable into SequentialCommandGroups so a routine could be assembled in one place.

DriveByTimeCommand
Open-loop dead reckoning. Drive at (velX, velY) for N milliseconds with a configurable acceleration ramp, derives per-axis acceleration from the velocity ratio so diagonal moves arrive together. Gyro-corrects yaw drift on every tick.
DriveByDistance
Same accel ramp, but closes the loop via OddPod odometry instead of a clock, runs until the robot has actually travelled the requested distance along Y.
DriveToDestination
Single-target XY navigator, accelerates each axis independently, signum-corrects direction so it works regardless of which side of the target you start, and ends when both axes have crossed their target.
DriveToDestination4
The fourth-iteration version, takes a full list of waypoints and walks through them one after another. Computes a unit-vector velocity to the next point so the robot always heads directly at it, and drops to the next coordinate once it's within one robot radius (~483 mm). Optional crash-sensor brake hook left in source.
positionX
Closed-loop wall-aligner. Reads two side-by-side TofPod time-of-flight sensors, drives forward at a velocity proportional to remaining distance, and rotates to zero out the angle between the two readings, so the robot ends up perpendicular to the wall at a target stand-off. Used for shooter alignment.
AutoSwerve
Pathweaver-style trajectory follower built on WPILib's HolonomicDriveController: separate X/Y PID controllers and a profiled rotation PID, sampled against a generated Trajectory. Mostly the team's experiment with the official trajectory toolchain.
Sensor pods (custom CAN devices)

We ran two custom sensor packages on the CAN bus, each on its own device ID, behind dirt-simple Java wrappers that just unpacked a 16-bit packet into typed fields:

OddPod — odometry pod
CAN packet → raw X/Y deltas + totals, then rotated through the navX gyro heading inside update() to keep the totals in field-relative coordinates. Powers DriveByDistance and the entire DriveToDestination family. Streamed to SmartDashboard as a number-array for live debugging.
TofPod — time-of-flight pair
Two paired ToF rangefinders. CAN packet decodes to Ldist, Rdist, and a signed angle result: the difference between them, used directly for perpendicular wall alignment. Drives positionX.
Naming: "OddPod" started as a joke about the odometry pod and stuck. "TofPod" is just the pattern repeated, pod-suffix everything attached to the chassis.
The path library

Every scored route was hand-tuned as a list of XY waypoints in millimetres, indexed by an enum in AutonomousConstants. The DriveToDestination4 walker would drive the robot through them in order, the actual trick was finding a sequence of points where the robot's natural arc through each waypoint produced a smooth path matching the field markings.

GALACTIC_SEARCH_RED_A
5 waypoints
GALACTIC_SEARCH_RED_B
5 waypoints
GALACTIC_SEARCH_BLUE_A
5 waypoints
GALACTIC_SEARCH_BLUE_B
5 waypoints
SLALEM
11 waypoints
BARREL
12 waypoints
BOUNCE
39 (with sentinel gaps)
SEQUENTIAL_COORDINATE_TEST
8 waypoints

The Galactic Search variant was the interesting one, the rules forbade telling the robot which of the four ball layouts you'd be facing, so we had a vision pipeline running a BallTrackingServer NetworkTable that exposed the size and X position of the nearest ball pre-match. The autonomous routine would read those values, pattern-match to one of the four layouts, and pick the matching path. (The detection switch was commented out in the final commit while we were tuning paths, but the four named paths are all there in source.)

How the autonomous code grew

The naming gives away the iteration: DriveToDestination, then 2, 3, 4. Each version solved the previous one's failure mode rather than refactoring in place, partly because we needed the older versions to keep working for the routes that already used them.

v1, DriveByTimeCommand
Time-based dead reckoning
"Drive at this velocity for this many milliseconds." Worked for short straight moves; drift compounded badly on longer sequences.
v2, DriveByDistance
Closed-loop on odometry
Same shape, but reads oddPod.getY() until the robot has actually moved the requested distance. Removed the clock-vs-physics drift problem.
v3, DriveToDestination
Single XY target
Decoupled X and Y into independent acceleration ramps with their own "are we there yet" flags. Ends when both axes have crossed their target. Handles either side of the start point via signum on the acceleration.
v4, DriveToDestination4
Multi-waypoint path follower
The version that actually got used for matches. Takes an int[][] of waypoints, computes a unit-vector toward the current target so the robot always faces straight down its trajectory, and advances to the next waypoint once within one robot radius. This is what every AutonomousConstants path runs through.
parallel, AutoSwerve
WPILib's trajectory toolchain
A separate experiment using PathWeaver-generated JSON trajectories and WPILib's HolonomicDriveController. Smoother in theory but added a lot of toolchain weight; the hand-tuned waypoint approach was less elegant but easier for us to debug from the side of the field.