FPV Steering Wheel Robot: Driving an FRC Robot With a Virtual Transmission
Most FRC robots are driven with gamepads. Two joysticks, a few buttons, and you are moving. It works. It is also nothing like driving an actual vehicle.
I wanted the robot to feel more like a car. A steering wheel for turning. A throttle trigger for speed. Bumper buttons to shift gears. An FPV camera to see where you are going. The result was an XRP robot controlled by a custom steering wheel with a virtual gearbox underneath.
The Virtual Gearbox
The core idea was a transmission with five states. Reverse, neutral, and three forward gears. Each state maps to a speed multiplier. Reverse caps at 0.7x. First gear at 0.6x. Second at 0.8x. Third at 1.0x. The driver shifts up or down by pressing the bumpers.
The implementation lives in one Shifting subsystem. Static variables track the current gear position and the corresponding multiplier. The arcade drive command reads the throttle and steering axes, scales them by the gear value, and feeds them to the drivetrain. It is simple enough to understand in one file.
I combined throttle and brake into one axis with an offset so the trigger range maps to a usable control band. The steering axis also scales with gear. Turning feels tighter at lower speeds and looser at higher ones.
Command-Based Architecture
The project follows WPILib command-based structure. The RobotContainer wires subsystems to commands. The drivetrain has a default ArcadeDrive command that runs continuously. It reads throttle, steering, and gear state from lambda suppliers that resolve fresh values each cycle.
Shifting commands are discrete. Press the bumper, increment or decrement the gear counter, finish. No continuous logic. Autonomous routines use SequentialCommandGroups that chain DriveDistance and TurnDegrees commands from SmartDashboard selection.
The FPV camera integration was the natural extension. Once the driver holds a steering wheel and looks at a screen, adding a camera feed closes the loop.
What I Would Fix
The shifting logic has edge cases. The increment method steps the counter before checking conditions, which leaves some states unreachable in practice. The getCounter method returns the gear multiplier, not the counter value. The naming is misleading and I would clean it up.
The autonomous routines reference TurnDegrees and TurnTime commands that were never implemented. The SequentialCommandGroups exist, but the individual turn commands do not. The autonomous code is structurally complete but functionally a placeholder.
There are hardcoded values throughout. The throttle offset of 0.85. The scaling divisor of 2. The autonomous distances and times. They work for this robot but make the code harder to port.
Driving an FRC robot with a steering wheel is less precise than a gamepad in some ways. It is more intuitive in others. The virtual gearbox was the part that made it interesting. Not because it was complex. Because it changed how the driver thought about speed.