...release 0.7.0

...update TX logic
...rewrite GCR decoding
...type fix
...add CommandManager
...preparing release
This commit is contained in:
Wastl Kraus 2025-09-04 14:41:05 +02:00 committed by GitHub
parent 5eaf066dab
commit 252209dd1b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 1124 additions and 72 deletions

View File

@ -11,7 +11,6 @@ concurrency:
cancel-in-progress: true
env:
ARDUINO_CLI_VERSION: '1.3.0'
ESP32_CORE_VERSION: '3.3.0'
jobs:
@ -29,8 +28,6 @@ jobs:
- name: Setup Arduino CLI
uses: arduino/setup-arduino-cli@v2
with:
version: ${{ env.ARDUINO_CLI_VERSION }}
- name: Cache Arduino data
uses: actions/cache@v4
@ -66,6 +63,7 @@ jobs:
matrix:
example:
- "examples/dshot300/dshot300.ino"
- "examples/command_manager/command_manager.ino"
build-flags:
- name: "Release"
flags: "Automated Build"
@ -76,8 +74,6 @@ jobs:
- name: Setup Arduino CLI
uses: arduino/setup-arduino-cli@v2
with:
version: ${{ env.ARDUINO_CLI_VERSION }}
- name: Cache Arduino data
uses: actions/cache@v4
@ -116,8 +112,6 @@ jobs:
- name: Setup Arduino CLI
uses: arduino/setup-arduino-cli@v2
with:
version: ${{ env.ARDUINO_CLI_VERSION }}
- name: Cache Arduino data
uses: actions/cache@v4
@ -143,7 +137,8 @@ jobs:
--language=c++ \
--platform=unix32 \
--inline-suppr \
./DShotRMT.cpp ./DShotRMT.h
./DShotRMT.cpp ./DShotRMT.h \
./DShotCommandManager.cpp ./DShotCommandManager.h
# ============================================================================
# Build Status Report

393
DShotCommandManager.cpp Normal file
View File

