Documentation/drivers/timers: Add documentation of DShot driver upper half

Add documentation for the DShot electronic speed controllers protocol

Signed-off-by: Jukka Laitinen <jukka.laitinen@tii.ae>
This commit is contained in:
Jukka Laitinen
2026-04-24 08:55:34 +03:00
committed by Xiang Xiao
parent 66f3b66eda
commit dde26e5589
2 changed files with 449 additions and 0 deletions
@@ -0,0 +1,448 @@
=============
DShot Drivers
=============
DShot (Digital Shot) is a digital communication protocol used primarily for controlling brushless motor Electronic Speed Controllers (ESCs) in applications such as drones, multirotors, and other robotics systems. Unlike traditional PWM-based protocols, DShot provides digital communication that is more robust and faster, with optional bidirectional telemetry support.
The NuttX DShot driver is split into two parts:
#. An "upper half", generic driver that provides the common DShot interface to application level code, handles packet encoding/decoding, telemetry parsing, and manages the character device interface.
#. A "lower half", platform-specific driver that implements the low-level hardware controls to generate the DShot signal timing and optionally capture telemetry responses.
Files supporting DShot can be found in the following locations:
- **Interface Definition**: The header file for the NuttX DShot driver resides at ``include/nuttx/timers/dshot.h``. This header file includes both the application level interface to the DShot driver as well as the interface between the "upper half" and "lower half" drivers.
- **"Upper Half" Driver**: The generic, "upper half" DShot driver resides at ``drivers/timers/dshot.c``.
- **"Lower Half" Drivers**: Platform-specific DShot drivers reside in ``arch/<architecture>/src/<hardware>`` directory for the specific processor ``<architecture>`` and for the specific ``<chip>`` peripheral devices. For example, the i.MX9 FlexIO-based implementation is at ``arch/arm64/src/imx9/imx9_flexio_dshot.c``.
DShot Protocol Overview
=======================
The DShot protocol transmits 16-bit frames at fixed bit rates. Each frame consists of:
- **11 bits**: Throttle value (0-2047) or special command
- **1 bit**: Telemetry request flag
- **4 bits**: CRC checksum
Supported bit rates (speeds):
- **DShot150**: 150 kbit/s
- **DShot300**: 300 kbit/s
- **DShot600**: 600 kbit/s (most common)
- **DShot1200**: 1200 kbit/s
- **DShot2400**: 2400 kbit/s (experimental)
- **DShot3600**: 3600 kbit/s (experimental)
Bidirectional DShot
-------------------
Bidirectional DShot allows ESCs to send telemetry data back to the controller on the same signal line used for commands. The telemetry includes:
- **eRPM**: Electrical RPM of the motor
- **Extended telemetry**: Temperature, voltage, current, debug values, etc.
When bidirectional mode is enabled, the CRC is inverted in the command packet, and the ESC responds with a GCR-encoded telemetry frame after the command.
Application Level Interface
============================
To use the DShot driver in an application, include the header file:
.. code-block:: c
#include <nuttx/timers/dshot.h>
The DShot driver is registered as a POSIX character device file into the ``/dev`` namespace. Open the device to get a file descriptor for operations:
.. code-block:: c
int fd = open("/dev/dshot0", O_RDWR);
if (fd < 0)
{
/* Handle error */
}
Configuration
-------------
Before sending commands, configure the DShot instance with the desired speed, telemetry frequency, active channels, and bidirectional mode:
.. code-block:: c
struct dshot_config_s config;
config.freq = DSHOT_SPEED_600; /* 600 kbit/s */
config.telem_freq = 750000; /* 1.25 * freq for standard ESCs */
config.active_mask = 0x0F; /* Enable channels 0-3 */
config.bidir = true; /* Enable bidirectional telemetry */
int ret = ioctl(fd, DSHOTIOC_CONFIGURE, (unsigned long)&config);
if (ret < 0)
{
/* Handle error */
}
The ``telem_freq`` should typically be set to 1.25 times the bit rate frequency. However, some ESCs (e.g., T-motor F55A) may require different values like 1.15 times the bit rate.
Setting Throttle
----------------
Send throttle values or special commands to one or more channels:
.. code-block:: c
struct dshot_throttle_s throttle;
memset(&throttle, 0, sizeof(throttle));
/* Set throttle values for channels 0-3 */
throttle.throttle[0] = 1000; /* Throttle range: 48-2047 (armed) */
throttle.throttle[1] = 1000; /* 0 = disarm */
throttle.throttle[2] = 1000; /* 1-47 = special commands */
throttle.throttle[3] = 1000;
throttle.ch_mask = 0x0F; /* Update channels 0-3 */
throttle.telemetry_req = 0x01; /* Request telemetry from channel 0 */
ret = ioctl(fd, DSHOTIOC_SET_THROTTLE, (unsigned long)&throttle);
if (ret < 0)
{
/* Handle error */
}
Special Commands
----------------
DShot supports special commands for ESC control and configuration. These commands use throttle values 0-47:
.. code-block:: c
struct dshot_throttle_s cmd;
memset(&cmd, 0, sizeof(cmd));
/* Send beep command to channel 0 */
cmd.throttle[0] = DSHOT_CMD_BEEP1;
cmd.ch_mask = 0x01;
/* Commands should be sent multiple times (typically 10x) */
for (int i = 0; i < 10; i++)
{
ioctl(fd, DSHOTIOC_SET_THROTTLE, (unsigned long)&cmd);
usleep(1000); /* Small delay between commands */
}
Common special commands:
- ``DSHOT_CMD_MOTOR_STOP`` (0): Disarm motor
- ``DSHOT_CMD_BEEP1`` through ``DSHOT_CMD_BEEP5`` (1-5): Audio tones
- ``DSHOT_CMD_ESC_INFO`` (6): Request ESC information
- ``DSHOT_CMD_SPIN_DIRECTION_NORMAL`` (20): Normal rotation
- ``DSHOT_CMD_SPIN_DIRECTION_REVERSED`` (21): Reversed rotation
- ``DSHOT_CMD_SIGNAL_LINE_TELEMETRY_ENABLE`` (33): Enable telemetry
- ``DSHOT_CMD_SIGNAL_LINE_CONTINUOUS_ERPM_TELEMETRY`` (34): Continuous eRPM
Reading Telemetry
-----------------
When bidirectional mode is enabled, retrieve telemetry data from ESCs:
.. code-block:: c
struct dshot_telemetry_s telem;
telem.ch_mask = 0x0F; /* Read telemetry from channels 0-3 */
ret = ioctl(fd, DSHOTIOC_GET_TELEMETRY, (unsigned long)&telem);
if (ret < 0)
{
/* Handle error */
}
/* Process telemetry for each channel */
for (int i = 0; i < 4; i++)
{
printf("Channel %d:\n", i);
printf(" eRPM: %u\n", telem.ch_telemetry[i].erpm);
printf(" EDT Type: 0x%02x\n", telem.ch_telemetry[i].edt_type);
printf(" EDT Value: %u\n", telem.ch_telemetry[i].edt_value);
}
The telemetry structure also includes timestamps indicating when the last valid response was received from each ESC.
Efficient Telemetry Handling
-----------------------------
For efficient operation, the ``DSHOTIOC_SET_THROTTLE`` ioctl can both send commands and retrieve the previous telemetry response in a single call:
.. code-block:: c
struct dshot_throttle_s throttle;
memset(&throttle, 0, sizeof(throttle));
throttle.throttle[0] = 1000;
throttle.ch_mask = 0x01;
throttle.telemetry_req = 0x01; /* Request telemetry */
ret = ioctl(fd, DSHOTIOC_SET_THROTTLE, (unsigned long)&throttle);
/* Previous telemetry is now available in throttle.ch_telemetry[] */
printf("Previous eRPM: %u\n", throttle.ch_telemetry[0].erpm);
Configuration Options
=====================
The following configuration options control DShot driver behavior:
- ``CONFIG_DSHOT``: Enable DShot driver support
- ``CONFIG_DSHOT_NCHANNELS``: Maximum number of DShot channels (default: 8, max: 16)
.. note::
In addition to these generic DShot options, you must enable and configure
the architecture-specific lower-half driver. For example, on i.MX9:
- ``CONFIG_IMX9_FLEXIO_DSHOT``: Enable FlexIO-based DShot driver
- ``CONFIG_IMX9_FLEXIO_DSHOT_CHANNEL_COUNT``: Number of hardware channels
Refer to your platform's Kconfig options for specific lower-half settings.
Example configuration in ``defconfig``:
.. code-block:: text
CONFIG_DSHOT=y
CONFIG_DSHOT_NCHANNELS=8
# Platform-specific (example for i.MX9)
CONFIG_IMX9_FLEXIO_DSHOT=y
CONFIG_IMX9_FLEXIO_DSHOT_CHANNEL_COUNT=8
Lower Half Driver Interface
============================
The lower half driver must implement the operations defined in ``struct dshot_ops_s``:
.. code-block:: c
struct dshot_ops_s
{
CODE int (*setup)(FAR struct dshot_lowerhalf_s *dev);
CODE int (*shutdown)(FAR struct dshot_lowerhalf_s *dev);
CODE int (*configure)(FAR struct dshot_lowerhalf_s *dev,
FAR const struct dshot_config_s *cfg);
CODE int (*send_command)(FAR struct dshot_lowerhalf_s *dev,
FAR const uint16_t *packets,
uint16_t ch_mask);
CODE int (*get_raw_telemetry)(FAR struct dshot_lowerhalf_s *dev,
FAR struct dshot_raw_telemetry_s *raw,
uint16_t ch_mask);
CODE int (*ioctl)(FAR struct dshot_lowerhalf_s *dev,
int cmd, unsigned long arg);
};
Operation Descriptions
----------------------
**setup**
Called when the device is opened. Initialize hardware and allocate resources.
**shutdown**
Called when the device is closed. Stop output and free hardware resources.
**configure**
Configure the DShot speed (bit rate), telemetry capture frequency, active channel mask, and bidirectional mode. The lower half driver should reconfigure timers/peripherals accordingly.
**send_command**
Transmit DShot packets to the specified channels. The ``packets`` array contains pre-encoded 16-bit DShot frames (including CRC), and ``ch_mask`` indicates which channels to update.
**get_raw_telemetry**
Retrieve raw GCR-encoded telemetry data captured from the ESC responses. The upper half will decode these into human-readable values.
**ioctl** (optional)
Handle platform-specific ioctl commands not covered by the standard interface.
Registering a Lower Half Driver
--------------------------------
The lower half driver registers itself with the upper half using:
.. code-block:: c
int dshot_register(FAR const char *path, FAR struct dshot_lowerhalf_s *dev);
Example from board initialization code:
.. code-block:: c
/* Initialize platform-specific DShot hardware */
FAR struct dshot_lowerhalf_s *dshot = imx9_dshot_initialize();
if (dshot == NULL)
{
return -ENODEV;
}
/* Register with upper half */
ret = dshot_register("/dev/dshot0", dshot);
if (ret < 0)
{
return ret;
}
Implementation Considerations
==============================
Timing Requirements
-------------------
DShot requires precise timing. For DShot600:
- Bit period: 1.67 μs
- Logic '1': ~1.25 μs high, ~0.42 μs low (75% duty cycle)
- Logic '0': ~0.63 μs high, ~1.04 μs low (37.5% duty cycle)
Lower half implementations typically use hardware timers (PWM/DMA) or specialized peripherals like FlexIO to generate accurate waveforms.
Telemetry Capture
-----------------
Bidirectional telemetry requires the signal line to transition from output to input mode after sending the command. The ESC responds with a 21-bit GCR-encoded frame approximately 30-40 μs after the command. Accurate capture requires:
- Fast pin direction switching
- Precise edge timing measurement
- GCR decoding and CRC validation (handled by upper half)
Multi-Channel Support
---------------------
For multi-channel systems, consider:
- Synchronizing output across channels (important for motor control)
- Efficient DMA usage to minimize CPU overhead
- Buffer management for simultaneous telemetry capture
Best Practices
==============
1. **Initialization Sequence**
- Wait for ESC to initialize (typically 3-5 seconds after power-on)
- Configure DShot before attempting to send commands
- Send ``DSHOT_CMD_MOTOR_STOP`` after initialization to ensure motors are disarmed
2. **Command Repetition**
Special commands should be sent multiple times (typically 6-10 repetitions) to ensure reliable reception.
3. **Telemetry Polling**
**Bidirectional Mode**: Requesting telemetry on every command cycle is normal and expected. The telemetry response is received on the same signal line immediately after the command.
**Non-Bidirectional Mode** (dedicated UART telemetry): Consider the UART line capability and timing of response packets. Handle UART receive separately from DShot command transmission.
4. **Error Handling**
Monitor telemetry timestamps to detect ESC communication failures. Stale telemetry may indicate signal integrity issues.
5. **Safety**
Always send ``DSHOT_CMD_MOTOR_STOP`` (throttle value 0) before closing the device or in emergency situations.
Example Application
===================
Complete example of DShot motor control:
.. code-block:: c
#include <nuttx/config.h>
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <nuttx/timers/dshot.h>
/****************************************************************************
* Public Functions
****************************************************************************/
int main(int argc, FAR char *argv[])
{
struct dshot_config_s config;
struct dshot_throttle_s throttle;
int fd;
int ret;
int i;
/* Open DShot device */
fd = open("/dev/dshot0", O_RDWR);
if (fd < 0)
{
fprintf(stderr, "Failed to open DShot device\n");
return -1;
}
/* Configure DShot600 with bidirectional telemetry */
config.freq = DSHOT_SPEED_600;
config.telem_freq = 750000;
config.active_mask = 0x0f; /* 4 motors */
config.bidir = true;
ret = ioctl(fd, DSHOTIOC_CONFIGURE, (unsigned long)&config);
if (ret < 0)
{
fprintf(stderr, "Configuration failed\n");
close(fd);
return -1;
}
/* Arm ESCs - send zero throttle */
memset(&throttle, 0, sizeof(throttle));
throttle.ch_mask = 0x0f;
for (i = 0; i < 100; i++)
{
ioctl(fd, DSHOTIOC_SET_THROTTLE, (unsigned long)&throttle);
usleep(10000);
}
/* Gradual throttle increase */
for (i = 48; i <= 1000; i += 10)
{
throttle.throttle[0] = i;
throttle.throttle[1] = i;
throttle.throttle[2] = i;
throttle.throttle[3] = i;
throttle.telemetry_req = 0x01;
ret = ioctl(fd, DSHOTIOC_SET_THROTTLE, (unsigned long)&throttle);
if (ret == 0 && throttle.ch_telemetry[0].erpm > 0)
{
printf("Throttle: %d, eRPM: %u\n", i,
throttle.ch_telemetry[0].erpm);
}
usleep(20000);
}
/* Stop motors */
memset(&throttle, 0, sizeof(throttle));
throttle.ch_mask = 0x0f;
ioctl(fd, DSHOTIOC_SET_THROTTLE, (unsigned long)&throttle);
close(fd);
return 0;
}
References
==========
- DShot Protocol Specification: https://github.com/betaflight/betaflight/wiki/DShot
- ESC Telemetry Protocol: https://github.com/bird-sanctuary/extended-dshot-telemetry
- DShot and Bidirectional DShot: https://brushlesswhoop.com/dshot-and-bidirectional-dshot/
@@ -11,6 +11,7 @@ Timers Drivers
watchdog.rst
rtc.rst
capture.rst
dshot.rst
The NuttX timing subsystem consists of four layers: