You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@nuttx.apache.org by xi...@apache.org on 2026/04/30 04:24:55 UTC

(nuttx) 01/04: drivers/timers: Add common include files, definitions and upper-half for DSHOT

This is an automated email from the ASF dual-hosted git repository.

xiaoxiang781216 pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/nuttx.git

commit b810cbe4d36e26f34f60a9c07ee6796c8b491010
Author: Jukka Laitinen <ju...@tii.ae>
AuthorDate: Wed Mar 18 11:48:50 2026 +0200

    drivers/timers: Add common include files, definitions and upper-half for DSHOT
    
    DShot is a packet based serial protocol, used for controlling motor controllers
    (ESCs), typically for drones.
    
    This adds an upper-half driver for dshot protocol, along with common
    configuration options and definitions definitions.
    
    Signed-off-by: Jukka Laitinen <ju...@tii.ae>
---
 drivers/timers/CMakeLists.txt |   4 +
 drivers/timers/Kconfig        |  18 ++
 drivers/timers/Make.defs      |   6 +
 drivers/timers/dshot.c        | 638 ++++++++++++++++++++++++++++++++++++++++++
 include/nuttx/fs/ioctl.h      |   8 +
 include/nuttx/timers/dshot.h  | 312 +++++++++++++++++++++
 6 files changed, 986 insertions(+)

diff --git a/drivers/timers/CMakeLists.txt b/drivers/timers/CMakeLists.txt
index d77de9b3ca4..1107f1139af 100644
--- a/drivers/timers/CMakeLists.txt
+++ b/drivers/timers/CMakeLists.txt
@@ -86,6 +86,10 @@ if(CONFIG_PWM)
   list(APPEND SRCS pwm.c)
 endif()
 
+if(CONFIG_DSHOT)
+  list(APPEND SRCS dshot.c)
+endif()
+
 if(CONFIG_CAPTURE)
   list(APPEND SRCS capture.c)
 
diff --git a/drivers/timers/Kconfig b/drivers/timers/Kconfig
index 14d3c2df34b..c98101103eb 100644
--- a/drivers/timers/Kconfig
+++ b/drivers/timers/Kconfig
@@ -112,6 +112,24 @@ config FAKE_CAPTURE
 
 endif # CAPTURE
 
+config DSHOT
+	bool "DShot Driver Support"
+	default n
+	---help---
+		This selection enables the DShot driver for ESC control.
+
+if DSHOT
+
+config DSHOT_NCHANNELS
+	int "Maximum DShot channels in API structs"
+	default 8
+	range 1 16
+	---help---
+		Specifies the number of output channels per timer.  Each timer
+		may support fewer output channels than this value.
+
+endif # DSHOT
+
 config TIMER
 	bool "Timer Support"
 	default n
diff --git a/drivers/timers/Make.defs b/drivers/timers/Make.defs
index 0cdbf677ab9..6e671ebdbd1 100644
--- a/drivers/timers/Make.defs
+++ b/drivers/timers/Make.defs
@@ -118,6 +118,12 @@ ifeq ($(CONFIG_PWM),y)
   TMRVPATH = :timers
 endif
 
+ifeq ($(CONFIG_DSHOT),y)
+  CSRCS += dshot.c
+  TMRDEPPATH = --dep-path timers
+  TMRVPATH = :timers
+endif
+
 ifeq ($(CONFIG_CAPTURE),y)
   CSRCS += capture.c
 