@ -0,0 +1,393 @@
/*
* DShotCommandManager.cpp
* Advanced DShot command management for DShotRMT library
* Author: Wastl Kraus
* Date: 2025-09-04
* License: MIT
*/
#include <DShotCommandManager.h>
// Constructor
DShotCommandManager::DShotCommandManager(DShotRMT &dshot_instance)
: _dshot(dshot_instance),
_total_commands_sent(0),
_failed_commands(0),
_last_execution_time_us(0),
_last_command_timestamp(0)
{
}
// Init command manager
bool DShotCommandManager::begin()
{
resetStatistics();
return true;
}
// --- BASIC COMMAND METHODS ---
dshot_command_result_t DShotCommandManager::sendCommand(dshot_commands_t command, uint16_t repeat_count)
{
return sendCommandWithDelay(command, repeat_count, DEFAULT_COMMAND_DELAY_MS);
}
//
dshot_command_result_t DShotCommandManager::sendCommandWithDelay(dshot_commands_t command, uint16_t repeat_count, uint32_t delay_ms)
{
dshot_command_result_t result = {false, 0, "Unknown error"};
if (!isValidCommand(command))
{
result.error_message = "Invalid command";
_updateStatistics(false, 0);
return result;
}
uint64_t start_time = esp_timer_get_time();
bool all_successful = true;
// Send command multiple times with delay
for (uint16_t i = 0; i < repeat_count; i++)
{
dshot_command_result_t single_result = _executeCommand(command);
if (!single_result.success)
{
all_successful = false;
result.error_message = single_result.error_message;
break;
}
// Add delay between repetitions (except for last repetition)
if (i < repeat_count - 1)
{
_delay(delay_ms);
}
}
uint64_t end_time = esp_timer_get_time();
result.execution_time_us = (uint32_t)(end_time - start_time);
result.success = all_successful;
if (result.success)
{
result.error_message = "Success";
}
_updateStatistics(result.success, result.execution_time_us);
return result;
}
// --- MOTOR CONTROL COMMANDS ---
dshot_command_result_t DShotCommandManager::stopMotor()
{
return sendCommand(DSHOT_CMD_MOTOR_STOP);
}
//
dshot_command_result_t DShotCommandManager::set3DMode(bool enable)
{
dshot_commands_t command = enable ? DSHOT_CMD_3D_MODE_ON : DSHOT_CMD_3D_MODE_OFF;
return sendCommandWithDelay(command, SETTINGS_COMMAND_REPEATS, SETTINGS_COMMAND_DELAY_MS);
}
//
dshot_command_result_t DShotCommandManager::setSpinDirection(bool reversed)
{
dshot_commands_t command = reversed ? DSHOT_CMD_SPIN_DIRECTION_REVERSED : DSHOT_CMD_SPIN_DIRECTION_NORMAL;
return sendCommandWithDelay(command, SETTINGS_COMMAND_REPEATS, SETTINGS_COMMAND_DELAY_MS);
}
//
dshot_command_result_t DShotCommandManager::saveSettings()
{
return sendCommandWithDelay(DSHOT_CMD_SAVE_SETTINGS, SETTINGS_COMMAND_REPEATS, SETTINGS_COMMAND_DELAY_MS);
}
// --- TELEMETRY COMMANDS ---
dshot_command_result_t DShotCommandManager::setExtendedTelemetry(bool enable)
{
dshot_commands_t command = enable ? DSHOT_CMD_EXTENDED_TELEMETRY_ENABLE : DSHOT_CMD_EXTENDED_TELEMETRY_DISABLE;
return sendCommand(command);
}
//
dshot_command_result_t DShotCommandManager::requestESCInfo()
{
return sendCommand(DSHOT_CMD_ESC_INFO);
}
// --- LED CONTROL COMMANDS ---
dshot_command_result_t DShotCommandManager::setLED(uint8_t led_number, bool state)
{
if (led_number > 3)
{
dshot_command_result_t result = {false, 0, "Invalid LED number (0-3)"};
_updateStatistics(false, 0);
return result;
}
dshot_commands_t command;
if (state)
{
// LED ON commands
switch (led_number)
{
case 0:
command = DSHOT_CMD_LED0_ON;
break;
case 1:
command = DSHOT_CMD_LED1_ON;
break;
case 2:
command = DSHOT_CMD_LED2_ON;
break;
case 3:
command = DSHOT_CMD_LED3_ON;
break;
}
}
else
{
// LED OFF commands
switch (led_number)
{
case 0:
command = DSHOT_CMD_LED0_OFF;
break;
case 1:
command = DSHOT_CMD_LED1_OFF;
break;
case 2:
command = DSHOT_CMD_LED2_OFF;
break;
case 3:
command = DSHOT_CMD_LED3_OFF;
break;
}
}
return sendCommand(command);
}
// --- BEACON COMMANDS ---
dshot_command_result_t DShotCommandManager::activateBeacon(uint8_t beacon_number)
{
if (beacon_number < 1 || beacon_number > 5)
{
dshot_command_result_t result = {false, 0, "Invalid beacon number (1-5)"};
_updateStatistics(false, 0);
return result;
}
dshot_commands_t command = static_cast<dshot_commands_t>(DSHOT_CMD_BEACON1 + beacon_number - 1);
return sendCommand(command);
}
// --- KISS ESC SPECIFIC COMMANDS ---
dshot_command_result_t DShotCommandManager::setAudioStreamMode(bool enable)
{
// KISS audio stream mode is a toggle command
return sendCommand(DSHOT_CMD_AUDIO_STREAM_MODE_ON_OFF);
}
//
dshot_command_result_t DShotCommandManager::setSilentMode(bool enable)
{
// KISS silent mode is a toggle command
return sendCommand(DSHOT_CMD_SILENT_MODE_ON_OFF);
}
// --- SEQUENCE COMMANDS ---
dshot_command_result_t DShotCommandManager::executeSequence(const dshot_command_sequence_item_t *sequence, size_t sequence_length)
{
dshot_command_result_t result = {true, 0, "Success"};
uint64_t total_start_time = esp_timer_get_time();
for (size_t i = 0; i < sequence_length; i++)
{
dshot_command_result_t item_result = sendCommandWithDelay(
sequence[i].command,
sequence[i].repeat_count,
DEFAULT_COMMAND_DELAY_MS);
if (!item_result.success)
{
result.success = false;
result.error_message = item_result.error_message;
break;
}
// Add delay after command if specified
if (sequence[i].delay_ms > 0)
{
_delay(sequence[i].delay_ms);
}
}
uint64_t total_end_time = esp_timer_get_time();
result.execution_time_us = (uint32_t)(total_end_time - total_start_time);
return result;
}
//
dshot_command_result_t DShotCommandManager::executeInitSequence()
{
// Basic ESC initialization sequence
dshot_command_sequence_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
};
return executeSequence(init_sequence, sizeof(init_sequence) / sizeof(init_sequence[0]));
}
//
dshot_command_result_t DShotCommandManager::executeCalibrationSequence()
{
// Basic ESC calibration sequence
dshot_command_sequence_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
{DSHOT_CMD_SAVE_SETTINGS, 10, 1000}, // Save settings
{DSHOT_CMD_MOTOR_STOP, 5, 100} // Final stop
};
return executeSequence(calibration_sequence, sizeof(calibration_sequence) / sizeof(calibration_sequence[0]));
}
// --- UTILITY METHODS ---
const char *DShotCommandManager::getCommandName(dshot_commands_t command)
{
switch (command)
{
case DSHOT_CMD_MOTOR_STOP:
return "MOTOR_STOP";
case DSHOT_CMD_BEACON1:
return "BEACON1";
case DSHOT_CMD_BEACON2:
return "BEACON2";
case DSHOT_CMD_BEACON3:
return "BEACON3";
case DSHOT_CMD_BEACON4:
return "BEACON4";
case DSHOT_CMD_BEACON5:
return "BEACON5";
case DSHOT_CMD_ESC_INFO:
return "ESC_INFO";
case DSHOT_CMD_SPIN_DIRECTION_1:
return "SPIN_DIRECTION_1";
case DSHOT_CMD_SPIN_DIRECTION_2:
return "SPIN_DIRECTION_2";
case DSHOT_CMD_3D_MODE_OFF:
return "3D_MODE_OFF";
case DSHOT_CMD_3D_MODE_ON:
return "3D_MODE_ON";
case DSHOT_CMD_SETTINGS_REQUEST:
return "SETTINGS_REQUEST";
case DSHOT_CMD_SAVE_SETTINGS:
return "SAVE_SETTINGS";
case DSHOT_CMD_EXTENDED_TELEMETRY_ENABLE:
return "EXTENDED_TELEMETRY_ENABLE";
case DSHOT_CMD_EXTENDED_TELEMETRY_DISABLE:
return "EXTENDED_TELEMETRY_DISABLE";
case DSHOT_CMD_SPIN_DIRECTION_NORMAL:
return "SPIN_DIRECTION_NORMAL";
case DSHOT_CMD_SPIN_DIRECTION_REVERSED:
return "SPIN_DIRECTION_REVERSED";
case DSHOT_CMD_LED0_ON:
return "LED0_ON";
case DSHOT_CMD_LED1_ON:
return "LED1_ON";
case DSHOT_CMD_LED2_ON:
return "LED2_ON";
case DSHOT_CMD_LED3_ON:
return "LED3_ON";
case DSHOT_CMD_LED0_OFF:
return "LED0_OFF";
case DSHOT_CMD_LED1_OFF:
return "LED1_OFF";
case DSHOT_CMD_LED2_OFF:
return "LED2_OFF";
case DSHOT_CMD_LED3_OFF:
return "LED3_OFF";
case DSHOT_CMD_AUDIO_STREAM_MODE_ON_OFF:
return "AUDIO_STREAM_MODE_ON_OFF";
case DSHOT_CMD_SILENT_MODE_ON_OFF:
return "SILENT_MODE_ON_OFF";
default:
return "UNKNOWN";
}
}
//
bool DShotCommandManager::isValidCommand(dshot_commands_t command)
{
return (command >= DSHOT_CMD_MOTOR_STOP && command <= DSHOT_CMD_MAX);
}
//
void DShotCommandManager::printStatistics(Stream &output) const
{
output.println("\n--- DShot Command Manager Statistics ---");
output.printf("Total commands sent: %u\n", _total_commands_sent);
output.printf("Failed commands: %u\n", _failed_commands);
output.printf("Success rate: %.2f%%\n",
_total_commands_sent > 0 ? (float)(_total_commands_sent - _failed_commands) / _total_commands_sent * 100.0f : 0.0f);
output.printf("Last execution time: %u us\n", _last_execution_time_us);
output.printf("Last command timestamp: %llu us\n", _last_command_timestamp);
}
//
void DShotCommandManager::resetStatistics()
{
_total_commands_sent = 0;
_failed_commands = 0;
_last_execution_time_us = 0;
_last_command_timestamp = 0;
}
// --- PRIVATE METHODS ---
dshot_command_result_t DShotCommandManager::_executeCommand(dshot_commands_t command)
{
dshot_command_result_t result = {false, 0, "Execution failed"};
uint64_t start_time = esp_timer_get_time();
// Execute the command using the DShotRMT instance
bool success = _dshot.sendCommand(static_cast<uint16_t>(command));
uint64_t end_time = esp_timer_get_time();
result.success = success;
result.execution_time_us = (uint32_t)(end_time - start_time);
result.error_message = success ? "Success" : "Command transmission failed";
_last_command_timestamp = end_time;
return result;
}
//
void DShotCommandManager::_delay(uint32_t delay_ms)
{
if (delay_ms > 0)
{
delay(delay_ms);
}
}
//
void DShotCommandManager::_updateStatistics(bool success, uint32_t execution_time_us)
{
_total_commands_sent++;
if (!success)
{
_failed_commands++;
}
_last_execution_time_us = execution_time_us;
}

146
DShotCommandManager.h Normal file
View File

@ -0,0 +1,146 @@
/*
* DShotCommandManager.h
* Advanced DShot command management for DShotRMT library
* Author: Wastl Kraus
* Date: 2025-09-04
* License: MIT
*/
#pragma once
#include <Arduino.h>
#include <DShotRMT.h>
#include <dshot_commands.h>
// Naming convention
typedef dshotCommands_e dshot_commands_t;
// Command execution result structure
typedef struct
{
bool success;
uint32_t execution_time_us;
const char *error_message;
} dshot_command_result_t;
// Command sequence item
typedef struct
{
dshot_commands_t command;
uint16_t repeat_count;
uint32_t delay_ms;
} dshot_command_sequence_item_t;
// Advanced DShot command manager class
class DShotCommandManager
{
public:
// Constructor
explicit DShotCommandManager(DShotRMT &dshot_instance);
// Initialize command manager
bool begin();
bool printMenu(Stream &output);
void handleMenuInput(const String &input, Stream &output = Serial);
// Send a single DShot command
dshot_command_result_t sendCommand(dshot_commands_t command, uint16_t repeat_count = 1);
// Send command with specified delay between repetitions
dshot_command_result_t sendCommandWithDelay(dshot_commands_t command, uint16_t repeat_count, uint32_t delay_ms);
// --- MOTOR CONTROL COMMANDS ---
// Stop motor (send MOTOR_STOP command)
dshot_command_result_t stopMotor();
// Enable/disable 3D mode
dshot_command_result_t set3DMode(bool enable);
// Set motor spin direction
dshot_command_result_t setSpinDirection(bool reversed);
// Save current settings to ESC
dshot_command_result_t saveSettings();
// --- TELEMETRY COMMANDS ---
// Enable/disable extended telemetry
dshot_command_result_t setExtendedTelemetry(bool enable);
// Request ESC information
dshot_command_result_t requestESCInfo();
// --- LED CONTROL COMMANDS (BLHeli32 only) ---
// Control ESC LEDs (BLHeli32 only)
dshot_command_result_t setLED(uint8_t led_number, bool state);
// --- BEACON COMMANDS ---
// Activate beacon (motor beeping)
dshot_command_result_t activateBeacon(uint8_t beacon_number);
// --- KISS ESC SPECIFIC COMMANDS ---
// Enable/disable audio stream mode (KISS ESCs)
dshot_command_result_t setAudioStreamMode(bool enable);
// Enable/disable silent mode (KISS ESCs)
dshot_command_result_t setSilentMode(bool enable);
// --- SEQUENCE COMMANDS ---
// Execute a sequence of DShot commands
dshot_command_result_t executeSequence(const dshot_command_sequence_item_t *sequence, size_t sequence_length);
// Execute ESC initialization sequence
dshot_command_result_t executeInitSequence();
// Execute ESC calibration sequence
dshot_command_result_t executeCalibrationSequence();
// --- UTILITY METHODS ---
// Get command name as string
static const char *getCommandName(dshot_commands_t command);
// Check if command is valid
static bool isValidCommand(dshot_commands_t command);
// Print command execution statistics
void printStatistics(Stream &output = Serial) const;
// Reset execution statistics
void resetStatistics();
// --- GETTERS ---
// Get total number of commands sent
uint32_t getTotalCommandCount() const { return _total_commands_sent; }
// Get number of failed commands
uint32_t getFailedCommandCount() const { return _failed_commands; }
// Get last command execution time
uint32_t getLastExecutionTime() const { return _last_execution_time_us; }
private:
// --- PRIVATE MEMBERS ---
DShotRMT &_dshot; // Reference to DShotRMT instance
uint32_t _total_commands_sent; // Total commands sent counter
uint32_t _failed_commands; // Failed commands counter
uint32_t _last_execution_time_us; // Last command execution time
uint64_t _last_command_timestamp; // Timestamp of last command
// --- PRIVATE METHODS ---
// Execute single command with timing
dshot_command_result_t _executeCommand(dshot_commands_t command);
// Wait for specified delay
void _delay(uint32_t delay_ms);
// Update execution statistics
void _updateStatistics(bool success, uint32_t execution_time_us);
// --- CONSTANTS ---
static constexpr uint32_t DEFAULT_COMMAND_DELAY_MS = 10;
static constexpr uint16_t DEFAULT_REPEAT_COUNT = 1;
static constexpr uint16_t SETTINGS_COMMAND_REPEATS = 10; // Settings commands need 10 repeats
static constexpr uint32_t SETTINGS_COMMAND_DELAY_MS = 5;
};

