Test Station Setup

Handle Regenerative Braking on a Test Bench

Learn how to keep regenerative braking from overvolting a motor test bench bus, with ramped deceleration, brake resistors and a bus voltage limit.

JJulien Buteau
advanced10 min readOctober 27, 2026

When a brushless motor slows down faster than friction and drag can stop it, the ESC returns the rotor's kinetic energy to the DC bus. On a battery the energy recharges the pack. On a switching bench supply it has nowhere to go, and the bus voltage rises. Learn why this happens, what it puts at risk, how to fix it on the bench, and how to make the bus peak a measurement with a limit in TofuPilot.

Why Deceleration Pumps Current Back

A spinning permanent-magnet motor is a generator. Its back-EMF is proportional to speed. A modern ESC drives the phases with synchronous rectification (both MOSFETs of each half-bridge are actively switched), so when the commanded speed falls below the actual speed, the back-EMF exceeds the average voltage the ESC applies and current reverses. It flows from the motor, through the ESC, into the bus. That reversed current is what brakes the rotor.

The energy available is the rotor's kinetic energy:

rotor-energy.txt
E = 1/2 x J x w^2w = rpm x 2 x pi / 60Example values: J = 5e-5 kg.m^2 (rotor with propeller), 22,600 rpm  w = 2,367 rad/s  E = 0.5 x 5e-5 x 2,367^2  ->  140 J

Part of that energy is lost in winding resistance, ESC switching and, with a propeller, aerodynamic drag. The rest goes back to the bus. The faster the deceleration, the larger the share that returns, because losses have less time to absorb it:

regen-power.txt
P_average = E / t_decel  140 J over 2.0 s  ->  70 W  140 J over 0.1 s  ->  1,400 W

Why a Bench Supply Lets the Bus Rise

A unidirectional switching supply (SMPS) can source current but cannot sink it. When the ESC pushes current back, the supply's output stage stops conducting and the returned charge accumulates in whatever capacitance sits on the bus: the ESC input capacitors and the supply's output capacitors. The voltage rises until the energy is absorbed:

bus-rise.txt
E_absorbed = 1/2 x C x (V_max^2 - V_0^2)Example value: C = 1,000 uF total on the bus, supply set to 25.2 V  rise to 36.6 V  ->  E = 0.5 x 0.001 x (36.6^2 - 25.2^2) = 0.35 J

Less than half a joule is enough to lift the bus by 11.4 V. On a bench with a 3115-size 1250KV motor, a 4-in-1 BLHeli_32 ESC and a 6S nominal bus, throttle steps without propeller drove the bus to 36.6 V, 45% above the 25.2 V full-charge voltage of a 6S pack. A battery in the same position absorbs the current as charge and the voltage barely moves.

What It Puts at Risk

ElementFailure mode
Bench supplyOver-voltage protection (OVP) trips and shuts the output; the bus collapses mid-test and the ESC browns out
Bench supply output stageRepeated reverse energy stresses output capacitors that were not designed to absorb it
ESCMOSFETs and input capacitors exposed beyond their voltage rating
Other loads on the busFlight controller, BEC, sensors and telemetry receive the over-voltage too
Test dataA run that ends in an OVP trip records a supply event, not a property of the unit

Check the maximum input voltage on the ESC datasheet and the OVP threshold of the supply. The bus peak must stay below both, with margin.

Fix 1: Ramp the Deceleration

The cheapest fix is to slow down more gently. Spreading the same kinetic energy over a longer time keeps the returned power below what the losses absorb, and little reaches the bus.

This requires control over the throttle profile. ArduPilot's MAV_CMD_DO_MOTOR_TEST cannot change throttle while a test runs, so a stepped profile built from it means stop and restart at each level. The stop is an instantaneous deceleration command, which is the worst case for regeneration. Driving DShot directly from a microcontroller lets the test command a true ramp down.

Ramping is a procedure change, not a hardware change. It is the first thing to try, and the only fix that also reduces the current spikes on acceleration.

Fix 2: Add Bulk Capacitance

More capacitance absorbs more energy for the same voltage rise. It works for small energies and fast spikes, and fails for rotor-scale energies:

capacitance.txt
C = 2 x E / (V_max^2 - V_0^2)Absorb 140 J between 25.2 V and 27.0 V:  C = 2 x 140 / (27.0^2 - 25.2^2) = 2.98 F

Three farads of electrolytic capacitance is not a practical bench component. Add low-ESR capacitance close to the ESC to tame switching spikes, and use one of the following fixes for the energy itself.

Fix 3: Brake Chopper with a Resistor

A brake chopper switches a power resistor across the bus when the voltage exceeds a threshold, and releases it below a lower threshold. The resistor turns the returned energy into heat. Size it from the threshold and the peak regenerated current:

brake-resistor.txt
R <= V_on / I_regen_peakExample values: V_on = 26.5 V, V_off = 26.0 V, I_regen_peak = 20 A  R <= 1.33 Ohm  ->  choose 1.2 Ohm  P_peak = V_on^2 / R = 26.5^2 / 1.2  ->  585 WAverage power, upper bound (all energy returned):  140 J per deceleration x 6 decelerations per minute / 60 s  ->  14 W

The resistor needs an average rating above the computed value and a pulse rating for P_peak. Mount it where its surface temperature cannot burn anyone or ignite anything. A TVS diode across the bus is a useful last-resort clamp for fast transients, but it is sized for pulses, not for repeated joules: choose its clamping voltage below the ESC rating and above the full-charge voltage, and do not rely on it as the energy sink.

Fix 4: Bidirectional Supply or Battery in Parallel

A bidirectional (source-sink, or regenerative) DC supply absorbs reverse current at a set voltage, either into internal loads or back into the mains. Check that its sink rating covers the peak regenerated power, not only the average.

A battery in parallel with the bench supply also absorbs the returned energy as charge. The supply covers the average load, the pack covers peaks and regeneration. Two conditions apply:

  • The pack must stay below full charge, otherwise the absorbed energy overcharges the cells. Hold the supply setpoint below the pack's full-charge voltage.
  • The supply must tolerate a back-fed output. Read its manual, and add a series diode if it does not.
FixCostHandles energyHandles spikesChanges procedure
Ramped decelerationNoneYes, by avoiding itYesYes
Bulk capacitanceLowSmall energies onlyYesNo
Brake chopper and resistorMediumYesPartlyNo
Bidirectional supplyHighYesDepends on loop speedNo
Battery in parallelMediumYesYesNo

Measure the Bus Peak with a Limit

Regeneration is invisible to a test that only records steady values. Make the bus peak a numeric measurement with a limit, and run the deceleration with several ramp times to find the shortest one that stays below it. The procedure below declares two phases that call the same function with different ramp durations, run one after the other because they share one motor:

procedure.yaml
50 lines
name: Regeneration Checkversion: 0.1.0config:  DSHOT_PORT:    required: true  MONITOR_PORT:    required: trueexecution:  workers: 1plugs:  - name: DShot Driver    key: motor    python: plugs.dshot_driver:DshotDriver    with:      port: "{config.DSHOT_PORT}"      motor_index: 0  - name: Bus Monitor    key: bus_monitor    python: plugs.bus_monitor:BusMonitor    with:      port: "{config.MONITOR_PORT}"main:  - name: Decel Ramp 2 s    key: decel_2s    python: phases.decel:decel_test    with:      hold_throttle: 70.0      ramp_s: 2.0    measurements:      - name: Bus Voltage Max        unit: V        validators:          - operator: "<="            expected_value: 26.0  - name: Decel Ramp 0.5 s    key: decel_0_5s    python: phases.decel:decel_test    with:      hold_throttle: 70.0      ramp_s: 0.5    measurements:      - name: Bus Voltage Max        unit: V        validators:          - operator: "<="            expected_value: 26.0

The 26.0 V limit is an example: 6S full charge plus a small margin, below the supply OVP and the ESC rating. Set yours from the same three values on your bench.

Supply readback over SCPI averages over milliseconds and misses the peak. The bus monitor plug talks to a microcontroller that samples the bus with its ADC and holds the maximum. The protocol is an example; adapt it to your firmware:

plugs/bus_monitor.py
23 lines
import serialclass BusMonitor:    def __init__(self, port):        self._ser = serial.Serial(port, 115200, timeout=1.0)    def arm_peak(self):        # Example protocol: "ARM" resets the peak hold, firmware answers "OK"        self._ser.write(b"ARM")        self._ser.readline()    def read_peak(self):        # Example protocol: "PEAK?" returns the highest bus voltage since ARM        self._ser.write(b"PEAK?")        return float(self._ser.readline().decode().strip())    def __del__(self):        self._ser.close()

The phase ramps up, holds, arms the peak hold, ramps down over ramp_s, and records the peak. The DshotDriver plug is the same one used in the guide on sizing the power source for a motor test bench:

phases/decel.py
25 lines
import timeSTEP_S = 0.02def ramp(motor, start, end, duration):    steps = max(1, int(duration / STEP_S))    for i in range(1, steps + 1):        motor.set_throttle(start + (end - start) * i / steps)        time.sleep(duration / steps)def decel_test(measurements, log, motor, bus_monitor, hold_throttle, ramp_s):    try:        ramp(motor, 0.0, hold_throttle, 2.0)        time.sleep(3.0)  # let the speed settle        bus_monitor.arm_peak()        ramp(motor, hold_throttle, 0.0, ramp_s)        time.sleep(0.5)  # catch the tail of the transient    finally:        motor.set_throttle(0.0)    peak = bus_monitor.read_peak()    measurements.bus_voltage_max = peak    log.info(f"Deceleration over {ramp_s} s: bus peak {peak:.2f} V")

Each run records the bus peak per ramp time against the unit serial number. On the dashboard, the control chart of bus_voltage_max shows whether a hardware fix (a brake resistor, a pack in parallel) moved the peak, and whether it stays stable across units and stations. Once a ramp time is validated, keep that phase in production and drop the characterization ones.

Common Pitfalls

Assuming a bench supply behaves like a battery. A unidirectional SMPS cannot sink current. The bus rises on every deceleration.

Raising the supply OVP to stop the trips. The over-voltage still reaches the ESC and every other load on the bus. Fix the energy path instead.

Relying on capacitance for rotor energy. Joules of kinetic energy need farads. Use capacitance for spikes, a resistor or a sink for energy.

Using a TVS diode as the brake resistor. It is a pulse clamp. Repeated decelerations overheat it.

Paralleling a full pack. The absorbed energy overcharges the cells. Keep the pack below full charge.

Measuring the bus with slow readback. SCPI readback averages away the peak. Use a peak-hold ADC or an oscilloscope.

Stopping and restarting the motor test to change throttle. Each stop is a full-speed deceleration step. Ramp instead.

Key Points

  • Deceleration turns the motor into a generator; the ESC returns current to the bus.
  • A unidirectional switching supply cannot sink that current, so the bus voltage rises; 36.6 V was measured on a 6S nominal bus with throttle steps and no propeller.
  • Ramp deceleration first; it costs nothing and reduces both regeneration and acceleration spikes.
  • For the remaining energy, use a brake chopper and resistor, a bidirectional supply, or a battery in parallel kept below full charge.
  • Record the bus peak as a measurement with a limit below the supply OVP and the ESC rating.

More Guides

Put this guide into practice