...release 0.7.2

...release 0.7.2
This commit is contained in:
Wastl Kraus 2025-09-08 23:05:49 +02:00 committed by GitHub
commit 17e49f65ea
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 437 additions and 474 deletions

View File

@ -10,9 +10,6 @@ concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
env:
ESP32_CORE_VERSION: '3.3.0'
jobs:
# ============================================================================
# Code Quality & Linting
@ -33,15 +30,15 @@ jobs:
uses: actions/cache@v4
with:
path: |
~/.arduino15
key: ${{ runner.os }}-arduino-${{ env.ESP32_CORE_VERSION }}-${{ hashFiles('**/libraries/**') }}
~/
key: ${{ runner.os }}-arduino-${{ hashFiles('**/libraries/**') }}
restore-keys: |
${{ runner.os }}-arduino-${{ env.ESP32_CORE_VERSION }}-
${{ runner.os }}-arduino-
- name: Install ESP32 core
run: |
arduino-cli core update-index
arduino-cli core install esp32:esp32@${{ env.ESP32_CORE_VERSION }}
arduino-cli core install esp32:esp32
- name: Arduino Lint
uses: arduino/arduino-lint-action@v1
@ -79,15 +76,15 @@ jobs:
uses: actions/cache@v4
with:
path: |
~/.arduino15
key: ${{ runner.os }}-arduino-${{ env.ESP32_CORE_VERSION }}-${{ hashFiles('**/libraries/**') }}
~/
key: ${{ runner.os }}-arduino-${{ hashFiles('**/libraries/**') }}
restore-keys: |
${{ runner.os }}-arduino-${{ env.ESP32_CORE_VERSION }}-
${{ runner.os }}-arduino-
- name: Install ESP32 core
run: |
arduino-cli core update-index
arduino-cli core install esp32:esp32@${{ env.ESP32_CORE_VERSION }}
arduino-cli core install esp32:esp32
- name: Install Repo as Library
run: |
@ -117,15 +114,15 @@ jobs:
uses: actions/cache@v4
with:
path: |
~/.arduino15
key: ${{ runner.os }}-arduino-${{ env.ESP32_CORE_VERSION }}-${{ hashFiles('**/libraries/**') }}
~/
key: ${{ runner.os }}-arduino-${{ hashFiles('**/libraries/**') }}
restore-keys: |
${{ runner.os }}-arduino-${{ env.ESP32_CORE_VERSION }}-
${{ runner.os }}-arduino-
- name: Install ESP32 core
run: |
arduino-cli core update-index
arduino-cli core install esp32:esp32@${{ env.ESP32_CORE_VERSION }}
arduino-cli core install esp32:esp32
- name: Install Cppcheck
run: sudo apt-get update && sudo apt-get install -y cppcheck

View File

