From 66482aeadbee9b9249c8549bfd1cd7f7c7ae30f5 Mon Sep 17 00:00:00 2001 From: Wastl Kraus Date: Wed, 11 Jun 2025 09:29:59 +0200 Subject: [PATCH] Rewrite ESP-IDF5 Now using "new" API (rmt_tx.h). "old" Version of this library is still available: Branch "oldAPI". --- DShotRMT.cpp | 338 +++++++++------------------------ DShotRMT.h | 212 ++++----------------- README.md | 113 ++++++++--- examples/dshot300/dshot300.ino | 76 ++++---- library.properties | 4 +- 5 files changed, 244 insertions(+), 499 deletions(-) diff --git a/DShotRMT.cpp b/DShotRMT.cpp index ac703ef..5abab84 100644 --- a/DShotRMT.cpp +++ b/DShotRMT.cpp @@ -1,277 +1,113 @@ -// -// Name: DShotRMT.cpp -// Created: 20.03.2021 00:49:15 -// Author: derdoktor667 -// +/** + * @file DShotRMT.cpp + * @brief Implementation of continuous DShot signal using ESP32 RMT encoder API with pause between frames + * @author Wastl Kraus + * @date 2025-06-11 + * @license MIT + */ #include -// Constructor that takes gpio and rmtChannel as arguments -DShotRMT::DShotRMT(gpio_num_t gpio, rmt_channel_t rmtChannel) -{ - // Initialize the dshot_config structure with the arguments passed to the constructor - dshot_config.gpio_num = gpio; - dshot_config.pin_num = static_cast(gpio); - dshot_config.rmt_channel = rmtChannel; - dshot_config.mem_block_num = static_cast(RMT_CHANNEL_MAX - static_cast(rmtChannel)); +DShotRMT::DShotRMT(gpio_num_t gpio, dshot_mode_t mode, bool isBidirectional) + : _gpio(gpio), _mode(mode), _isBidirectional(isBidirectional) {} - // Create an empty packet using the DSHOT_NULL_PACKET and the buildTxRmtItem function - buildTxRmtItem(DSHOT_NULL_PACKET); +void DShotRMT::begin() +{ + rmt_tx_channel_config_t tx_config = { + .gpio_num = _gpio, + .clk_src = RMT_CLK_SRC_DEFAULT, + .resolution_hz = DEFAULT_RES_HZ, + .mem_block_symbols = 64, + .trans_queue_depth = 1, + .flags = { + .invert_out = _isBidirectional, + .with_dma = false}}; + + rmt_new_tx_channel(&tx_config, &_channel); + rmt_enable(_channel); + + // Create encoder only once + if (!_encoder) + { + rmt_copy_encoder_config_t enc_cfg = {}; + rmt_new_copy_encoder(&enc_cfg, &_encoder); + } + + _tx_config.loop_count = -1; // Infinite loop + _tx_config.flags.eot_level = 0; } -// Constructor that takes pin and channel as arguments -DShotRMT::DShotRMT(uint8_t pin, uint8_t channel) +void DShotRMT::setThrottle(uint16_t throttle, bool telemetry) { - // Initialize the dshot_config structure with the arguments passed to the constructor - dshot_config.gpio_num = static_cast(pin); - dshot_config.pin_num = pin; - dshot_config.rmt_channel = static_cast(channel); - dshot_config.mem_block_num = RMT_CHANNEL_MAX - channel; + // Clamp to 11 bits + throttle &= 0x07FF; + if (throttle == _lastThrottle && telemetry == _lastTelemetry) + return; - // Create an empty packet using the DSHOT_NULL_PACKET and the buildTxRmtItem function - buildTxRmtItem(DSHOT_NULL_PACKET); + _lastThrottle = throttle; + _lastTelemetry = telemetry; + + // Build 16-bit DShot packet + uint16_t packet = (throttle << 1) | (telemetry ? 1 : 0); + uint8_t crc = (packet ^ (packet >> 4) ^ (packet >> 8)) & 0x0F; + packet = (packet << 4) | crc; + + // Build symbols + rmt_symbol_word_t symbols[32] = {}; + size_t count = 0; + + buildFrameSymbols(packet, symbols, count); + + // Transmit + rmt_disable(_channel); // Ensure safe restart + rmt_enable(_channel); + + rmt_transmit(_channel, _encoder, symbols, count * sizeof(rmt_symbol_word_t), &_tx_config); } -// ...simplest but only for testing -DShotRMT::DShotRMT(uint8_t pin) +void DShotRMT::buildFrameSymbols(uint16_t dshot_packet, rmt_symbol_word_t *symbols, size_t &count) { - // Initialize the dshot_config structure with the arguments passed to the constructor - dshot_config.gpio_num = static_cast(pin); - dshot_config.pin_num = pin; - dshot_config.rmt_channel = static_cast(RMT_CHANNEL_MAX -1); - dshot_config.mem_block_num = RMT_CHANNEL_MAX - 1; + uint32_t ticks_per_bit = 0; + uint32_t ticks_zero_high = 0; + uint32_t ticks_one_high = 0; - // Create an empty packet using the DSHOT_NULL_PACKET and the buildTxRmtItem function - buildTxRmtItem(DSHOT_NULL_PACKET); -} - -DShotRMT::~DShotRMT() -{ - // Uninstall the RMT driver - rmt_driver_uninstall(dshot_config.rmt_channel); -} - -DShotRMT::DShotRMT(DShotRMT const &) -{ - // ...write me -} - -bool DShotRMT::begin(dshot_mode_t dshot_mode, bool is_bidirectional) -{ - // Set DShot configuration parameters based on input parameters - dshot_config.mode = dshot_mode; - dshot_config.clk_div = DSHOT_CLK_DIVIDER; - dshot_config.name_str = dshot_mode_name[dshot_mode]; - dshot_config.is_bidirectional = is_bidirectional; - - // Set timing parameters based on selected DShot mode - switch (dshot_config.mode) + switch (_mode) { case DSHOT150: - dshot_config.ticks_per_bit = 64; - dshot_config.ticks_zero_high = 24; - dshot_config.ticks_one_high = 48; + ticks_per_bit = 67; + ticks_zero_high = 25; + ticks_one_high = 50; break; - case DSHOT300: - dshot_config.ticks_per_bit = 32; - dshot_config.ticks_zero_high = 12; - dshot_config.ticks_one_high = 24; + ticks_per_bit = 33; + ticks_zero_high = 12; + ticks_one_high = 25; break; - case DSHOT600: - dshot_config.ticks_per_bit = 16; - dshot_config.ticks_zero_high = 6; - dshot_config.ticks_one_high = 12; - break; - - case DSHOT1200: - dshot_config.ticks_per_bit = 8; - dshot_config.ticks_zero_high = 3; - dshot_config.ticks_one_high = 6; - break; - - // Default case to handle invalid input - default: - dshot_config.ticks_per_bit = 0; - dshot_config.ticks_zero_high = 0; - dshot_config.ticks_one_high = 0; + ticks_per_bit = 17; + ticks_zero_high = 6; + ticks_one_high = 13; break; } - // Calculate low signal timing - dshot_config.ticks_zero_low = (dshot_config.ticks_per_bit - dshot_config.ticks_zero_high); - dshot_config.ticks_one_low = (dshot_config.ticks_per_bit - dshot_config.ticks_one_high); + uint32_t ticks_zero_low = ticks_per_bit - ticks_zero_high; + uint32_t ticks_one_low = ticks_per_bit - ticks_one_high; - // Set up RMT configuration for DShot transmission - dshot_tx_rmt_config.rmt_mode = RMT_MODE_TX; - dshot_tx_rmt_config.channel = dshot_config.rmt_channel; - dshot_tx_rmt_config.gpio_num = dshot_config.gpio_num; - dshot_tx_rmt_config.mem_block_num = dshot_config.mem_block_num; - dshot_tx_rmt_config.clk_div = dshot_config.clk_div; - dshot_tx_rmt_config.tx_config.loop_en = false; - dshot_tx_rmt_config.tx_config.carrier_en = false; - dshot_tx_rmt_config.tx_config.idle_output_en = true; - - // Set idle level for RMT transmission based on input parameter - if (dshot_config.is_bidirectional) + // Encode 16 bits + for (int i = 15; i >= 0; i--) { - dshot_tx_rmt_config.tx_config.idle_level = RMT_IDLE_LEVEL_HIGH; - } - else - { - dshot_tx_rmt_config.tx_config.idle_level = RMT_IDLE_LEVEL_LOW; + bool bit = (dshot_packet >> i) & 0x01; + symbols[count].level0 = 1; + symbols[count].duration0 = bit ? ticks_one_high : ticks_zero_high; + symbols[count].level1 = 0; + symbols[count].duration1 = bit ? ticks_one_low : ticks_zero_low; + count++; } - // Set up selected DShot mode - rmt_config(&dshot_tx_rmt_config); - - // Install RMT driver and return result - return rmt_driver_install(dshot_tx_rmt_config.channel, 0, 0); -} - -// Define a function to send a DShot command over an RMT interface to control a brushless motor's speed. -void DShotRMT::sendThrottleValue(uint16_t throttle_value) -{ - dshot_packet_t dshot_rmt_packet = {}; - - // Check if the throttle value is less than the minimum allowed value for the DShot protocol. - if (throttle_value < DSHOT_THROTTLE_MIN) - { - throttle_value = DSHOT_THROTTLE_MIN; - } - - // Check if the throttle value is greater than the maximum allowed value for the DShot protocol. - if (throttle_value > DSHOT_THROTTLE_MAX) - { - throttle_value = DSHOT_THROTTLE_MAX; - } - - dshot_rmt_packet.throttle_value = throttle_value; - - // Telemetric using additional pin on the ESC is not supported. - dshot_rmt_packet.telemetric_request = NO_TELEMETRIC; - - // Calculate the checksum for the DShot packet using the calculateCRC function. - dshot_rmt_packet.checksum = calculateCRC(dshot_rmt_packet); - - // Send the DShot packet over the RMT interface to control the motor's speed. - sendRmtPaket(dshot_rmt_packet); -} - -// This method builds the RMT data transmission sequence for the DShot protocol -rmt_item32_t *DShotRMT::buildTxRmtItem(uint16_t parsed_packet) -{ - // Check if DShot is set to bidirectional mode - if (dshot_config.is_bidirectional) - { - // If bidirectional, invert the high/low bits - for (int i = 0; i < DSHOT_PAUSE_BIT; i++, parsed_packet <<= 1) - { - if (parsed_packet & 0b1000000000000000) - { - // Set RMT item for a logic high signal - dshot_tx_rmt_item[i].duration0 = dshot_config.ticks_one_low; - dshot_tx_rmt_item[i].duration1 = dshot_config.ticks_one_high; - } - else - { - // Set RMT item for a logic low signal - dshot_tx_rmt_item[i].duration0 = dshot_config.ticks_zero_low; - dshot_tx_rmt_item[i].duration1 = dshot_config.ticks_zero_high; - } - - // Set level of RMT item - dshot_tx_rmt_item[i].level0 = 0; - dshot_tx_rmt_item[i].level1 = 1; - } - } - else - { - // If not bidirectional, set the RMT items as usual - for (int i = 0; i < DSHOT_PAUSE_BIT; i++, parsed_packet <<= 1) - { - if (parsed_packet & 0b1000000000000000) - { - // Set RMT item for a logic high signal - dshot_tx_rmt_item[i].duration0 = dshot_config.ticks_one_high; - dshot_tx_rmt_item[i].duration1 = dshot_config.ticks_one_low; - } - else - { - // Set RMT item for a logic low signal - dshot_tx_rmt_item[i].duration0 = dshot_config.ticks_zero_high; - dshot_tx_rmt_item[i].duration1 = dshot_config.ticks_zero_low; - } - - // Set level of RMT item - dshot_tx_rmt_item[i].level0 = 1; - dshot_tx_rmt_item[i].level1 = 0; - } - } - - // Set end marker for each frame - if (dshot_config.is_bidirectional) - { - dshot_tx_rmt_item[DSHOT_PAUSE_BIT].level0 = 1; - dshot_tx_rmt_item[DSHOT_PAUSE_BIT].level1 = 0; - } - else - { - dshot_tx_rmt_item[DSHOT_PAUSE_BIT].level0 = 0; - dshot_tx_rmt_item[DSHOT_PAUSE_BIT].level1 = 1; - } - - // Add packet seperator aka DShot Pause. - dshot_tx_rmt_item[DSHOT_PAUSE_BIT].duration1 = DSHOT_PAUSE; - - // Return the rmt_item - return dshot_tx_rmt_item; -} - -// Calculates a CRC value for a DShot digital control signal packet -uint16_t DShotRMT::calculateCRC(const dshot_packet_t &dshot_packet) -{ - uint16_t crc; - - // Combine the throttle value and telemetric request flag into a 16-bit packet value - const uint16_t packet = (dshot_packet.throttle_value << 1) | dshot_packet.telemetric_request; - - // Calculate the CRC value using different bitwise operations depending on the DShot configuration - if (dshot_config.is_bidirectional) - { - // Bidirectional configuration: perform a bitwise negation of the result of XORing the packet with its right-shifted values by 4 and 8 bits, - // and then bitwise AND the result with 0x0F - const uint16_t intermediate_result = packet ^ (packet >> 4) ^ (packet >> 8); - crc = (~intermediate_result) & 0x0F; - } - else - { - // Unidirectional configuration: XOR the packet with its right-shifted values by 4 and 8 bits, - // and then bitwise AND the result with 0x0F - crc = (packet ^ (packet >> 4) ^ (packet >> 8)) & 0x0F; - } - - // Return the calculated CRC value as a 16-bit unsigned integer - return crc; -} - -uint16_t DShotRMT::parseRmtPaket(const dshot_packet_t &dshot_packet) -{ - uint16_t parsedRmtPaket = DSHOT_NULL_PACKET; - uint16_t crc = calculateCRC(dshot_packet); - - // Complete the paket - parsedRmtPaket = (dshot_packet.throttle_value << 1) | dshot_packet.telemetric_request; - parsedRmtPaket = (parsedRmtPaket << 4) | crc; - - return parsedRmtPaket; -} - -// Output using ESP32 RMT -void DShotRMT::sendRmtPaket(const dshot_packet_t &dshot_packet) -{ - buildTxRmtItem(parseRmtPaket(dshot_packet)); - - rmt_write_items(dshot_tx_rmt_config.channel, dshot_tx_rmt_item, DSHOT_PACKET_LENGTH, false); + // Add pause + symbols[count].level0 = 0; + symbols[count].duration0 = ticks_per_bit * PAUSE_BITS; + symbols[count].level1 = 0; + symbols[count].duration1 = 0; + count++; } diff --git a/DShotRMT.h b/DShotRMT.h index cb1e446..494d054 100644 --- a/DShotRMT.h +++ b/DShotRMT.h @@ -1,201 +1,53 @@ -// -// Name: DShotRMT.h -// Created: 20.03.2021 00:49:15 -// Author: derdoktor667 -// +/** + * @file DShotRMT.h + * @brief DShot signal generation using ESP32 RMT with continuous repeat and pause between frames + * @author Wastl Kraus + * @date 2025-06-11 + * @license MIT + */ -#ifndef _DSHOTRMT_h -#define _DSHOTRMT_h +#pragma once #include +#include -// The RMT (Remote Control) module library is used for generating the DShot signal. -#include +static constexpr auto DSHOT_THROTTLE_FAILSAVE = 0; +static constexpr auto DSHOT_THROTTLE_MIN = 48; +static constexpr auto DSHOT_THROTTLE_MAX = 2047; +static constexpr auto DEFAULT_RES_HZ = 10 * 1000 * 1000; // 10 MHz resolution +static constexpr auto PAUSE_BITS = 21; +static constexpr auto DSHOT_NULL_PACKET = 0b0000000000000000; -// Defines the library version -constexpr auto DSHOT_LIB_VERSION = "0.2.4"; - -// Constants related to the DShot protocol -constexpr auto DSHOT_CLK_DIVIDER = 8; // Slow down RMT clock to 0.1 microseconds / 100 nanoseconds per cycle -constexpr auto DSHOT_PACKET_LENGTH = 17; // Last pack is the pause -constexpr auto DSHOT_THROTTLE_MIN = 48; -constexpr auto DSHOT_THROTTLE_MAX = 2047; -constexpr auto DSHOT_NULL_PACKET = 0b0000000000000000; -constexpr auto DSHOT_PAUSE = 21; // 21-bit is recommended -constexpr auto DSHOT_PAUSE_BIT = 16; -constexpr auto F_CPU_RMT = (80 * 1000 * 1000); // unit: Hz -constexpr auto RMT_CYCLES_PER_SEC = (F_CPU_RMT / DSHOT_CLK_DIVIDER); -constexpr auto RMT_CYCLES_PER_ESP_CYCLE = (F_CPU / RMT_CYCLES_PER_SEC); - -// Enumeration for the DShot mode +/// DShot Mode typedef enum dshot_mode_e { - DSHOT_OFF, DSHOT150, DSHOT300, - DSHOT600, - DSHOT1200 + DSHOT600 } dshot_mode_t; -// Array of human-readable DShot mode names -static const char *const dshot_mode_name[] = { - "DSHOT_OFF", - "DSHOT150", - "DSHOT300", - "DSHOT600", - "DSHOT1200"}; - -// Enumeration for telemetric request -typedef enum telemetric_request_e -{ - NO_TELEMETRIC, - ENABLE_TELEMETRIC, -} telemetric_request_t; - -// Structure for DShot packets -typedef struct dshot_packet_s -{ - uint16_t throttle_value : 11; - telemetric_request_t telemetric_request : 1; - uint16_t checksum : 4; -} dshot_packet_t; - -// Structure for eRPM packets -typedef struct eRPM_packet_s -{ - uint16_t eRPM_data : 12; - uint8_t checksum : 4; -} eRPM_packet_t; - -// return states of the RPM getting function -typedef enum dshot_erpm_exit_mode_e -{ - DECODE_SUCCESS = 0, - ERR_EMPTY_QUEUE, - ERR_NO_PACKETS, - ERR_CHECKSUM_FAIL, - ERR_BIDIRECTION_DISABLED, - -} dshot_erpm_exit_mode_t; - -// Structure for all settings for the DShot mode -typedef struct dshot_config_s -{ - dshot_mode_t mode; - String name_str; - bool is_bidirectional; - gpio_num_t gpio_num; - uint8_t pin_num; - rmt_channel_t rmt_channel; - uint8_t mem_block_num; - uint16_t ticks_per_bit; - uint8_t clk_div; - uint16_t ticks_zero_high; - uint16_t ticks_zero_low; - uint16_t ticks_one_high; - uint16_t ticks_one_low; -} dshot_config_t; - -// The official DShot Commands -typedef enum dshot_cmd_e -{ - DSHOT_CMD_MOTOR_STOP = 0, // Currently not implemented - STOP Motors - DSHOT_CMD_BEEP1, // Wait at least length of beep (380ms) before next command - DSHOT_CMD_BEEP2, // Wait at least length of beep (380ms) before next command - DSHOT_CMD_BEEP3, // Wait at least length of beep (400ms) before next command - DSHOT_CMD_BEEP4, // Wait at least length of beep (400ms) before next command - DSHOT_CMD_BEEP5, // Wait at least length of beep (400ms) before next command - DSHOT_CMD_ESC_INFO, // Currently not implemented - DSHOT_CMD_SPIN_DIRECTION_1, // Need 6x, no wait required - DSHOT_CMD_SPIN_DIRECTION_2, // Need 6x, no wait required - DSHOT_CMD_3D_MODE_OFF, // Need 6x, no wait required - DSHOT_CMD_3D_MODE_ON, // Need 6x, no wait required - DSHOT_CMD_SETTINGS_REQUEST, // Currently not implemented - DSHOT_CMD_SAVE_SETTINGS, // Need 6x, wait at least 12ms before next command - DSHOT_CMD_SPIN_DIRECTION_NORMAL, // Need 6x, no wait required - DSHOT_CMD_SPIN_DIRECTION_REVERSED, // Need 6x, no wait required - DSHOT_CMD_LED0_ON, // Currently not implemented - DSHOT_CMD_LED1_ON, // Currently not implemented - DSHOT_CMD_LED2_ON, // Currently not implemented - DSHOT_CMD_LED3_ON, // Currently not implemented - DSHOT_CMD_LED0_OFF, // Currently not implemented - DSHOT_CMD_LED1_OFF, // Currently not implemented - DSHOT_CMD_LED2_OFF, // Currently not implemented - DSHOT_CMD_LED3_OFF, // Currently not implemented - DSHOT_CMD_36, // Not yet assigned - DSHOT_CMD_37, // Not yet assigned - DSHOT_CMD_38, // Not yet assigned - DSHOT_CMD_39, // Not yet assigned - DSHOT_CMD_40, // Not yet assigned - DSHOT_CMD_41, // Not yet assigned - DSHOT_CMD_SIGNAL_LINE_TEMPERATURE_TELEMETRY, // No wait required - DSHOT_CMD_SIGNAL_LINE_VOLTAGE_TELEMETRY, // No wait required - DSHOT_CMD_SIGNAL_LINE_CURRENT_TELEMETRY, // No wait required - DSHOT_CMD_SIGNAL_LINE_CONSUMPTION_TELEMETRY, // No wait required - DSHOT_CMD_SIGNAL_LINE_ERPM_TELEMETRY, // No wait required - DSHOT_CMD_SIGNAL_LINE_ERPM_PERIOD_TELEMETRY, // No wait required (also command 47) - DSHOT_CMD_MAX = 47 -} dshot_cmd_t; - -// ...Mapping for GCR -static const unsigned char GCR_encode[16] = - { - 0x19, 0x1B, 0x12, 0x13, - 0x1D, 0x15, 0x16, 0x17, - 0x1A, 0x09, 0x0A, 0x0B, - 0x1E, 0x0D, 0x0E, 0x0F}; - -// ...shifting 5 bits > 4 bits (0xff => invalid) -static const unsigned char GCR_decode[32] = - { - 0xFF, 0xFF, 0xFF, 0xFF, // 0 - 3 - 0xFF, 0xFF, 0xFF, 0xFF, // 4 - 7 - 0xFF, 9, 10, 11, // 8 - 11 - 0xFF, 13, 14, 15, // 12 - 15 - - 0xFF, 0xFF, 2, 3, // 16 - 19 - 0xFF, 5, 6, 7, // 20 - 23 - 0xFF, 0, 8, 1, // 24 - 27 - 0xFF, 4, 12, 0xFF, // 28 - 31 -}; - -// The main DShotRMT class class DShotRMT { public: - // Constructor for the DShotRMT class - DShotRMT(gpio_num_t gpio, rmt_channel_t rmtChannel); - DShotRMT(uint8_t pin, uint8_t channel); - DShotRMT(uint8_t pin); + DShotRMT(gpio_num_t gpio, dshot_mode_t mode = DSHOT300, bool isBidirectional = false); - // Destructor for the DShotRMT class - ~DShotRMT(); + void begin(); + void setThrottle(uint16_t throttle, bool telemetry = false); - // Copy constructor for the DShotRMT class - DShotRMT(DShotRMT const &); - - // The begin() function initializes the DShotRMT class with - // a given DShot mode (DSHOT_OFF, DSHOT150, DSHOT300, DSHOT600, DSHOT1200) - // and a bidirectional flag. It returns a boolean value - // indicating whether or not the initialization was successful. - bool begin(dshot_mode_t dshot_mode = DSHOT_OFF, bool is_bidirectional = false); - - // The sendThrottleValue() function sends a DShot packet with a given - // throttle value (between 49 and 2047) and an optional telemetry - // request flag. - // void sendThrottleValue(uint16_t throttle_value, telemetric_request_t telemetric_request = NO_TELEMETRIC); - void sendThrottleValue(uint16_t throttle_value); + gpio_num_t getGPIO() const { return _gpio; } + dshot_mode_t getDShotMode() const { return _mode; } private: - rmt_item32_t dshot_tx_rmt_item[DSHOT_PACKET_LENGTH]; // An array of RMT items used to send a DShot packet. - rmt_config_t dshot_tx_rmt_config; // The RMT configuration used for sending DShot packets. - dshot_config_t dshot_config; // The configuration for the DShot mode. + gpio_num_t _gpio; + dshot_mode_t _mode; + bool _isBidirectional; - rmt_item32_t *buildTxRmtItem(uint16_t parsed_packet); // Constructs an RMT item from a parsed DShot packet. - uint16_t calculateCRC(const dshot_packet_t &dshot_packet); // Calculates the CRC checksum for a DShot packet. - uint16_t parseRmtPaket(const dshot_packet_t &dshot_packet); // Parses an RMT packet to obtain a DShot packet. + rmt_channel_handle_t _channel = nullptr; + rmt_encoder_handle_t _encoder = nullptr; + rmt_transmit_config_t _tx_config = {}; - void sendRmtPaket(const dshot_packet_t &dshot_packet); // Sends a DShot packet via RMT. + uint16_t _lastThrottle = 0; + bool _lastTelemetry = false; + + void buildFrameSymbols(uint16_t frame, rmt_symbol_word_t *symbols, size_t &count); }; - -#endif diff --git a/README.md b/README.md index 6dfa8ed..87f3ed3 100644 --- a/README.md +++ b/README.md @@ -1,44 +1,97 @@ - [![Arduino CI](https://github.com/derdoktor667/DShotRMT/actions/workflows/esp32.yml/badge.svg?event=push)](https://github.com/derdoktor667/DShotRMT/actions/workflows/esp32.yml) +[![Arduino CI](https://github.com/derdoktor667/DShotRMT/actions/workflows/esp32.yml/badge.svg?event=push)](https://github.com/derdoktor667/DShotRMT/actions/workflows/esp32.yml) -## This is going to be deprecated soon. Rewriting for new IDF APIs. Status can be seen by using "reIDF5" branch. +# DShot ESP32 Library using RMT (Rewrite for ESP-IDF 5) -## DShot ESP32 Library utilizing RMT +This is a complete rewrite of the original DShotRMT library to support the new ESP-IDF 5 RMT encoder API (`rmt_tx.h`). +The library sends continuous DShot frames with a configurable pause between them and supports all standard DShot modes (150, 300, 600). -### The DShot Protocol -The DSHOT protocol consists of transmitting 16-bit packets to the ESCs: 11-bit throttle value, 1-bit to request telemetry and a 4-bit checksum. There are three major protocol speeds: DSHOT150, DSHOT300 and DSHOT600. +The old Version without encoding (rmt.h) is still available by using "oldAPI" Branch. -| DSHOT | Bitrate | TH1 | TH0 | Bit Time µs | Frame Time µs | -|-------|------------|-------|--------|------------|---------------| -| 150 | 150kbit/s | 5.00 | 2.50 | 6.67 | 106.72 | -| 300 | 300kbit/s | 2.50 | 1.25 | 3.33 | 53.28 | -| 600 | 600kbit/s | 1.25 | 0.625 | 1.67 | 26.72 | -| 1200 | 1200kbit/s | 0.625 | 0.313 | 0.83 | 13.28 | +--- -#### Calculating the CRC -The checksum is calculated over the throttle value and the telemetry bit, so the “first” 12 bits our value in the following example: +## The DShot Protocol - crc = (value ^ (value >> 4) ^ (value >> 8)) & 0x0F; +The DShot protocol transmits 16-bit packets to brushless ESCs: -### Bidirectional DSHOT -Bidirectional DSHOT is also known as inverted DSHOT, because the signal level is inverted, so 1 is low and a 0 is high. This is done in order to let the ESC know, that we are operating in bidirectional mode and that it should be sending back eRPM telemetry packages. +- 11-bit throttle value +- 1-bit telemetry request +- 4-bit checksum -#### Calculating the Bidirectional CRC -The calculation of the checksum is basically the same, just before the last step the values are inverted: +Data is transmitted MSB-first. Pulse timing depends on the selected DShot mode. - crc = (~(value ^ (value >> 4) ^ (value >> 8))) & 0x0F; +| DSHOT | Bitrate | TH1 | TH0 | Bit Time (µs) | Frame Time (µs) | +|-------|-------------|-------|--------|---------------|-----------------| +| 150 | 150 kbit/s | 5.00 | 2.50 | 6.67 | ~106.72 | +| 300 | 300 kbit/s | 2.50 | 1.25 | 3.33 | ~53.28 | +| 600 | 600 kbit/s | 1.25 | 0.625 | 1.67 | ~26.72 | -### Using RMT on ESP32 -The RMT (Remote Control) is a peripheral designed to generate accurate and stable signals to control external devices such as LEDs, motors, and other peripherals. It is well suited for generating the DShot signals in a high-performance and accurate way on the ESP32 platform. +Each frame is followed by a 21-bit time pause at low level. This helps ESCs detect separate frames. -#### Advantages of using RMT -- Generates accurate signals -- Supports programmable timing -- Configurable number of channels +--- -#### DShot RMT Library for ESP32 -The DShot RMT Library for ESP32 provides a convenient way of generating DShot signals using the RMT peripheral on the ESP32 platform. The library supports all three major DShot speeds: DSHOT150, DSHOT300, and DSHOT600. +## Checksum Calculation -#### References -- [DSHOT - the missing Handbook](https://brushlesswhoop.com/dshot-and-bidirectional-dshot/) -- [DSHOT in the Dark](https://dmrlawson.co.uk/index.php/2017/12/04/dshot-in-the-dark/) +The checksum is calculated over the first 12 bits (throttle + telemetry): + +```c +crc = (value ^ (value >> 4) ^ (value >> 8)) & 0x0F; +``` + +For Bidirectional DShot (not yet implemented), the CRC is inverted: + +```c +crc = (~(value ^ (value >> 4) ^ (value >> 8))) & 0x0F; +``` + +--- + +## RMT on the ESP32 + +The RMT peripheral on the ESP32 is perfect for generating precise time-based signals like DShot. + +### Advantages: + +- Hardware-timed pulses +- CPU-independent signal generation +- Loop mode with inter-frame pause +- Reliable under system load + +--- + +## About This Library + +This C++ library provides a simple class to generate DShot signals using any RMT-capable GPIO. +It uses a `copy_encoder` to continuously send a prebuilt symbol buffer. New throttle values are applied only when they change. + +### Supported Modes: + +- DSHOT150 +- DSHOT300 (default) +- DSHOT600 + +### Frame Structure: + +- 16-bit DShot data +- 21-bit times worth of pause (LOW) + +--- + +## References + +- [DSHOT – the missing Handbook](https://brushlesswhoop.com/dshot-and-bidirectional-dshot/) +- [DSHOT in the Dark](https://dmrlawson.co.uk/index.php/2017/12/04/dshot-in-the-dark/) - [ESP32 Technical Reference Manual](https://www.espressif.com/sites/default/files/documentation/esp32_technical_reference_manual_en.pdf) + +--- + +## License + +MIT License – see LICENSE + +--- + +## Author + +Wastl Kraus +GitHub: [@derdoktor667](https://github.com/derdoktor667) +Website: [wir-sind-die-matrix.de](https://wir-sind-die-matrix.de) diff --git a/examples/dshot300/dshot300.ino b/examples/dshot300/dshot300.ino index ee5d0e1..14cab61 100644 --- a/examples/dshot300/dshot300.ino +++ b/examples/dshot300/dshot300.ino @@ -1,64 +1,68 @@ -/* - * Title: dshot300.ino - * Author: derdoktor667 - * Date: 2023-04-13 - * - * Description: A simple example of using the DShotRMT library to - * generate a DShot300 signal for blheli_s escs. +/** + * @file dshot300.ino + * @brief Demo sketch for continuous DShot signal using ESP32 and DShotRMT library + * @author Wastl Kraus + * @date 2025-06-07 + * @license MIT */ #include #include -// USB serial port needed for this example -const auto USB_SERIAL_BAUD = 115200; +// USB serial port settings #define USB_Serial Serial +const uint32_t USB_SERIAL_BAUD = 115200; -// Define the GPIO pin connected to the motor and the DShot protocol used -const auto MOTOR01_PIN = GPIO_NUM_17; -const auto DSHOT_MODE = DSHOT300; +// Motor configuration +const gpio_num_t MOTOR01_PIN = GPIO_NUM_17; +const dshot_mode_t DSHOT_MODE = DSHOT300; -// Define the failsafe and initial throttle values -const auto FAILSAFE_THROTTLE = 999; -const auto INITIAL_THROTTLE = 48; - -// Initialize a DShotRMT object for the motor -DShotRMT motor01(MOTOR01_PIN, RMT_CHANNEL_0); +// Create DShotRMT instance +DShotRMT motor01(MOTOR01_PIN, DSHOT_MODE); void setup() { USB_Serial.begin(USB_SERIAL_BAUD); - // Start generating DShot signal for the motor - motor01.begin(DSHOT_MODE); + // Wait for serial port + while (!USB_Serial) + delay(10); - Serial.println("DShotRMT Demo started."); - Serial.println("Enter a throttle value (0–2047):"); + USB_Serial.println("DShotRMT Demo started."); + USB_Serial.println("Enter a throttle value (48–2047):"); + + motor01.begin(); + + // Arm ESC with minimum throttle + motor01.setThrottle(DSHOT_THROTTLE_MIN); } void loop() { - // Read the throttle value from the USB serial input - int throttle_input = read_SerialThrottle(); + // Simple as can be + int throttle_input = readSerialThrottle(); - // Send the throttle value to the motor - motor01.sendThrottleValue(throttle_input); + motor01.setThrottle(throttle_input); } -// ...just for this example -// Read the throttle value from the USB serial input -int read_SerialThrottle() +// Reads throttle value from serial input +int readSerialThrottle() { - static int last_throttle = INITIAL_THROTTLE; + static int last_throttle = DSHOT_THROTTLE_MIN; if (USB_Serial.available() > 0) { - auto throttle_input = (USB_Serial.readStringUntil('\n')).toInt(); + String input = USB_Serial.readStringUntil('\n'); + int throttle_input = input.toInt(); + + // Clamp the value to the DShot range + throttle_input = constrain(throttle_input, 48, 2047); last_throttle = throttle_input; - Serial.print("Throttle set to: "); - Serial.println(last_throttle); - Serial.println(" "); - Serial.println("Enter a throttle value (0–2047):"); + + USB_Serial.print("Throttle set to: "); + USB_Serial.println(last_throttle); + + USB_Serial.println("Enter a throttle value (48–2047):"); } return last_throttle; -} +} \ No newline at end of file diff --git a/library.properties b/library.properties index 6e1c2ce..46be3b7 100644 --- a/library.properties +++ b/library.properties @@ -1,9 +1,9 @@ name=DShotRMT -version=0.2.4 +version=0.4.0 author=derdoktor667 maintainer=derdoktor667 sentence=DShotRMT Library supporting all DShot Types and speeds. Tested with BlHeli_S. paragraph=This library can control a BlHeli_S by using encoded DShot commands. -category=Device Control +category=Signal Input/Output url=https://github.com/derdoktor667/DShotRMT architectures=esp32