View File

@ -63,6 +63,7 @@ DShotRMT::~DShotRMT()
{
rmt_disable(_rmt_tx_channel);
rmt_del_channel(_rmt_tx_channel);
_rmt_tx_channel = nullptr;
}
//
@ -70,32 +71,28 @@ DShotRMT::~DShotRMT()
{
rmt_disable(_rmt_rx_channel);
rmt_del_channel(_rmt_rx_channel);
_rmt_rx_channel = nullptr;
}
//
if (_dshot_encoder)
{
rmt_del_encoder(_dshot_encoder);
_dshot_encoder = nullptr;
}
//
if (_rx_queue)
{
vQueueDelete(_rx_queue);
_rx_queue = nullptr;
}
}
// Initialize DShotRMT
uint16_t DShotRMT::begin()
{
// Init TX channel
if (!_initTXChannel())
{
_dshot_log(TX_INIT_FAILED);
return DSHOT_ERROR;
}
// Init RX channel
// Init RX channel first
if (_is_bidirectional)
{
if (!_initRXChannel())
@ -105,6 +102,13 @@ uint16_t DShotRMT::begin()
}
}
// Init TX channel
if (!_initTXChannel())
{
_dshot_log(TX_INIT_FAILED);
return DSHOT_ERROR;
}
// Init DShot encoder
if (_initDShotEncoder() != DSHOT_OK)
{
@ -297,7 +301,7 @@ dshot_packet_t DShotRMT::_buildDShotPacket(const uint16_t value)
}
// Build packet
packet.throttle_value = value;
packet.throttle_value = value & 0b0000011111111111;
packet.telemetric_request = _is_bidirectional ? 1 : 0;
// CRC is calculated over 11bit
@ -334,7 +338,7 @@ uint16_t DShotRMT::_calculateCRC(const uint16_t data)
}
// Transmit DShot packet via RMT
uint16_t DShotRMT::_sendDShotFrame(const dshot_packet_t &packet)
bool DShotRMT::_sendDShotFrame(const dshot_packet_t &packet)
{
// Check timing requirements
if (!_timer_signal())
@ -349,9 +353,6 @@ uint16_t DShotRMT::_sendDShotFrame(const dshot_packet_t &packet)
rmt_symbol_word_t rx_symbols[DSHOT_BITS_PER_FRAME];
rmt_receive(_rmt_rx_channel, rx_symbols, sizeof(rx_symbols), &_receive_config);
// Disable RMT RX for sending
rmt_disable(_rmt_rx_channel);
}
// Local for performance
@ -363,11 +364,21 @@ uint16_t DShotRMT::_sendDShotFrame(const dshot_packet_t &packet)
// Calculate transmission data size
size_t tx_size_bytes = DSHOT_BITS_PER_FRAME * sizeof(rmt_symbol_word_t);
// Perform RMT transmission
uint16_t result = rmt_transmit(_rmt_tx_channel, _dshot_encoder, tx_symbols, tx_size_bytes, &_transmit_config);
if (result != DSHOT_OK)
// TODO: Find out, why this is needed
if (_is_bidirectional)
{
// Disable RMT RX for sending
if (rmt_disable(_rmt_rx_channel) != DSHOT_OK)
{
_dshot_log(RX_RMT_RECEIVER_ERROR);
return DSHOT_ERROR;
}
}
// Perform RMT transmission
if (rmt_transmit(_rmt_tx_channel, _dshot_encoder, tx_symbols, tx_size_bytes, &_transmit_config) != DSHOT_OK)
{
_dshot_log(TRANSMITTER_ERROR);
return DSHOT_ERROR;
}
@ -377,11 +388,13 @@ uint16_t DShotRMT::_sendDShotFrame(const dshot_packet_t &packet)
if (rmt_enable(_rmt_rx_channel) != DSHOT_OK)
{
_dshot_log(RX_RMT_RECEIVER_ERROR);
return DSHOT_ERROR;
}
}
// Update timestamp and return success
_timer_reset();
return DSHOT_OK;
}
@ -406,47 +419,51 @@ bool IRAM_ATTR DShotRMT::_encodeDShotFrame(const dshot_packet_t &packet, rmt_sym
return DSHOT_OK;
}
// Decode received RMT symbols
// Decodes a DShot telemetry frame from received RMT symbols.
uint16_t DShotRMT::_decodeDShotFrame(const rmt_symbol_word_t *symbols)
{
// DShot answer is GCR encoded.
// GCR decoding: bit_N = gcr_bit_N ^ gcr_bit_(N-1)
uint32_t raw_gcr_data = 0;
uint32_t gcr_value = 0;
// Based on DShot bidirectional protocol, idle state is high,
// so the first duration is a low pulse.
// Bit 1: long low pulse, short high pulse
// Bit 0: short low pulse, long high pulse
// Decode GCR symbols into a 21-bit value.
// '1' has a longer low pulse (duration0 > duration1).
// '0' has a longer high pulse (duration1 > duration0).
for (size_t i = 0; i < GCR_BITS_PER_FRAME; ++i)
{
// Check which duration is longer to determine if it's a '1' bit
bool bit_is_one = symbols[i].duration0 > symbols[i].duration1;
raw_gcr_data = (raw_gcr_data << 1) | bit_is_one;
gcr_value = (gcr_value << 1) | bit_is_one;
}
// Extract the 10-bit data from the GCR frame
uint16_t gcr_data = (raw_gcr_data >> 5) & 0b0000001111111111; // Mask for 10 bits
// Perform GCR decoding: data = gcr ^ (gcr >> 1).
uint32_t decoded_frame = gcr_value ^ (gcr_value >> 1);
// GCR decoding over the "throttle" bits
uint16_t received_data = gcr_data ^ (gcr_data >> 1);
// 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);
// Extract CRC from gcr answer (4 bits)
uint16_t received_crc = raw_gcr_data & 0b0000000000001111; // Mask for 4 bits
// Cutting 4 bits?
uint16_t received_data = data_and_crc >> 4;
// Calculate expected CRC using the new, centralized function
// Telemetry request bit is always 1 for bidirectional
uint16_t data_for_crc = (received_data << 1) | 1;
// Masking CRC
uint16_t received_crc = data_and_crc & 0b0000000000001111;
// Telemetry request bit is always 1.
if (!(received_data & (1 << 11)))
{
return DSHOT_NULL_PACKET;
}
// Calculate expected CRC
uint16_t data_for_crc = received_data;
uint16_t calculated_crc = _calculateCRC(data_for_crc);
// Validate CRC
if (received_crc != calculated_crc)
{
_dshot_log(CRC_CHECK_FAILED);
return DSHOT_NULL_PACKET;
}
// The data is eRPM * 100
return received_data;
// Return the eRPM value (first 11 bits of received data).
return received_data & 0b0000011111111111;
}
// Check if enough time has passed for next transmission
@ -468,7 +485,7 @@ bool DShotRMT::_timer_reset()
}
// Print timing diagnostic information to specified stream
void DShotRMT::printDshotInfo(Stream &output) const
void DShotRMT::printDShotInfo(Stream &output) const
{
output.println(" ");
output.println(" === DShot Signal Info === ");

View File

@ -27,7 +27,7 @@ constexpr auto DSHOT_RX_TIMEOUT_MS = 2;
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 = 20;
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;
@ -50,7 +50,7 @@ typedef enum
typedef struct
{
uint16_t throttle_value : 11;
uint16_t telemetric_request : 1;
bool telemetric_request : 1;
uint16_t checksum : 4;
} dshot_packet_t;
@ -101,8 +101,8 @@ public:
bool is_bidirectional() const { return _is_bidirectional; }
// --- INFO ---
void printDshotInfo(Stream &output = Serial0) const;
void printCpuInfo(Stream &output = Serial0) const;
void printDShotInfo(Stream &output = Serial) const;
void printCpuInfo(Stream &output = Serial) const;
// --- DEPRECATED METHODS ---
[[deprecated("Use sendThrottle() instead")]]
@ -147,7 +147,7 @@ private:
uint16_t _calculateCRC(const uint16_t data);
// --- FRAME PROCESSING ---
uint16_t _sendDShotFrame(const dshot_packet_t &packet);
bool _sendDShotFrame(const dshot_packet_t &packet);
bool IRAM_ATTR _encodeDShotFrame(const dshot_packet_t &packet, rmt_symbol_word_t *symbols);
uint16_t _decodeDShotFrame(const rmt_symbol_word_t *symbols);
@ -160,12 +160,15 @@ private:
rmt_rx_event_callbacks_t _rx_event_callbacks;
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) { output.println(msg); }
// --- CONSTANTS & ERROR MESSAGES ---
static constexpr uint16_t DSHOT_OK = 0;
static constexpr uint16_t DSHOT_ERROR = 1;
static constexpr bool DSHOT_OK = 0;
static constexpr bool DSHOT_ERROR = 1;
static constexpr char *NEW_LINE = " ";
static constexpr char *TX_INIT_FAILED = "Failed to initialize TX channel!";
@ -177,4 +180,5 @@ private:
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!";
};