diff --git a/drivers/timers/dshot.c b/drivers/timers/dshot.c
new file mode 100644
index 00000000000..37f4c875339
--- /dev/null
+++ b/drivers/timers/dshot.c
@@ -0,0 +1,638 @@
+/****************************************************************************
+ * drivers/timers/dshot.c
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.  The
+ * ASF licenses this file to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ *
+ ****************************************************************************/
+
+/****************************************************************************
+ * Included Files
+ ****************************************************************************/
+
+#include <nuttx/config.h>
+
+#include <sys/types.h>
+#include <stdint.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <debug.h>
+
+#include <nuttx/fs/fs.h>
+#include <nuttx/kmalloc.h>
+#include <nuttx/mutex.h>
+#include <nuttx/timers/dshot.h>
+
+#ifdef CONFIG_DSHOT
+
+/****************************************************************************
+ * Pre-processor Definitions
+ ****************************************************************************/
+
+#define DSHOT_GCR_MASK       0x000fffffu
+#define DSHOT_ERPMSCALE_NUM  600000u
+
+/****************************************************************************
+ * Private Types
+ ****************************************************************************/
+
+struct dshot_upperhalf_s
+{
+  uint8_t crefs;
+  mutex_t lock;
+  FAR struct dshot_lowerhalf_s *dev;
+  struct dshot_ch_telemetry_s telemetry[DSHOT_NCHANNELS];
+  bool bidir;
+};
+
+/****************************************************************************
+ * Private Function Prototypes
+ ****************************************************************************/
+
+static int dshot_open(FAR struct file *filep);
+static int dshot_close(FAR struct file *filep);
+static ssize_t dshot_read(FAR struct file *filep, FAR char *buffer,
+                          size_t buflen);
+static ssize_t dshot_write(FAR struct file *filep, FAR const char *buffer,
+                           size_t buflen);
+static int dshot_ioctl(FAR struct file *filep, int cmd, unsigned long arg);
+
+/****************************************************************************
+ * Private Data
+ ****************************************************************************/
+
+static const struct file_operations g_dshotops =
+{
+  dshot_open,  /* open */
+  dshot_close, /* close */
+  dshot_read,  /* read */
+  dshot_write, /* write */
+  NULL,        /* seek */
+  dshot_ioctl, /* ioctl */
+};
+
+/****************************************************************************
+ * Private Functions
+ ****************************************************************************/
+
+/****************************************************************************
+ * Name: dshot_crc
+ *
+ * Description:
+ *   Calculate the 4-bit CRC for a DShot packet.
+ *
+ * Input Parameters:
+ *   packet - The DShot packet to calculate CRC for.
+ *   bidir  - Boolean indicating if bidirectional DShot is enabled.
+ *
+ * Returned Value:
+ *   The calculated 4-bit CRC value.
+ *
+ ****************************************************************************/
+
+static uint16_t dshot_crc(uint16_t packet, bool bidir)
+{
+  uint16_t crc;
+
+  crc = packet >> 4;
+  crc = crc ^ (crc >> 4) ^ (crc >> 8);
+
+  if (bidir)
+    {
+      crc = ~crc;
+    }
+
+  return crc & 0xf;
+}
+
+/****************************************************************************
+ * Name: dshot_build_packet
+ *
+ * Description:
+ *   Build a complete DShot packet from throttle, telemetry, and bidir
+ *   values.
+ *
+ * Input Parameters:
+ *   throttle   - Throttle value (0-2047).
+ *   telemetry  - Boolean indicating if telemetry request is enabled.
+ *   bidir      - Boolean indicating if bidirectional DShot is enabled.
+ *
+ * Returned Value:
+ *   The constructed 16-bit DShot packet.
+ *
+ ****************************************************************************/
+
+static uint16_t dshot_build_packet(uint16_t throttle, bool telemetry,
+                                   bool bidir)
+{
+  uint16_t packet = 0;
+  packet |= throttle << 5;
+  packet |= (telemetry ? 1 << 4 : 0);
+  packet |= dshot_crc(packet, bidir);
+
+  return packet;
+}
+
+/****************************************************************************
+ * Name: dshot_parse_raw_packet
+ *
+ * Description:
+ *   Parse raw pattern from the line into a 16-bit packet and verify CRC.
+ *   Steps:
+ *   1. Extract GCR20 value from the raw value
+ *   2. Extract the 16-bit packet from the GCR20
+ *   3. Check the packet CRC
+ *
+ * Input Parameters:
+ *   raw - The raw encoded packet value to parse.
+ *
+ * Returned Value:
+ *   On success, returns the 12-bit payload as a positive integer.
+ *   On error, returns a negative error code.
+ *
+ ****************************************************************************/
+
+static int dshot_parse_raw_packet(uint32_t raw)
+{
+  static const int8_t gcr_decode[32] =
+    {
+      -1, -1, -1, -1, -1, -1, -1, -1,
+      -1,  9, 10, 11, -1, 13, 14, 15,
+      -1, -1,  2,  3, -1,  5,  6,  7,
+      -1,  0,  8,  1, -1,  4, 12, -1
+    };
+
+  uint32_t gcr20 = (raw ^ (raw >> 1)) & DSHOT_GCR_MASK;
+  uint16_t packet = 0;
+  int i;
+
+  for (i = 0; i < 4; i++)
+    {
+      int16_t nibble = gcr_decode[gcr20 & 0x1f];
+
+      if (nibble < 0)
+        {
+          return -EINVAL;
+        }
+
+      packet |= nibble << (4 * i);
+      gcr20 >>= 5;
+    }
+
+  if (dshot_crc(packet, true) != (packet & 0xf))
+    {
+      return -EINVAL;
+    }
+
+  return packet >> 4;
+}
+
+/****************************************************************************
+ * Name: dshot_copy_telemetry
+ *
+ * Description:
+ *   Copy telemetry data for selected channels from upper-half to output.
+ *
+ * Input Parameters:
+ *   upper   - Pointer to the upper-half driver state.
+ *   out     - Pointer to output telemetry array.
+ *   ch_mask - Bitmask indicating which channels to copy.
+ *
+ * Returned Value:
+ *   None.
+ *
+ ****************************************************************************/
+
+static void dshot_copy_telemetry(FAR struct dshot_upperhalf_s *upper,
+                                 FAR struct dshot_ch_telemetry_s *out,
+                                 uint16_t ch_mask)
+{
+  int i;
+
+  for (i = 0; i < DSHOT_NCHANNELS; i++)
+    {
+      if ((ch_mask & (1u << i)) != 0)
+        {
+          out[i] = upper->telemetry[i];
+        }
+    }
+}
+
+/****************************************************************************
+ * Name: dshot_update_telemetry
+ *
+ * Description:
+ *   Update telemetry data from lower-half driver for selected channels.
+ *   Parses raw telemetry data and extracts EDT or eRPM information.
+ *
+ * Input Parameters:
+ *   upper   - Pointer to the upper-half driver state.
+ *   ch_mask - Bitmask indicating which channels to update.
+ *
+ * Returned Value:
+ *   OK on success; a negated errno value on failure.
+ *
+ ****************************************************************************/
+
+static int dshot_update_telemetry(FAR struct dshot_upperhalf_s *upper,
+                                  uint16_t ch_mask)
+{
+  FAR struct dshot_lowerhalf_s *lower = upper->dev;
+  struct dshot_raw_telemetry_s raw[DSHOT_NCHANNELS];
+  int i;
+  int ret;
+
+  memset(raw, 0, sizeof(raw));
+
+  DEBUGASSERT(lower->ops->get_raw_telemetry != NULL);
+  ret = lower->ops->get_raw_telemetry(lower, raw, ch_mask);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  for (i = 0; i < DSHOT_NCHANNELS; i++)
+    {
+      if ((ch_mask & (1u << i)) != 0)
+        {
+          int value = dshot_parse_raw_packet(raw[i].raw);
+
+          if (value > 0)
+            {
+              uint16_t telem = (uint16_t)value & 0x0fff;
+              uint8_t exponent = telem >> 9;
+
+              if ((exponent & 1) == 0)
+                {
+                  upper->telemetry[i].edt_type = (uint8_t)(telem >> 8);
+                  upper->telemetry[i].edt_value = (uint8_t)(telem & 0xff);
+                  upper->telemetry[i].timestamp = raw[i].timestamp;
+                }
+              else
+                {
+                  uint32_t mantissa = telem & 0x1ff;
+                  uint32_t period = mantissa << exponent;
+                  if (period != 0)
+                    {
+                      uint32_t rpm = DSHOT_ERPMSCALE_NUM / period;
+                      upper->telemetry[i].erpm =
+                        (uint16_t)((rpm > 0xffff) ? 0xffff : rpm);
+                      upper->telemetry[i].timestamp = raw[i].timestamp;
+                    }
+                }
+            }
+        }
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * Name: dshot_open
+ *
+ * Description:
+ *   This function is called whenever the DShot device is opened.
+ *
+ * Input Parameters:
+ *   filep - A pointer to the file structure instance.
+ *
+ * Returned Value:
+ *   OK on success; a negated errno value on failure.
+ *
+ ****************************************************************************/
+
+static int dshot_open(FAR struct file *filep)
+{
+  FAR struct inode *inode = filep->f_inode;
+  FAR struct dshot_upperhalf_s *upper = inode->i_private;
+  uint8_t tmp;
+  int ret;
+
+  ret = nxmutex_lock(&upper->lock);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  tmp = upper->crefs + 1;
+  if (tmp == 0)
+    {
+      nxmutex_unlock(&upper->lock);
+      return -EMFILE;
+    }
+
+  if (tmp == 1)
+    {
+      DEBUGASSERT(upper->dev->ops->setup != NULL);
+      ret = upper->dev->ops->setup(upper->dev);
+      if (ret < 0)
+        {
+          nxmutex_unlock(&upper->lock);
+          return ret;
+        }
+    }
+
+  upper->crefs = tmp;
+
+  nxmutex_unlock(&upper->lock);
+  return OK;
+}
+
+/****************************************************************************
+ * Name: dshot_close
+ *
+ * Description:
+ *   This function is called when the DShot device is closed.
+ *
+ * Input Parameters:
+ *   filep - A pointer to the file structure instance.
+ *
+ * Returned Value:
+ *   OK on success; a negated errno value on failure.
+ *
+ ****************************************************************************/
+
+static int dshot_close(FAR struct file *filep)
+{
+  FAR struct inode *inode = filep->f_inode;
+  FAR struct dshot_upperhalf_s *upper = inode->i_private;
+  int ret;
+
+  ret = nxmutex_lock(&upper->lock);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  if (upper->crefs > 1)
+    {
+      upper->crefs--;
+    }
+  else
+    {
+      upper->crefs = 0;
+      DEBUGASSERT(upper->dev->ops->shutdown != NULL);
+      upper->dev->ops->shutdown(upper->dev);
+    }
+
+  nxmutex_unlock(&upper->lock);
+  return OK;
+}
+
+/****************************************************************************
+ * Name: dshot_read
+ *
+ * Description:
+ *   A dummy read method. This is provided only to satisfy the VFS layer.
+ *
+ * Input Parameters:
+ *   filep  - A pointer to the file structure instance.
+ *   buffer - The user-provided buffer into which data will be returned.
+ *   buflen - The size of the buffer in bytes.
+ *
+ * Returned Value:
+ *   Always returns 0 (end-of-file).
+ *
+ ****************************************************************************/
+
+static ssize_t dshot_read(FAR struct file *filep, FAR char *buffer,
+                          size_t buflen)
+{
+  return 0;
+}
+
+/****************************************************************************
+ * Name: dshot_write
+ *
+ * Description:
+ *   A dummy write method. This is provided only to satisfy the VFS layer.
+ *
+ * Input Parameters:
+ *   filep  - A pointer to the file structure instance.
+ *   buffer - The user-provided buffer from which data will be written.
+ *   buflen - The number of bytes to be written.
+ *
+ * Returned Value:
+ *   Always returns -EPERM (operation not permitted).
+ *
+ ****************************************************************************/
+
+static ssize_t dshot_write(FAR struct file *filep, FAR const char *buffer,
+                           size_t buflen)
+{
+  return -EPERM;
+}
+
+/****************************************************************************
+ * Name: dshot_ioctl
+ *
+ * Description:
+ *   The standard ioctl method. This is used to perform DShot-specific
+ *   operations including setting throttle, configuring channels, and
+ *   retrieving telemetry data.
+ *
+ * Input Parameters:
+ *   filep - A pointer to the file structure instance.
+ *   cmd   - The ioctl command code.
+ *   arg   - The argument provided with the ioctl command.
+ *
+ * Returned Value:
+ *   OK on success; a negated errno value on failure.
+ *
+ ****************************************************************************/
+
+static int dshot_ioctl(FAR struct file *filep, int cmd, unsigned long arg)
+{
+  FAR struct inode *inode = filep->f_inode;
+  FAR struct dshot_upperhalf_s *upper = inode->i_private;
+  FAR struct dshot_lowerhalf_s *lower = upper->dev;
+  int ret;
+
+  ret = nxmutex_lock(&upper->lock);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  switch (cmd)
+    {
+      case DSHOTIOC_CONFIGURE:
+        {
+          FAR struct dshot_config_s *cfg =
+            (FAR struct dshot_config_s *)((uintptr_t)arg);
+
+          if (cfg == NULL)
+            {
+              ret = -EINVAL;
+              break;
+            }
+
+          DEBUGASSERT(lower->ops->configure);
+          ret = lower->ops->configure(lower, cfg);
+          upper->bidir = cfg->bidir;
+        }
+        break;
+
+      case DSHOTIOC_SET_THROTTLE:
+        {
+          FAR struct dshot_throttle_s *req =
+            (FAR struct dshot_throttle_s *)((uintptr_t)arg);
+          uint16_t packets[DSHOT_NCHANNELS];
+          int i;
+
+          if (req == NULL)
+            {
+              ret = -EINVAL;
+              break;
+            }
+
+          /* Retrieve the latest telemetry */
+
+          if (req->telemetry_req != 0)
+            {
+              ret = dshot_update_telemetry(upper, req->telemetry_req);
+              if (ret < 0)
+                {
+                  break;
+                }
+
+              dshot_copy_telemetry(upper, req->ch_telemetry,
+                                   req->telemetry_req);
+            }
+
+          /* Build DShot packets for each channel */
+
+          for (i = 0; i < DSHOT_NCHANNELS; i++)
+            {
+              if ((req->ch_mask & (1u << i)) != 0)
+                {
+                  bool telemetry = (req->telemetry_req & (1u << i)) != 0;
+                  packets[i] =
+                    dshot_build_packet(req->throttle[i], telemetry,
+                                       upper->bidir);
+                }
+            }
+
+          /* Send the packets */
+
+          DEBUGASSERT(lower->ops->send_command);
+          ret = lower->ops->send_command(lower, packets, req->ch_mask);
+        }
+        break;
+
+      case DSHOTIOC_GET_TELEMETRY:
+        {
+          FAR struct dshot_telemetry_s *tlm =
+            (FAR struct dshot_telemetry_s *)((uintptr_t)arg);
+
+          if (tlm == NULL)
+            {
+              ret = -EINVAL;
+              break;
+            }
+
+          ret = dshot_update_telemetry(upper, tlm->ch_mask);
+          if (ret < 0)
+            {
+              break;
+            }
+
+          dshot_copy_telemetry(upper, tlm->ch_telemetry, tlm->ch_mask);
+          ret = OK;
+        }
+        break;
+
+      default:
+        {
+          if (lower->ops->ioctl != NULL)
+            {
+              ret = lower->ops->ioctl(lower, cmd, arg);
+            }
+          else
+            {
+              ret = -ENOTTY;
+            }
+        }
+        break;
+    }
+
+  nxmutex_unlock(&upper->lock);
+  return ret;
+}
+
+/****************************************************************************
+ * Public Functions
+ ****************************************************************************/
+
+/****************************************************************************
+ * Name: dshot_register
+ *
+ * Description:
+ *   This function binds an instance of a "lower half" DShot driver with the
+ *   "upper half" DShot device and registers that device so that it can be
+ *   used by application code.
+ *
+ *   When this function is called, the "lower half" driver should be in the
+ *   reset state (as if the shutdown() method had already been called).
+ *
+ * Input Parameters:
+ *   path - The full path to the driver to be registered in the NuttX pseudo-
+ *     filesystem. The recommended convention is to name all DShot drivers
+ *     as "/dev/dshot0", "/dev/dshot1", etc., where the driver path differs
+ *     only in the "minor" number at the end of the device name.
+ *   dev  - A pointer to an instance of lower half DShot driver. This
+ *     instance is bound to the DShot driver and must persist as long as the
+ *     driver persists.
+ *
+ * Returned Value:
+ *   Zero on success; a negated errno value on failure.
+ *
+ ****************************************************************************/
+
+int dshot_register(FAR const char *path, FAR struct dshot_lowerhalf_s *dev)
+{
+  FAR struct dshot_upperhalf_s *upper;
+  int ret;
+
+  if (dev == NULL || dev->ops == NULL)
+    {
+      return -EINVAL;
+    }
+
+  upper = kmm_zalloc(sizeof(struct dshot_upperhalf_s));
+  if (upper == NULL)
+    {
+      return -ENOMEM;
+    }
+
+  nxmutex_init(&upper->lock);
+  upper->dev = dev;
+
+  ret = register_driver(path, &g_dshotops, 0666, upper);
+  if (ret < 0)
+    {
+      nxmutex_destroy(&upper->lock);
+      kmm_free(upper);
+      return ret;
+    }
+
+  return OK;
+}
+
+#endif /* CONFIG_DSHOT */
diff --git a/include/nuttx/fs/ioctl.h b/include/nuttx/fs/ioctl.h
index a231b827dd0..6a520f24a41 100644
--- a/include/nuttx/fs/ioctl.h
+++ b/include/nuttx/fs/ioctl.h
@@ -113,6 +113,7 @@
 #define _1WIREBASE      (0x4500) /* 1WIRE ioctl commands */
 #define _EEPIOCBASE     (0x4600) /* EEPROM driver ioctl commands */
 #define _PTPBASE        (0x4700) /* PTP ioctl commands */
+#define _DSHOTIOCBASE   (0x4800) /* Dshot device ioctl commands */
 #define _WLIOCBASE      (0x8b00) /* Wireless modules ioctl network commands */
 
 /* boardctl() commands share the same number space */
@@ -807,6 +808,13 @@
 #define _PTPIOCVALID(c)       (_IOC_TYPE(c)==_PTPBASE)
 #define _PTPIOC(nr)           _IOC(_PTPBASE,nr)
 
+/* DSHOT ioctl definitions (see nuttx/timers/dshot.h) ***********************/
+
+/* see nuttx/include/timers/dshot.h */
+
+#define _DSHOTIOCVALID(c)   (_IOC_TYPE(c)==_DSHOTIOCBASE)
+#define _DSHOTIOC(nr)       _IOC(_DSHOTIOCBASE,nr)
+
 /****************************************************************************
  * Public Type Definitions
  ****************************************************************************/
diff --git a/include/nuttx/timers/dshot.h b/include/nuttx/timers/dshot.h
new file mode 100644
index 00000000000..78f0c65ab59
--- /dev/null
+++ b/include/nuttx/timers/dshot.h
@@ -0,0 +1,312 @@
+/****************************************************************************
+ * include/nuttx/timers/dshot.h
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.  The
+ * ASF licenses this file to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ *
+ ****************************************************************************/
+
+#ifndef __INCLUDE_NUTTX_TIMERS_DSHOT_H
+#define __INCLUDE_NUTTX_TIMERS_DSHOT_H
+
+/* Common definitions for DShot drivers */
+
+/****************************************************************************
+ * Included Files
+ ****************************************************************************/
+
+#include <nuttx/config.h>
+#include <nuttx/compiler.h>
+#include <nuttx/fs/ioctl.h>
+
+#include <time.h>
+
+/****************************************************************************
+ * Pre-processor Definitions
+ ****************************************************************************/
+
+#ifdef CONFIG_DSHOT_NCHANNELS
+#  if CONFIG_DSHOT_NCHANNELS > 16
+#    error "max 16 channels supported"
+#  endif
+#  define DSHOT_NCHANNELS CONFIG_DSHOT_NCHANNELS
+#else
+#  define DSHOT_NCHANNELS 1
+#endif
+
+/* IOCTL Commands ***********************************************************/
+
+/* Configure the DShot instance; speed, bidirectional mode, active channels */
+
+#define DSHOTIOC_CONFIGURE       _DSHOTIOC(0x01)
+
+/* Write throttle data or special command for one or more channels */
+
+#define DSHOTIOC_SET_THROTTLE    _DSHOTIOC(0x02)
+
+/* Retrieve telemetry from the last successful bidirectional RX */
+
+#define DSHOTIOC_GET_TELEMETRY   _DSHOTIOC(0x03)
+
+/* DSHOT Speeds *************************************************************/
+
+#define DSHOT_SPEED_150             150000u
+#define DSHOT_SPEED_300             300000u
+#define DSHOT_SPEED_600             600000u
+#define DSHOT_SPEED_1200            1200000u
+#define DSHOT_SPEED_2400            2400000u
+#define DSHOT_SPEED_3600            3600000u
+
+/* DSHOT special commands (throttle values 0-47)*****************************/
+
+#define DSHOT_CMD_MOTOR_STOP                                   0
+#define DSHOT_CMD_BEEP1                                        1
+#define DSHOT_CMD_BEEP2                                        2
+#define DSHOT_CMD_BEEP3                                        3
+#define DSHOT_CMD_BEEP4                                        4
+#define DSHOT_CMD_BEEP5                                        5
+#define DSHOT_CMD_ESC_INFO                                     6
+#define DSHOT_CMD_SPIN_DIRECTION_1                             7
+#define DSHOT_CMD_SPIN_DIRECTION_2                             8
+#define DSHOT_CMD_3D_MODE_OFF                                  9
+#define DSHOT_CMD_3D_MODE_ON                                   10
+#define DSHOT_CMD_SETTINGS_REQUEST                             11
+#define DSHOT_CMD_SAVE_SETTINGS                                12
+#define DSHOT_CMD_EXTENDED_TELEMETRY_ENABLE                    13
+#define DSHOT_CMD_EXTENDED_TELEMETRY_DISABLE                   14
+
+/* 15 - 19 Unassigned */
+
+#define DSHOT_CMD_SPIN_DIRECTION_NORMAL                        20
+#define DSHOT_CMD_SPIN_DIRECTION_REVERSED                      21
+#define DSHOT_CMD_LED0_ON                                      22
+#define DSHOT_CMD_LED1_ON                                      23
+#define DSHOT_CMD_LED2_ON                                      24
+#define DSHOT_CMD_LED3_ON                                      25
+#define DSHOT_CMD_LED0_OFF                                     26
+#define DSHOT_CMD_LED1_OFF                                     27
+#define DSHOT_CMD_LED2_OFF                                     28
+#define DSHOT_CMD_LED3_OFF                                     29
+
+#define DSHOT_CMD_AUDIO_STREAM_MODE                            30
+#define DSHOT_CMD_SILENT_MODE                                  31
+
+#define DSHOT_CMD_SIGNAL_LINE_TELEMETRY_DISABLE                32
+#define DSHOT_CMD_SIGNAL_LINE_TELEMETRY_ENABLE                 33
+#define DSHOT_CMD_SIGNAL_LINE_CONTINUOUS_ERPM_TELEMETRY        34
+#define DSHOT_CMD_SIGNAL_LINE_CONTINUOUS_ERPM_PERIOD_TELEMETRY 35
+
+/* 36 - 41 Unassigned */
+
+#define DSHOT_CMD_SIGNAL_LINE_TEMPERATURE_TELEMETRY            42
+#define DSHOT_CMD_SIGNAL_LINE_VOLTAGE_TELEMETRY                43
+#define DSHOT_CMD_SIGNAL_LINE_CURRENT_TELEMETRY                44
+#define DSHOT_CMD_SIGNAL_LINE_CONSUMPTION_TELEMETRY            45
+#define DSHOT_CMD_SIGNAL_LINE_ERPM_TELEMETRY                   46
+#define DSHOT_CMD_SIGNAL_LINE_ERPM_PERIOD_TELEMETRY            47
+
+/* Extended telemetry types *************************************************/
+
+#define DSHOT_EDT_TYPE_TEMP  0x02 /* Temperature in C */
+#define DSHOT_EDT_TYPE_V     0x04 /* Voltage: 0.25V per step */
+#define DSHOT_EDT_TYPE_A     0x06 /* Current in Amp */
+#define DSHOT_EDT_TYPE_DBG1  0x08 /* Debug value 1 */
+#define DSHOT_EDT_TYPE_DBG2  0x0A /* Debug value 2 */
+#define DSHOT_EDT_TYPE_DBG3  0x0C /* Debug value 3 */
+#define DSHOT_EDT_TYPE_STATE 0x0E /* State/Event */
+
+/****************************************************************************
+ * Public Types
+ ****************************************************************************/
+
+/* Configuration argument for DSHOTIOC_CONFIGURE
+ *
+ * freq       - Bit rate in Hz: DSHOT_SPEED_150/300/600/1200/2400/3600
+ * telem_freq - Telemetry frequency. Use 1.25 * freq for standard speed
+ *              (e.g., 750kHz for DShot600). Some ESCs may require
+ *              different values (e.g., 1.15 * freq for T-motor F55A).
+ * active_mask - Bitmask of active channels (bit0=ch0, bit1=ch1, etc.)
+ * bidir       - Enable bidirectional DShot (inverted command data,
+ *               response on same line).
+ */
+
+struct dshot_config_s
+{
+  uint32_t freq;
+  uint32_t telem_freq;
+  uint16_t active_mask;
+  bool bidir;
+};
+
+/* Telemetry data for a single DShot channel. Contains eRPM, extended
+ * telemetry type/value, and timestamp of the last valid response.
+ *
+ * erpm      - eRPM value from telemetry response.
+ * edt_type  - Extended telemetry type (if supported).
+ * edt_value - Extended telemetry value (if supported).
+ * timestamp - Timestamp of the last valid response.
+ */
+
+struct dshot_ch_telemetry_s
+{
+  uint16_t erpm;
+  uint8_t  edt_type;
+  uint8_t  edt_value;
+  struct timespec timestamp;
+};
+
+/* Telemetry result for DSHOTIOC_GET_TELEMETRY ioctl command.
+ * Contains bitmask of channels to read and telemetry data for each.
+ *
+ * ch_mask      - Bitmask of logical channels to read telemetry from.
+ * ch_telemetry - Telemetry data array for each channel.
+ */
+
+struct dshot_telemetry_s
+{
+  uint16_t ch_mask;
+  struct dshot_ch_telemetry_s ch_telemetry[DSHOT_NCHANNELS];
+};
+
+/* Per-channel throttle value for DSHOTIOC_SET_THROTTLE ioctl command.
+ * Specifies throttle values, channel mask, and optional telemetry
+ * request for efficient batching of operations.
+ *
+ * throttle      - Throttle value array (0 = disarm, 48-2047 = armed range,
+ *                 or special command values).
+ * ch_mask       - Bitmask of logical channels to set.
+ * telemetry_req - Bitmask: bit N requests telemetry from channel N.
+ * ch_telemetry  - Telemetry data from previous request. Allows reading
+ *                 previous telemetry response without separate ioctl.
+ */
+
+struct dshot_throttle_s
+{
+  uint16_t throttle[DSHOT_NCHANNELS];
+  uint16_t ch_mask;
+  uint16_t telemetry_req;
+  struct dshot_ch_telemetry_s ch_telemetry[DSHOT_NCHANNELS];
+};
+
+/* Raw telemetry frame captured from the DShot signal line.
+ * Contains the raw GCR-encoded packet and capture timestamp.
+ *
+ * raw       - Raw 20-bit GCR encoded packet data.
+ * timestamp - Capture timestamp of the raw packet.
+ */
+
+struct dshot_raw_telemetry_s
+{
+  uint32_t raw;
+  struct timespec timestamp;
+};
+
+/* Lower-half driver operations table. Provides callback functions for
+ * the upper-half driver to control DShot hardware. All methods except
+ * ioctl are mandatory.
+ *
+ * setup            - Called when driver is opened. Configure and initialize
+ *                    device for use.
+ * shutdown         - Called when driver is closed. Stop output and free
+ *                    hardware resources.
+ * configure        - Configure speed, active channels, and bidirectional
+ *                    mode according to dshot_config_s.
+ * send_command     - Send DShot command packets to specified channels.
+ * get_raw_telemetry - Fetch raw telemetry packets from specified channels.
+ * ioctl            - Optional platform-specific ioctl handler.
+ */
+
+struct dshot_lowerhalf_s;
+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);
+};
+
+/* Public representation of lower-half state. */
+
+struct dshot_lowerhalf_s
+{
+  FAR const struct dshot_ops_s *ops;
+};
+
+/****************************************************************************
+ * Public Data
+ ****************************************************************************/
+
+/****************************************************************************
+ * Public Function Prototypes
+ ****************************************************************************/
+
+#ifdef __cplusplus
+#define EXTERN extern "C"
+extern "C"
+{
+#else
+#define EXTERN extern
+#endif
+
+/****************************************************************************
+ * "Upper-Half" DShot Driver Interfaces
+ ****************************************************************************/
+
+/****************************************************************************
+ * Name: dshot_register
+ *
+ * Description:
+ *   This function binds an instance of a "lower half" timer driver with the
+ *   "upper half" DSHOT device and registers that device so that can be used
+ *   by application code.
+ *
+ *   When this function is called, the "lower half" driver should be in the
+ *   reset state (as if the shutdown() method had already been called).
+ *
+ * Input Parameters:
+ *   path - The full path to the driver to be registered in the NuttX pseudo-
+ *     filesystem.
+ *   dev - A pointer to an instance of lower half DShot driver. This instance
+ *     is bound to the DShot driver and must persist as long as the driver
+ *     persists.
+ *
+ * Returned Value:
+ *   Zero on success; a negated errno value on failure.
+ *
+ ****************************************************************************/
+
+int dshot_register(FAR const char *path, FAR struct dshot_lowerhalf_s *dev);
+
+#undef EXTERN
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* __INCLUDE_NUTTX_TIMERS_DSHOT_H */