...add BiDirectional support

This commit is contained in:
Wastl Kraus 2025-06-13 00:45:48 +02:00
parent 33bfb6a0be
commit 8cfe24e132
4 changed files with 103 additions and 60 deletions

View File

@ -1,6 +1,6 @@
/** /**
* @file DShotRMT.cpp * @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 * @author Wastl Kraus
* @date 2025-06-11 * @date 2025-06-11
* @license MIT * @license MIT
@ -8,11 +8,28 @@
#include <DShotRMT.h> #include <DShotRMT.h>
//
DShotRMT::DShotRMT(gpio_num_t gpio, dshot_mode_t mode, bool isBidirectional) DShotRMT::DShotRMT(gpio_num_t gpio, dshot_mode_t mode, bool isBidirectional)
: _gpio(gpio), _mode(mode), _isBidirectional(isBidirectional) {} : _gpio(gpio), _mode(mode), _isBidirectional(isBidirectional) {}
//
void DShotRMT::begin() 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 = { rmt_tx_channel_config_t rmt_tx_channel_config = {
.gpio_num = _gpio, .gpio_num = _gpio,
.clk_src = RMT_CLK_SRC_DEFAULT, .clk_src = RMT_CLK_SRC_DEFAULT,
@ -20,54 +37,71 @@ void DShotRMT::begin()
.mem_block_symbols = 64, .mem_block_symbols = 64,
.trans_queue_depth = 1, .trans_queue_depth = 1,
.flags = { .flags = {
// invert Signal if BiDirectional DShot Mode
.invert_out = _isBidirectional, .invert_out = _isBidirectional,
.with_dma = false}}; .with_dma = false}};
rmt_new_tx_channel(&rmt_tx_channel_config, &_rmt_channel); rmt_new_tx_channel(&rmt_tx_channel_config, &_rmt_tx_channel);
rmt_enable(_rmt_channel); rmt_enable(_rmt_tx_channel);
// Create encoder only once // Create new encoder
if (!_encoder) if (!_dshot_encoder)
{ {
rmt_copy_encoder_config_t enc_cfg = {}; 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.loop_count = -1;
_transmit_config.flags.eot_level = 0; _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 // Fake 10 Bit transformation to be sure
static uint16_t _lastThrottle = 0; throttle = throttle & 0b0000011111111111;
// Clamp to 11 bits
throttle &= 0x07FF;
// Has Throttle really changed?
if (throttle == _lastThrottle) if (throttle == _lastThrottle)
return; return;
_lastThrottle = throttle; _lastThrottle = throttle;
// Build 16-bit DShot packet // Prepare Throttle paket
uint16_t packet = (throttle << 1) | (telemetry ? 1 : 0); dshot_packet = (throttle << 1) | (_isBidirectional ? 1 : 0);
uint8_t crc = (packet ^ (packet >> 4) ^ (packet >> 8)) & 0x0F;
packet = (packet << 4) | crc;
// 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] = {}; rmt_symbol_word_t symbols[32] = {};
size_t count = 0; size_t count = 0;
buildFrameSymbols(packet, symbols, count); buildFrameSymbols(dshot_packet, symbols, count);
// Transmit // Reset RMT Signnal loop before sending new value
rmt_disable(_rmt_channel); // Ensure safe restart rmt_disable(_rmt_tx_channel);
rmt_enable(_rmt_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) void DShotRMT::buildFrameSymbols(uint16_t dshot_packet, rmt_symbol_word_t *symbols, size_t &count)
{ {
uint32_t ticks_per_bit = 0; uint32_t ticks_per_bit = 0;
@ -77,26 +111,25 @@ void DShotRMT::buildFrameSymbols(uint16_t dshot_packet, rmt_symbol_word_t *symbo
switch (_mode) switch (_mode)
{ {
case DSHOT150: case DSHOT150:
ticks_per_bit = 67; ticks_per_bit = 64;
ticks_zero_high = 25; ticks_zero_high = 24;
ticks_one_high = 50; ticks_one_high = 48;
break; break;
case DSHOT300: case DSHOT300:
ticks_per_bit = 33; ticks_per_bit = 32;
ticks_zero_high = 12; ticks_zero_high = 12;
ticks_one_high = 25; ticks_one_high = 24;
break; break;
case DSHOT600: case DSHOT600:
ticks_per_bit = 17; ticks_per_bit = 16;
ticks_zero_high = 6; ticks_zero_high = 6;
ticks_one_high = 13; ticks_one_high = 12;
break; break;
} }
uint32_t ticks_zero_low = ticks_per_bit - ticks_zero_high; uint32_t ticks_zero_low = ticks_per_bit - ticks_zero_high;
uint32_t ticks_one_low = ticks_per_bit - ticks_one_high; uint32_t ticks_one_low = ticks_per_bit - ticks_one_high;
// Encode 16 bits
for (int i = 15; i >= 0; i--) for (int i = 15; i >= 0; i--)
{ {
bool bit = (dshot_packet >> i) & 0x01; bool bit = (dshot_packet >> i) & 0x01;
@ -107,7 +140,6 @@ void DShotRMT::buildFrameSymbols(uint16_t dshot_packet, rmt_symbol_word_t *symbo
count++; count++;
} }
// Add pause
symbols[count].level0 = 0; symbols[count].level0 = 0;
symbols[count].duration0 = ticks_per_bit * PAUSE_BITS; symbols[count].duration0 = ticks_per_bit * PAUSE_BITS;
symbols[count].level1 = 0; symbols[count].level1 = 0;

View File

@ -1,6 +1,6 @@
/** /**
* @file DShotRMT.h * @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 * @author Wastl Kraus
* @date 2025-06-11 * @date 2025-06-11
* @license MIT * @license MIT
@ -10,15 +10,15 @@
#include <Arduino.h> #include <Arduino.h>
#include <driver/rmt_tx.h> #include <driver/rmt_tx.h>
#include <driver/rmt_rx.h>
static constexpr auto DSHOT_THROTTLE_FAILSAVE = 0; static constexpr auto DSHOT_THROTTLE_FAILSAVE = 0;
static constexpr auto DSHOT_THROTTLE_MIN = 48; static constexpr auto DSHOT_THROTTLE_MIN = 48;
static constexpr auto DSHOT_THROTTLE_MAX = 2047; 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 PAUSE_BITS = 21;
static constexpr auto DSHOT_NULL_PACKET = 0b0000000000000000; static constexpr auto DSHOT_NULL_PACKET = 0b0000000000000000;
/// DShot Mode
typedef enum dshot_mode_e typedef enum dshot_mode_e
{ {
DSHOT150, DSHOT150,
@ -32,7 +32,7 @@ public:
DShotRMT(gpio_num_t gpio, dshot_mode_t mode = DSHOT300, bool isBidirectional = false); DShotRMT(gpio_num_t gpio, dshot_mode_t mode = DSHOT300, bool isBidirectional = false);
void begin(); void begin();
void setThrottle(uint16_t throttle, bool telemetry = false); void setThrottle(uint16_t throttle);
gpio_num_t getGPIO() const { return _gpio; } gpio_num_t getGPIO() const { return _gpio; }
dshot_mode_t getDShotMode() const { return _mode; } dshot_mode_t getDShotMode() const { return _mode; }
@ -41,10 +41,14 @@ private:
gpio_num_t _gpio; gpio_num_t _gpio;
dshot_mode_t _mode; dshot_mode_t _mode;
bool _isBidirectional; bool _isBidirectional;
uint16_t _lastThrottle = 0;
uint16_t dshot_packet = DSHOT_NULL_PACKET;
rmt_channel_handle_t _rmt_channel = nullptr; rmt_channel_handle_t _rmt_tx_channel = nullptr;
rmt_encoder_handle_t _encoder = nullptr; rmt_channel_handle_t _rmt_rx_channel = nullptr;
rmt_encoder_handle_t _dshot_encoder = nullptr;
rmt_transmit_config_t _transmit_config = {}; rmt_transmit_config_t _transmit_config = {};
void buildFrameSymbols(uint16_t frame, rmt_symbol_word_t *symbols, size_t &count); 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);
}; };

View File

@ -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) [![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`). 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 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. 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: The DShot protocol transmits 16-bit packets to brushless ESCs:
- 11-bit throttle value - 11-bit throttle value
- 1-bit telemetry request - 1-bit telemetry request
- 4-bit checksum - 4-bit checksum
Data is transmitted MSB-first. Pulse timing depends on the selected DShot mode. 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 | | 300 | 300 kbit/s | 2.50 | 1.25 | 3.33 | ~53.28 |
| 600 | 600 kbit/s | 1.25 | 0.625 | 1.67 | ~26.72 | | 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 ## 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 ```c
crc = (value ^ (value >> 4) ^ (value >> 8)) & 0x0F; 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 ```c
crc = (~(value ^ (value >> 4) ^ (value >> 8))) & 0x0F; crc = (~(value ^ (value >> 4) ^ (value >> 8))) & 0x0F;
@ -47,7 +53,7 @@ crc = (~(value ^ (value >> 4) ^ (value >> 8))) & 0x0F;
## RMT on the ESP32 ## 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: ### 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. 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. 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 - DSHOT150
- DSHOT300 (default) - DSHOT300 (default)
@ -72,7 +78,7 @@ It uses a `copy_encoder` to continuously send a prebuilt symbol buffer. New thro
### Frame Structure: ### Frame Structure:
- 16-bit DShot data - 16-bit DShot data
- 21-bit times worth of pause (LOW) - 21-bit times worth of pause
--- ---

View File

@ -1,8 +1,8 @@
/** /**
* @file dshot300.ino * @file dshot300.ino
* @brief Demo sketch for continuous DShot signal using ESP32 and DShotRMT library * @brief Demo sketch for DShotRMT library
* @author Wastl Kraus * @author Wastl Kraus
* @date 2025-06-07 * @date 2025-06-11
* @license MIT * @license MIT
*/ */
@ -17,24 +17,25 @@ constexpr auto USB_SERIAL_BAUD = 115200;
constexpr auto MOTOR01_PIN = GPIO_NUM_17; constexpr auto MOTOR01_PIN = GPIO_NUM_17;
constexpr auto DSHOT_MODE = DSHOT300; constexpr auto DSHOT_MODE = DSHOT300;
// Create DShotRMT instance // BiDirectional DShot Support
DShotRMT motor01(MOTOR01_PIN, DSHOT_MODE); constexpr auto IS_BIDIRECTIONAL = true;
// Setup Motor Pin, DShot Mode and optional BiDirectional Support
DShotRMT motor01(MOTOR01_PIN, DSHOT_MODE, IS_BIDIRECTIONAL);
void setup() void setup()
{ {
USB_Serial.begin(USB_SERIAL_BAUD); USB_Serial.begin(USB_SERIAL_BAUD);
// Wait for serial port // Initialize DShot Signal
while (!USB_Serial)
delay(10);
USB_Serial.println("DShotRMT Demo started.");
USB_Serial.println("Enter a throttle value (482047):");
motor01.begin(); motor01.begin();
// Arm ESC with minimum throttle // Arm ESC with minimum throttle
motor01.setThrottle(DSHOT_THROTTLE_MIN); motor01.setThrottle(DSHOT_THROTTLE_MIN);
//
USB_Serial.println("DShotRMT Demo started.");
USB_Serial.println("Enter a throttle value (482047):");
} }
void loop() void loop()
@ -67,4 +68,4 @@ int readSerialThrottle()
} }
return last_throttle; return last_throttle;
} }