| /**************************************************************************** |
| * 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 */ |