-
Notifications
You must be signed in to change notification settings - Fork 0
DShot
English · Deutsch
What the coprocessor puts on an output pin, and what comes back. Four drivers: servo PWM (pulse-width modulation), PPM (pulse-position modulation), DShot, and bidirectional DShot.
The rules every output obeys — travel, role, rest, slew, clamping, arming and
the silence timeout — are in shared/outputs/ and are the same for all four.
This page is about the wire.
More than one coprocessor will exist. The one the bring-up runs on is a Pico-form-factor module wired by hand; a later board carries its outputs on pins that are soldered rather than chosen. They do not have the same pins, and a pin index on one means a different pin -- or no pin -- on the other.
The coprocessor therefore says which board it is, in the hardware register of the identity page, and the panel offers that board's pins and no other. Changing board clears any selection, because a pin index on one board means a different pin on another.
A board the panel has a catalogue for uses that catalogue. It has been read by somebody, it names the exact signal holding each reserved pin, and it cannot change under a running bench.
A board the panel has never heard of describes its own pins, on the catalogue page: which GPIOs it brings out, the pad number printed beside each, and what holds the ones an output may not have. So a coprocessor newer than the panel is usable rather than blank.
A board that also serves the shape page says where its pads are -- the outline, the pitch, and which corner pad 1 sits at -- so the panel can draw it rather than only list it. Only a picture needs that: a board that does not answer is used from its catalogue and simply is not drawn. A picture drawn from a guessed shape would point at the wrong pad with the same confidence as the right one, which on a screen whose job is "find this pad on the board in front of you" is the failure worth avoiding.
What a board cannot do is make one of its pins safe. The coprocessor reserves its own set at its own end whatever the catalogue page says, so a catalogue that is wrong costs a pin rather than the safety line. A board that neither describes itself nor is known offers nothing: guessing a pin map is how an output ends up on the safety line.
A board whose outputs are soldered is shown rather than offered. The coprocessor configures itself and the panel displays what it reads back without letting it be edited, because a screen that re-bound pins which are not on connectors would be offering a choice the board cannot honour.
On a board that is wired by hand, no output pin is fixed in the firmware. A slot on the OUTPUTS page carries its pin number, so the pin is a setting rather than a build.
The coprocessor refuses a pin it must not drive: GP3 (the safety heartbeat), GP8 to GP12 (the CAN (Controller Area Network) controller's SPI (Serial Peripheral Interface) and interrupt), and any number above the last GPIO (general-purpose input/output) the part has — 29 on the RP2350A the bring-up module carries, 47 on the RP2350B the final board needs. A refused slot is left unbound. The page still reads back what was asked for, and no register on it says whether a slot is bound, so an unbound slot reads back exactly like a driving one. What reaches the operator is a lead that does not move.
A PIO (programmable input/output) block addresses 32 pins from a base of 0 or 16, fixed while the block holds a program. Which block can reach a given pin is therefore not known until the pin arrives, and the block is chosen when the slot is bound. A pin no free block can reach is refused like any other.
Hardware PWM, not PIO: the part has 12 slices and 24 channels, and a servo pulse is exactly what a slice does. The counter runs at 1 MHz, so a pulse is a count of microseconds and the frame period is 1,000,000 divided by the rate.
| Frame rate | 40 to 400 Hz |
| Pulse | 500 to 2500 µs, refused outside |
| Resolution | 1 µs |
A slice is two channels sharing one counter, so two pins on the same slice run at the same frame rate. A second binding that asks for a different rate on a slice already in use is refused rather than retimed, because retiming would move an output that nobody touched.
GPIO numbers fold onto the 12 slices. GP0 to GP31 take slice (pin / 2) modulo 8; GP32 to GP47 take slice 8 + (pin / 2) modulo 4; the channel is the low bit of the pin number in both ranges. Two pins 16 apart below GP32 are therefore the same channel of the same slice, and above GP32 the distance is 8. A channel is one compare register and a compare register is one pulse width, so the second pin of such a pair is refused rather than bound: binding it would mux it onto the first pin's pulse width, and one lead would follow the other with nothing on any screen to say so.
Six of those pairs are both free on the header the coprocessor offers: GP0 and GP16, GP1 and GP17, GP2 and GP18, GP4 and GP20, GP5 and GP21, GP6 and GP22. Either pin of a pair may be used; the two together may not.
One pin, up to eight channels. A frame is a run of 300 µs marks; the time from the start of one mark to the start of the next is one channel's pulse width. After the last channel comes a terminating mark and then the sync gap, and that gap is what tells a receiver where the next frame starts.
Channels and frame rate are not independent. Eight channels at 2000 µs already spend 16 ms of a 22.5 ms frame. The bench refuses a frame whose sync gap would fall below 3000 µs, which is the shortest gap no receiver can read as a channel: the channel ceiling is 2500 µs.
The shortest frame that carries n channels whatever they are commanded to is
n × 2500 µs + 300 µs + 3000 µs
which is 23.3 ms for eight channels — so eight channels at 50 Hz is refused and eight at 40 Hz is not. The refusal happens when the slot is configured, not when the sticks reach the end of their travel.
Polarity is a pad inversion, not a different program.
The frame plays from a pair of DMA (direct memory access) channels that retrigger each other, so the processor is not in the timing path. The frame buffer is rewritten in place while it is being played: a channel updated mid-frame takes effect on the next frame, and the rest of that frame carries the previous values.
Sixteen bits: eleven of value, one asking for telemetry on the separate serial wire, and four of checksum. Most significant bit first. There is no start bit and no idle pattern — the bit period is the whole of the synchronisation, so both ends agree on the rate by configuration.
| Bit | High for |
|---|---|
| 0 | 37.5% of the bit period |
| 1 | 75% |
The state machine runs at eight times the bit rate, because eight is the smallest number of cycles that makes both those fractions whole. DShot600 is therefore 4.8 MHz.
The value is not a throttle across its whole range:
| Value | Meaning |
|---|---|
| 0 | motor stop |
| 1 to 47 | commands: beeps, direction, 3D mode, save settings, extended telemetry |
| 48 to 2047 | throttle |
A command has to be repeated ten times before an ESC (electronic speed controller) acts on it; sent once it does nothing, which looks exactly like a driver that is not working.
The bench maps travel so that a command of zero is motor stop and everything above it lands in 48 to 2047. A driver that mapped travel straight onto 0 to 2047 would send beeps and direction changes on its way up from idle.
Frames go out at 1 kHz. That rate is not on the wire: the OUTPUTS page's rate field is a bit rate for DShot rather than a frame rate, so the bench chooses it. 1 kHz is twenty times the panel's poll rate and well inside every ESC's own signal timeout.
Nothing is sent while the bench is not driving. No frames means no edges, and an ESC stops on silence after its own timeout. Sending explicit zeros instead would keep it armed and waiting, which is not what a disarmed bench should look like from the ESC's side.
The same frame with the line inverted and the checksum complemented, so an ESC set up for one protocol ignores the other rather than acting on it. It is a separate driver number on the OUTPUTS page, not a flag: the two are different wires, not one wire with a setting.
After each frame the coprocessor releases the line and the ESC answers on it, about 30 µs later, with 21 bits at five quarters of the DShot rate. The turnaround happens inside the PIO block — the transmitter releases the pin and raises a flag, and the receiver starts on that flag — because 30 µs is not a deadline a loop can meet.
The reply is group-coded. Each nibble of a 16-bit value becomes a five-bit code
word chosen so the line changes level often, and the line is differential: each
bit is the previous one exclusive-ored with the data. Undoing it is
x ^ (x >> 1). The first bit sent is a zero, so the burst starts with a falling
edge from the idle level and can be found at all.
The 16 bits are twelve of payload and four of checksum, and the checksum is the complement of the one on an outgoing frame. Every single-bit error is caught: the check is the exclusive-or of the four nibbles, and a flipped bit changes exactly one of them.
The payload is a period, not a speed:
period_us = mantissa (9 bits) << exponent (3 bits)
electrical rpm = 60,000,000 / period_us
A payload of 0x0FFF means the motor is not turning.
The line is inverted by the pad's output override rather than by the PIO program, so the same pin still reads the line's true level once the transmitter has let go of it, and the pull-up holds the idle in between.
The override is applied after the state machine's initialisation, never
before. That initialisation begins with pio_gpio_init(), which is
gpio_set_function(), which assigns the pad's whole control register instead
of masking it. The override lives in that register and is lost. The pull-up is
not, because pulls are in the pad block, so a pin that lost its inversion still
idles high and looks like a protocol fault rather than a pin fault.
The bind reads the register back and refuses rather than driving a line whose polarity it could not set. Ordering is the whole of the polarity here, and nothing else in the driver would notice losing it.
The coprocessor samples the line at five times the reply's bit rate and decodes
in software. The ESC's bit rate comes from its own crystal, a percent or two
from the bench's, and 21 bits is long enough for that to walk a fixed sample
point off the end of a bit — so the decoder resets its phase at every
transition, the way a UART (universal asynchronous receiver-transmitter)
resynchronises on a start bit, and reads each bit from the middle of its window.
test_dshot_telem holds this against a reply laid down at 4.75 and 5.25 samples
a bit.
An ESC reports electrical periods and has no idea what it is bolted to, so
mechanical rpm (revolutions per minute) needs the motor's magnet count. That is
the one number the wire does not carry. The panel sends it from the Motor poles setting when a coprocessor starts answering, again whenever the setting
changes, and again before the write that arms if a change is still owed, on
the CONTROL page. A write nobody answers stays owed and
goes out again at the next 50 ms poll; one the coprocessor refuses is not
retried, and waits for the next edit or link-up edge.
Until it arrives the coprocessor reports no speed at all. A speed derived from a guessed pole count is a plausible number with nothing to mark it as wrong, which is worse than an empty field.
That covers zero only. Any even count between 2 and 42 is accepted and sets the rpm valid bit -- an odd one is refused, because a typo that reached the far end would put a plausible wrong speed on the screen -- so a coprocessor holding an out-of-date count reports the actual speed times the motor's poles over the count it holds. Fitting a 12-pole motor to a bench still holding 14 reads 14.3 % low; leaving 12 in place for a 14-pole motor reads 16.7 % high. The direction follows the edit, and the ends of the range are a factor of 21 either way: a 42-pole motor converted as 2 reads twenty-one times its speed, and a 2-pole motor converted as 42 reads a twenty-first of it. The ordinary edit is the one to watch, because a factor of 21 is wrong on sight and 14 % is not.
An ESC sent command 13 interleaves temperature, voltage, current, stress and status frames between the speed ones, marked by the top nibble of the payload.
The bench sends command 13 on every edge into driving, ten times, before any throttle. Ten frames at 1 kHz is 10 ms, and command 13 is inside the command range, so nothing turns while it goes out; a throttle already asked for arrives 10 ms later than it otherwise would. It is sent again on each edge rather than once at bind time, because extended telemetry is a runtime setting an ESC forgets when it loses power and an ESC can be swapped between runs.
Each of the ten frames carries the telemetry bit set. On a value of 1 to 47
that bit is what marks the frame as a command for the BLHeli_S family
(Bluejay): it discards a command whose telemetry bit is clear and zeroes its
repeat counter with it, so ten frames without the bit never reach the six
repeats its command handler counts. AM32 has no such gate and acts on either.
On a bidirectional pin the frame is 0x01B5 with the bit and 0x01A4
without it — a different payload and a different checksum nibble.
test_dshot_frame holds that word. The bit's other meaning is a request on
the separate serial telemetry wire, which nothing on this bench reads.
The two frame kinds cannot be told apart from the bits alone: the nibble that
marks an extended frame is an ordinary exponent and mantissa in a speed frame,
and only an ESC with extended telemetry enabled guarantees the normalisation
that separates them. The decoder therefore takes the mode as an argument
rather than inferring it, and outputs_hw.c passes true once the ten repeats
have gone.
An ESC that does not know command 13 ignores it and keeps sending periods. Those still read as periods: a frame is taken for an extended one only when the mantissa's top bit is clear and the type nibble is not zero, which an ESC that normalises its exponent never sends. An ESC that does not normalise is the case this cannot survive, and no such ESC has been tried.
Temperature, voltage and current reach the bench numbers from here and from nowhere else: there is no measurement front end on the coprocessor. Each keeps its own staleness window of 2000 ms, against 200 ms for speed, because the extended frames are interleaved a few a second while a period comes back on every frame. Power is the product of voltage and current, and stays empty unless both arrived.
The implementation is written from the published description of the protocol. Everything below is exercised by the host suite against frames the same code builds, which proves the arithmetic and not the wire.
One thing is not on this list. Plain DShot has run a motor from the panel on the bring-up bench, which puts its bit timing inside that one ESC's tolerance -- against one ESC, with no instrument on the pin, so it is evidence and not a measurement. Nothing here has been on an oscilloscope, and every bidirectional item below is unconfirmed in every sense:
- the reply rate of five quarters of the DShot rate;
- the leading-bit convention of the group code;
- the turnaround delay, and whether 30 µs is what an ESC actually waits;
- the extended-telemetry frame types and their units, and whether an ESC accepts command 13 at all;
- whether the specification's "wait at least 35 ms" note belongs to command 13 or to command 12 (save settings). The table admits both readings, and the bench waits neither way: the throttle follows the tenth repeat at the next 1 ms tick. Settling it needs the specification text rather than a board;
- every bit timing, against a real ESC's tolerance rather than against the specification.
| Frames, group code, checksum, speed, sampler | shared/dshot/ |
| PPM frame layout | shared/ppm/ |
| Travel, role, rest, slew, arming, timeout | shared/outputs/ |
| Which PWM slice and channel a GPIO reaches | shared/outputs/out_pwm_map.c |
| PIO programs |
firmware/iomcu/src/ppm.pio, dshot.pio
|
| Hardware PWM, PPM and DShot backends | firmware/iomcu/src/out_*.c |
| Binding the bank to the pins | firmware/iomcu/src/outputs_hw.c |
rcbench
English
Using the bench
- What this is for
- Building
- Bringing up the link
- First run on hardware
- Screens
- Balancing
- Servo procedures
- Receiver buses
- Safety
- BLHeli_32 parameters
Reference
Deutsch
Den Prüfstand benutzen
- Worum es geht
- Bauen
- Den Link in Betrieb nehmen
- Erster Lauf auf der Hardware
- Bildschirme
- Auswuchten
- Servoverfahren
- Empfängerbusse
- Sicherheit
- BLHeli_32-Parameter
Referenz