Samuel Anes

CMU Formula SAE Electric · Driverless Controls

One-pedal regenerative braking

A speed-dependent accelerator map that lets the driver brake with regen just by lifting off the pedal. I checked it against logs from earlier sessions, then wrote the firmware that ran it on track.

Track-tested · September 2026

Sept 2026 · Pittsburgh, PA

C (STM32 firmware) · Python · NumPy · Matplotlib · asammdf

CMU Formula SAE Electric car 46 on track, driverless
The 26x car on track, running driverless.

Why

Regenerative braking turns speed back into battery charge instead of brake heat, which matters most in endurance, where efficiency is scored. The team also needed regen-heavy running early in the season to measure cell temperatures and motor derating, data that feeds the design of the 2027 car.

The previous design put regen on a steering-wheel paddle. It never worked, and its throw was awkward enough that pulling it all the way meant moving your whole hand. The goal was to move regen onto the accelerator: lift off and the car slows down, the way most electric road cars behave.

What didn’t work

The obvious version, regen whenever the pedal is released, needs a threshold. Replaying logs showed the pedal crossing any threshold hundreds of times a session, and it only behaved with 0.15 s of hysteresis bolted on, which cut predicted regen episodes from 942 to 115 on one log.

Splitting the pedal in half, regen below 50% and drive above, avoids that. But the driver has to press past halfway just to pull away, and the drive side loses half its resolution.

The map

The final map moves the coast point, the pedal position where torque crosses zero, with speed:

z = p(1 + k) − k, k = clamp(avg motor RPM / regen_max_rpm, 0, 1)

p is pedal travel from 0 to 1, and z is the torque request from −1 to 1. Negative z is scaled by a regen gain (0.1 on the car).

At a standstill k is 0, so z = p and the pedal behaves exactly as it always has. As the car speeds up, the coast point climbs toward half the pedal and a regen zone opens beneath it. Three useful properties come for free: regen fades to zero as the car stops, so it can never push the car backwards; full throttle always reaches full torque; and because the map passes straight through zero, pedal noise near the coast point barely changes the torque, so no hysteresis is needed.

One-pedal map: torque request versus pedal position, and coast point versus speed
Left: torque request against pedal position at 70 km/h, with other speeds in grey. Right: how the coast point and full-lift deceleration change with speed. Plotted in km/h with speed_max = 100 km/h and gain 0.10; the firmware uses motor RPM.

Checking it against old logs

Before it went on the car, I replayed logged sessions through the same function in Python. Throttle, brake position, and per-motor RPM go onto a common 100 Hz grid, and average motor RPM is rebuilt the way the firmware computes it. The replay answers one primary question: when the driver actually braked, would regen already have been on? On an autocross run, yes for 99.8% of braking time, and 96.6% on a September test session. In the window below, requested deceleration peaks at 0.28 g, well within what the tires can take.

That coverage number is the trustworthy one. The replay assumes the driver’s foot does what it did under the old pedal map, and under the new one they would press further for the same torque, so how often regen engages is only an upper bound. But a driver about to brake lifts off the throttle either way.

Logged autocross window comparing actual throttle, recomputed one-pedal output, and brake position
Autocross, 95–135 s. Blue is logged throttle, green is the same pedal passed through the map (speed_max = 100 km/h, gain 0.15), dashed grey is the coast point, and peach is brake position. Every brake application in the window already has regen engaged underneath it.

On the car

The map runs in the Endurance gear on the drive control module. It only reinterprets the pedal. The result feeds the existing torque allocator, which splits it across all four motors within each tire’s grip limit and blocks regen when the battery can’t accept charge. Older logs already showed individual motors regenerating, so the hardware path wasn’t new; the change was making regen deliberate.