...add BiDirectional support
This commit is contained in:
parent
33bfb6a0be
commit
8cfe24e132
98
DShotRMT.cpp
98
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.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;
|
||||
|
|
|
|||
16
DShotRMT.h
16
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 <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);
|
||||
};
|
||||
|
|
|
|||
24
README.md
24
README.md
|
|
@ -1,10 +1,12 @@
|
|||
[](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
|
||||
|
||||
---
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue