VOXL ESC Calibration
Calibrate ModalAI ESCs for closed-loop RPM control - safely
Table of contents
- What Calibration Is
- Safety First
- Power Source: Battery, Not Bench Supply
- Prerequisites
- Procedure
- Reading the Plots
- Applying the Results
- RPM Controller Parameters
- Troubleshooting
What Calibration Is
Unlike hobby ESCs, ModalAI ESCs normally run closed-loop RPM control: PX4 commands an RPM, and the ESC holds it regardless of battery sag. To do this well, the ESC needs a feed-forward model of your specific motor + propeller: a quadratic fit of motor voltage versus RPM, stored in the ESC params as pwm_vs_rpm_curve_a0, a1, and a2. Calibration is the procedure that measures that curve.
When you need it:
- You are using a motor/propeller combination that ModalAI does not ship params for, or
- You changed battery cell count (e.g. 4S to 6S) for an existing combination.
When you do NOT need it:
- Stock ModalAI drones and dev kits — correct params are already loaded and available on GitLab.
- Replacing a motor or prop with an identical part. Calibration characterizes the motor/prop type, not the individual unit.
- Installing a new ESC board into an existing vehicle — just load the vehicle’s existing calibrated params onto it.
Safety First
Calibration is the highest-risk ESC operation. It runs the motor in open-loop power mode with fewer safety checks than normal RPM-mode operation, with propellers attached, at up to high power. Bolt the vehicle down rigidly, keep everyone clear of the propellers, and wear safety glasses.
- Propellers must be attached. Calibrating without props produces plausible-looking but wrong coefficients — the motor must be under real load.
- The frame or test stand must be rigidly attached to a solid structure.
- Start with a power-limited pass (
--pwm-max 50) before going to full power. - The tools abort automatically if ESC temperature exceeds 100°C; keep an eye on the live temperature output. Ideal is under 60-70°C, 80°C is acceptable, 90°C is worrying.
Power Source: Battery, Not Bench Supply
Use a battery of the nominal voltage you will fly with, not a bench power supply. Two reasons:
- Current limit. A small supply (for example a 3A wall adapter) hits its current limit as the motor loads up and its voltage sags — the ESC can’t draw the required power, and the resulting calibration is useless. If you see the voltage dropping in the tool output as power steps up, your supply is the problem.
- Regenerative braking. ESCs push energy back into the supply when decelerating. A battery absorbs this; most bench supplies cannot sink current, so the voltage spikes into the ESC’s protection diode and can eventually damage the board. See the regenerative braking explainer.
If you must use a supply: it needs an adequate current rating for your motors, set the brake_to_stop ESC param to 0 (coast to stop), avoid aggressive high-to-low RPM transitions, and step --pwm-max up gradually (40-50 first, then 60, 70, 80, 90 across runs). Calibration itself steps power upward and ends with a coast-down, so it does not produce large regen events — but the current-limit problem still applies.
Prerequisites
Before calibrating, load a params file with correct basic values for your setup — start from a generic board file and set:
num_cycles_per_rev— the motor’s pole pairs (not magnet count; typically 6 or 7 for 5-inch class motors). If this is wrong, every RPM number is wrong.vbat_nominal_mv— nominal battery voltage (e.g. 4S = 15200, 6S = 22200).motor_kv— the motor’s true kV rating (used by the spin-up procedure).spinup_type=1(sinusoidal spin-up — the reliable, recommended mode).
Then confirm basics with a low-power spin test (props on, 10% power) and check rotation direction before calibrating.
On VOXL 2, stop PX4 first and use the installed tools:
systemctl stop voxl-px4
cd /usr/share/modalai/voxl-esc-tools
The SDK-installed tools may lag the latest; for newest fixes use the voxl-esc repo dev branch. Alternatively, connect the ESC directly to a Linux PC with a USB-serial adapter — you get plots instantly and it removes VOXL 2 from the equation.
Procedure
Calibrate one ESC channel (the tool spins one motor at a time by design):
# power-limited first pass
./voxl-esc-calibrate.py --id 0 --pwm-min 10 --pwm-max 50
# full-range pass once the limited pass looks good
./voxl-esc-calibrate.py --id 0
The tool steps open-loop power from --pwm-min to --pwm-max, holds each step half a second, and fits the settled samples. It prints the three coefficients and writes calibration_results.html into the directory you ran it from. Viewing the plot requires internet access (it fetches the plotting library) — on VOXL 2 pull the file to your PC with adb pull and open it there.
On the FPV ESC (M0138), calibrate on ID 2 if you want current shown in the plots — it is the only channel that measures (total board) current. On the Mini ESC (M0129) that channel is ID 3. Current is only a sanity check; it is not used in the calibration math.
Reading the Plots
Judge the calibration by the plots, not the coefficients — a0/a1/a2 are just a curve fit and can look reasonable even when the underlying data is bad.
- Motor voltage vs RPM (the fit itself): should be a smooth shape with a slight quadratic upward trend. An upward curl at the high end means the motor isn’t running optimally — try raising
timing_advancefrom 0 to 20 and re-run. - Current vs time: roughly quadratic as power steps up. Sudden jumps, or current flatlining while voltage sags, mean supply problems or desync.
- RPM vs time: the highlighted samples are the settled ones actually used for the fit. Look for sudden jumps up or down — a jump to a bogus high RPM with low current is a desync signature.
Good reference plots are in the repo’s calibration guide.
Applying the Results
- Copy
pwm_vs_rpm_curve_a0/a1/a2into your params XML with at least 6 decimal places — truncated coefficients bend the curve. - Measure
min_rpm: spin at--power 10and read the RPM. Measuremax_rpm: spin at--power 100 --ramp-time 3.0 --timeout 3.5. - Upload and verify:
./voxl-esc-upload-params.py --params-file my_vehicle.xml
./voxl-esc-verify-params.py
If you calibrated multiple motors and want a single curve: do not average the coefficients — that produces a wrong curve. Refit one polynomial over the pooled data points instead. In practice CW and CCW props produce slightly different curves and either (or the pooled fit) works; the ESC’s PI controller absorbs small deviations.
RPM Controller Parameters
Calibration produces only the feed-forward curve. The closed-loop RPM controller in the ESC firmware also depends on the feedback parameters in the same TuneParams block, and the untuned board starting files (for example esc_params_prod_m0138.xml) ship with all of them set to 0, commented “RPM controller params are disabled since motor is not tuned”. Copying only a0/a1/a2 into such a file leaves the controller disabled.
How the controller uses these parameters:
- Feed-forward: for the desired RPM, the calibration curve gives the motor voltage (duty cycle) to apply. Battery voltage compensation keeps this accurate as the pack voltage changes.
- Feedback: proportional and integral terms with gains
kpandki, and error capsmax_kpeandmax_kie. max_rpm_delta: caps the target RPM relative to the current RPM. The controller computesrpm_error = rpm_desired - rpm_current, limits it tomax_rpm_delta, and usesrpm_current + rpm_erroras the operating point for the feed-forward lookup. The cap prevents very aggressive drive when a motor is spinning far below its target (for example when it is fouled), which could burn out or desync the motor.
Because the capped error also feeds the feed-forward lookup, max_rpm_delta set to 0 forces the error to zero and the motor never leaves minimum RPM, even with a valid calibration curve. kp and ki at 0 are acceptable as a starting point: the ESC then runs on feed-forward plus voltage compensation alone and responds like a conventional ESC. A non-zero max_rpm_delta is required in every case.
Symptoms in PX4 when max_rpm_delta is 0: motors spin up on arming but stay near arming speed regardless of throttle; the PX4 log shows every motor capped at a low RPM (about 2200 RPM in a reported case); the QGroundControl actuator sliders behave the same way in RPM mode, but work once ESC Command Type is switched to PWM, because open-loop power commands bypass the RPM controller.
Inspect the parameters currently loaded on the ESC (props off, PX4 stopped):
systemctl stop voxl-px4
cd /usr/share/modalai/voxl-esc-tools
./voxl-esc-verify-params.py --save-params 1
This writes esc0_params.xml through esc3_params.xml into the current directory. Check kp, ki, max_kpe, max_kie and max_rpm_delta in TuneParams.
Fix: set the controller parameters in your params XML and upload again. ModalAI’s tuned files in voxl-esc-params use kp 50 to 100, ki 20 to 30, max_kpe 100 to 300, max_kie 30 to 100 and max_rpm_delta 5000 to 6000. For a new motor and propeller combination, start with kp and ki at 0 and a non-zero max_rpm_delta, confirm RPM tracking with ./voxl-esc-spin.py --id <id> --rpm <rpm> --ramp-time 2.0 (props on, vehicle secured), then raise max_rpm_delta and add kp and ki while checking that the response stays stable.
./voxl-esc-upload-params.py --params-file my_vehicle.xml
./voxl-esc-verify-params.py
Troubleshooting
| Symptom | Likely cause |
|---|---|
| Motors hold minimum/arming RPM in PX4 regardless of throttle; PWM command type works | max_rpm_delta is 0 in the ESC params, see RPM Controller Parameters |
| Voltage sags as power steps up | Power supply current-limited — use a battery |
| Motor won’t even attempt to spin | Supply voltage dipping below the MOSFET driver’s ~4.5V lockout — weak supply or bad wiring |
| Motor squeals, RPM jumps to a bogus value, current low | Desync — use sinusoidal spin-up; for motors under ~800kV see the low-kV tuning guide |
| Tool aborts with overheat error | ESC hit 100°C — let it cool, add airflow, reduce --pwm-max |
| Coefficients look fine but flight is bad | Calibrated without props, wrong num_cycles_per_rev, or prop too large for the motor |
| RPM never settles between steps | Prop too large for the motor |
More depth: the repo’s calibration.md and the ESC FAQ.