Motor current is the measurement that catches a dragging bearing, a shorted winding or a bad ESC channel on a brushless motor bench, but only if the number is right. Learn how to choose a current sensing method, size a shunt for an INA228 monitor, set the sampling so that the mean and the peaks mean something, and record both with limits in a TofuPilot procedure.
Four Ways to Measure Motor Current
A drone motor bench usually has four current readings available. They are not equivalent.
| Method | Typical resolution | Bandwidth | Calibrated | Per motor | Use it for |
|---|---|---|---|---|---|
| Shunt + INA228 (I2C, 20-bit) | 0.3 to 0.7 mA with a 0.5 to 1 mOhm shunt | Set by conversion time (50 µs to 4.12 ms) | Yes, by the shunt tolerance and the INA228 gain error | Yes, one shunt per DC input | Pass/fail limits |
| Hall-effect current sensor | Tens of mA, limited by offset and noise | Tens to hundreds of kHz | Offset drifts with temperature and remanence | Yes | Galvanic isolation, fast transients |
| ESC telemetry current | 0.01 A field, real accuracy unknown | One frame per motor per polling cycle | No | Yes | Cross-check, field diagnostics |
| Bench supply readback | 1 to 10 mA display | A few readings per second | Yes, but averaged | No, total of all loads | Sanity check of the total |
A shunt read by a precision monitor is the only option that is calibrated, fast enough and attributable to one motor. A Hall sensor wins when you need isolation or the waveform of a sub-millisecond transient, at the cost of an offset you must zero before every run. The ESC telemetry and the supply readback are useful context, not measurements to put limits on.
On a 4-in-1 ESC there is one DC input for four motors. A shunt on that input measures the sum. To attribute current to one motor, run one motor at a time, or put the shunt between the supply and a single-channel ESC. The phase currents between ESC and motor are AC switching waveforms and are not where a DC shunt goes.
Know the Current Range Before Choosing Parts
The range sets the shunt value, its power rating and the monitor range. Example values measured on one 3115-size, 1250 KV motor (12N14P) on a 6S bus with a BLHeli_32 ESC:
| Throttle | Speed | Current without propeller |
|---|---|---|
| 3 % | idle | about 0.1 A |
| 25 % | 8,350 rpm | 0.65 A |
| 70 % | 22,600 rpm | 2.65 A |
| 100 % | 31,000 rpm | 3.5 to 4.5 A |
With a propeller, the same motor draws tens of amps. For a typical propeller on 6S, 60 to 80 A at full throttle is a reasonable estimate, not a measured value. Size for the propeller case if the bench will ever run one, and check that the resolution still covers the 0.1 A idle case.
Size the Shunt
Three numbers decide the shunt: the full-scale voltage of the monitor, the power dissipated in the shunt, and the resolution at low current.
The INA228 measures the differential shunt voltage with a 20-bit delta-sigma ADC. In its default range (ADCRANGE = 0) the full scale is ±163.84 mV and the LSB is 312.5 nV. The bus voltage input accepts up to 85 V, so a 6S pack (25.2 V fully charged) is well inside it, high side or low side.
V_shunt = I x R must stay below 163.84 mV at I_maxP_shunt = I^2 x R shunt power rating >= 2 x P_shuntI_lsb = 312.5 nV / R current resolutionV_burden = I x R voltage the motor no longer seesApplied to an 80 A peak and a 100 A ESC rating:
| Shunt | V at 80 A | P at 80 A | P at 100 A | Resolution | Max current before clipping |
|---|---|---|---|---|---|
| 0.5 mOhm | 40 mV | 3.2 W | 5 W | 0.625 mA | 327 A |
| 1 mOhm | 80 mV | 6.4 W | 10 W | 0.3125 mA | 163 A |
| 2 mOhm | 160 mV | 12.8 W | 20 W | 0.156 mA | 81.9 A |
The 2 mOhm shunt clips at the first transient above 82 A and dissipates more heat than a small shunt can take. The 0.5 mOhm shunt keeps dissipation low, still resolves 0.1 A idle current to better than 1 %, and costs 40 mV of burden at 80 A, which is 0.18 % of a 22.2 V nominal bus. For a bench that never runs a propeller, a 5 mOhm shunt gives 25 mV at 5 A for 0.125 W.
Three details decide whether the shunt value on the label is the value you get:
- Use a four-terminal shunt and Kelvin sense lines. At 0.5 mOhm, 0.1 mOhm of solder or screw contact resistance in the sense path is a 20 % error. The sense lines go to the dedicated sense terminals, never to the current lugs.
- Mind the temperature coefficient. A shunt that heats from 25 °C to 85 °C with a TCR of 50 ppm/°C changes by 0.3 %. Choose a low-TCR alloy and mount the shunt where it can shed heat.
- Route the sense pair together. Twist it and keep it away from the motor phase leads, which carry the ESC switching edges.
Choose the Sampling Rate, Averaging and What a Peak Means
The current drawn by an ESC is not a DC value. It carries ripple at the ESC PWM frequency (tens of kHz), and transients every time the throttle changes, because the motor needs extra torque to accelerate its rotor and propeller. Three settings of the INA228 decide what you see:
- Conversion time (VSHCT, 50 µs to 4.12 ms per conversion). The delta-sigma ADC integrates over this window, so PWM ripple averages out inside one conversion.
- Averaging count (AVG, 1 to 1024 conversions). The chip averages internally before updating the result register.
- Read rate. Polling over I2C from Python reaches a few hundred readings per second at best, with jitter from the operating system.
| Goal | VSHCT | AVG | Update period (shunt and bus) | What you get |
|---|---|---|---|---|
| Mean current at steady throttle | 540 µs | 16 | about 17 ms | Low noise, ripple and short spikes averaged out |
| Visible acceleration transients | 150 µs | 1 | about 0.3 ms, limited by the read rate | Noisier samples, peaks of tens of ms are captured |
| Sub-millisecond events | Not the INA228 | Use a Hall probe on an oscilloscope |
The update period is VSHCT x AVG for each enabled channel added together: 2 x 540 µs x 16 = 17.3 ms with shunt and bus both converted. A peak recorded with this setting is the highest 17 ms average, not the instantaneous peak. That is the correct quantity for a thermal or supply-sizing limit, and the wrong one for a MOSFET stress limit. State which one your limit is in the measurement description.
Do not poll faster than the register updates. Reading the same value twice does not add information, and polling slower than the update rate with averaging off skips data between reads, which can make a ripple look like a spike or hide a real one.
Step 1: Write the INA228 Plug
The plug owns the I2C bus and returns plain numbers and lists. This example uses smbus2 on a Linux host with an I2C bus (a single-board computer, or a USB-to-I2C adapter exposed as /dev/i2c-N):
smbus2>=0.4plugs/ina228.py60 lines
import timefrom smbus2 import SMBusREG_CONFIG = 0x00REG_ADC_CONFIG = 0x01REG_VSHUNT = 0x04REG_VBUS = 0x05VSHUNT_LSB = 312.5e-9 # V, ADCRANGE = 0 (+/-163.84 mV)VBUS_LSB = 195.3125e-6 # V# Conversion time codes for VBUSCT / VSHCT / VTCTCT_CODES = {50: 0, 84: 1, 150: 2, 280: 3, 540: 4, 1052: 5, 2074: 6, 4120: 7}AVG_CODES = {1: 0, 4: 1, 16: 2, 64: 3, 128: 4, 256: 5, 512: 6, 1024: 7}MODE_CONT_SHUNT_BUS = 0xBclass INA228: def __init__(self, bus, address, shunt_ohm): self.address = address self.shunt_ohm = shunt_ohm self.i2c = SMBus(bus) self.i2c.write_i2c_block_data(self.address, REG_CONFIG, [0x80, 0x00]) # reset time.sleep(0.01) self.configure(540, 16) def __del__(self): if hasattr(self, "i2c"): self.i2c.close() def configure(self, conversion_us, averages): ct = CT_CODES[conversion_us] value = (MODE_CONT_SHUNT_BUS << 12) | (ct << 9) | (ct << 6) | (ct << 3) | AVG_CODES[averages] self.i2c.write_i2c_block_data(self.address, REG_ADC_CONFIG, [value >> 8, value & 0xFF]) # Update period of one shunt + bus result, used to pace sampling self.period_s = 2 * conversion_us * 1e-6 * averages def _read20(self, register, signed): b = self.i2c.read_i2c_block_data(self.address, register, 3) raw = ((b[0] << 16) | (b[1] << 8) | b[2]) >> 4 if signed and raw & 0x80000: raw -= 1 << 20 return raw def read_current(self): return self._read20(REG_VSHUNT, True) * VSHUNT_LSB / self.shunt_ohm def read_bus_voltage(self): return self._read20(REG_VBUS, False) * VBUS_LSB def sample_current(self, duration_s): """Return [times_s, currents_a] sampled at the register update rate.""" times, currents = [], [] t0 = time.monotonic() while (now := time.monotonic() - t0) < duration_s: times.append(round(now, 4)) currents.append(self.read_current()) time.sleep(self.period_s) return [times, currents]The current is computed from the shunt voltage register and the shunt value, which keeps the calibration in one visible number. The INA228 can also compute current internally with its SHUNT_CAL register. Both give the same result; the direct form is easier to audit.
Declare it with its bus, address and shunt value in with:, so a different station or a replaced shunt is a configuration change, not a code change:
procedure.yaml21 lines
name: Motor Current Checkversion: 0.1.0execution: workers: 1plugs: - name: Current Monitor key: current_monitor python: plugs.ina228:INA228 scope: station with: bus: 1 address: 0x40 shunt_ohm: 0.0005 - name: Motor Driver key: motor python: plugs.motor:MotorDriver scope: station with: port: /dev/ttyACM0workers: 1 matters on a motor bench. TofuPilot runs independent phases in parallel by default, and two phases driving the same motor or reading the same I2C bus at once give meaningless results. The motor plug is whatever sets the throttle on your bench, for example a microcontroller generating DShot over USB serial.
Step 2: Record Mean and Peak Current With Limits
Record the samples as a multi-dimensional measurement, so the dashboard shows the trace, and put the limits on aggregations of that trace:
procedure.yaml43 lines
main: - name: Current at 25 % Throttle key: current_25 python: phases.current:hold_and_measure timeout: 30s with: throttle_pct: 25 settle_s: 2.0 measure_s: 3.0 measurements: - name: Motor Current 25 % key: motor_current description: INA228, VSHCT 540 us, AVG 16. Peak is the highest 17 ms average. x_axis: legend: Time unit: s y_axis: - legend: Current key: current unit: A aggregations: - type: mean validators: - operator: ">=" expected_value: 0.5 - operator: "<=" expected_value: 0.8 - type: max validators: - operator: "<=" expected_value: 1.2 - name: Bus Voltage key: bus_voltage unit: V validators: - operator: ">=" expected_value: 21.0 - operator: "<=" expected_value: 25.5teardown: - name: Stop Motor python: phases.current:stop_motorThe 0.5 to 0.8 A window brackets the 0.65 A measured on one motor without propeller. These are example values: set yours from a set of known-good motors, then tighten them with the Cpk the dashboard computes for the measurement.
phases/current.py22 lines
import timedef hold_and_measure(motor, current_monitor, measurements, log, throttle_pct, settle_s, measure_s): motor.ramp_to(throttle_pct) # Let the acceleration transient pass before measuring steady current time.sleep(settle_s) times, currents = current_monitor.sample_current(measure_s) measurements.motor_current.x_axis = times measurements.motor_current.y_axis.current = currents aggs = measurements.motor_current.y_axis.current.aggregations aggs.mean = sum(currents) / len(currents) aggs.max = max(currents) measurements.bus_voltage = current_monitor.read_bus_voltage() log.info(f"{len(currents)} samples, mean {aggs.mean:.3f} A, max {aggs.max:.3f} A")def stop_motor(motor): motor.ramp_to(0)The teardown phase runs even when a limit fails or the phase times out, so the motor is always brought back to zero. It is a convenience, not a safety function: the bench still needs a hardwired emergency stop that removes power.
Every run then stores the trace, the mean, the max and the bus voltage against the motor serial number. On the dashboard, a mean current that creeps up across a production lot points at bearings or magnets before the limit is crossed.
Why ESC Telemetry Current Is Not a Measurement
BLHeli_32 ESCs report current in their telemetry frame with a 0.01 A field. The field resolution says nothing about accuracy:
- The ESC sensing is not calibrated per unit. A 4-in-1 ESC measures current with its own low-value shunt or the MOSFET drop, scaled by a firmware constant. Two ESCs of the same model read differently.
- The offset dominates at low current. Without a propeller the motor draws 0.1 to 4.5 A, which is the bottom of a 100 A sensor range. Readings of 0.00 A at idle are common.
- The update rate is low. Telemetry is requested one motor at a time, so each motor gets a fresh frame only once per polling cycle.
Record the ESC current as its own measurement without tight limits, next to the INA228 value. A large disagreement between the two flags a wiring or ESC problem; it does not decide pass or fail.
Common Pitfalls
Connecting the sense lines to the current lugs. Contact resistance becomes part of the shunt. Use the Kelvin terminals.
Sizing the shunt for the no-propeller bench, then fitting a propeller. A 10 mOhm shunt that read 5 A cleanly clips at 16 A and dissipates 64 W at 80 A.
Leaving the phases in parallel. Two phases commanding the motor at once ruin both readings. Set workers: 1 or chain the phases with depends_on.
Measuring during the acceleration transient. A mean that includes the spin-up is not a steady-state current. Settle first.
Calling a 17 ms average a peak current without saying so. Write the averaging window in the measurement description.
Putting limits on ESC telemetry current. It is uncalibrated and coarse at low current. Use it as a cross-check.
Key Points
- A Kelvin-connected shunt read by an INA228 is the reference for motor current; Hall sensors, ESC telemetry and supply readback are secondary.
- Size the shunt from V = I x R below 163.84 mV, P = I^2 x R with a 2x power margin, and resolution = 312.5 nV / R.
- Conversion time and averaging decide whether you record a mean or a peak, and what "peak" means.
- Record the trace as a multi-dimensional measurement with mean and max aggregations carrying the limits.
- Run motor phases one at a time and stop the motor in teardown.
For the wider picture of motor and actuator test benches, see Motor and Actuator Testing with TofuPilot.