@ -22,7 +22,7 @@ dshot_result_t DShotCommandManager::begin()
{
dshot_result_t result;
result.success = true;
result.error_message = "Success";
result.msg = "Success";
return result;
}
@ -39,7 +39,7 @@ dshot_result_t DShotCommandManager::sendCommandWithDelay(dshot_commands_t comman
if (!isValidCommand(command))
{
result.error_message = "Invalid command";
result.msg = "Invalid command";
return result;
}
@ -53,7 +53,7 @@ dshot_result_t DShotCommandManager::sendCommandWithDelay(dshot_commands_t comman
if (!single_result.success)
{
all_successful = false;
result.error_message = single_result.error_message;
result.msg = single_result.msg;
break;
}
@ -69,7 +69,7 @@ dshot_result_t DShotCommandManager::sendCommandWithDelay(dshot_commands_t comman
if (result.success)
{
result.error_message = "Success";
result.msg = "Success";
}
return result;
@ -194,7 +194,7 @@ dshot_result_t DShotCommandManager::setSilentMode(bool enable)
}
// --- SEQUENCE COMMANDS ---
dshot_result_t DShotCommandManager::executeSequence(const dshot_command_sequence_item_t *sequence, size_t sequence_length)
dshot_result_t DShotCommandManager::executeSequence(const dshot_commandmanager_item_t *sequence, size_t sequence_length)
{
dshot_result_t result = {true, "Success"};
uint64_t total_start_time = esp_timer_get_time();
@ -209,7 +209,7 @@ dshot_result_t DShotCommandManager::executeSequence(const dshot_command_sequence
if (!item_result.success)
{
result.success = false;
result.error_message = item_result.error_message;
result.msg = item_result.msg;
break;
}
@ -229,7 +229,7 @@ dshot_result_t DShotCommandManager::executeSequence(const dshot_command_sequence
dshot_result_t DShotCommandManager::executeInitSequence()
{
// Basic ESC initialization sequence
dshot_command_sequence_item_t init_sequence[] = {
dshot_commandmanager_item_t init_sequence[] = {
{DSHOT_CMD_MOTOR_STOP, 5, 100}, // Stop motor, repeat 5 times, wait 100ms
{DSHOT_CMD_EXTENDED_TELEMETRY_ENABLE, 1, 50}, // Enable telemetry, wait 50ms
{DSHOT_CMD_ESC_INFO, 1, 100} // Request ESC info, wait 100ms
@ -242,7 +242,7 @@ dshot_result_t DShotCommandManager::executeInitSequence()
dshot_result_t DShotCommandManager::executeCalibrationSequence()
{
// Basic ESC calibration sequence
dshot_command_sequence_item_t calibration_sequence[] = {
dshot_commandmanager_item_t calibration_sequence[] = {
{DSHOT_CMD_MOTOR_STOP, 10, 500}, // Ensure motor is stopped
{DSHOT_CMD_SPIN_DIRECTION_NORMAL, 10, 100}, // Set normal spin direction
{DSHOT_CMD_3D_MODE_OFF, 10, 100}, // Disable 3D mode

View File

@ -12,13 +12,13 @@
#include <DShotRMT.h>
#include <dshot_commands.h>
// Command sequence item
// Command item
typedef struct
{
dshot_commands_t command;
uint16_t repeat_count;
uint32_t delay_ms;
} dshot_command_sequence_item_t;
} dshot_commandmanager_item_t;
// Advanced DShot command manager class
class DShotCommandManager
@ -76,7 +76,7 @@ public:
// --- SEQUENCE COMMANDS ---
// Execute a sequence of DShot commands
dshot_result_t executeSequence(const dshot_command_sequence_item_t *sequence, size_t sequence_length);
dshot_result_t executeSequence(const dshot_commandmanager_item_t *sequence, size_t sequence_length);
// Execute ESC initialization sequence
dshot_result_t executeInitSequence();

View File

@ -23,25 +23,27 @@ DShotRMT::DShotRMT(gpio_num_t gpio, dshot_mode_t mode, bool is_bidirectional)
: _gpio(gpio),
_mode(mode),
_is_bidirectional(is_bidirectional),
_last_erpm_atomic(0),
_telemetry_ready_flag(false),
_frame_timer_us(0),
_timing_config(DSHOT_TIMINGS[mode]),
_last_throttle(DSHOT_CMD_MOTOR_STOP),
_last_transmission_time(0),
_last_erpm(0),
_parsed_packet(0),
_packet{0},
_total_transmissions(0),
_failed_transmissions(0),
_bitPositions{0},
_level0(_is_bidirectional ? 0 : 1),
_level1(_is_bidirectional ? 1 : 0),
_rmt_tx_channel(nullptr),
_rmt_rx_channel(nullptr),
_dshot_encoder(nullptr),
_tx_channel_config{},
_rx_channel_config{},
_transmit_config{},
_receive_config{},
_rx_queue(nullptr)
_receive_config{}
{
// Calculate frame timing including switch/pause time
_frame_timer_us = _timing_config.frame_length_us + DSHOT_SWITCH_TIME;
_frame_timer_us = _timing_config.frame_length_us + DSHOT_PAUSE_US;
// Double frame time for bidirectional mode (includes response time)
if (_is_bidirectional)
@ -60,79 +62,66 @@ DShotRMT::DShotRMT(uint16_t pin_nr, dshot_mode_t mode, bool is_bidirectional)
// Destructor for "better" code
DShotRMT::~DShotRMT()
{
// ...kill them all
// ...TX
if (_rmt_tx_channel)
{
rmt_disable(_rmt_tx_channel);
if (rmt_disable(_rmt_tx_channel) == DSHOT_OK)
{
rmt_del_channel(_rmt_tx_channel);
_rmt_tx_channel = nullptr;
}
}
//
// ...RX
if (_rmt_rx_channel)
{
rmt_disable(_rmt_rx_channel);
if (rmt_disable(_rmt_rx_channel) == DSHOT_OK)
{
rmt_del_channel(_rmt_rx_channel);
_rmt_rx_channel = nullptr;
}
}
//
// ...Encoder
if (_dshot_encoder)
{
rmt_del_encoder(_dshot_encoder);
_dshot_encoder = nullptr;
}
//
if (_rx_queue)
{
vQueueDelete(_rx_queue);
_rx_queue = nullptr;
}
}
// Initialize DShotRMT
// Init DShotRMT
dshot_result_t DShotRMT::begin()
{
dshot_result_t result = {false, UNKNOWN_ERROR};
uint64_t start_time = esp_timer_get_time();
// Init RX channel first
if (_is_bidirectional)
{
if (!_initRXChannel())
if (!_initRXChannel().success)
{
result.error_message = RX_INIT_FAILED;
_dshot_log(RX_INIT_FAILED);
return result;
return {false, RX_INIT_FAILED};
}
}
// Init TX channel
if (!_initTXChannel())
if (!_initTXChannel().success)
{
result.error_message = TX_INIT_FAILED;
_dshot_log(TX_INIT_FAILED);
return result;
return {false, TX_INIT_FAILED};
}
// Init DShot encoder
if (_initDShotEncoder() != DSHOT_OK)
if (!_initDShotEncoder().success)
{
result.error_message = ENCODER_INIT_FAILED;
_dshot_log(ENCODER_INIT_FAILED);
return result;
return {false, ENCODER_INIT_FAILED};
}
uint64_t end_time = esp_timer_get_time();
result.success = true;
result.error_message = INIT_SUCCESS;
// Bit positions precalculation
_preCalculateBitPositions();
return result;
return {true, INIT_SUCCESS};
}
// Init RMT TX channel
bool DShotRMT::_initTXChannel()
dshot_result_t DShotRMT::_initTXChannel()
{
// Configure TX channel
_tx_channel_config.gpio_num = _gpio;
@ -148,22 +137,24 @@ bool DShotRMT::_initTXChannel()
// Create RMT TX channel
if (rmt_new_tx_channel(&_tx_channel_config, &_rmt_tx_channel) != DSHOT_OK)
{
_dshot_log(TX_INIT_FAILED);
return DSHOT_ERROR;
return {false, TX_INIT_FAILED};
}
return (rmt_enable(_rmt_tx_channel) == DSHOT_OK);
//
if (rmt_enable(_rmt_tx_channel) != DSHOT_OK)
{
return {false, TX_INIT_FAILED};
}
return {true, TX_INIT_SUCCESS};
}
// Init RMT RX channel
bool DShotRMT::_initRXChannel()
dshot_result_t DShotRMT::_initRXChannel()
{
// Create a queue for RX callback data
_rx_queue = xQueueCreate(RMT_QUEUE_DEPTH, sizeof(rmt_rx_done_event_data_t));
if (_rx_queue == nullptr)
{
return DSHOT_ERROR;
}
// Direct RMT symbol processing - Performance optimized
_rx_event_callbacks.on_recv_done = _rmt_rx_done_callback;
// Config RMT RX
_rx_channel_config.gpio_num = _gpio;
@ -178,37 +169,43 @@ bool DShotRMT::_initRXChannel()
// Create RMT RX channel
if (rmt_new_rx_channel(&_rx_channel_config, &_rmt_rx_channel) != DSHOT_OK)
{
_dshot_log(RX_INIT_FAILED);
return DSHOT_ERROR;
return {false, RX_INIT_FAILED};
}
// Register RX callback
_rx_event_callbacks.on_recv_done = _rmt_rx_done_callback;
if (rmt_rx_register_event_callbacks(_rmt_rx_channel, &_rx_event_callbacks, _rx_queue) != DSHOT_OK)
//
if (rmt_enable(_rmt_rx_channel) != DSHOT_OK)
{
_dshot_log(RX_INIT_FAILED);
return DSHOT_ERROR;
return {false, RX_INIT_FAILED};
}
return (rmt_enable(_rmt_rx_channel) == DSHOT_OK);
return {true, RX_INIT_SUCCESS};
}
// Callback for RMT RX
bool IRAM_ATTR DShotRMT::_rmt_rx_done_callback(rmt_channel_handle_t rmt_rx_channel, const rmt_rx_done_event_data_t *edata, void *user_data)
{
// Init RX buffer
QueueHandle_t rx_queue = (QueueHandle_t)user_data;
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
DShotRMT *instance = static_cast<DShotRMT *>(user_data);
// Copy callback data into RX buffer
xQueueGenericSendFromISR(rx_queue, edata, &xHigherPriorityTaskWoken, queueSEND_TO_BACK);
// ISR check for valid data
if (edata && edata->num_symbols >= GCR_BITS_PER_FRAME && edata->num_symbols <= GCR_BITS_PER_FRAME)
{
return (xHigherPriorityTaskWoken == pdTRUE);
// Direct decoding
uint16_t erpm = instance->_decodeDShotFrame(edata->received_symbols);
if (erpm != DSHOT_NULL_PACKET)
{
// Atomic writes - thread-safe
instance->_last_erpm_atomic = erpm;
instance->_telemetry_ready_flag = true;
}
}
return false;
}
// Initialize DShot encoder
bool DShotRMT::_initDShotEncoder()
dshot_result_t DShotRMT::_initDShotEncoder()
{
// Create copy encoder configuration
rmt_copy_encoder_config_t encoder_config = {};
@ -216,37 +213,27 @@ bool DShotRMT::_initDShotEncoder()
// Create encoder instance
if (rmt_new_copy_encoder(&encoder_config, &_dshot_encoder) != DSHOT_OK)
{
_dshot_log(ENCODER_INIT_FAILED);
return DSHOT_ERROR;
return {false, ENCODER_INIT_FAILED};
}
return DSHOT_OK;
return {true, TX_INIT_SUCCESS};
}
// Send throttle value
dshot_result_t DShotRMT::sendThrottle(uint16_t throttle)
{
static uint16_t last_throttle = DSHOT_CMD_MOTOR_STOP;
dshot_result_t result = {false, UNKNOWN_ERROR};
// Special case: if throttle is 0, use sendCommand() instead
if (throttle == 0)
{
return sendCommand(DSHOT_CMD_MOTOR_STOP);
}
// Log only if throttle is out of range and different from last time
if ((throttle < DSHOT_THROTTLE_MIN || throttle > DSHOT_THROTTLE_MAX) && throttle != last_throttle)
{
_dshot_log(THROTTLE_NOT_IN_RANGE);
result.error_message = THROTTLE_NOT_IN_RANGE;
}
// Always store the original throttle value
last_throttle = throttle;
_last_throttle = throttle;
// Constrain throttle for transmission and send
uint16_t new_throttle = constrain(throttle, DSHOT_THROTTLE_MIN, DSHOT_THROTTLE_MAX);
_packet = _buildDShotPacket(new_throttle);
return _sendDShotFrame(_packet);
@ -255,81 +242,54 @@ dshot_result_t DShotRMT::sendThrottle(uint16_t throttle)
// Send DShot command to ESC
dshot_result_t DShotRMT::sendCommand(uint16_t command)
{
dshot_result_t result = {false, UNKNOWN_ERROR};
// Validate command is within DShot specification range
if (command < DSHOT_CMD_MOTOR_STOP || command > DSHOT_CMD_MAX)
{
_dshot_log(COMMAND_NOT_VALID);
result.error_message = COMMAND_NOT_VALID;
return result;
return {false, COMMAND_NOT_VALID};
}
// Build packet and transmit
_packet = _buildDShotPacket(command);
return _sendDShotFrame(_packet);
}
// Get telemetry data with timing and error handling
dshot_telemetry_result_t DShotRMT::getTelemetry(uint8_t magnet_count)
dshot_telemetry_result_t DShotRMT::getTelemetry(uint16_t magnet_count)
{
dshot_telemetry_result_t result = {false, 0, 0, UNKNOWN_ERROR};
// Result container
dshot_telemetry_result_t result = {false, NO_DSHOT_ERPM, NO_DSHOT_RPM, TELEMETRY_FAILED};
// Check if bidirectional mode is enabled
if (!_is_bidirectional)
{
result.error_message = BIDIR_NOT_ENABLED;
_dshot_log(BIDIR_NOT_ENABLED);
result.msg = BIDIR_NOT_ENABLED;
return result;
}
// Get eRPM
uint16_t erpm = getERPM();
if (erpm == DSHOT_NULL_PACKET)
//
if (_telemetry_ready_flag)
{
result.error_message = TELEMETRY_TIMEOUT;
return result;
}
_telemetry_ready_flag = false;
// Calculate motor RPM
uint8_t pole_pairs = max(1, magnet_count / 2);
uint32_t motor_rpm = erpm / pole_pairs;
uint16_t erpm = _last_erpm_atomic;
//
if (erpm != DSHOT_NULL_PACKET && magnet_count >= 1)
{
uint8_t pole_pairs = max(MIN_POLE_PAIRS, (magnet_count / MAGNETS_PER_POLE_PAIR));
uint32_t motor_rpm = (erpm / pole_pairs);
result.success = true;
result.erpm = erpm;
result.motor_rpm = motor_rpm;
result.error_message = TELEMETRY_SUCCESS;
result.msg = TELEMETRY_SUCCESS;
}
}
return result;
}
// Get RPM from ESC (bidirectional mode only) - backward compatibility
uint16_t DShotRMT::getERPM()
{
// Check if bidirectional mode is enabled
if (!_is_bidirectional)
{
_dshot_log(BIDIR_NOT_ENABLED);
return _last_erpm;
}
// RMT RX event data
rmt_rx_done_event_data_t rx_data;
// Wait for data from the RX callback for a certain timeout
if (xQueueReceive(_rx_queue, &rx_data, pdMS_TO_TICKS(DSHOT_RX_TIMEOUT_MS)) == pdTRUE)
{
// Decode the received symbols if a valid frame was received
if (rx_data.num_symbols > DSHOT_NULL_PACKET)
{
_last_erpm = _decodeDShotFrame(rx_data.received_symbols);
}
}
return _last_erpm;
}
// Build a complete DShot packet
dshot_packet_t DShotRMT::_buildDShotPacket(const uint16_t value)
{
@ -339,8 +299,6 @@ dshot_packet_t DShotRMT::_buildDShotPacket(const uint16_t value)
// Re-check for valid value
if (value > DSHOT_THROTTLE_MAX)
{
_dshot_log(PACKET_BUILD_ERROR);
// Something is really wrong
return packet;
}
@ -371,39 +329,47 @@ uint16_t DShotRMT::_parseDShotPacket(const dshot_packet_t &packet)
uint16_t DShotRMT::_calculateCRC(const uint16_t data)
{
// DShot CRC
uint16_t crc = (data ^ (data >> 4) ^ (data >> 8)) & 0b0000000000001111;
uint16_t crc = (data ^ (data >> 4) ^ (data >> 8)) & DSHOT_CRC_MASK;
// Invert CRC for bidirectional DShot mode
if (_is_bidirectional)
{
crc = (~crc) & 0b0000000000001111;
crc = (~crc) & DSHOT_CRC_MASK;
}
return crc;
}
// Per calculate bits - Performance optimized
void DShotRMT::_preCalculateBitPositions()
{
for (int i = 0; i < DSHOT_BITS_PER_FRAME; ++i)
{
_bitPositions[i] = DSHOT_BITS_PER_FRAME - 1 - i;
}
}
// Transmit DShot packet via RMT
dshot_result_t DShotRMT::_sendDShotFrame(const dshot_packet_t &packet)
{
dshot_result_t result = {false, UNKNOWN_ERROR};
uint64_t start_time = esp_timer_get_time();
// Check timing requirements
if (!_timer_signal())
{
return result;
return {false, TIMING_CORRECTION};
}
// Enable RMT RX before RMT TX
if (_is_bidirectional)
{
// Calculate transmission data size
size_t rx_size_bytes = GCR_BITS_PER_FRAME * sizeof(rmt_symbol_word_t);
// Performance reasons
rmt_symbol_word_t rx_symbols[DSHOT_BITS_PER_FRAME];
if (rmt_receive(_rmt_rx_channel, rx_symbols, sizeof(rx_symbols), &_receive_config) != DSHOT_OK)
if (rmt_receive(_rmt_rx_channel, rx_symbols, rx_size_bytes, &_receive_config) != DSHOT_OK)
{
result.error_message = RX_RMT_RECEIVER_ERROR;
return result;
return {false, RECEIVER_FAILED};
}
}
@ -422,18 +388,14 @@ dshot_result_t DShotRMT::_sendDShotFrame(const dshot_packet_t &packet)
// Disable RMT RX for sending
if (rmt_disable(_rmt_rx_channel) != DSHOT_OK)
{
result.error_message = RX_RMT_RECEIVER_ERROR;
_dshot_log(RX_RMT_RECEIVER_ERROR);
return result;
return {false, RECEIVER_FAILED};
}
}
// Perform RMT transmission
if (rmt_transmit(_rmt_tx_channel, _dshot_encoder, tx_symbols, tx_size_bytes, &_transmit_config) != DSHOT_OK)
{
result.error_message = TRANSMITTER_ERROR;
_dshot_log(TRANSMITTER_ERROR);
return result;
return {false, TRANSMISSION_FAILED};
}
// Re-enable RMT RX
@ -441,20 +403,14 @@ dshot_result_t DShotRMT::_sendDShotFrame(const dshot_packet_t &packet)
{
if (rmt_enable(_rmt_rx_channel) != DSHOT_OK)
{
result.error_message = RX_RMT_RECEIVER_ERROR;
_dshot_log(RX_RMT_RECEIVER_ERROR);
return result;
return {false, RECEIVER_FAILED};
}
}
// Update timestamp and calculate execution time
_timer_reset();
uint64_t end_time = esp_timer_get_time();
result.success = true;
result.error_message = TRANSMISSION_SUCCESS;
return result;
return {true, TRANSMISSION_SUCCESS};
}
// Encode DShot packet into RMT symbol format (placed in IRAM for performance)
@ -462,16 +418,16 @@ bool IRAM_ATTR DShotRMT::_encodeDShotFrame(const dshot_packet_t &packet, rmt_sym
{
_parsed_packet = _parseDShotPacket(packet);
const uint16_t level0 = _is_bidirectional ? 0 : 1;
const uint16_t level1 = _is_bidirectional ? 1 : 0;
for (int i = 0; i < DSHOT_BITS_PER_FRAME; i++)
{
// Decode MSB
bool bit = (_parsed_packet >> (DSHOT_BITS_PER_FRAME - 1 - i)) & 0b0000000000000001;
symbols[i].level0 = level0;
for (int i = 0; i < DSHOT_BITS_PER_FRAME; ++i)
{
// Use precalculated bit positions - Performace optimized
int bit_position = _bitPositions[i];
bool bit = (_parsed_packet >> bit_position) & 0b0000000000000001;
symbols[i].level0 = _level0;
symbols[i].duration0 = bit ? _timing_config.ticks_one_high : _timing_config.ticks_zero_high;
symbols[i].level1 = level1;
symbols[i].level1 = _level1;
symbols[i].duration1 = bit ? _timing_config.ticks_one_low : _timing_config.ticks_zero_low;
}
@ -497,15 +453,15 @@ uint16_t DShotRMT::_decodeDShotFrame(const rmt_symbol_word_t *symbols)
// Extract 16 data bits and 4 CRC bits from 20-bit frame.
// The first bit of the GCR frame is a start bit and is discarded.
uint16_t data_and_crc = (decoded_frame & 0xFFFF);
uint16_t data_and_crc = (decoded_frame & DSHOT_FULL_PACKET);
// Cutting 4 bits?
uint16_t received_data = data_and_crc >> 4;
// Masking CRC
uint16_t received_crc = data_and_crc & 0b0000000000001111;
uint16_t received_crc = data_and_crc & DSHOT_CRC_MASK;
// Telemetry request bit is always 1.
// Telemetry request bit has to be 1
if (!(received_data & (1 << 11)))
{
return DSHOT_NULL_PACKET;
@ -522,7 +478,7 @@ uint16_t DShotRMT::_decodeDShotFrame(const rmt_symbol_word_t *symbols)
}
// Return the eRPM value (first 11 bits of received data).
return received_data & 0b0000011111111111;
return received_data & DSHOT_THROTTLE_MAX;
}
// Check if enough time has passed for next transmission
@ -540,6 +496,7 @@ bool IRAM_ATTR DShotRMT::_timer_signal()
bool DShotRMT::_timer_reset()
{
_last_transmission_time = esp_timer_get_time();
return DSHOT_OK;
}
@ -558,14 +515,11 @@ void DShotRMT::printDShotInfo(Stream &output) const
output.printf("Bidirectional: %s\n", _is_bidirectional ? "YES" : "NO");
// Timing Info
output.printf("Frame Length: %u us\n", _timing_config.frame_length_us);
// Packet Info
output.printf("Current Packet: ");
// Print bit by bit
for (int i = 15; i >= 0; --i)
for (int i = DSHOT_BITS_PER_FRAME - 1; i >= 0; --i)
{
if ((_parsed_packet >> i) & 1)
{
@ -592,3 +546,25 @@ void DShotRMT::printCpuInfo(Stream &output) const
output.printf("XTAL Freq = %lu MHz\n", getXtalFrequencyMhz());
output.printf("APB Freq = %lu Hz\n", getApbFrequency());
}
// --- HELPERS ---
void printDShotResult(dshot_result_t &result, Stream &output)
{
output.printf("Status: %s - %s\n", result.success ? "SUCCESS" : "FAILED", result.msg);
output.println(" ");
}
//
void printDShotTelemetry(dshot_telemetry_result_t &result, Stream &output)
{
if (result.success)
{
output.printf("Telemetry: eRPM=%u, Motor RPM=%u\n", result.erpm, result.motor_rpm);
}
else
{
output.printf("Telemetry: FAILED - %s\n", result.msg);
}
output.println(" ");
}

View File

@ -15,19 +15,26 @@
#include <driver/rmt_rx.h>
// DShot Protocol Constants
constexpr auto DSHOT_THROTTLE_FAILSAFE = 0;
constexpr auto DSHOT_THROTTLE_MIN = 48;
constexpr auto DSHOT_THROTTLE_MAX = 2047;
constexpr auto DSHOT_BITS_PER_FRAME = 16;
constexpr auto DSHOT_SWITCH_TIME = 30; // Time in us between TX and RX
constexpr auto DSHOT_NULL_PACKET = 0b0000000000000000;
constexpr auto DSHOT_RX_TIMEOUT_MS = 2;
static constexpr auto DSHOT_THROTTLE_FAILSAFE = 0;
static constexpr auto DSHOT_THROTTLE_MIN = 48;
static constexpr auto DSHOT_THROTTLE_MAX = 2047;
static constexpr auto DSHOT_BITS_PER_FRAME = 16;
static constexpr auto DSHOT_PAUSE_US = 30; // Additional frame pause time
static constexpr auto DSHOT_NULL_PACKET = 0b0000000000000000;
static constexpr auto DSHOT_FULL_PACKET = 0b1111111111111111;
static constexpr auto DSHOT_CRC_MASK = 0b0000000000001111;
static constexpr auto DSHOT_RX_TIMEOUT_MS = 2; // Never reached, just a timeeout
static constexpr auto GCR_BITS_PER_FRAME = 21; // Number of GCR bits in a DShot answer frame (1 start + 16 data + 4 CRC)
static constexpr auto DEFAULT_MOTOR_MAGNET_COUNT = 14;
static constexpr auto MAGNETS_PER_POLE_PAIR = 2;
static constexpr auto MIN_POLE_PAIRS = 1;
static constexpr auto NO_DSHOT_ERPM = 0;
static constexpr auto NO_DSHOT_RPM = 0;
// RMT Configuration Constants
constexpr auto DSHOT_CLOCK_SRC_DEFAULT = RMT_CLK_SRC_DEFAULT;
constexpr auto DSHOT_RMT_RESOLUTION = 10 * 1000 * 1000; // 10 MHz resolution
constexpr auto RMT_BUFFER_SIZE = DSHOT_BITS_PER_FRAME;
constexpr auto GCR_BITS_PER_FRAME = 21; // Number of GCR bits in a DShot answer frame (1 start + 16 data + 4 CRC)
constexpr auto RMT_BUFFER_SYMBOLS = 64;
constexpr auto RMT_QUEUE_DEPTH = 1;
@ -36,7 +43,7 @@ constexpr auto RMT_QUEUE_DEPTH = 1;
constexpr uint32_t DSHOT_PULSE_MIN = 3000;
constexpr uint32_t DSHOT_PULSE_MAX = 60000;
// DShot Mode Enumeration
// DShot Modes
typedef enum
{
DSHOT_OFF,
@ -46,7 +53,7 @@ typedef enum
DSHOT1200
} dshot_mode_t;
// DShot Packet Structure
// DShot Packet
typedef struct
{
uint16_t throttle_value : 11;
@ -54,7 +61,7 @@ typedef struct
uint16_t checksum : 4;
} dshot_packet_t;
// DShot Timing Configuration Structure
// DShot Timing Configuration
typedef struct
{
uint32_t frame_length_us;
@ -65,25 +72,29 @@ typedef struct
uint16_t ticks_zero_low;
} dshot_timing_t;
// Command execution result structure
// Error handling
typedef struct
{
bool success;
const char *error_message;
const char *msg;
} dshot_result_t;
// DShot telemetry result structure
// DShot telemetry result
typedef struct
{
bool success;
uint16_t erpm;
uint32_t motor_rpm;
const char *error_message;
uint16_t motor_rpm;
const char *msg;
} dshot_telemetry_result_t;
// Naming convention
typedef dshotCommands_e dshot_commands_t;
// --- HELPERS ---
void printDShotResult(dshot_result_t &result, Stream &output = Serial);
void printDShotTelemetry(dshot_telemetry_result_t &result, Stream &output = Serial);
//
class DShotRMT
{
@ -106,21 +117,12 @@ public:
// Send DShot command (0-47)
dshot_result_t sendCommand(uint16_t command);
// Get telemetry data (bidirectional mode only)
dshot_telemetry_result_t getTelemetry(uint8_t magnet_count = 14);
// Get eRPM only (bidirectional mode only)
uint16_t getERPM();
// --- GETTERS ---
gpio_num_t getGPIO() const { return _gpio; }
uint16_t getDShotPacket() const { return _parsed_packet; }
bool is_bidirectional() const { return _is_bidirectional; }
dshot_mode_t getMode() const { return _mode; }
// Get execution statistics
uint32_t getTotalTransmissions() const { return _total_transmissions; }
uint32_t getFailedTransmissions() const { return _failed_transmissions; }
dshot_telemetry_result_t getTelemetry(uint16_t magnet_count = DEFAULT_MOTOR_MAGNET_COUNT);
// --- INFO ---
void printDShotInfo(Stream &output = Serial) const;
@ -155,16 +157,15 @@ private:
bool _is_bidirectional;
uint32_t _frame_timer_us;
const dshot_timing_t &_timing_config;
uint16_t _last_throttle;
// --- TIMING & STATE VARIABLES ---
// --- TIMING & PACKET VARIABLES ---
uint64_t _last_transmission_time;
uint16_t _last_erpm;
uint16_t _parsed_packet;
dshot_packet_t _packet;
// --- STATISTICS ---
uint32_t _total_transmissions;
uint32_t _failed_transmissions;
uint8_t _bitPositions[DSHOT_BITS_PER_FRAME];
uint16_t _level0;
uint16_t _level1;
// --- RMT HARDWARE HANDLES ---
rmt_channel_handle_t _rmt_tx_channel;
@ -178,14 +179,15 @@ private:
rmt_receive_config_t _receive_config;
// --- INITS ---
bool _initTXChannel();
bool _initRXChannel();
bool _initDShotEncoder();
dshot_result_t _initTXChannel();
dshot_result_t _initRXChannel();
dshot_result_t _initDShotEncoder();
// --- PACKET MANAGEMENT ---
dshot_packet_t _buildDShotPacket(const uint16_t value);
uint16_t _parseDShotPacket(const dshot_packet_t &packet);
uint16_t _calculateCRC(const uint16_t data);
void _preCalculateBitPositions();
// --- FRAME PROCESSING ---
dshot_result_t _sendDShotFrame(const dshot_packet_t &packet);
@ -197,34 +199,38 @@ private:
bool _timer_reset();
// -- CALLBACKS ---
QueueHandle_t _rx_queue;
rmt_rx_event_callbacks_t _rx_event_callbacks;
volatile rmt_symbol_word_t _rx_symbols_direct[GCR_BITS_PER_FRAME];
volatile uint16_t _last_erpm_atomic;
volatile bool _telemetry_ready_flag;
static bool IRAM_ATTR _rmt_rx_done_callback(rmt_channel_handle_t rmt_rx_channel, const rmt_rx_done_event_data_t *edata, void *user_data);
// --- DSHOT DEFAULTS ---
static constexpr auto DSHOT_TELEMETRY_INVALID = (0xffff);
// --- ERROR HANDLING & LOGGING ---
void _dshot_log(const char *msg, Stream &output = Serial) const { output.println(msg); }
static constexpr auto const DSHOT_TELEMETRY_INVALID = (0xffff);
// --- CONSTANTS & ERROR MESSAGES ---
static constexpr bool DSHOT_OK = 0;
static constexpr bool DSHOT_ERROR = 1;
static constexpr char *NEW_LINE = " ";
static constexpr char *UNKNOWN_ERROR = "Unknown Error!";
static constexpr char *TX_INIT_FAILED = "Failed to initialize TX channel!";
static constexpr char *RX_INIT_FAILED = "Failed to initialize RX channel!";
static constexpr char *ENCODER_INIT_FAILED = "Failed to initialize DShot encoder!";
static constexpr char *CRC_CHECK_FAILED = "RX CRC Check failed!";
static constexpr char *THROTTLE_NOT_IN_RANGE = "Throttle value not in range (48 - 2047)!";
static constexpr char *COMMAND_NOT_VALID = "Not a valid DShot Command (0 - 47)!";
static constexpr char *BIDIR_NOT_ENABLED = "Bidirectional DShot support not enabled!";
static constexpr char *RX_RMT_RECEIVER_ERROR = "RX RMT receiver error!";
static constexpr char *PACKET_BUILD_ERROR = "Value too big for DShot Packet!";
static constexpr char *TRANSMITTER_ERROR = "RMT TX Transmitter Error!";
static constexpr char *INIT_SUCCESS = "DShotRMT initialized successfully";
static constexpr char *TELEMETRY_SUCCESS = "Telemetry read successfully";
static constexpr char *TELEMETRY_TIMEOUT = "Telemetry read timeout";
static constexpr char *TRANSMISSION_SUCCESS = "Transmission successful";
static constexpr char const *NONE = "";
static constexpr char const *UNKNOWN_ERROR = "Unknown Error!";
static constexpr char const *INIT_SUCCESS = "SignalGeneratorRMT initialized successfully";
static constexpr char const *INIT_FAILED = "SignalGeneratorRMT init failed!";
static constexpr char const *TX_INIT_SUCCESS = "TX RMT channel initialized successfully";
static constexpr char const *TX_INIT_FAILED = "TX RMT channel init failed!";
static constexpr char const *RX_INIT_SUCCESS = "RX RMT channel initialized successfully";
static constexpr char const *RX_INIT_FAILED = "RX RMT channel init failed!";
static constexpr char const *RX_BUFFER_FAILED = "RX RMT buffer init failed!";
static constexpr char const *ENCODER_INIT_SUCCESS = "RMT encoder initialized successfully";
static constexpr char const *ENCODER_INIT_FAILED = "RMT encoder init failed!";
static constexpr char const *TRANSMISSION_SUCCESS = "Transmission successfully";
static constexpr char const *TRANSMISSION_FAILED = "Transmission failed!";
static constexpr char const *RECEIVER_FAILED = "RMT receiver failed!";
static constexpr char const *THROTTLE_NOT_IN_RANGE = "Throttle not in range! (48 - 2047)";
static constexpr char const *COMMAND_NOT_VALID = "Command not valid! (0 - 47)";
static constexpr char const *BIDIR_NOT_ENABLED = "Bidirectional DShot not enabled!";
static constexpr char const *TELEMETRY_SUCCESS = "Valid Telemetric Frame received!";
static constexpr char const *TELEMETRY_FAILED = "No valid Telemetric Frame received!";
static constexpr char const *INVALID_MAGNET_COUNT = "Invalid motor magnet count!";
static constexpr char const *TIMING_CORRECTION = "Timing correction!";
};

326
README.md
View File

@ -3,7 +3,7 @@
# DShotRMT - ESP32 Library (Rewrite for ESP-IDF 5)
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`).
Supports all standard DShot modes (150, 300, 600) and features continuous frame transmission with configurable pause.
Supports all standard DShot modes (150, 300, 600, 1200) and features continuous frame transmission with configurable timing.
**Now with BiDirectional DShot support and advanced command management!**
> The legacy version (using the old `rmt.h` API) is still available in the `oldAPI` branch.
@ -12,20 +12,36 @@ Supports all standard DShot modes (150, 300, 600) and features continuous frame
## 🚀 Features
- **All DShot Modes:** DSHOT150, DSHOT300 (default), DSHOT600, (DSHOT1200)
- **BiDirectional DShot:** Experimental support for RPM feedback
- **All DShot Modes:** DSHOT150, DSHOT300 (default), DSHOT600, DSHOT1200
- **BiDirectional DShot:** Full support for RPM telemetry feedback
- **Advanced Command Manager:** High-level API for ESC configuration and control
- **Command Sequences:** Predefined initialization and calibration sequences
- **Continuous Frames:** Independent timed, Hardware signal generation
- **Configurable Pause:** Ensures ESCs can reliably detect frame boundaries
- **Hardware-Timed Signals:** Independent, precise signal generation using ESP32 RMT peripheral
- **Configurable Timing:** Ensures ESCs can reliably detect frame boundaries
- **Error Handling:** Comprehensive result reporting with success/failure status
- **Simple API:** Easy integration into your Arduino or ESP-IDF project
---
## 📦 Installation
Clone this repository and add it to your Arduino libraries or ESP-IDF components.
### Arduino IDE
1. Search "Arduino Library Manager" for "DShotRMT"
or
1. Clone this repository or download as ZIP
2. Place in your Arduino libraries folder (`~/Arduino/libraries/DShotRMT/`)
3. Restart Arduino IDE
### PlatformIO
Add to your `platformio.ini`:
```ini
lib_deps =
https://github.com/derdoktor667/DShotRMT.git
```
### Manual Installation
```sh
git clone https://github.com/derdoktor667/DShotRMT.git
```
@ -39,216 +55,208 @@ git clone https://github.com/derdoktor667/DShotRMT.git
```cpp
#include <DShotRMT.h>
// Create motor instance
DShotRMT motor(17, DSHOT300);
// Create motor instance (GPIO 17, DSHOT300, non-bidirectional)
DShotRMT motor(17, DSHOT300, false);
void setup() {
Serial.begin(115200);
motor.begin();
// Initialize the motor
dshot_result_t result = motor.begin();
if (result.success) {
Serial.println("Motor initialized successfully");
} else {
Serial.printf("Motor init failed: %s\n", result.msg);
}
}
void loop() {
motor.sendThrottle(1000); // Send throttle value
delay(20);
// Send throttle value (48-2047)
dshot_result_t result = motor.sendThrottle(1000);
if (!result.success) {
Serial.printf("Throttle command failed: %s\n", result.msg);
}
}
```
### Advanced Usage (DShotCommandManager)
### Bidirectional DShot (RPM Telemetry)
```cpp
#include <DShotRMT.h>
#include <DShotCommandManager.h>
// Create motor and command manager instances
DShotRMT motor(17, DSHOT300);
DShotCommandManager cmdManager(motor);
// Enable bidirectional mode for telemetry
DShotRMT motor(17, DSHOT300, true);
void setup() {
Serial.begin(115200);
motor.begin();
cmdManager.begin();
// Execute initialization sequence
cmdManager.executeInitSequence();
}
void loop() {
// Your main code here
// Send throttle
motor.sendThrottle(1000);
// Get telemetry data
dshot_telemetry_result_t telemetry = motor.getTelemetry(14); // 14 magnets
if (telemetry.success) {
Serial.printf("eRPM: %u, Motor RPM: %u\n",
telemetry.erpm,
telemetry.motor_rpm);
}
}
```
---
## 🎛️ DShotCommandManager API
The `DShotCommandManager` provides a high-level interface for ESC control and configuration:
### Motor Control
- `stopMotor()` - Stop motor immediately
- `set3DMode(bool enable)` - Enable/disable 3D mode
- `setSpinDirection(bool reversed)` - Set motor spin direction
- `saveSettings()` - Save current settings to ESC
### LED Control (BLHeli32 only)
- `setLED(uint8_t led_number, bool state)` - Control ESC LEDs (0-3)
### Beacon Functions
- `activateBeacon(uint8_t beacon_number)` - Activate motor beeping (1-5)
### Telemetry
- `setExtendedTelemetry(bool enable)` - Enable/disable extended telemetry
- `requestESCInfo()` - Request ESC information
### Command Sequences
- `executeInitSequence()` - Basic ESC initialization
- `executeCalibrationSequence()` - ESC calibration sequence
- `executeSequence(sequence, length)` - Custom command sequences
### Utility Functions
- `getCommandName(command)` - Get command name as string
- `isValidCommand(command)` - Validate command
- `printStatistics()` - Print execution statistics
- `resetStatistics()` - Reset execution counters
---
## 📚 Examples
### 1. Basic DShot Control
Use the `dshot300.ino` example for simple throttle control.
The library includes comprehensive examples:
### 2. Advanced Command Management
Use the `command_manager.ino` example for interactive ESC control:
### 1. Basic DShot Control (`dshot300.ino`)
- Simple throttle control
- Command execution
- Serial interface for testing
- Telemetry reading (if bidirectional enabled)
### 2. Advanced Command Management (`command_manager.ino`)
Interactive ESC control with full menu system:
```
=== DShot Command Manager Menu ===
Basic Commands:
1 - Stop Motor
2 - Activate Beacon 1
3 - Set Normal Spin Direction
4 - Set Reversed Spin Direction
5 - Enable 3D Mode
6 - Disable 3D Mode
7 - Save Settings
8 - Turn LED 0 ON
9 - Turn LED 0 OFF
5 - Get ESC Info
6 - Turn LED 0 ON
7 - Turn LED 0 OFF
0 - Emergency Stop
Sequences:
i - Execute Initialization Sequence
c - Execute Calibration Sequence
Advanced:
cmd <number> - Send DShot command (0 - 47)
throttle <value> - Set throttle (48 - 2047)
throttle 0 - Stop sending throttle
Advanced Commands:
cmd <number> - Send DShot command (0-47)
throttle <value> - Set throttle (48-2047)
repeat cmd <num> count <count> - Repeat command
```
---
## 📚 DShot Protocol Overview
## 🔧 Hardware Configuration
DShot transmits 16-bit packets to brushless ESCs:
### Supported DShot Modes
- **11 bits:** Throttle value
- **1 bit:** Telemetry request
- **4 bits:** Checksum (CRC)
| Mode | Bitrate | Bit Time | Frame Time | Use Case |
|----------|-------------|----------|------------|----------|
| DSHOT150 | 150 kbit/s | 6.67 µs | ~107 µs | Long wires, EMI-prone |
| DSHOT300 | 300 kbit/s | 3.33 µs | ~53 µs | Standard (recommended) |
| DSHOT600 | 600 kbit/s | 1.67 µs | ~27 µs | High performance |
| DSHOT1200| 1200 kbit/s | 0.83 µs | ~13 µs | Racing applications |
Data is sent MSB-first. Pulse timing depends on the selected DShot mode.
### GPIO Configuration
```cpp
// Using GPIO number
DShotRMT motor(17, DSHOT300);
| DSHOT | Bitrate | TH1 | TH0 | Bit Time (µs) | Frame Time (µs) |
|-------|-------------|-------|--------|---------------|-----------------|
| 150 | 150 kbit/s | 5.00 | 2.50 | 6.67 | ~106.72 |
| 300 | 300 kbit/s | 2.50 | 1.25 | 3.33 | ~53.28 |
| 600 | 600 kbit/s | 1.25 | 0.625 | 1.67 | ~26.72 |
// Using GPIO enum
DShotRMT motor(GPIO_NUM_17, DSHOT300);
Each frame is followed by a pause to help ESCs detect separate frames.
![DShotRMT](https://raw.githubusercontent.com/derdoktor667/DShotRMT/refs/heads/main/img/dshot300.png)
---
## 🔒 Checksum Calculation
The checksum is calculated over the first 12 bits (throttle + telemetry):
```c
crc = (value ^ (value >> 4) ^ (value >> 8)) & 0x0F;
// With bidirectional support
DShotRMT motor(17, DSHOT300, true);
```
### Bidirectional DSHOT
Bidirectional DSHOT (sometimes called "inverted DSHOT") inverts the signal level:
A logical '1' is low, and a '0' is high. This signals the ESC to send telemetry packets back.
**Bidirectional CRC:**
```c
crc = (~(value ^ (value >> 4) ^ (value >> 8))) & 0x0F;
```
> **Note:** Bidirectional DShot is experimental. Further hardware testing is needed.
---
## 🛠️ ESP32 RMT Peripheral
The RMT (Remote Control) peripheral generates accurate, hardware-timed signals for controlling external devices.
Perfect for DShot:
- Utilizes latest ESP-IDF APIs
- Hardware-timed pulses
- CPU-independent
- Loop mode with inter-frame pause
- Reliable under system load
---
## 📝 Core API Reference
### DShotRMT Class
- `DShotRMT(gpio_num_t gpio, dshot_mode_t mode, bool isBidirectional)`
- `uint16_t begin()`
- `bool sendThrottle(uint16_t throttle)`
- `bool sendCommand(uint16_t command)`
- `uint16_t getERPM()` - Get eRPM (bidirectional mode only)
- `uint32_t getMotorRPM(uint8_t magnet_count)` - Convert to motor RPM
### DShotCommandManager Class
- `DShotCommandManager(DShotRMT &dshot_instance)`
- `bool begin()`
- `dshot_command_result_t sendCommand(dshot_commands_t command, uint16_t repeat_count = 1)`
- `dshot_command_result_t sendCommandWithDelay(dshot_commands_t command, uint16_t repeat_count, uint32_t delay_ms)`
All command methods return a `dshot_command_result_t` structure containing:
- `bool success` - Command execution status
- `uint32_t execution_time_us` - Execution time in microseconds
- `const char* error_message` - Error description
---
## 🎯 DShot Commands
The library supports all standard DShot commands:
| Command | Value | Description |
|---------|-------|-------------|
| MOTOR_STOP | 0 | Stop motor |
| BEACON1-5 | 1-5 | Motor beeping |
| ESC_INFO | 6 | Request ESC information |
| SPIN_DIRECTION_1/2 | 7-8 | Set spin direction |
| 3D_MODE_OFF/ON | 9-10 | 3D mode control |
| SAVE_SETTINGS | 12 | Save settings to ESC |
| EXTENDED_TELEMETRY_ENABLE/DISABLE | 13-14 | Telemetry control |
| LED0-3_ON/OFF | 22-29 | LED control (BLHeli32) |
| AUDIO_STREAM_MODE | 30 | KISS audio mode |
| SILENT_MODE | 31 | KISS silent mode |
| Command | Value | Description | Usage |
|---------|-------|-------------|-------|
| MOTOR_STOP | 0 | Stop motor | Always available |
| BEACON1 - 5 | 1 - 5 | Motor beeping | Motor identification |
| ESC_INFO | 6 | Request ESC info | Get ESC version/settings |
| SPIN_DIRECTION_1/2 | 7 - 8 | Set spin direction | Motor configuration |
| 3D_MODE_OFF/ON | 9 - 10 | 3D mode control | Bidirectional flight |
| SAVE_SETTINGS | 12 | Save to EEPROM | Permanent configuration |
| EXTENDED_TELEMETRY_ENABLE/DISABLE | 13 - 14 | Telemetry control | Data transmission |
| SPIN_DIRECTION_NORMAL/REVERSED | 20 - 21 | Spin direction | Alias commands |
| LED0-3_ON/OFF | 22 - 29 | LED control | BLHeli32 only |
| AUDIO_STREAM_MODE | 30 | Audio mode toggle | KISS ESCs |
| SILENT_MODE | 31 | Silent mode toggle | KISS ESCs |
---
## 📖 References
## 📚 DShot Protocol Details
![DShotRMT](https://raw.githubusercontent.com/derdoktor667/DShotRMT/refs/heads/main/img/dshot300.png)
### Packet Structure
Each DShot frame consists of 16 bits:
- **11 bits:** Throttle/command value (0-2047)
- **1 bit:** Telemetry request flag
- **4 bits:** CRC checksum
### Checksum Calculation
```cpp
// Standard DShot CRC
uint16_t crc = (data ^ (data >> 4) ^ (data >> 8)) & 0x0F;
// Bidirectional DShot (inverted CRC)
uint16_t crc = (~(data ^ (data >> 4) ^ (data >> 8))) & 0x0F;
```
### Bidirectional DShot
- **Inverted Logic:** High/low levels are inverted
- **GCR Encoding:** Telemetry uses Group Code Recording
- **21-bit Response:** 1 start + 16 data + 4 CRC bits
- **eRPM Data:** Electrical RPM transmitted back to controller
---
## 🛠️ ESP32 RMT Peripheral
The library utilizes the ESP32's RMT (Remote Control) peripheral for precise signal generation:
### Advantages
- **Hardware Timing:** No CPU intervention during transmission
- **Concurrent Operation:** Multiple channels can run simultaneously
- **DMA Support:** Efficient memory-to-peripheral transfers
---
## 📖 References & Documentation
### DShot Protocol
- [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/)
- [Betaflight DShot Implementation](https://github.com/betaflight/betaflight)
### ESP32 Documentation
- [ESP32 Technical Reference Manual](https://www.espressif.com/sites/default/files/documentation/esp32_technical_reference_manual_en.pdf)
- [ESP-IDF RMT Driver](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/peripherals/rmt.html)
- [Arduino ESP32 Core](https://github.com/espressif/arduino-esp32)
---
## 🤝 Contributing
We welcome contributions! Please:
1. Fork the repository
2. Create a feature branch
3. Make your changes with tests
4. Submit a pull request
### Development Guidelines
- Follow existing code style
- Add documentation for new features
- Include examples where appropriate
- Test with real hardware when possible
### Reporting Issues
When reporting issues, please include:
- ESP32 board type and version
- Arduino/ESP-IDF version
- ESC type and firmware
- Complete error messages
- Minimal reproduction code
---
@ -261,5 +269,5 @@ MIT License see [LICENSE](LICENSE)
## 👤 Author
**Wastl Kraus**
GitHub: [@derdoktor667](https://github.com/derdoktor667)
Website: [wir-sind-die-matrix.de](https://wir-sind-die-matrix.de)
- GitHub: [@derdoktor667](https://github.com/derdoktor667)
- Website: [wir-sind-die-matrix.de](https://wir-sind-die-matrix.de)

View File

@ -37,7 +37,7 @@ void printTelemetryResult(const dshot_telemetry_result_t &result)
}
else
{
USB_SERIAL.printf("Telemetry: FAILED - %s\n", result.error_message);
USB_SERIAL.printf("Telemetry: FAILED - %s\n", result.msg);
}
}
@ -270,7 +270,7 @@ void printResult(const dshot_result_t &result)
}
else
{
USB_SERIAL.printf("FAILED - %s \n", result.error_message);
USB_SERIAL.printf("FAILED - %s \n", result.msg);
}
}

View File

@ -53,7 +53,7 @@ void loop()
static bool continuous_throttle = true;
// Time Measurement
static uint32_t last_stats_print = 0;
static uint64_t last_stats_print = 0;
// Handle serial input
if (USB_SERIAL.available() > 0)
@ -73,20 +73,24 @@ void loop()
motor01.sendThrottle(throttle);
}
// Print motor stats every 5 seconds in continuous mode
if (continuous_throttle && (millis() - last_stats_print >= 5000))
// Print motor stats every 3 seconds in continuous mode
if (continuous_throttle && (esp_timer_get_time() - last_stats_print >= 3000000))
{
motor01.printDShotInfo();
USB_SERIAL.println(" ");
// Get Motor RPM if bidirectional
if (IS_BIDIRECTIONAL)
{
dshot_telemetry_result_t telem_result = motor01.getTelemetry(MOTOR01_MAGNET_COUNT);
printTelemetryResult(telem_result);
printDShotTelemetry(telem_result);
}
USB_SERIAL.println("Type 'help' to show Menu");
// Time Stamp
last_stats_print = millis();
last_stats_print = esp_timer_get_time();
}
}
@ -94,47 +98,21 @@ void loop()
void printMenu()
{
USB_SERIAL.println(" ");
USB_SERIAL.println("******************************************");
USB_SERIAL.println("*******************************************");
USB_SERIAL.println(" DShotRMT Demo ");
USB_SERIAL.println("******************************************");
USB_SERIAL.println("*******************************************");
USB_SERIAL.println(" <value> - Set throttle (48 2047)");
USB_SERIAL.println(" 0 - Stop motor");
USB_SERIAL.println("******************************************");
USB_SERIAL.println(" cmd <number> - Send DShot command (0-47)");
USB_SERIAL.println("*******************************************");
USB_SERIAL.println(" cmd <number> - Send DShot command (0 - 47)");
USB_SERIAL.println(" info - Show motor info");
if (IS_BIDIRECTIONAL)
{
USB_SERIAL.println(" rpm - Get telemetry data");
}
USB_SERIAL.println("******************************************");
USB_SERIAL.println("*******************************************");
USB_SERIAL.println(" h / help - Show this Menu");
USB_SERIAL.println("******************************************");
}
// Helper to print command results
void printCommandResult(const dshot_result_t &result, const String &operation)
{
if (result.success)
{
USB_SERIAL.printf("%s: SUCCESS\n", operation.c_str());
}
else
{
USB_SERIAL.printf("%s: FAILED - %s\n", operation.c_str(), result.error_message);
}
}
// Helper to print telemetry results
void printTelemetryResult(const dshot_telemetry_result_t &result)
{
if (result.success)
{
USB_SERIAL.printf("Telemetry: eRPM=%u, Motor RPM=%u (%s)\n", result.erpm, result.motor_rpm, result.error_message);
}
else
{
USB_SERIAL.printf("Telemetry: FAILED - %s\n", result.error_message);
}
USB_SERIAL.println("*******************************************");
}
//
@ -144,19 +122,18 @@ void handleSerialInput(const String &input, uint16_t &throttle, bool &continuous
{
// Stop motor
throttle = 0;
continuous_throttle = true; // kill motor for sure
continuous_throttle = true;
dshot_result_t result = motor01.sendCommand(DSHOT_CMD_MOTOR_STOP);
printCommandResult(result, "Stop Motor");
printDShotResult(result);
}
else if (input == "info")
{
continuous_throttle = false;
motor01.printDShotInfo();
}
else if (input == "rpm" && IS_BIDIRECTIONAL)
{
dshot_telemetry_result_t result = motor01.getTelemetry(MOTOR01_MAGNET_COUNT);
printTelemetryResult(result);
printDShotTelemetry(result);
}
else if (input.startsWith("cmd "))
{
@ -168,7 +145,7 @@ void handleSerialInput(const String &input, uint16_t &throttle, bool &continuous
if (cmd_num >= DSHOT_CMD_MOTOR_STOP && cmd_num <= DSHOT_CMD_MAX)
{
dshot_result_t result = motor01.sendCommand(cmd_num);
printCommandResult(result, "DShot Command " + String(cmd_num));
printDShotResult(result);
}
else
{
@ -190,13 +167,12 @@ void handleSerialInput(const String &input, uint16_t &throttle, bool &continuous
continuous_throttle = true;
dshot_result_t result = motor01.sendThrottle(throttle);
printCommandResult(result, "Set Throttle " + String(throttle));
USB_SERIAL.println("Continuous throttle mode enabled. Send '0' to stop.");
printDShotResult(result);
}
else
{
USB_SERIAL.printf("Invalid input: '%s'\n", input.c_str());
USB_SERIAL.println(" ");
USB_SERIAL.printf("Invalid input: '%s'\n", input);
USB_SERIAL.printf("Valid throttle range: %d - %d\n", DSHOT_THROTTLE_MIN, DSHOT_THROTTLE_MAX);
}
}

View File

@ -1,5 +1,5 @@
name=DShotRMT
version=0.7.1
version=0.7.2
author=derdoktor667
maintainer=derdoktor667
sentence=DShotRMT Library supporting all DShot Types and speeds. Tested with BlHeli_S.