...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
* @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.h>
//
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;

View File

@ -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 <Arduino.h>
#include <driver/rmt_tx.h>
#include <driver/rmt_rx.h>
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);
};

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)
# 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
---

View File

@ -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 (482047):");
// 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 (482047):");
}
void loop()
@ -67,4 +68,4 @@ int readSerialThrottle()
}
return last_throttle;
}
}