blob: 05bd14922e8151b0fc19d0f3e726743a42607953 [file]
/****************************************************************************
* 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, 0600, upper);
if (ret < 0)
{
nxmutex_destroy(&upper->lock);
kmm_free(upper);
return ret;
}
return OK;
}
#endif /* CONFIG_DSHOT */