From 8cfe24e1324f3fb0e4a194b73d942e11f53e431e Mon Sep 17 00:00:00 2001 From: Wastl Kraus Date: Fri, 13 Jun 2025 00:45:48 +0200 Subject: [PATCH] ...add BiDirectional support --- DShotRMT.cpp | 98 ++++++++++++++++++++++------------ DShotRMT.h | 16 +++--- README.md | 24 +++++---- examples/dshot300/dshot300.ino | 25 ++++----- 4 files changed, 103 insertions(+), 60 deletions(-) diff --git a/DShotRMT.cpp b/DShotRMT.cpp index aea8923..4e8fd4f 100644 --- a/DShotRMT.cpp +++ b/DShotRMT.cpp @@ -1,6 +1,6 @@ /** * @file DShotRMT.cpp - * @brief Implementation of continuous DShot signal using ESP32 RMT encoder API with pause between frames + * @brief DShot signal generation using ESP32 RMT with continuous repeat and pause between frames, including BiDirectional support * @author Wastl Kraus * @date 2025-06-11 * @license MIT @@ -8,11 +8,28 @@ #include +// DShotRMT::DShotRMT(gpio_num_t gpio, dshot_mode_t mode, bool isBidirectional) : _gpio(gpio), _mode(mode), _isBidirectional(isBidirectional) {} +// void DShotRMT::begin() { + if (_isBidirectional) + { + rmt_rx_channel_config_t rmt_rx_channel_config = { + .gpio_num = _gpio, + .clk_src = RMT_CLK_SRC_DEFAULT, + .resolution_hz = DEFAULT_RES_HZ, + .mem_block_symbols = 64, + .flags = { + .invert_in = false, + .with_dma = false}}; + + rmt_new_rx_channel(&rmt_rx_channel_config, &_rmt_rx_channel); + rmt_enable(_rmt_rx_channel); + } + rmt_tx_channel_config_t rmt_tx_channel_config = { .gpio_num = _gpio, .clk_src = RMT_CLK_SRC_DEFAULT, @@ -20,54 +37,71 @@ void DShotRMT::begin() .mem_block_symbols = 64, .trans_queue_depth = 1, .flags = { + // invert Signal if BiDirectional DShot Mode .invert_out = _isBidirectional, .with_dma = false}}; - rmt_new_tx_channel(&rmt_tx_channel_config, &_rmt_channel); - rmt_enable(_rmt_channel); + rmt_new_tx_channel(&rmt_tx_channel_config, &_rmt_tx_channel); + rmt_enable(_rmt_tx_channel); - // Create encoder only once - if (!_encoder) + // Create new encoder + if (!_dshot_encoder) { rmt_copy_encoder_config_t enc_cfg = {}; - rmt_new_copy_encoder(&enc_cfg, &_encoder); + rmt_new_copy_encoder(&enc_cfg, &_dshot_encoder); } - _transmit_config.loop_count = -1; // Infinite loop - _transmit_config.flags.eot_level = 0; + _transmit_config.loop_count = -1; + _transmit_config.flags.eot_level = _isBidirectional; } -void DShotRMT::setThrottle(uint16_t throttle, bool telemetry) +// +void DShotRMT::setThrottle(uint16_t throttle) { - // Send only new Throttle values - static uint16_t _lastThrottle = 0; - - // Clamp to 11 bits - throttle &= 0x07FF; + // Fake 10 Bit transformation to be sure + throttle = throttle & 0b0000011111111111; + // Has Throttle really changed? if (throttle == _lastThrottle) return; _lastThrottle = throttle; - // 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; + // Prepare Throttle paket + dshot_packet = (throttle << 1) | (_isBidirectional ? 1 : 0); - // Build symbols + // CRC Calculation + uint16_t crc = 0; + + if (_isBidirectional) + { + // Calculate checksum in inverted/BiDirectional Mode + crc = (~(dshot_packet ^ (dshot_packet >> 4) ^ (dshot_packet >> 8))) & 0x0F; + } + else + { + // + crc = (dshot_packet ^ (dshot_packet >> 4) ^ (dshot_packet >> 8)) & 0x0F; + } + + // attach CRC to DShot Paket + dshot_packet = (dshot_packet << 4) | crc; + + // Encode DShot Paket rmt_symbol_word_t symbols[32] = {}; size_t count = 0; - buildFrameSymbols(packet, symbols, count); + buildFrameSymbols(dshot_packet, symbols, count); - // Transmit - rmt_disable(_rmt_channel); // Ensure safe restart - rmt_enable(_rmt_channel); + // Reset RMT Signnal loop before sending new value + rmt_disable(_rmt_tx_channel); + rmt_enable(_rmt_tx_channel); - rmt_transmit(_rmt_channel, _encoder, symbols, count * sizeof(rmt_symbol_word_t), &_transmit_config); + // Finally transmit the complete DShot Paket + rmt_transmit(_rmt_tx_channel, _dshot_encoder, symbols, count * sizeof(rmt_symbol_word_t), &_transmit_config); } +// void DShotRMT::buildFrameSymbols(uint16_t dshot_packet, rmt_symbol_word_t *symbols, size_t &count) { uint32_t ticks_per_bit = 0; @@ -77,26 +111,25 @@ void DShotRMT::buildFrameSymbols(uint16_t dshot_packet, rmt_symbol_word_t *symbo switch (_mode) { case DSHOT150: - ticks_per_bit = 67; - ticks_zero_high = 25; - ticks_one_high = 50; + ticks_per_bit = 64; + ticks_zero_high = 24; + ticks_one_high = 48; break; case DSHOT300: - ticks_per_bit = 33; + ticks_per_bit = 32; ticks_zero_high = 12; - ticks_one_high = 25; + ticks_one_high = 24; break; case DSHOT600: - ticks_per_bit = 17; + ticks_per_bit = 16; ticks_zero_high = 6; - ticks_one_high = 13; + ticks_one_high = 12; break; } uint32_t ticks_zero_low = ticks_per_bit - ticks_zero_high; uint32_t ticks_one_low = ticks_per_bit - ticks_one_high; - // Encode 16 bits for (int i = 15; i >= 0; i--) { bool bit = (dshot_packet >> i) & 0x01; @@ -107,7 +140,6 @@ void DShotRMT::buildFrameSymbols(uint16_t dshot_packet, rmt_symbol_word_t *symbo count++; } - // Add pause symbols[count].level0 = 0; symbols[count].duration0 = ticks_per_bit * PAUSE_BITS; symbols[count].level1 = 0; diff --git a/DShotRMT.h b/DShotRMT.h index a13cd7a..ac8bdbb 100644 --- a/DShotRMT.h +++ b/DShotRMT.h @@ -1,6 +1,6 @@ /** * @file DShotRMT.h - * @brief DShot signal generation using ESP32 RMT with continuous repeat and pause between frames + * @brief DShot signal generation using ESP32 RMT with continuous repeat and pause between frames, including BiDirectional support * @author Wastl Kraus * @date 2025-06-11 * @license MIT @@ -10,15 +10,15 @@ #include #include +#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 DEFAULT_RES_HZ = 10 * 1000 * 1000; // 10 MHz static constexpr auto PAUSE_BITS = 21; static constexpr auto DSHOT_NULL_PACKET = 0b0000000000000000; -/// DShot Mode typedef enum dshot_mode_e { DSHOT150, @@ -32,7 +32,7 @@ public: DShotRMT(gpio_num_t gpio, dshot_mode_t mode = DSHOT300, bool isBidirectional = false); void begin(); - void setThrottle(uint16_t throttle, bool telemetry = false); + void setThrottle(uint16_t throttle); gpio_num_t getGPIO() const { return _gpio; } dshot_mode_t getDShotMode() const { return _mode; } @@ -41,10 +41,14 @@ private: gpio_num_t _gpio; dshot_mode_t _mode; bool _isBidirectional; + uint16_t _lastThrottle = 0; + uint16_t dshot_packet = DSHOT_NULL_PACKET; - rmt_channel_handle_t _rmt_channel = nullptr; - rmt_encoder_handle_t _encoder = nullptr; + rmt_channel_handle_t _rmt_tx_channel = nullptr; + rmt_channel_handle_t _rmt_rx_channel = nullptr; + rmt_encoder_handle_t _dshot_encoder = nullptr; rmt_transmit_config_t _transmit_config = {}; void buildFrameSymbols(uint16_t frame, rmt_symbol_word_t *symbols, size_t &count); + bool decodeTelemetrySymbol(const rmt_symbol_word_t *symbols, size_t count, uint16_t &result); }; diff --git a/README.md b/README.md index 87f3ed3..12dc236 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,12 @@ [![Arduino CI](https://github.com/derdoktor667/DShotRMT/actions/workflows/esp32.yml/badge.svg?event=push)](https://github.com/derdoktor667/DShotRMT/actions/workflows/esp32.yml) -# DShot ESP32 Library using RMT (Rewrite for ESP-IDF 5) +## DShotRMT - ESP32 Library (Rewrite for ESP-IDF 5) 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). +### Now with BiDirectional DShot Support!!! + The old Version without encoding (rmt.h) is still available by using "oldAPI" Branch. --- @@ -13,8 +15,8 @@ The old Version without encoding (rmt.h) is still available by using "oldAPI" Br The DShot protocol transmits 16-bit packets to brushless ESCs: -- 11-bit throttle value -- 1-bit telemetry request +- 11-bit throttle value +- 1-bit telemetry request - 4-bit checksum Data is transmitted MSB-first. Pulse timing depends on the selected DShot mode. @@ -25,19 +27,23 @@ Data is transmitted MSB-first. Pulse timing depends on the selected DShot mode. | 300 | 300 kbit/s | 2.50 | 1.25 | 3.33 | ~53.28 | | 600 | 600 kbit/s | 1.25 | 0.625 | 1.67 | ~26.72 | -Each frame is followed by a 21-bit time pause at low level. This helps ESCs detect separate frames. +Each frame is followed by a 21-bit time pause. This helps ESCs detect separate frames. --- ## Checksum Calculation -The checksum is calculated over the first 12 bits (throttle + telemetry): +The checksum is calculated over the first 12 bits (throttle + bit): ```c crc = (value ^ (value >> 4) ^ (value >> 8)) & 0x0F; ``` -For Bidirectional DShot (not yet implemented), the CRC is inverted: +### 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 telemetry packages. + +#### Calculating the Bidirectional CRC +The calculation of the checksum is basically the same as before, but the inverted: ```c crc = (~(value ^ (value >> 4) ^ (value >> 8))) & 0x0F; @@ -47,7 +53,7 @@ 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. +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. ### Advantages: @@ -63,7 +69,7 @@ The RMT peripheral on the ESP32 is perfect for generating precise time-based sig 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: +### Supported Modes (optional BiDirectional): - DSHOT150 - DSHOT300 (default) @@ -72,7 +78,7 @@ It uses a `copy_encoder` to continuously send a prebuilt symbol buffer. New thro ### Frame Structure: - 16-bit DShot data -- 21-bit times worth of pause (LOW) +- 21-bit times worth of pause --- diff --git a/examples/dshot300/dshot300.ino b/examples/dshot300/dshot300.ino index 07e916f..6e71efe 100644 --- a/examples/dshot300/dshot300.ino +++ b/examples/dshot300/dshot300.ino @@ -1,8 +1,8 @@ /** * @file dshot300.ino - * @brief Demo sketch for continuous DShot signal using ESP32 and DShotRMT library + * @brief Demo sketch for DShotRMT library * @author Wastl Kraus - * @date 2025-06-07 + * @date 2025-06-11 * @license MIT */ @@ -17,24 +17,25 @@ constexpr auto USB_SERIAL_BAUD = 115200; constexpr auto MOTOR01_PIN = GPIO_NUM_17; constexpr auto DSHOT_MODE = DSHOT300; -// Create DShotRMT instance -DShotRMT motor01(MOTOR01_PIN, DSHOT_MODE); +// BiDirectional DShot Support +constexpr auto IS_BIDIRECTIONAL = true; + +// Setup Motor Pin, DShot Mode and optional BiDirectional Support +DShotRMT motor01(MOTOR01_PIN, DSHOT_MODE, IS_BIDIRECTIONAL); void setup() { USB_Serial.begin(USB_SERIAL_BAUD); - // Wait for serial port - while (!USB_Serial) - delay(10); - - USB_Serial.println("DShotRMT Demo started."); - USB_Serial.println("Enter a throttle value (48–2047):"); - + // Initialize DShot Signal motor01.begin(); // Arm ESC with minimum throttle motor01.setThrottle(DSHOT_THROTTLE_MIN); + + // + USB_Serial.println("DShotRMT Demo started."); + USB_Serial.println("Enter a throttle value (48–2047):"); } void loop() @@ -67,4 +68,4 @@ int readSerialThrottle() } return last_throttle; -} \ No newline at end of file +}