151
README.md
View File

@ -4,7 +4,7 @@
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.
**Now with BiDirectional DShot support!**
**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.
@ -14,6 +14,8 @@ Supports all standard DShot modes (150, 300, 600) and features continuous frame
- **All DShot Modes:** DSHOT150, DSHOT300 (default), DSHOT600, (DSHOT1200)
- **BiDirectional DShot:** Experimental support for RPM 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
- **Simple API:** Easy integration into your Arduino or ESP-IDF project
@ -32,8 +34,113 @@ git clone https://github.com/derdoktor667/DShotRMT.git
## ⚡ Quick Start
### Basic Usage (DShotRMT)
```cpp
Use "dshot300.ino" example sketch
#include <DShotRMT.h>
// Create motor instance
DShotRMT motor(17, DSHOT300);
void setup() {
Serial.begin(115200);
motor.begin();
}
void loop() {
motor.sendThrottle(1000); // Send throttle value
delay(20);
}
```
### Advanced Usage (DShotCommandManager)
```cpp
#include <DShotRMT.h>
#include <DShotCommandManager.h>
// Create motor and command manager instances
DShotRMT motor(17, DSHOT300);
DShotCommandManager cmdManager(motor);
void setup() {
Serial.begin(115200);
motor.begin();
cmdManager.begin();
// Execute initialization sequence
cmdManager.executeInitSequence();
}
void loop() {
// Your main code here
}
```
---
## 🎛️ 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.
### 2. Advanced Command Management
Use the `command_manager.ino` example for interactive ESC control:
```
=== 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
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
```
---
@ -95,13 +202,45 @@ Perfect for DShot:
---
## 📝 API Reference
## 📝 Core API Reference
### DShotRMT Class
- `DShotRMT(gpio_num_t gpio, dshot_mode_t mode, bool isBidirectional)`
- `void begin()`
- `void sendThrottle(uint16_t throttle)`
- `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
See [examples/dshot300/dshot300.ino](examples/dshot300/dshot300.ino) for a more informations.
### 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 |
---

View File

@ -0,0 +1,275 @@
/*
* command_manager_example.ino
* Example sketch demonstrating DShotCommandManager usage
* Author: Wastl Kraus
* Date: 2025-09-04
* License: MIT
*
* Modified by Especiallist to support continuous throttle sending.
*/
#include <Arduino.h>
#include <DShotRMT.h>
#include <DShotCommandManager.h>
// USB serial port settings
static constexpr auto &USB_SERIAL = Serial0;
static constexpr auto USB_SERIAL_BAUD = 115200;
// Motor configuration
static constexpr auto MOTOR01_PIN = 17;
static constexpr auto IS_BIDIRECTIONAL = false;
// Motor magnet count for RPM calculation
static constexpr auto MOTOR01_MAGNET_COUNT = 14;
// Create motor and command manager instances
DShotRMT motor01(MOTOR01_PIN, DSHOT300, IS_BIDIRECTIONAL);
DShotCommandManager commandManager(motor01);
// Global variable to store the desired continuous throttle value
static volatile uint16_t throttle_now = NULL;
//
void setup()
{
// Start USB Serial Port
USB_SERIAL.begin(USB_SERIAL_BAUD);
USB_SERIAL.println("=== DShotRMT Command Manager Example ===");
// Initialize DShot
if (motor01.begin() != 0)
{
USB_SERIAL.println("ERROR: Failed to initialize DShotRMT!");
while (1)
delay(1000);
}
// Init Command Manager
if (!commandManager.begin())
{
USB_SERIAL.println("ERROR: Failed to initialize DShotCommandManager!");
while (1)
delay(1000);
}
USB_SERIAL.println("Initialization successful!");
// Print Menu
printMenu();
}
//
void loop()
{
// Time Measurement
static uint64_t last_stats_print = 0;
// Check for serial input
if (USB_SERIAL.available() > 0)
{
String input = USB_SERIAL.readStringUntil('\n');
input.trim();
handleUserInput(input);
}
// Continuously send the stored throttle value
if (throttle_now != NULL)
{
motor01.sendThrottle(throttle_now);
// Print motor stats every 2 seconds
if (esp_timer_get_time() - last_stats_print >= 2000000)
{
motor01.printDShotInfo();
// Get Motor RPM
if (IS_BIDIRECTIONAL)
{
uint32_t rpm = motor01.getMotorRPM(MOTOR01_MAGNET_COUNT);
USB_SERIAL.printf("Motor RPM: %u\n", rpm);
}
// Time Stamp
last_stats_print = esp_timer_get_time();
}
}
}
//
void handleUserInput(const String &input)
{
dshot_command_result_t result;
if (input == "1")
{
// Stop motor command should also reset the continuous throttle value
throttle_now = 0;
USB_SERIAL.print("Stopping motor... ");
result = commandManager.stopMotor();
printResult(result);
}
else if (input == "2")
{
USB_SERIAL.print("Activating beacon 1... ");
result = commandManager.activateBeacon(1);
printResult(result);
}
else if (input == "3")
{
USB_SERIAL.print("Setting normal spin direction... ");
result = commandManager.setSpinDirection(false);
printResult(result);
}
else if (input == "4")
{
USB_SERIAL.print("Setting reversed spin direction... ");
result = commandManager.setSpinDirection(true);
printResult(result);
}
else if (input == "5")
{
USB_SERIAL.print("Enabling 3D mode... ");
result = commandManager.set3DMode(true);
printResult(result);
}
else if (input == "6")
{
USB_SERIAL.print("Disabling 3D mode... ");
result = commandManager.set3DMode(false);
printResult(result);
}
else if (input == "7")
{
USB_SERIAL.print("Saving settings... ");
result = commandManager.saveSettings();
printResult(result);
}
else if (input == "8")
{
USB_SERIAL.print("Turning LED 0 ON... ");
result = commandManager.setLED(0, true);
printResult(result);
}
else if (input == "9")
{
USB_SERIAL.print("Turning LED 0 OFF... ");
result = commandManager.setLED(0, false);
printResult(result);
}
else if (input == "i")
{
USB_SERIAL.print("Executing initialization sequence... ");
result = commandManager.executeInitSequence();
printResult(result);
}
else if (input == "c")
{
USB_SERIAL.print("Executing calibration sequence... ");
result = commandManager.executeCalibrationSequence();
printResult(result);
}
else if (input == "s")
{
commandManager.printStatistics();
}
else if (input == "r")
{
commandManager.resetStatistics();
USB_SERIAL.println("Statistics reset.");
}
else if (input == "h")
{
printMenu();
}
else if (input == "help")
{
printMenu();
}
else if (input.startsWith("cmd "))
{
// Direct command execution: "cmd 5" sends command 5
int cmd_num = input.substring(4).toInt();
if (DShotCommandManager::isValidCommand(static_cast<dshot_commands_t>(cmd_num)))
{
USB_SERIAL.printf("Sending command %d (%s)... ", cmd_num,
DShotCommandManager::getCommandName(static_cast<dshot_commands_t>(cmd_num)));
result = commandManager.sendCommand(static_cast<dshot_commands_t>(cmd_num));
printResult(result);
}
else
{
USB_SERIAL.printf("Invalid command number: %d (valid range: 0-%d)\n", cmd_num, DSHOT_CMD_MAX);
}
}
else if (input.startsWith("throttle "))
{
// Throttle control: "throttle 1000" sets throttle to 1000
int throttle_value = input.substring(9).toInt();
if (throttle_value >= DSHOT_THROTTLE_MIN && throttle_value <= DSHOT_THROTTLE_MAX)
{
throttle_now = throttle_value;
USB_SERIAL.printf("Setting continuous throttle to %d\n", throttle_now);
}
else if (throttle_value == 0)
{
throttle_now = 0;
USB_SERIAL.println("Continuous throttle stopped.");
}
else
{
USB_SERIAL.printf("Invalid throttle value: %d (valid range: 48-2047, use 0 to stop)\n", throttle_value);
}
}
else
{
USB_SERIAL.println("Unknown command. Type 'h' for help.");
}
}
//
void printResult(const dshot_command_result_t &result)
{
if (!result.success)
{
USB_SERIAL.printf("SUCCESS (%u us)\n", result.execution_time_us);
}
else
{
USB_SERIAL.printf("FAILED - %s (%u us)\n", result.error_message, result.execution_time_us);
}
}
//
void printMenu()
{
USB_SERIAL.println("================================================");
USB_SERIAL.println("\n=== DShot Command Manager Menu ===");
USB_SERIAL.println("Basic Commands:");
USB_SERIAL.println(" 1 - Stop Motor");
USB_SERIAL.println(" 2 - Activate Beacon 1");
USB_SERIAL.println(" 3 - Set Normal Spin Direction");
USB_SERIAL.println(" 4 - Set Reversed Spin Direction");
USB_SERIAL.println(" 5 - Enable 3D Mode");
USB_SERIAL.println(" 6 - Disable 3D Mode");
USB_SERIAL.println(" 7 - Save Settings");
USB_SERIAL.println(" 8 - Turn LED 0 ON");
USB_SERIAL.println(" 9 - Turn LED 0 OFF");
USB_SERIAL.println("");
USB_SERIAL.println("Sequences:");
USB_SERIAL.println(" i - Execute Initialization Sequence");
USB_SERIAL.println(" c - Execute Calibration Sequence");
USB_SERIAL.println("");
USB_SERIAL.println("Advanced:");
USB_SERIAL.println(" cmd <number> - Send DShot command (0 - 47)");
USB_SERIAL.println(" throttle <value> - Set throttle (48 - 2047)");
USB_SERIAL.println(" throttle 0 - Stop sending throttle");
USB_SERIAL.println("");
USB_SERIAL.println(" h - Show this Menu");
USB_SERIAL.println("");
USB_SERIAL.println("Examples:");
USB_SERIAL.println(" cmd 1 - Stop Motor");
USB_SERIAL.println(" throttle 1000 - Set throttle to 1000");
USB_SERIAL.println("================================================");
}

View File

@ -71,7 +71,7 @@ void loop()
// Print motor stats every 2 seconds
if (millis() - last_stats_print >= 2000)
{
motor01.printDshotInfo();
motor01.printDShotInfo();
// Get Motor RPM
if (IS_BIDIRECTIONAL)

View File

@ -7,9 +7,13 @@
#######################################
DShotRMT KEYWORD1
DShotCommandManager KEYWORD1
dshot_mode_t KEYWORD1
dshot_packet_t KEYWORD1
dshot_timing_t KEYWORD1
dshot_commands_t KEYWORD1
dshot_command_result_t KEYWORD1
dshot_command_sequence_item_t KEYWORD1
dshotCommands_e KEYWORD1
dshotCommandType_e KEYWORD1
@ -17,6 +21,7 @@ dshotCommandType_e KEYWORD1
# Methods and Functions (KEYWORD2)
#######################################
# DShotRMT Methods
begin KEYWORD2
setThrottle KEYWORD2
sendThrottle KEYWORD2
@ -27,23 +32,101 @@ getMotorRPM KEYWORD2
getGPIO KEYWORD2
getDShotPacket KEYWORD2
is_bidirectional KEYWORD2
printDShotInfo KEYWORD2
printCpuInfo KEYWORD2
# DShotCommandManager Methods
sendCommand KEYWORD2
sendCommandWithDelay KEYWORD2
stopMotor KEYWORD2
set3DMode KEYWORD2
setSpinDirection KEYWORD2
saveSettings KEYWORD2
setExtendedTelemetry KEYWORD2
requestESCInfo KEYWORD2
setLED KEYWORD2
activateBeacon KEYWORD2
setAudioStreamMode KEYWORD2
setSilentMode KEYWORD2
executeSequence KEYWORD2
executeInitSequence KEYWORD2
executeCalibrationSequence KEYWORD2
getCommandName KEYWORD2
isValidCommand KEYWORD2
printStatistics KEYWORD2
resetStatistics KEYWORD2
getTotalCommandCount KEYWORD2
getFailedCommandCount KEYWORD2
getLastExecutionTime KEYWORD2
printMenu KEYWORD2
handleMenuInput KEYWORD2
#######################################
# Constants (LITERAL1)
#######################################
# DShot Modes
DSHOT_OFF LITERAL1
DSHOT150 LITERAL1
DSHOT300 LITERAL1
DSHOT600 LITERAL1
DSHOT1200 LITERAL1
# DShot Throttle Constants
DSHOT_THROTTLE_FAILSAFE LITERAL1
DSHOT_THROTTLE_MIN LITERAL1
DSHOT_THROTTLE_MAX LITERAL1
# DShot Commands
DSHOT_CMD_MOTOR_STOP LITERAL1
DSHOT_CMD_BEACON1 LITERAL1
DSHOT_CMD_BEACON2 LITERAL1
DSHOT_CMD_BEACON3 LITERAL1
DSHOT_CMD_BEACON4 LITERAL1
DSHOT_CMD_BEACON5 LITERAL1
DSHOT_CMD_ESC_INFO LITERAL1
DSHOT_CMD_SPIN_DIRECTION_1 LITERAL1
DSHOT_CMD_SPIN_DIRECTION_2 LITERAL1
DSHOT_CMD_3D_MODE_OFF LITERAL1
DSHOT_CMD_3D_MODE_ON LITERAL1
DSHOT_CMD_SETTINGS_REQUEST LITERAL1
DSHOT_CMD_SAVE_SETTINGS LITERAL1
DSHOT_CMD_EXTENDED_TELEMETRY_ENABLE LITERAL1
DSHOT_CMD_EXTENDED_TELEMETRY_DISABLE LITERAL1
DSHOT_CMD_SPIN_DIRECTION_NORMAL LITERAL1
DSHOT_CMD_SPIN_DIRECTION_REVERSED LITERAL1
DSHOT_CMD_LED0_ON LITERAL1
DSHOT_CMD_LED1_ON LITERAL1
DSHOT_CMD_LED2_ON LITERAL1
DSHOT_CMD_LED3_ON LITERAL1
DSHOT_CMD_LED0_OFF LITERAL1
DSHOT_CMD_LED1_OFF LITERAL1
DSHOT_CMD_LED2_OFF LITERAL1
DSHOT_CMD_LED3_OFF LITERAL1
DSHOT_CMD_AUDIO_STREAM_MODE_ON_OFF LITERAL1
DSHOT_CMD_SILENT_MODE_ON_OFF LITERAL1
DSHOT_CMD_MAX LITERAL1
# DShot Command Types
DSHOT_CMD_TYPE_INLINE LITERAL1
DSHOT_CMD_TYPE_BLOCKING LITERAL1
# Protocol Constants
DSHOT_BITS_PER_FRAME LITERAL1
DSHOT_SWITCH_TIME LITERAL1
DSHOT_NULL_PACKET LITERAL1
DSHOT_RX_TIMEOUT_MS LITERAL1
GCR_BITS_PER_FRAME LITERAL1
# RMT Constants
DSHOT_CLOCK_SRC_DEFAULT LITERAL1
DSHOT_RMT_RESOLUTION LITERAL1
RMT_BUFFER_SIZE LITERAL1
RMT_BUFFER_SYMBOLS LITERAL1
RMT_QUEUE_DEPTH LITERAL1
DSHOT_PULSE_MIN LITERAL1
DSHOT_PULSE_MAX LITERAL1
# Status Constants
DSHOT_OK LITERAL1
DSHOT_ERROR LITERAL1

View File

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