...unified
...now featuring a much more "clean" appearance ^^
This commit is contained in:
parent
314e888a38
commit
1adde68072
|
|
@ -1,32 +1,30 @@
|
||||||
name: DShotRMT Example Sketch
|
name: Build DShotRMT Example Sketch
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- main
|
- '*'
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout Repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@main
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Install Repository as Library
|
- name: Set up Arduino CLI
|
||||||
run: |
|
run: |
|
||||||
mkdir -p "$HOME/Arduino/libraries"
|
curl -fsSL https://raw.githubusercontent.com/arduino/arduino-cli/master/install.sh | BINDIR=/usr/local/bin sh
|
||||||
ln -s "$PWD" "$HOME/Arduino/libraries/."
|
arduino-cli core update-index
|
||||||
|
arduino-cli core install esp32:esp32
|
||||||
|
|
||||||
- name: Install Arduino CLI
|
- name: Install DShotRMT as library
|
||||||
run: |
|
run: |
|
||||||
curl -fsSL https://raw.githubusercontent.com/arduino/arduino-cli/master/install.sh | BINDIR=/usr/local/bin sh
|
mkdir -p $HOME/Arduino/libraries
|
||||||
arduino-cli core update-index
|
ln -s $PWD $HOME/Arduino/libraries/DShotRMT
|
||||||
arduino-cli core install esp32:esp32
|
|
||||||
|
- name: Compile dshot300.ino example
|
||||||
|
run: |
|
||||||
|
arduino-cli compile --fqbn esp32:esp32:esp32 examples/dshot300/dshot300.ino
|
||||||
|
|
||||||
- name: Compile Sketch
|
|
||||||
run: |
|
|
||||||
arduino-cli compile --fqbn esp32:esp32:esp32 ${{ github.workspace }}/examples/dshot300
|
|
||||||
env:
|
|
||||||
ARDUINO_LIBRARY_PATH: ${{ github.workspace }}/libraries
|
|
||||||
ARDUINO_DATA_PATH: ${{ github.workspace }}/arduino-data
|
|
||||||
|
|
|
||||||
100
DShotRMT.cpp
100
DShotRMT.cpp
|
|
@ -11,13 +11,14 @@
|
||||||
// --- DShotRMT Class ---
|
// --- DShotRMT Class ---
|
||||||
// This class provides an abstraction for sending and optionally receiving DShot frames.
|
// This class provides an abstraction for sending and optionally receiving DShot frames.
|
||||||
// It uses ESP32's RMT peripheral for precise timing control, including BiDirectional RX.
|
// It uses ESP32's RMT peripheral for precise timing control, including BiDirectional RX.
|
||||||
|
|
||||||
DShotRMT::DShotRMT(gpio_num_t gpio, dshot_mode_t mode, bool isBidirectional, uint8_t pauseDuration)
|
DShotRMT::DShotRMT(gpio_num_t gpio, dshot_mode_t mode, bool isBidirectional, uint8_t pauseDuration)
|
||||||
: _gpio(gpio), _mode(mode), _isBidirectional(isBidirectional), _pauseDuration(pauseDuration) {}
|
: _gpio(gpio), _mode(mode), _isBidirectional(isBidirectional), _pauseDuration(pauseDuration) {}
|
||||||
|
|
||||||
// Sets up RMT TX and RX channels as well as encoder configuration
|
// Initializes RMT TX and RX channels and encoder configuration
|
||||||
void DShotRMT::begin()
|
void DShotRMT::begin()
|
||||||
{
|
{
|
||||||
// RX RMT Channel Configuration (for BiDirectional DShot)
|
// Configure RX RMT Channel for BiDirectional DShot
|
||||||
if (_isBidirectional)
|
if (_isBidirectional)
|
||||||
{
|
{
|
||||||
_rmt_rx_channel_config = {
|
_rmt_rx_channel_config = {
|
||||||
|
|
@ -42,7 +43,7 @@ void DShotRMT::begin()
|
||||||
_receive_config.signal_range_max_ns = 5000;
|
_receive_config.signal_range_max_ns = 5000;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TX RMT Channel Configuration
|
// Configure TX RMT Channel
|
||||||
_rmt_tx_channel_config = {
|
_rmt_tx_channel_config = {
|
||||||
.gpio_num = _gpio,
|
.gpio_num = _gpio,
|
||||||
.clk_src = DSHOT_CLOCK_SRC_DEFAULT,
|
.clk_src = DSHOT_CLOCK_SRC_DEFAULT,
|
||||||
|
|
@ -51,7 +52,7 @@ void DShotRMT::begin()
|
||||||
.trans_queue_depth = 10,
|
.trans_queue_depth = 10,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Configure transmission looping
|
// Transmission configuration
|
||||||
_transmit_config.loop_count = 0;
|
_transmit_config.loop_count = 0;
|
||||||
_transmit_config.flags.eot_level = _isBidirectional;
|
_transmit_config.flags.eot_level = _isBidirectional;
|
||||||
|
|
||||||
|
|
@ -66,7 +67,7 @@ void DShotRMT::begin()
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Use a copy encoder to send raw symbols
|
// Create copy encoder for raw symbol transmission
|
||||||
if (!_dshot_encoder)
|
if (!_dshot_encoder)
|
||||||
{
|
{
|
||||||
rmt_copy_encoder_config_t enc_cfg = {};
|
rmt_copy_encoder_config_t enc_cfg = {};
|
||||||
|
|
@ -78,112 +79,109 @@ void DShotRMT::begin()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Encodes and transmits a valid DShot Throttle value (48 - 2047)
|
// Encodes and transmits a valid DShot throttle value (48 - 2047)
|
||||||
void DShotRMT::setThrottle(uint16_t throttle)
|
void DShotRMT::setThrottle(uint16_t throttle)
|
||||||
{
|
{
|
||||||
// Safety first - double check input range and 11 bit "translation"
|
// Clamp input range and mask to 11 bits
|
||||||
throttle = constrain(throttle, DSHOT_THROTTLE_MIN, DSHOT_THROTTLE_MAX) & 0b0000011111111111;
|
throttle = constrain(throttle, DSHOT_THROTTLE_MIN, DSHOT_THROTTLE_MAX) & 0x7FF;
|
||||||
|
|
||||||
_lastThrottle = throttle;
|
_lastThrottle = throttle;
|
||||||
|
|
||||||
// Convert throttle value to DShot Paket Format
|
// Convert throttle value to DShot packet format
|
||||||
_tx_packet = assambleDShotPaket(_lastThrottle);
|
_tx_packet = assambleDShotPaket(_lastThrottle);
|
||||||
|
|
||||||
// Encode RMT symbols
|
// Encode RMT symbols
|
||||||
size_t count = 0;
|
size_t count = 0;
|
||||||
encodeDShotTX(_tx_packet, _tx_symbols, count);
|
encodeDShotTX(_tx_packet, _tx_symbols, count);
|
||||||
|
|
||||||
// Send the packet
|
// Transmit the packet
|
||||||
if (rmt_transmit(_rmt_tx_channel, _dshot_encoder, _tx_symbols, count * sizeof(rmt_symbol_word_t), &_transmit_config) != 0)
|
if (rmt_transmit(_rmt_tx_channel, _dshot_encoder, _tx_symbols, count * sizeof(rmt_symbol_word_t), &_transmit_config) != 0)
|
||||||
{
|
{
|
||||||
Serial.println("Failed to transmit DShot packet");
|
Serial.println("Failed to transmit DShot packet");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Take a break
|
// Pause between frames
|
||||||
esp_rom_delay_us(_pauseDuration);
|
esp_rom_delay_us(_pauseDuration);
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- Get eRPM from ESC ---
|
|
||||||
// Receives and decodes a response frame from ESC containing eRPM info
|
// Receives and decodes a response frame from ESC containing eRPM info
|
||||||
uint32_t DShotRMT::getERPM()
|
uint32_t DShotRMT::getERPM()
|
||||||
{
|
{
|
||||||
if (_isBidirectional)
|
if (_isBidirectional)
|
||||||
{
|
{
|
||||||
if (_rmt_rx_channel == nullptr)
|
if (_rmt_rx_channel == nullptr)
|
||||||
|
Serial.println("No bidirectional DShot support.");
|
||||||
return _last_erpm;
|
return _last_erpm;
|
||||||
|
|
||||||
// Attempt to receive a new frame
|
// Try to receive a new frame
|
||||||
if (!rmt_receive(_rmt_rx_channel, _rx_symbols, sizeof(_rx_symbols), &_receive_config))
|
if (!rmt_receive(_rmt_rx_channel, _rx_symbols, sizeof(_rx_symbols), &_receive_config))
|
||||||
|
Serial.println("No valid DShot frame received");
|
||||||
return _last_erpm;
|
return _last_erpm;
|
||||||
|
|
||||||
_last_erpm = decodeDShotRX(_rx_symbols, DSHOT_BITS_PER_FRAME);
|
_last_erpm = decodeDShotRX(_rx_symbols, DSHOT_BITS_PER_FRAME);
|
||||||
return _last_erpm;
|
return _last_erpm;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Nothing to do here
|
// No RX possible in non-bidirectional mode
|
||||||
return _last_erpm;
|
return _last_erpm;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Translate eRPM value to RPM taking magnet count as parameter
|
// Converts eRPM value to RPM using magnet count
|
||||||
uint32_t DShotRMT::getMotorRPM(uint8_t magnet_count)
|
uint32_t DShotRMT::getMotorRPM(uint8_t magnet_count)
|
||||||
{
|
{
|
||||||
uint8_t pole_count = magnet_count / 2;
|
uint8_t pole_count = magnet_count / 2;
|
||||||
|
|
||||||
if (pole_count == 0)
|
if (pole_count == 0)
|
||||||
pole_count = 1;
|
pole_count = 1;
|
||||||
|
|
||||||
uint32_t rpm = getERPM() / pole_count;
|
return getERPM() / pole_count;
|
||||||
return rpm;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Calculate CRC for DShot Paket
|
// Calculates CRC for DShot packet
|
||||||
uint16_t DShotRMT::calculateCRC(uint16_t dshot_packet)
|
uint16_t DShotRMT::calculateCRC(uint16_t dshot_packet)
|
||||||
{
|
{
|
||||||
uint16_t _packet = (dshot_packet << 1) | (_isBidirectional ? 1 : 0);
|
uint16_t packet = (dshot_packet << 1) | (_isBidirectional ? 1 : 0);
|
||||||
|
|
||||||
// Clear container before new calculation
|
// Reset CRC container
|
||||||
_packet_crc = DSHOT_NULL_PACKET;
|
_packet_crc = DSHOT_NULL_PACKET;
|
||||||
|
|
||||||
// CRC calculation for DShot (4 bits)
|
// CRC calculation for DShot (4 bits)
|
||||||
_packet_crc = ((_packet ^ (_packet >> 4) ^ (_packet >> 8)) & 0b0000000000001111);
|
_packet_crc = ((packet ^ (packet >> 4) ^ (packet >> 8)) & 0xF);
|
||||||
|
|
||||||
// CRC is inverted for biDirectional DShot
|
// CRC is inverted for bidirectional DShot
|
||||||
if (_isBidirectional)
|
if (_isBidirectional)
|
||||||
_packet_crc = (~_packet_crc) & 0b0000000000001111;
|
_packet_crc = (~_packet_crc) & 0xF;
|
||||||
|
|
||||||
return _packet_crc;
|
return _packet_crc;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Assamble DShot Paket (11 bit throttle + 1 bit telemetry request + 4 bit crc)
|
// Assembles DShot packet (11 bit throttle + 1 bit telemetry request + 4 bit CRC)
|
||||||
uint16_t DShotRMT::assambleDShotPaket(uint16_t value)
|
uint16_t DShotRMT::assambleDShotPaket(uint16_t value)
|
||||||
{
|
{
|
||||||
// Dummy conversion to 11 bits
|
uint16_t throttle = value & 0x7FF;
|
||||||
uint16_t _value = value & 0b0000011111111111;
|
|
||||||
|
|
||||||
// Clear container
|
// Reset packet container
|
||||||
_tx_packet = DSHOT_NULL_PACKET;
|
_tx_packet = DSHOT_NULL_PACKET;
|
||||||
|
|
||||||
// Assemble raw DShot packet and add checksum
|
// Assemble raw DShot packet and add checksum
|
||||||
_packet_crc = calculateCRC(_value);
|
_packet_crc = calculateCRC(throttle);
|
||||||
|
|
||||||
_tx_packet = (_value << 1) | (_isBidirectional ? 1 : 0);
|
_tx_packet = (throttle << 1) | (_isBidirectional ? 1 : 0);
|
||||||
_tx_packet = (_tx_packet << 4) | _packet_crc;
|
_tx_packet = (_tx_packet << 4) | _packet_crc;
|
||||||
|
|
||||||
return _tx_packet;
|
return _tx_packet;
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- Encode DShot TX Frame ---
|
// Converts a 16-bit packet into a valid DShot frame for RMT
|
||||||
// Converts a 16-bit packet into a valid DShot Frame for RMT
|
|
||||||
void DShotRMT::encodeDShotTX(uint16_t dshot_packet, rmt_symbol_word_t *symbols, size_t &count)
|
void DShotRMT::encodeDShotTX(uint16_t dshot_packet, rmt_symbol_word_t *symbols, size_t &count)
|
||||||
{
|
{
|
||||||
// Always start encoding from the top
|
|
||||||
count = 0;
|
count = 0;
|
||||||
|
|
||||||
uint32_t ticks_per_bit = 0;
|
uint32_t ticks_per_bit = 0;
|
||||||
uint32_t ticks_zero_high = 0;
|
uint32_t ticks_zero_high = 0;
|
||||||
uint32_t ticks_one_high = 0;
|
uint32_t ticks_one_high = 0;
|
||||||
|
|
||||||
|
// Select timing based on DShot mode
|
||||||
switch (_mode)
|
switch (_mode)
|
||||||
{
|
{
|
||||||
case DSHOT150:
|
case DSHOT150:
|
||||||
|
|
@ -217,10 +215,10 @@ void DShotRMT::encodeDShotTX(uint16_t dshot_packet, rmt_symbol_word_t *symbols,
|
||||||
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;
|
||||||
|
|
||||||
// Fill the 16 DShot-Bits Array with selected timings
|
// Fill the 16 DShot bits array with selected timings
|
||||||
for (int i = 15; i >= 0; i--)
|
for (int i = 15; i >= 0; i--)
|
||||||
{
|
{
|
||||||
bool bit = (dshot_packet >> i) & 0b0000000000000001;
|
bool bit = (dshot_packet >> i) & 0x1;
|
||||||
if (_isBidirectional)
|
if (_isBidirectional)
|
||||||
{
|
{
|
||||||
symbols[count].level0 = 0;
|
symbols[count].level0 = 0;
|
||||||
|
|
@ -242,35 +240,33 @@ void DShotRMT::encodeDShotTX(uint16_t dshot_packet, rmt_symbol_word_t *symbols,
|
||||||
// Decodes a response frame from ESC containing eRPM info
|
// Decodes a response frame from ESC containing eRPM info
|
||||||
uint16_t DShotRMT::decodeDShotRX(const rmt_symbol_word_t *symbols, uint32_t count)
|
uint16_t DShotRMT::decodeDShotRX(const rmt_symbol_word_t *symbols, uint32_t count)
|
||||||
{
|
{
|
||||||
// Container for received frame
|
uint16_t received_frame = DSHOT_NULL_PACKET;
|
||||||
uint16_t _rec_frame = DSHOT_NULL_PACKET;
|
|
||||||
|
|
||||||
// Fill the Frame bit by bit
|
// Build the frame bit by bit
|
||||||
for (size_t i = 0; i < DSHOT_BITS_PER_FRAME && i < count; ++i)
|
for (size_t i = 0; i < DSHOT_BITS_PER_FRAME && i < count; ++i)
|
||||||
{
|
{
|
||||||
bool bit = (symbols[i].duration0 < symbols[i].duration1);
|
bool bit = (symbols[i].duration0 < symbols[i].duration1);
|
||||||
_rec_frame = (_rec_frame << 1) | bit;
|
received_frame = (received_frame << 1) | bit;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cut the received CRC for checking
|
// Extract CRC and payload
|
||||||
uint16_t _temp = _rec_frame >> 4;
|
uint16_t payload = received_frame >> 4;
|
||||||
|
uint8_t crc_received = received_frame & 0xF;
|
||||||
// Store the received CRC
|
|
||||||
uint8_t crc_recv = _rec_frame & 0b0000000000001111;
|
|
||||||
|
|
||||||
// Calculate CRC for received frame again
|
|
||||||
uint8_t crc_calc = (_temp ^ (_temp >> 4) ^ (_temp >> 8)) & 0b0000000000001111;
|
|
||||||
|
|
||||||
|
// Calculate CRC for received frame
|
||||||
|
uint8_t crc_calculated = (payload ^ (payload >> 4) ^ (payload >> 8)) & 0xF;
|
||||||
if (_isBidirectional)
|
if (_isBidirectional)
|
||||||
crc_calc = (~crc_calc) & 0b0000000000001111;
|
crc_calculated = (~crc_calculated) & 0xF;
|
||||||
|
|
||||||
// Checking CRC
|
// Check CRC
|
||||||
if (crc_recv != crc_calc)
|
if (crc_received != crc_calculated)
|
||||||
|
{
|
||||||
Serial.println("RX - CRC check failed.");
|
Serial.println("RX - CRC check failed.");
|
||||||
return _last_erpm;
|
return _last_erpm;
|
||||||
|
}
|
||||||
|
|
||||||
// Cut "telemetric" bit leaving "raw" value
|
// Remove telemetry bit, keep raw value
|
||||||
uint16_t raw = _temp >> 1;
|
uint16_t raw = payload >> 1;
|
||||||
|
|
||||||
return _last_erpm = raw;
|
return _last_erpm = raw;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
37
DShotRMT.h
37
DShotRMT.h
|
|
@ -15,24 +15,24 @@
|
||||||
#include <driver/rmt_rx.h>
|
#include <driver/rmt_rx.h>
|
||||||
|
|
||||||
// --- DShot Protocol Constants ---
|
// --- DShot Protocol Constants ---
|
||||||
static constexpr auto DSHOT_THROTTLE_FAILSAVE = 0;
|
static constexpr uint16_t DSHOT_THROTTLE_FAILSAVE = 0;
|
||||||
static constexpr auto DSHOT_THROTTLE_MIN = 48;
|
static constexpr uint16_t DSHOT_THROTTLE_MIN = 48;
|
||||||
static constexpr auto DSHOT_THROTTLE_MAX = 2047;
|
static constexpr uint16_t DSHOT_THROTTLE_MAX = 2047;
|
||||||
static constexpr auto DSHOT_BITS_PER_FRAME = 16;
|
static constexpr uint8_t DSHOT_BITS_PER_FRAME = 16;
|
||||||
|
|
||||||
static constexpr auto DSHOT_NULL_PACKET = 0b0000000000000000;
|
static constexpr uint16_t DSHOT_NULL_PACKET = 0x0000;
|
||||||
static constexpr auto DSHOT_FULL_PACKET = 0b1111111111111111;
|
static constexpr uint16_t DSHOT_FULL_PACKET = 0xFFFF;
|
||||||
static constexpr auto NO_ERPM_SIGNAL = 0;
|
static constexpr uint16_t NO_ERPM_SIGNAL = 0;
|
||||||
|
|
||||||
// RMT configuration parameters
|
// RMT configuration parameters
|
||||||
static constexpr auto DSHOT_CLOCK_SRC_DEFAULT = RMT_CLK_SRC_DEFAULT;
|
static constexpr rmt_clock_source_t DSHOT_CLOCK_SRC_DEFAULT = RMT_CLK_SRC_DEFAULT;
|
||||||
static constexpr auto DSHOT_RMT_RESOLUTION = 10 * 1000 * 1000; // 10 MHz Clock
|
static constexpr uint32_t DSHOT_RMT_RESOLUTION = 10 * 1000 * 1000; // 10 MHz Clock
|
||||||
|
|
||||||
static constexpr auto TX_BUFFER_SIZE = DSHOT_BITS_PER_FRAME;
|
static constexpr size_t TX_BUFFER_SIZE = DSHOT_BITS_PER_FRAME;
|
||||||
static constexpr auto RX_BUFFER_SIZE = 32; // Padding for RX decoding
|
static constexpr size_t RX_BUFFER_SIZE = 32; // Padding for RX decoding
|
||||||
|
|
||||||
// DShot Packet
|
// DShot Packet structure
|
||||||
typedef struct dshot_packet_s
|
typedef struct
|
||||||
{
|
{
|
||||||
uint16_t throttle_value : 11;
|
uint16_t throttle_value : 11;
|
||||||
bool telemetric_request : 1;
|
bool telemetric_request : 1;
|
||||||
|
|
@ -40,7 +40,7 @@ typedef struct dshot_packet_s
|
||||||
} dshot_packet_t;
|
} dshot_packet_t;
|
||||||
|
|
||||||
// --- DShot Mode Selection ---
|
// --- DShot Mode Selection ---
|
||||||
typedef enum dshot_mode_e
|
typedef enum
|
||||||
{
|
{
|
||||||
DSHOT_OFF,
|
DSHOT_OFF,
|
||||||
DSHOT150,
|
DSHOT150,
|
||||||
|
|
@ -66,20 +66,20 @@ public:
|
||||||
uint32_t getERPM();
|
uint32_t getERPM();
|
||||||
uint32_t getMotorRPM(uint8_t magnet_count);
|
uint32_t getMotorRPM(uint8_t magnet_count);
|
||||||
|
|
||||||
// Accessors for GPIO and DShot Settings
|
// Accessors for GPIO and DShot settings
|
||||||
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; }
|
||||||
uint8_t getPauseDuration() const { return _pauseDuration; }
|
uint8_t getPauseDuration() const { return _pauseDuration; }
|
||||||
void setPauseDuration(uint8_t pauseDuration) { _pauseDuration = pauseDuration; }
|
void setPauseDuration(uint8_t pauseDuration) { _pauseDuration = pauseDuration; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
// Calculates the checksum for throttle value
|
// Calculates the checksum for a DShot packet
|
||||||
uint16_t calculateCRC(uint16_t dshot_packet);
|
uint16_t calculateCRC(uint16_t dshot_packet);
|
||||||
|
|
||||||
// Assembles DShot packet (11 bit throttle + 1 bit telemetry request + 4 bit crc)
|
// Assembles DShot packet (11 bit throttle + 1 bit telemetry request + 4 bit CRC)
|
||||||
uint16_t assambleDShotPaket(uint16_t value);
|
uint16_t assambleDShotPaket(uint16_t value);
|
||||||
|
|
||||||
// Converts a 16-bit DShot packet into RMT symbols and appends pause
|
// Converts a 16-bit DShot packet into RMT symbols
|
||||||
void encodeDShotTX(uint16_t dshot_packet, rmt_symbol_word_t *symbols, size_t &count);
|
void encodeDShotTX(uint16_t dshot_packet, rmt_symbol_word_t *symbols, size_t &count);
|
||||||
|
|
||||||
// Decodes the ESC answer
|
// Decodes the ESC answer
|
||||||
|
|
@ -96,7 +96,6 @@ private:
|
||||||
uint16_t _rx_packet = DSHOT_NULL_PACKET;
|
uint16_t _rx_packet = DSHOT_NULL_PACKET;
|
||||||
uint16_t _tx_packet = DSHOT_NULL_PACKET;
|
uint16_t _tx_packet = DSHOT_NULL_PACKET;
|
||||||
uint8_t _packet_crc = 0;
|
uint8_t _packet_crc = 0;
|
||||||
dshot_packet_t _dshot_packet = {};
|
|
||||||
|
|
||||||
// --- RMT Channel Handles ---
|
// --- RMT Channel Handles ---
|
||||||
rmt_channel_handle_t _rmt_rx_channel = nullptr;
|
rmt_channel_handle_t _rmt_rx_channel = nullptr;
|
||||||
|
|
|
||||||
125
README.md
125
README.md
|
|
@ -1,25 +1,70 @@
|
||||||
[](https://github.com/derdoktor667/DShotRMT/actions/workflows/esp32.yml)
|
[](https://github.com/derdoktor667/DShotRMT/actions/workflows/esp32.yml)
|
||||||
|
|
||||||
## DShotRMT - ESP32 Library (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` / `rmt_rx.h`).
|
A modern, robust C++ library for generating DShot signals on the ESP32 using the new ESP-IDF 5 RMT encoder API (`rmt_tx.h` / `rmt_rx.h`).
|
||||||
The library sends continuous DShot frames with a pause between them and supports all standard DShot modes (150, 300, 600).
|
Supports all standard DShot modes (150, 300, 600) and features continuous frame transmission with configurable pause.
|
||||||
|
**Now with BiDirectional DShot support!**
|
||||||
|
|
||||||
### Now with BiDirectional DShot Support!!!
|
> The legacy version (using the old `rmt.h` API) is still available in the `oldAPI` branch.
|
||||||
|
|
||||||
The old Version without encoding (rmt.h) is still available by using "oldAPI" Branch.
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## The DShot Protocol
|
## 🚀 Features
|
||||||
|
|
||||||
The DShot protocol transmits 16-bit packets to brushless ESCs:
|
- **All DShot Modes:** DSHOT150, DSHOT300 (default), DSHOT600
|
||||||
|
- **BiDirectional DShot:** Experimental support for telemetry and RPM feedback
|
||||||
|
- **Continuous Frames:** Hardware-timed, CPU-independent signal generation
|
||||||
|
- **Configurable Pause:** Ensures ESCs can reliably detect frame boundaries
|
||||||
|
- **Simple API:** Easy integration into your Arduino or ESP-IDF project
|
||||||
|
|
||||||
- 11-bit throttle value
|
---
|
||||||
- 1-bit telemetry request
|
|
||||||
- 4-bit checksum
|
|
||||||
|
|
||||||
Data is transmitted MSB-first. Pulse timing depends on the selected DShot mode.
|
## 📦 Installation
|
||||||
|
|
||||||
|
Clone this repository and add it to your Arduino libraries or ESP-IDF components.
|
||||||
|
|
||||||
|
```sh
|
||||||
|
git clone https://github.com/derdoktor667/DShotRMT.git
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ⚡ Quick Start
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
#include <DShotRMT.h>
|
||||||
|
|
||||||
|
constexpr gpio_num_t MOTOR_PIN = GPIO_NUM_17;
|
||||||
|
constexpr dshot_mode_t MODE = DSHOT300;
|
||||||
|
constexpr bool BIDIRECTIONAL = true;
|
||||||
|
|
||||||
|
DShotRMT motor(MOTOR_PIN, MODE, BIDIRECTIONAL);
|
||||||
|
|
||||||
|
void setup() {
|
||||||
|
Serial.begin(115200);
|
||||||
|
motor.begin();
|
||||||
|
motor.setThrottle(1000); // Set throttle value (48–2047)
|
||||||
|
}
|
||||||
|
|
||||||
|
void loop() {
|
||||||
|
// Optionally read RPM if bidirectional mode is enabled
|
||||||
|
uint32_t rpm = motor.getMotorRPM(14); // 14 magnets
|
||||||
|
Serial.println(rpm);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📚 DShot Protocol Overview
|
||||||
|
|
||||||
|
DShot transmits 16-bit packets to brushless ESCs:
|
||||||
|
|
||||||
|
- **11 bits:** Throttle value
|
||||||
|
- **1 bit:** Telemetry request
|
||||||
|
- **4 bits:** Checksum (CRC)
|
||||||
|
|
||||||
|
Data is sent MSB-first. Pulse timing depends on the selected DShot mode.
|
||||||
|
|
||||||
| DSHOT | Bitrate | TH1 | TH0 | Bit Time (µs) | Frame Time (µs) |
|
| DSHOT | Bitrate | TH1 | TH0 | Bit Time (µs) | Frame Time (µs) |
|
||||||
|-------|-------------|-------|--------|---------------|-----------------|
|
|-------|-------------|-------|--------|---------------|-----------------|
|
||||||
|
|
@ -27,65 +72,59 @@ 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 pause. This helps ESCs detect separate frames.
|
Each frame is followed by a pause to help ESCs detect separate frames.
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Checksum Calculation
|
## 🔒 Checksum Calculation
|
||||||
|
|
||||||
The checksum is calculated over the first 12 bits (throttle + bit):
|
The checksum is calculated over the first 12 bits (throttle + telemetry):
|
||||||
|
|
||||||
```c
|
```c
|
||||||
crc = (value ^ (value >> 4) ^ (value >> 8)) & 0x0F;
|
crc = (value ^ (value >> 4) ^ (value >> 8)) & 0x0F;
|
||||||
```
|
```
|
||||||
|
|
||||||
### Bidirectional DSHOT
|
### 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
|
Bidirectional DSHOT (sometimes called "inverted DSHOT") inverts the signal level:
|
||||||
The calculation of the checksum is basically the same as before, but the inverted:
|
A logical '1' is low, and a '0' is high. This signals the ESC to send telemetry packets back.
|
||||||
|
|
||||||
|
**Bidirectional CRC:**
|
||||||
|
|
||||||
```c
|
```c
|
||||||
crc = (~(value ^ (value >> 4) ^ (value >> 8))) & 0x0F;
|
crc = (~(value ^ (value >> 4) ^ (value >> 8))) & 0x0F;
|
||||||
```
|
```
|
||||||
|
|
||||||
...biDirectional DShot is experimental. Further Hardware testing needed.
|
> **Note:** Bidirectional DShot is experimental. Further hardware testing is needed.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## RMT on the ESP32
|
## 🛠️ ESP32 RMT Peripheral
|
||||||
|
|
||||||
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:
|
|
||||||
|
|
||||||
|
The RMT (Remote Control) peripheral generates accurate, hardware-timed signals for controlling external devices.
|
||||||
|
Perfect for DShot:
|
||||||
- Hardware-timed pulses
|
- Hardware-timed pulses
|
||||||
- CPU-independent signal generation
|
- CPU-independent
|
||||||
- Loop mode with inter-frame pause
|
- Loop mode with inter-frame pause
|
||||||
- Reliable under system load
|
- Reliable under system load
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## About This Library
|
## 📝 API Reference
|
||||||
|
|
||||||
This C++ library provides a simple class to generate DShot signals using any RMT-capable GPIO.
|
- `DShotRMT(gpio_num_t gpio, dshot_mode_t mode, bool isBidirectional, uint8_t pauseDuration = 120)`
|
||||||
It uses a `copy_encoder` to continuously send a prebuilt symbol buffer. New throttle values are applied only when they change.
|
- `void begin()`
|
||||||
|
- `void setThrottle(uint16_t throttle)`
|
||||||
|
- `uint32_t getERPM()`
|
||||||
|
- `uint32_t getMotorRPM(uint8_t magnet_count)`
|
||||||
|
|
||||||
### Supported Modes (optional BiDirectional):
|
See [examples/dshot300/dshot300.ino](examples/dshot300/dshot300.ino) for a full demo.
|
||||||
|
|
||||||
- DSHOT150
|
|
||||||
- DSHOT300 (default)
|
|
||||||
- DSHOT600
|
|
||||||
|
|
||||||
### Frame Structure:
|
|
||||||
|
|
||||||
- 16-bit DShot data
|
|
||||||
- 21-bit times worth of pause
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## References
|
## 📖 References
|
||||||
|
|
||||||
- [DSHOT – the missing Handbook](https://brushlesswhoop.com/dshot-and-bidirectional-dshot/)
|
- [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/)
|
- [DSHOT in the Dark](https://dmrlawson.co.uk/index.php/2017/12/04/dshot-in-the-dark/)
|
||||||
|
|
@ -93,14 +132,14 @@ It uses a `copy_encoder` to continuously send a prebuilt symbol buffer. New thro
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## License
|
## 📄 License
|
||||||
|
|
||||||
MIT License – see LICENSE
|
MIT License – see [LICENSE](LICENSE)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Author
|
## 👤 Author
|
||||||
|
|
||||||
Wastl Kraus
|
**Wastl Kraus**
|
||||||
GitHub: [@derdoktor667](https://github.com/derdoktor667)
|
GitHub: [@derdoktor667](https://github.com/derdoktor667)
|
||||||
Website: [wir-sind-die-matrix.de](https://wir-sind-die-matrix.de)
|
Website: [wir-sind-die-matrix.de](https://wir-sind-die-matrix.de)
|
||||||
|
|
|
||||||
|
|
@ -11,17 +11,17 @@
|
||||||
|
|
||||||
// USB serial port settings
|
// USB serial port settings
|
||||||
constexpr auto &USB_SERIAL = Serial0;
|
constexpr auto &USB_SERIAL = Serial0;
|
||||||
constexpr auto USB_SERIAL_BAUD = 115200;
|
constexpr uint32_t USB_SERIAL_BAUD = 115200;
|
||||||
|
|
||||||
// Motor configuration
|
// Motor configuration
|
||||||
constexpr auto MOTOR01_PIN = GPIO_NUM_17;
|
constexpr gpio_num_t MOTOR01_PIN = GPIO_NUM_17;
|
||||||
constexpr auto DSHOT_MODE = DSHOT300;
|
constexpr dshot_mode_t DSHOT_MODE = DSHOT300;
|
||||||
|
|
||||||
// BiDirectional DShot Support (default: false)
|
// BiDirectional DShot Support (default: false)
|
||||||
constexpr auto IS_BIDIRECTIONAL = false;
|
constexpr bool IS_BIDIRECTIONAL = false;
|
||||||
|
|
||||||
// Motor Magnet count for RPM calculation
|
// Motor magnet count for RPM calculation
|
||||||
constexpr auto MOTOR01_MAGNET_COUNT = 14;
|
constexpr uint8_t MOTOR01_MAGNET_COUNT = 14;
|
||||||
|
|
||||||
// Setup Motor Pin, DShot Mode and optional BiDirectional Support
|
// Setup Motor Pin, DShot Mode and optional BiDirectional Support
|
||||||
DShotRMT motor01(MOTOR01_PIN, DSHOT_MODE, IS_BIDIRECTIONAL);
|
DShotRMT motor01(MOTOR01_PIN, DSHOT_MODE, IS_BIDIRECTIONAL);
|
||||||
|
|
@ -32,85 +32,83 @@ void printRPMPeriodically(uint16_t throttle);
|
||||||
// Reads throttle value from serial input
|
// Reads throttle value from serial input
|
||||||
uint16_t readSerialThrottle();
|
uint16_t readSerialThrottle();
|
||||||
|
|
||||||
//
|
|
||||||
void setup()
|
void setup()
|
||||||
{
|
{
|
||||||
// Start the USB Serial Port
|
// Start the USB Serial Port
|
||||||
USB_SERIAL.begin(USB_SERIAL_BAUD);
|
USB_SERIAL.begin(USB_SERIAL_BAUD);
|
||||||
|
|
||||||
// Initialize DShot Signal
|
// Initialize DShot Signal
|
||||||
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("**********************");
|
USB_SERIAL.println("**********************");
|
||||||
USB_SERIAL.println("DShotRMT Demo started.");
|
USB_SERIAL.println("DShotRMT Demo started.");
|
||||||
USB_SERIAL.println("Enter a throttle value (48–2047):");
|
USB_SERIAL.println("Enter a throttle value (48–2047):");
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
|
||||||
void loop()
|
void loop()
|
||||||
{
|
{
|
||||||
// Read value input from Serial
|
// Read value input from Serial
|
||||||
uint16_t throttle_input = readSerialThrottle();
|
uint16_t throttle_input = readSerialThrottle();
|
||||||
|
|
||||||
// Send the value to the ESC
|
// Send the value to the ESC
|
||||||
motor01.setThrottle(throttle_input);
|
motor01.setThrottle(throttle_input);
|
||||||
|
|
||||||
// Print RPM if BiDirectional DShot is enabled
|
// Print RPM if BiDirectional DShot is enabled
|
||||||
if (IS_BIDIRECTIONAL)
|
if (IS_BIDIRECTIONAL)
|
||||||
{
|
{
|
||||||
printRPMPeriodically(throttle_input);
|
printRPMPeriodically(throttle_input);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reads throttle value from serial input
|
// Reads throttle value from serial input
|
||||||
uint16_t readSerialThrottle()
|
uint16_t readSerialThrottle()
|
||||||
{
|
{
|
||||||
static uint16_t last_throttle = DSHOT_THROTTLE_MIN;
|
static uint16_t last_throttle = DSHOT_THROTTLE_MIN;
|
||||||
|
|
||||||
if (USB_SERIAL.available() > 0)
|
if (USB_SERIAL.available() > 0)
|
||||||
{
|
|
||||||
String input = USB_SERIAL.readStringUntil('\n');
|
|
||||||
int throttle_input = input.toInt();
|
|
||||||
|
|
||||||
// Clamp the value to the DShot range
|
|
||||||
throttle_input = constrain(throttle_input, DSHOT_THROTTLE_MIN, DSHOT_THROTTLE_MAX);
|
|
||||||
|
|
||||||
if (throttle_input < DSHOT_THROTTLE_MIN || throttle_input > DSHOT_THROTTLE_MAX)
|
|
||||||
{
|
{
|
||||||
USB_SERIAL.println("Invalid input. Please enter a value between 48 and 2047");
|
String input = USB_SERIAL.readStringUntil('\n');
|
||||||
}
|
int throttle_input = input.toInt();
|
||||||
else
|
|
||||||
{
|
// Clamp the value to the DShot range
|
||||||
last_throttle = throttle_input;
|
throttle_input = constrain(throttle_input, DSHOT_THROTTLE_MIN, DSHOT_THROTTLE_MAX);
|
||||||
USB_SERIAL.print("Throttle set to: ");
|
|
||||||
USB_SERIAL.println(last_throttle);
|
if (throttle_input < DSHOT_THROTTLE_MIN || throttle_input > DSHOT_THROTTLE_MAX)
|
||||||
|
{
|
||||||
|
USB_SERIAL.println("Invalid input. Please enter a value between 48 and 2047.");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
last_throttle = throttle_input;
|
||||||
|
USB_SERIAL.print("Throttle set to: ");
|
||||||
|
USB_SERIAL.println(last_throttle);
|
||||||
|
}
|
||||||
|
|
||||||
|
USB_SERIAL.println("*********************************");
|
||||||
|
USB_SERIAL.println("Enter a throttle value (48–2047):");
|
||||||
}
|
}
|
||||||
|
|
||||||
USB_SERIAL.println("*********************************");
|
return last_throttle;
|
||||||
USB_SERIAL.println("Enter a throttle value (48–2047):");
|
|
||||||
}
|
|
||||||
|
|
||||||
return last_throttle;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Prints RPM and throttle every 2 seconds
|
// Prints RPM and throttle every 2 seconds
|
||||||
void printRPMPeriodically(uint16_t throttle)
|
void printRPMPeriodically(uint16_t throttle)
|
||||||
{
|
{
|
||||||
static unsigned long last_print_time = 0;
|
static unsigned long last_print_time = 0;
|
||||||
unsigned long now = millis();
|
unsigned long now = millis();
|
||||||
|
|
||||||
if (now - last_print_time >= 2000)
|
if (now - last_print_time >= 2000)
|
||||||
{
|
{
|
||||||
last_print_time = now;
|
last_print_time = now;
|
||||||
|
|
||||||
uint32_t rpm = motor01.getMotorRPM(MOTOR01_MAGNET_COUNT);
|
uint32_t rpm = motor01.getMotorRPM(MOTOR01_MAGNET_COUNT);
|
||||||
|
|
||||||
USB_SERIAL.print("Throttle: ");
|
USB_SERIAL.print("Throttle: ");
|
||||||
USB_SERIAL.print(throttle);
|
USB_SERIAL.print(throttle);
|
||||||
USB_SERIAL.print(" | RPM: ");
|
USB_SERIAL.print(" | RPM: ");
|
||||||
USB_SERIAL.println(rpm);
|
USB_SERIAL.println(rpm);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue