Author: Elia Cereda

The Crazyflie 2.1 Brushless uses the DSHOT protocol to command its ESCs — a clean, digital, CRC-protected signal that replaced the old analog PWM signals. But until now, that communication was strictly one-way: the firmware sends a throttle command and hopes for the best. There’s no direct feedback from the motors about what they’re actually doing, even if the brushless motors are naturally able to do that by sensing the back-EMF on their windings.

As part of our recent work at IDSIA on nonlinear system identification for the Crazyflie 2.1 Brushless, we developed a new bidirectional DSHOT driver to change this. Each ESC now reports its electrical RPM back over the same signal line used for commands, giving us per-motor RPM telemetry at every control cycle. The new driver was just merged in the firmware and it’s now available for everyone to use (PR #1556).

Plot showing RPM measurements during hover and landing

What is Bidirectional DSHOT?

Standard DSHOT encodes a throttle command as a sequence of bit periods on a digital line: each bit is either a short or a long pulse, with the full frame containing an 11-bit throttle value, a telemetry request flag, and a 4-bit CRC. It’s fast, noise-resistant, and needs no calibration. In other words, a major step up from the analog PWM for the old Crazyflie 2.1 Brushed.

Bidirectional DSHOT extends this with a simple trick. After the flight controller finishes transmitting a command frame, it releases the line (sets the GPIO to input). The ESC responds with a telemetry packet containing its current electrical RPM (eRPM), encoded using a GCR (Group Code Recording) scheme. The whole exchange, i.e., command out, telemetry back, takes roughly 150 µs. To enable bidirectional mode, the STM32 inverts the polarity of the DSHOT line (idle-high instead of idle-low), which the ESC recognises as a request to reply with telemetry.

The eRPM value from the ESC reports electrical rotations. To get the mechanical RPM that actually matters, you divide by half the number of motor poles: RPM = eRPM / (poles / 2). For the motors on the Crazyflie Brushless, this is 12 poles per motor, but the driver can be customized with a fixed constant set at build time.

This protocol is well-established in the FPV and Betaflight ecosystem, where it’s used for RPM filtering in flight controllers. The ESC firmware on the Crazyflie Brushless (BlueJay) already supports it. The missing piece and technical challenge was the firmware on the STM32 side.

Logic analyser captures of the standard and bidirectional DSHOT exchanges. In bidirectional DSHOT the command frame is followed by the GCR-encoded telemetry response.

Implementation

Bidirectional DSHOT turns a transmit-only interface into a half-duplex one: the same physical line must alternate between output (command) and input (telemetry). On the Crazyflie’s STM32, implementing this must account for a few hardware constraints.

DMA Input Capture for receiving telemetry

Rather than polling or bit-banging the telemetry response, the firmware uses DMA Input Capture: it configures the timer to record a timestamp at every edge transition on the DSHOT line, with DMA transferring each timestamp to a buffer without CPU intervention. After the command frame is sent, the code opens a 100 µs receive window (accounting for 30 µs of wait time, around 50 µs for the telemetry packet, plus some headroom). Once the window closes, the buffer contains a sequence of edge timestamps that can be decoded into the GCR telemetry frame at leisure.

This is the same approach used in Betaflight’s bidirectional DSHOT implementation, adapted to the Crazyflie’s timer and DMA configuration.

Hardware conflicts

All four motor DSHOT outputs share the TIM2 timer on the STM32. This was not an issue with traditional DSHOT: the timer is left free-running, and each motor can be controlled independently. With bidirectional DSHOT, the transmit and receive phases require different timer periods (the command and telemetry frames have different bit rates), so the transmit and receive phases can’t overlap between motors.

The solution is a two-phase scheme within each control cycle:

  1. Phase 1: Transmit command frames to motors M1, M3, and M4. After transmission, switch the lines to input and capture their telemetry replies.
  2. Phase 2: Once the receive window closes (detected by a TIM2 interrupt), transmit the command to M2 and capture its reply.

This sequencing means M2’s command is delayed by roughly 100 µs relative to the other motors — the cost of sharing a single timer. In practice, at the Crazyflie’s control rate, this additional latency is negligible.

What you get

With bidirectional DSHOT enabled, the firmware exposes four new log variables:

  • motor.m1_rpm
  • motor.m2_rpm
  • motor.m3_rpm
  • motor.m4_rpm

These can be streamed and recorded through cfclient like any other log variable, at the full control loop rate.

Validation

Desired throttle vs. measured RPMs over a 30s flight.

To verify that the telemetry readings are correct, we compared the reported RPM values against the thrust-stand characterisation performed for the battery compensation work. At a given commanded thrust and battery voltage, the expected RPM can be computed from the motor voltage–thrust polynomial and the motor’s known Kv constant. The onboard RPM readings match this prediction well across the operating range.

During flight, the RPM traces clearly show the motor dynamics that the open-loop command model doesn’t capture: spin-up and spin-down transients, asymmetries between motors, and the RPM dips that correspond to aggressive attitude changes. This is exactly the kind of information that was previously invisible to the firmware.

Applications

Per-motor RPM telemetry opens the door to several applications.

System identification with measured actuator signals

RPM telemetry changes what’s possible for system identification. In previous nano-drone datasets, the “motor input” is the commanded PWM or throttle value — but the actual motor response differs from the command due to ESC dynamics, battery sag, and nonlinear torque curves. Any model trained on commanded inputs cannot disentangle these actuation nonlinearities from the airframe dynamics it’s actually trying to capture.

This is exactly the problem we addressed in Busetto et al. (2025): we released a benchmark for nonlinear system identification based on the Crazyflie 2.1 Brushless. The dataset contains 75k real-world samples across four aggressive flight trajectories, with synchronised 4-dimensional motor RPM inputs and 13-dimensional output measurements (IMU + motion capture). Crucially, the motor inputs are measured RPMs from bidirectional DSHOT — not commanded values. This cleanly separates the actuation subsystem from the airframe dynamics, giving identification algorithms a much better signal to work with.

The benchmark includes multi-horizon prediction metrics for evaluating both one-step and multi-step error propagation, along with baseline models ranging from physics-based to neural network approaches. All data, scripts, and reference implementations are open-source at github.com/idsia-robotics/nanodrone-sysid-benchmark. The firmware feature described in this post directly enabled this data collection.

Closed-loop RPM control

Beyond identification, using RPM telemetry as closed-loop feedback enables new opportunities in control. The battery compensation scheme introduced in PR #1526 solves the voltage-sag problem by adjusting the PWM command based on measured battery voltage and a motor voltage–thrust polynomial. It works well, but it’s fundamentally still open-loop with respect to the motor itself: the firmware corrects for the expected effect of voltage on thrust, without ever checking whether the motor actually reached the intended speed.

With RPM feedback, a different approach becomes possible: close the loop at the motor level. Instead of commanding a PWM duty cycle and compensating for voltage, command a target RPM and let a per-motor controller (or the ESC’s own closed-loop mode) handle the rest. This makes the thrust response inherently invariant to battery voltage, temperature drift, and propeller wear — anything that shifts the relationship between PWM and actual speed.

Broader impact

Having RPM as a standard logged variable on an open-source, commercially available nano-drone lowers the barrier for the entire research community. Anyone with a Crazyflie Brushless can now collect flight dynamics datasets with true actuator measurements, validate sim-to-real transfer (e.g., with Crazyflow), or prototype RPM-aware controllers — without any hardware modifications.

Try It

Bidirectional DSHOT is available today and enabled by default in the main branch of the Crazyflie firmware. To use it:

  • You need a Crazyflie 2.1 Brushless with BlueJay ESCs (the stock configuration).
  • Log the motor.m{1,2,3,4}_rpm variables through cfclient.

It is also used by the supervisor to check that all motors has spun up during arming or if a motor is blocked during flight. We’re looking forward to community feedback. Let us know how it works for you.