TBH is a single-gain velocity controller that outperforms bang-bang for flywheel control with almost no added complexity. One parameter to tune. Dramatically less oscillation. The right tool when your game has a launcher, catapult, or disc shooter.
1
How It Works
2
Implementation
3
Tuning
// Section 01
The Take Back Half Concept
TBH works like a smart integrator. It accumulates error over time — like PID's I-term — but when the error crosses zero (the motor passes the target velocity), it takes back half the accumulated output instead of just continuing to integrate.
💡
The key insight: when the flywheel velocity passes through the target (error changes sign), TBH cuts the output estimate in half. This prevents the overshoot that makes a pure integrator oscillate — instead of sailing past and correcting back, TBH converges to the target from one side.
Why TBH Beats Bang-Bang for Flywheels
Bang-bang is always at full power or zero. The flywheel oscillates around the target — fine for some games, but each shot that fires while velocity is swinging gives inconsistent results.
TBH converges to the target and holds it smoothly. Shot-to-shot consistency is dramatically better because the flywheel is at a stable velocity, not a bouncing one.
One gain to tune vs PID’s three. Significantly easier to dial in under competition pressure.
ℹ️
TBH is specifically designed for systems that accelerate faster than they decelerate — which describes most VRC flywheels exactly. The motor spins up quickly under full power but loses speed slowly due to flywheel inertia. TBH’s asymmetric handling of positive vs negative error matches this behavior well.
1 / 3
// Section 02
Complete TBH Implementation
The full algorithm in ~20 lines. One gain constant, one state variable.
include/flywheel.hpp
#pragma oncevoidflywheelSetTarget(double rpm);
voidflywheelUpdate(); // call every 10ms in a taskboolflywheelAtSpeed(); // true when within tolerance
src/flywheel.cpp
#include "main.h"#include "flywheel.hpp"// ── TBH state ─────────────────────────────────────────────────────────staticdouble tbh_output = 0; // current motor output (0.0 - 1.0)staticdouble tbh_prev = 0; // the "take back half" referencestaticdouble tbh_error = 0; // last loop's errorstaticdouble tbh_target = 0; // target RPM// ── TBH gain — start at 0.0005, tune up if too slow, down if overshootconstdouble TBH_GAIN = 0.0005;
constdouble AT_SPEED_TOLERANCE = 50; // RPMvoidflywheelSetTarget(double rpm) {
tbh_target = rpm;
tbh_output = 0; // reset on target change
tbh_prev = 0;
}
voidflywheelUpdate() {
double velocity = flywheel.get_actual_velocity();
double error = tbh_target - velocity;
// Accumulate output (like an integrator)
tbh_output += TBH_GAIN * error;
tbh_output = std::clamp(tbh_output, 0.0, 1.0);
// "Take back half" — when error crosses zero, average with last referenceif ((error >= 0) != (tbh_error >= 0)) {
tbh_output = 0.5 * (tbh_output + tbh_prev);
tbh_prev = tbh_output;
}
tbh_error = error;
flywheel.move((int)(tbh_output * 127));
}
boolflywheelAtSpeed() {
return abs(tbh_target - flywheel.get_actual_velocity()) < AT_SPEED_TOLERANCE;
}
src/main.cpp — run TBH in a background task
// In initialize() — start the flywheel control taskpros::Taskfw_task([](){
while(true) {
flywheelUpdate();
pros::delay(10);
}
});
// In opcontrol() — set target and fire when readyflywheelSetTarget(3000); // start spinning at 3000 RPMif (master.get_digital_new_press(DIGITAL_R1) && flywheelAtSpeed()) {
fireIndexer(); // only fire when flywheel is at target speed
}
2 / 3
// Section 03
Tuning TBH_GAIN
One parameter. Two symptoms. Easy to fix.
Too slow to reach target — increase TBH_GAIN. The flywheel takes more than 2–3 seconds to spin up. Start at 0.0005 and double it: 0.001, 0.002.
Overshoots and oscillates — decrease TBH_GAIN. The flywheel blows past the target and bounces. Halve it: 0.00025.
Good convergence, slight steady-state error — increase TBH_GAIN by 10–20% at a time.
✅
Tuning procedure: set target RPM, start a timer, watch the Brain screen’s motor velocity until it stabilizes. Good TBH converges in under 2 seconds with no visible overshoot. Log the gain value that works in your notebook — it counts as testing data for the Design Award.
ℹ️
The gain varies with gear ratio and flywheel mass. A heavier flywheel needs a smaller gain (more inertia, slower to change speed). A lighter flywheel needs a larger gain. Always retune when the mechanical design changes.
⚙ STEM HighlightMathematics: Convergence, Overshoot & Velocity Control
Take Back Half exploits a key property of asymmetric systems: flywheels accelerate slowly (high inertia) but decelerate quickly (friction + inertia). TBH’s “take back half” operation — averaging current output with previous output whenever the sign of error changes — is a bisection algorithm applied to control output. Each crossing bisects the search interval, converging toward the steady-state output value geometrically (halving the error bound each cycle). The mathematical guarantee: the output converges to the true steady-state value from both sides, eliminating the bang-bang limit cycle.
🎤 Interview line: “TBH uses a bisection algorithm — each time error crosses zero, we halve the output adjustment. This is a classic numerical methods technique for root finding. It guarantees convergence because we approach the equilibrium from alternating sides, narrowing the interval by half each crossing.”
🔬 Check for Understanding
TBH divides the output by 2 each time the error crosses zero. After 5 zero-crossings starting from an initial output error of 0.8, what is the maximum remaining output error?