...update result codes

This commit is contained in:
Wastl Kraus 2025-09-05 12:16:19 +02:00
parent 252209dd1b
commit 29c851d4e2
6 changed files with 505 additions and 302 deletions

View File

@ -13,43 +13,42 @@ 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()
dshot_result_t DShotCommandManager::begin()
{
resetStatistics();
return true;
dshot_result_t result;
result.success = true;
result.error_message = "Success";
return result;
}
// --- BASIC COMMAND METHODS ---
dshot_command_result_t DShotCommandManager::sendCommand(dshot_commands_t command, uint16_t repeat_count)
dshot_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_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"};
dshot_result_t result = {false, "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);
dshot_result_t single_result = _executeCommand(command);
if (!single_result.success)
{
@ -61,12 +60,11 @@ dshot_command_result_t DShotCommandManager::sendCommandWithDelay(dshot_commands_
// Add delay between repetitions (except for last repetition)
if (i < repeat_count - 1)
{
_delay(delay_ms);
_delay_ms(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)
@ -74,57 +72,54 @@ dshot_command_result_t DShotCommandManager::sendCommandWithDelay(dshot_commands_
result.error_message = "Success";
}
_updateStatistics(result.success, result.execution_time_us);
return result;
}
// --- MOTOR CONTROL COMMANDS ---
dshot_command_result_t DShotCommandManager::stopMotor()
dshot_result_t DShotCommandManager::stopMotor()
{
return sendCommand(DSHOT_CMD_MOTOR_STOP);
}
//
dshot_command_result_t DShotCommandManager::set3DMode(bool enable)
dshot_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_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()
dshot_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_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()
dshot_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)
dshot_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);
dshot_result_t result = {false, "Invalid LED number (0-3)"};
return result;
}
@ -172,12 +167,11 @@ dshot_command_result_t DShotCommandManager::setLED(uint8_t led_number, bool stat
}
// --- BEACON COMMANDS ---
dshot_command_result_t DShotCommandManager::activateBeacon(uint8_t beacon_number)
dshot_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);
dshot_result_t result = {false, "Invalid beacon number (1-5)"};
return result;
}
@ -186,28 +180,28 @@ dshot_command_result_t DShotCommandManager::activateBeacon(uint8_t beacon_number
}
// --- KISS ESC SPECIFIC COMMANDS ---
dshot_command_result_t DShotCommandManager::setAudioStreamMode(bool enable)
dshot_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)
dshot_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_result_t DShotCommandManager::executeSequence(const dshot_command_sequence_item_t *sequence, size_t sequence_length)
{
dshot_command_result_t result = {true, 0, "Success"};
dshot_result_t result = {true, "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(
dshot_result_t item_result = sendCommandWithDelay(
sequence[i].command,
sequence[i].repeat_count,
DEFAULT_COMMAND_DELAY_MS);
@ -222,18 +216,17 @@ dshot_command_result_t DShotCommandManager::executeSequence(const dshot_command_
// Add delay after command if specified
if (sequence[i].delay_ms > 0)
{
_delay(sequence[i].delay_ms);
_delay_ms(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()
dshot_result_t DShotCommandManager::executeInitSequence()
{
// Basic ESC initialization sequence
dshot_command_sequence_item_t init_sequence[] = {
@ -246,7 +239,7 @@ dshot_command_result_t DShotCommandManager::executeInitSequence()
}
//
dshot_command_result_t DShotCommandManager::executeCalibrationSequence()
dshot_result_t DShotCommandManager::executeCalibrationSequence()
{
// Basic ESC calibration sequence
dshot_command_sequence_item_t calibration_sequence[] = {
@ -330,64 +323,25 @@ 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_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));
dshot_result_t result = _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)
void DShotCommandManager::_delay_ms(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;
}

View File

@ -12,17 +12,6 @@
#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
{
@ -39,63 +28,61 @@ public:
explicit DShotCommandManager(DShotRMT &dshot_instance);
// Initialize command manager
bool begin();
bool printMenu(Stream &output);
dshot_result_t begin();
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);
dshot_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);
dshot_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();
dshot_result_t stopMotor();
// Enable/disable 3D mode
dshot_command_result_t set3DMode(bool enable);
dshot_result_t set3DMode(bool enable);
// Set motor spin direction
dshot_command_result_t setSpinDirection(bool reversed);
dshot_result_t setSpinDirection(bool reversed);
// Save current settings to ESC
dshot_command_result_t saveSettings();
dshot_result_t saveSettings();
// --- TELEMETRY COMMANDS ---
// Enable/disable extended telemetry
dshot_command_result_t setExtendedTelemetry(bool enable);
dshot_result_t setExtendedTelemetry(bool enable);
// Request ESC information
dshot_command_result_t requestESCInfo();
dshot_result_t requestESCInfo();
// --- LED CONTROL COMMANDS (BLHeli32 only) ---
// Control ESC LEDs (BLHeli32 only)
dshot_command_result_t setLED(uint8_t led_number, bool state);
dshot_result_t setLED(uint8_t led_number, bool state);
// --- BEACON COMMANDS ---
// Activate beacon (motor beeping)
dshot_command_result_t activateBeacon(uint8_t beacon_number);
dshot_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);
dshot_result_t setAudioStreamMode(bool enable);
// Enable/disable silent mode (KISS ESCs)
dshot_command_result_t setSilentMode(bool enable);
dshot_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);
dshot_result_t executeSequence(const dshot_command_sequence_item_t *sequence, size_t sequence_length);
// Execute ESC initialization sequence
dshot_command_result_t executeInitSequence();
dshot_result_t executeInitSequence();
// Execute ESC calibration sequence
dshot_command_result_t executeCalibrationSequence();
dshot_result_t executeCalibrationSequence();
// --- UTILITY METHODS ---
// Get command name as string
@ -104,12 +91,6 @@ public:
// 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; }
@ -117,26 +98,23 @@ public:
// 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; }
// Get reference to underlying DShotRMT instance
DShotRMT &getDShotRMT() { return _dshot; }
const DShotRMT &getDShotRMT() const { return _dshot; }
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);
dshot_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);
void _delay_ms(uint32_t delay_ms);
// --- CONSTANTS ---
static constexpr uint32_t DEFAULT_COMMAND_DELAY_MS = 10;

View File

@ -29,6 +29,8 @@ DShotRMT::DShotRMT(gpio_num_t gpio, dshot_mode_t mode, bool is_bidirectional)
_last_erpm(0),
_parsed_packet(0),
_packet{0},
_total_transmissions(0),
_failed_transmissions(0),
_rmt_tx_channel(nullptr),
_rmt_rx_channel(nullptr),
_dshot_encoder(nullptr),
@ -90,33 +92,43 @@ DShotRMT::~DShotRMT()
}
// Initialize DShotRMT
uint16_t DShotRMT::begin()
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())
{
result.error_message = RX_INIT_FAILED;
_dshot_log(RX_INIT_FAILED);
return DSHOT_ERROR;
return result;
}
}
// Init TX channel
if (!_initTXChannel())
{
result.error_message = TX_INIT_FAILED;
_dshot_log(TX_INIT_FAILED);
return DSHOT_ERROR;
return result;
}
// Init DShot encoder
if (_initDShotEncoder() != DSHOT_OK)
{
result.error_message = ENCODER_INIT_FAILED;
_dshot_log(ENCODER_INIT_FAILED);
return DSHOT_ERROR;
return result;
}
return DSHOT_OK;
uint64_t end_time = esp_timer_get_time();
result.success = true;
result.error_message = INIT_SUCCESS;
return result;
}
// Init RMT TX channel
@ -212,9 +224,10 @@ bool DShotRMT::_initDShotEncoder()
}
// Send throttle value
bool DShotRMT::sendThrottle(uint16_t throttle)
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)
@ -226,6 +239,7 @@ bool DShotRMT::sendThrottle(uint16_t throttle)
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
@ -234,17 +248,21 @@ bool DShotRMT::sendThrottle(uint16_t 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);
}
// Send DShot command to ESC
bool DShotRMT::sendCommand(uint16_t command)
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);
return DSHOT_ERROR;
result.error_message = COMMAND_NOT_VALID;
return result;
}
// Build packet and transmit
@ -252,7 +270,41 @@ bool DShotRMT::sendCommand(uint16_t command)
return _sendDShotFrame(_packet);
}
// Get RPM from ESC (bidirectional mode only)
// Get telemetry data with timing and error handling
dshot_telemetry_result_t DShotRMT::getTelemetry(uint8_t magnet_count)
{
dshot_telemetry_result_t result = {false, 0, 0, UNKNOWN_ERROR};
// Check if bidirectional mode is enabled
if (!_is_bidirectional)
{
result.error_message = BIDIR_NOT_ENABLED;
_dshot_log(BIDIR_NOT_ENABLED);
return result;
}
// Get eRPM
uint16_t erpm = getERPM();
if (erpm == DSHOT_NULL_PACKET)
{
result.error_message = TELEMETRY_TIMEOUT;
return result;
}
// Calculate motor RPM
uint8_t pole_pairs = max(1, magnet_count / 2);
uint32_t motor_rpm = erpm / pole_pairs;
result.success = true;
result.erpm = erpm;
result.motor_rpm = motor_rpm;
result.error_message = TELEMETRY_SUCCESS;
return result;
}
// Get RPM from ESC (bidirectional mode only) - backward compatibility
uint16_t DShotRMT::getERPM()
{
// Check if bidirectional mode is enabled
@ -278,13 +330,6 @@ uint16_t DShotRMT::getERPM()
return _last_erpm;
}
// Convert eRPM to actual motor RPM
uint32_t DShotRMT::getMotorRPM(uint8_t magnet_count)
{
uint8_t pole_pairs = max(1, magnet_count / 2);
return getERPM() / pole_pairs;
}
// Build a complete DShot packet
dshot_packet_t DShotRMT::_buildDShotPacket(const uint16_t value)
{
@ -338,12 +383,15 @@ uint16_t DShotRMT::_calculateCRC(const uint16_t data)
}
// Transmit DShot packet via RMT
bool DShotRMT::_sendDShotFrame(const dshot_packet_t &packet)
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 DSHOT_ERROR;
return result;
}
// Enable RMT RX before RMT TX
@ -352,7 +400,11 @@ bool DShotRMT::_sendDShotFrame(const dshot_packet_t &packet)
// Performance reasons
rmt_symbol_word_t rx_symbols[DSHOT_BITS_PER_FRAME];
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) != DSHOT_OK)
{
result.error_message = RX_RMT_RECEIVER_ERROR;
return result;
}
}
// Local for performance
@ -370,16 +422,18 @@ bool 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 DSHOT_ERROR;
return result;
}
}
// 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 DSHOT_ERROR;
return result;
}
// Re-enable RMT RX
@ -387,15 +441,20 @@ bool 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 DSHOT_ERROR;
return result;
}
}
// Update timestamp and return success
// Update timestamp and calculate execution time
_timer_reset();
uint64_t end_time = esp_timer_get_time();
return DSHOT_OK;
result.success = true;
result.error_message = TRANSMISSION_SUCCESS;
return result;
}
// Encode DShot packet into RMT symbol format (placed in IRAM for performance)

View File

@ -65,14 +65,31 @@ typedef struct
uint16_t ticks_zero_low;
} dshot_timing_t;
// Command execution result structure
typedef struct
{
bool success;
const char *error_message;
} dshot_result_t;
// DShot telemetry result structure
typedef struct
{
bool success;
uint16_t erpm;
uint32_t motor_rpm;
const char *error_message;
} dshot_telemetry_result_t;
// Naming convention
typedef dshotCommands_e dshot_commands_t;
//
class DShotRMT
{
public:
// Constructor with GPIO enum
explicit DShotRMT(gpio_num_t gpio = GPIO_NUM_16,
dshot_mode_t mode = DSHOT300,
bool is_bidirectional = false);
explicit DShotRMT(gpio_num_t gpio = GPIO_NUM_16, dshot_mode_t mode = DSHOT300, bool is_bidirectional = false);
// Constructor with pin number
DShotRMT(uint16_t pin_nr, dshot_mode_t mode, bool is_bidirectional);
@ -81,24 +98,29 @@ public:
~DShotRMT();
// Initialize the RMT module and DShot config
uint16_t begin();
dshot_result_t begin();
// Send throttle value (48-2047)
bool sendThrottle(uint16_t throttle);
dshot_result_t sendThrottle(uint16_t throttle);
// Send DShot command (0-47)
bool sendCommand(uint16_t command);
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();
// Convert eRPM to motor RPM
uint32_t getMotorRPM(uint8_t magnet_count);
//
// --- 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; }
// --- INFO ---
void printDShotInfo(Stream &output = Serial) const;
@ -106,10 +128,25 @@ public:
// --- DEPRECATED METHODS ---
[[deprecated("Use sendThrottle() instead")]]
bool setThrottle(uint16_t throttle) { return sendThrottle(throttle); }
bool setThrottle(uint16_t throttle)
{
auto result = sendThrottle(throttle);
return result.success;
}
[[deprecated("Use sendCommand() instead")]]
bool sendDShotCommand(uint16_t command) { return sendCommand(command); }
bool sendDShotCommand(uint16_t command)
{
auto result = sendCommand(command);
return result.success;
}
[[deprecated("Use getTelemetry() instead")]]
uint32_t getMotorRPM(uint8_t magnet_count)
{
auto result = getTelemetry(magnet_count);
return result.success;
}
private:
// --- CONFIG ---
@ -125,6 +162,10 @@ private:
uint16_t _parsed_packet;
dshot_packet_t _packet;
// --- STATISTICS ---
uint32_t _total_transmissions;
uint32_t _failed_transmissions;
// --- RMT HARDWARE HANDLES ---
rmt_channel_handle_t _rmt_tx_channel;
rmt_channel_handle_t _rmt_rx_channel;
@ -147,7 +188,7 @@ private:
uint16_t _calculateCRC(const uint16_t data);
// --- FRAME PROCESSING ---
bool _sendDShotFrame(const dshot_packet_t &packet);
dshot_result_t _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);
@ -164,13 +205,14 @@ private:
static constexpr auto DSHOT_TELEMETRY_INVALID = (0xffff);
// --- ERROR HANDLING & LOGGING ---
void _dshot_log(const char *msg, Stream &output = Serial) { output.println(msg); }
void _dshot_log(const char *msg, Stream &output = Serial) const { output.println(msg); }
// --- 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!";
@ -181,4 +223,8 @@ private:
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";
};

View File

@ -1,11 +1,9 @@
/*
* command_manager_example.ino
* Example sketch demonstrating DShotCommandManager usage
* command_manager.ino
* Example sketch for DShotCommandManager
* Author: Wastl Kraus
* Date: 2025-09-04
* License: MIT
*
* Modified by Especiallist to support continuous throttle sending.
*/
#include <Arduino.h>
@ -28,7 +26,20 @@ 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;
static volatile uint16_t throttle_now = 0;
// Helper function to print telemetry results
void printTelemetryResult(const dshot_telemetry_result_t &result)
{
if (result.success)
{
USB_SERIAL.printf("Telemetry: eRPM=%u, Motor RPM=%u\n", result.erpm, result.motor_rpm);
}
else
{
USB_SERIAL.printf("Telemetry: FAILED - %s\n", result.error_message);
}
}
//
void setup()
@ -36,25 +47,11 @@ 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);
}
motor01.begin();
// Init Command Manager
if (!commandManager.begin())
{
USB_SERIAL.println("ERROR: Failed to initialize DShotCommandManager!");
while (1)
delay(1000);
}
USB_SERIAL.println("Initialization successful!");
commandManager.begin();
// Print Menu
printMenu();
@ -75,9 +72,15 @@ void loop()
}
// Continuously send the stored throttle value
if (throttle_now != NULL)
if (throttle_now != 0)
{
motor01.sendThrottle(throttle_now);
dshot_result_t result = motor01.sendThrottle(throttle_now);
// Only print errors to avoid spam
if (!result.success)
{
printResult(result);
}
// Print motor stats every 2 seconds
if (esp_timer_get_time() - last_stats_print >= 2000000)
@ -87,8 +90,8 @@ void loop()
// Get Motor RPM
if (IS_BIDIRECTIONAL)
{
uint32_t rpm = motor01.getMotorRPM(MOTOR01_MAGNET_COUNT);
USB_SERIAL.printf("Motor RPM: %u\n", rpm);
dshot_telemetry_result_t telem_result = motor01.getTelemetry(MOTOR01_MAGNET_COUNT);
printTelemetryResult(telem_result);
}
// Time Stamp
@ -100,92 +103,64 @@ void loop()
//
void handleUserInput(const String &input)
{
dshot_command_result_t result;
dshot_result_t cmd_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);
cmd_result = commandManager.stopMotor();
printResult(cmd_result);
}
else if (input == "2")
{
USB_SERIAL.print("Activating beacon 1... ");
result = commandManager.activateBeacon(1);
printResult(result);
cmd_result = commandManager.activateBeacon(1);
printResult(cmd_result);
}
else if (input == "3")
{
USB_SERIAL.print("Setting normal spin direction... ");
result = commandManager.setSpinDirection(false);
printResult(result);
cmd_result = commandManager.setSpinDirection(false);
printResult(cmd_result);
}
else if (input == "4")
{
USB_SERIAL.print("Setting reversed spin direction... ");
result = commandManager.setSpinDirection(true);
printResult(result);
cmd_result = commandManager.setSpinDirection(true);
printResult(cmd_result);
}
else if (input == "5")
{
USB_SERIAL.print("Enabling 3D mode... ");
result = commandManager.set3DMode(true);
printResult(result);
USB_SERIAL.print("Getting ESC Info... ");
cmd_result = commandManager.requestESCInfo();
printResult(cmd_result);
}
else if (input == "6")
{
USB_SERIAL.print("Disabling 3D mode... ");
result = commandManager.set3DMode(false);
printResult(result);
USB_SERIAL.print("Turning LED 0 ON... ");
cmd_result = commandManager.setLED(0, true);
printResult(cmd_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);
cmd_result = commandManager.setLED(0, false);
printResult(cmd_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")
else if (input == "h" || input == "help")
{
printMenu();
}
else if (input == "help")
else if (input == "info")
{
printMenu();
motor01.printDShotInfo();
}
else if (input == "rpm" && IS_BIDIRECTIONAL)
{
dshot_telemetry_result_t result = motor01.getTelemetry(MOTOR01_MAGNET_COUNT);
printTelemetryResult(result);
}
else if (input.startsWith("cmd "))
{
@ -195,12 +170,12 @@ void handleUserInput(const String &input)
{
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);
cmd_result = commandManager.sendCommand(static_cast<dshot_commands_t>(cmd_num));
printResult(cmd_result);
}
else
{
USB_SERIAL.printf("Invalid command number: %d (valid range: 0-%d)\n", cmd_num, DSHOT_CMD_MAX);
USB_SERIAL.printf("Invalid command number: %d (valid range: 0 - %d)\n", cmd_num, DSHOT_CMD_MAX);
}
}
else if (input.startsWith("throttle "))
@ -211,65 +186,139 @@ void handleUserInput(const String &input)
{
throttle_now = throttle_value;
USB_SERIAL.printf("Setting continuous throttle to %d\n", throttle_now);
// Send first throttle command and show result
dshot_result_t result = motor01.sendThrottle(throttle_now);
printResult(result);
if (result.success)
{
USB_SERIAL.println("Continuous throttle mode enabled. Send '0' or 'throttle 0' to stop.");
}
}
else if (throttle_value == 0)
{
throttle_now = 0;
USB_SERIAL.println("Continuous throttle stopped.");
// Send stop command
dshot_result_t result = motor01.sendCommand(DSHOT_CMD_MOTOR_STOP);
printResult(result);
}
else
{
USB_SERIAL.printf("Invalid throttle value: %d (valid range: 48-2047, use 0 to stop)\n", throttle_value);
USB_SERIAL.printf("Invalid throttle value: %d (valid range: %d-%d, use 0 to stop)\n",
throttle_value, DSHOT_THROTTLE_MIN, DSHOT_THROTTLE_MAX);
}
}
else if (input == "0")
{
// Quick stop
throttle_now = 0;
USB_SERIAL.print("Emergency stop... ");
dshot_result_t result = motor01.sendCommand(DSHOT_CMD_MOTOR_STOP);
printResult(result);
}
else if (input.startsWith("repeat "))
{
// Repeat command: "repeat cmd 5 count 10" - sends command 5 ten times
String params = input.substring(7);
if (params.startsWith("cmd "))
{
int space_pos = params.indexOf(' ', 4);
if (space_pos > 0 && params.substring(space_pos + 1).startsWith("count "))
{
int cmd_num = params.substring(4, space_pos).toInt();
int repeat_count = params.substring(space_pos + 7).toInt();
if (DShotCommandManager::isValidCommand(static_cast<dshot_commands_t>(cmd_num)) &&
repeat_count > 0 && repeat_count <= 100)
{
USB_SERIAL.printf("Sending command %d (%s) %d times... ", cmd_num,
DShotCommandManager::getCommandName(static_cast<dshot_commands_t>(cmd_num)),
repeat_count);
cmd_result = commandManager.sendCommand(static_cast<dshot_commands_t>(cmd_num), repeat_count);
printResult(cmd_result);
}
else
{
USB_SERIAL.println("Invalid command or repeat count (1-100)");
}
}
else
{
USB_SERIAL.println("Unknown command. Type 'h' for help.");
USB_SERIAL.println("Usage: repeat cmd <number> count <repeat_count>");
}
}
else
{
USB_SERIAL.println("Usage: repeat cmd <number> count <repeat_count>");
}
}
else
{
USB_SERIAL.printf("Unknown command: '%s'. Type 'h' or 'help' for help.\n", input.c_str());
}
}
//
void printResult(const dshot_command_result_t &result)
void printResult(const dshot_result_t &result)
{
if (!result.success)
if (result.success)
{
USB_SERIAL.printf("SUCCESS (%u us)\n", result.execution_time_us);
USB_SERIAL.printf("SUCCESS\n");
}
else
{
USB_SERIAL.printf("FAILED - %s (%u us)\n", result.error_message, result.execution_time_us);
USB_SERIAL.printf("FAILED - %s \n", result.error_message);
}
}
//
void printSystemStatus()
{
USB_SERIAL.println("\n=== System Status ===");
USB_SERIAL.printf("Current throttle: %u\n", throttle_now);
USB_SERIAL.printf("Continuous mode: %s\n", throttle_now > 0 ? "ACTIVE" : "INACTIVE");
USB_SERIAL.printf("GPIO Pin: %d\n", motor01.getGPIO());
USB_SERIAL.printf("DShot Mode: DSHOT%d\n",
motor01.getMode() == DSHOT150 ? 150 : motor01.getMode() == DSHOT300 ? 300
: motor01.getMode() == DSHOT600 ? 600
: motor01.getMode() == DSHOT1200 ? 1200
: 0);
USB_SERIAL.printf("Bidirectional: %s\n", motor01.is_bidirectional() ? "YES" : "NO");
USB_SERIAL.printf("Free heap: %u bytes\n", ESP.getFreeHeap());
USB_SERIAL.printf("Uptime: %lu seconds\n", millis() / 1000);
}
//
void printMenu()
{
USB_SERIAL.println("================================================");
USB_SERIAL.println("\n=== DShot Command Manager Menu ===");
USB_SERIAL.println("Basic Commands:");
USB_SERIAL.println("**********************************************");
USB_SERIAL.println(" DShot Command Manager Menu ");
USB_SERIAL.println("**********************************************");
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(" 3 - Set Normal Spin");
USB_SERIAL.println(" 4 - Set Reversed Spin");
USB_SERIAL.println(" 5 - Get ESC Info");
USB_SERIAL.println(" 6 - Turn LED 0 ON");
USB_SERIAL.println(" 7 - Turn LED 0 OFF");
USB_SERIAL.println(" 0 - Emergency Stop");
USB_SERIAL.println("**********************************************");
USB_SERIAL.println(" cmd <number> - Send 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("**********************************************");
USB_SERIAL.println(" info - Show DShot signal info");
USB_SERIAL.println(" status - Show system status");
if (IS_BIDIRECTIONAL)
{
USB_SERIAL.println(" rpm - Get telemetry data");
}
USB_SERIAL.println(" h / help - Show this Menu");
USB_SERIAL.println("**********************************************");
USB_SERIAL.println("EXAMPLE INPUT:");
USB_SERIAL.println(" cmd 5 - Get ESC Info");
USB_SERIAL.println(" throttle 1000 - Set throttle to 1000");
USB_SERIAL.println("================================================");
USB_SERIAL.println("**********************************************");
}

View File

@ -9,9 +9,6 @@
#include <Arduino.h>
#include <DShotRMT.h>
// Debug output
static constexpr auto DEBUG = false;
// USB serial port settings
static constexpr auto &USB_SERIAL = Serial0;
static constexpr auto USB_SERIAL_BAUD = 115200;
@ -38,49 +35,169 @@ void setup()
// Starts the USB Serial Port
USB_SERIAL.begin(USB_SERIAL_BAUD);
// Initializes DShot Signal
// Initialize DShot Signal
motor01.begin();
// Print CPU Info
motor01.printCpuInfo();
USB_SERIAL.println(" ");
USB_SERIAL.println("***********************************");
USB_SERIAL.println(" === DShotRMT Demo started. === ");
USB_SERIAL.println("Enter a throttle value (48 2047):");
//
printMenu();
}
//
void loop()
{
/// ...safety first
// Safety first
static uint16_t throttle = DSHOT_CMD_MOTOR_STOP;
static bool continuous_throttle = true;
// Time Measurement
static uint32_t last_stats_print = 0;
// Read throttle value
// Handle serial input
if (USB_SERIAL.available() > 0)
{
throttle = USB_SERIAL.readStringUntil('\n').toInt();
String input = USB_SERIAL.readStringUntil('\n');
input.trim();
if (input.length() > 0)
{
handleSerialInput(input, throttle, continuous_throttle);
}
}
// Send the current throttle value
// Send throttle value in continuous mode
if (continuous_throttle)
{
motor01.sendThrottle(throttle);
}
// Print motor stats every 2 seconds
if (millis() - last_stats_print >= 2000)
// Print motor stats every 5 seconds in continuous mode
if (continuous_throttle && (millis() - last_stats_print >= 5000))
{
motor01.printDShotInfo();
// Get Motor RPM
// Get Motor RPM if bidirectional
if (IS_BIDIRECTIONAL)
{
uint32_t rpm = motor01.getMotorRPM(MOTOR01_MAGNET_COUNT);
USB_SERIAL.printf("Motor RPM: %u\n", rpm);
dshot_telemetry_result_t telem_result = motor01.getTelemetry(MOTOR01_MAGNET_COUNT);
printTelemetryResult(telem_result);
}
// Time Stamp
last_stats_print = millis();
}
}
//
void printMenu()
{
USB_SERIAL.println(" ");
USB_SERIAL.println("******************************************");
USB_SERIAL.println(" DShotRMT Demo ");
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(" info - Show motor info");
if (IS_BIDIRECTIONAL)
{
USB_SERIAL.println(" rpm - Get telemetry data");
}
USB_SERIAL.println("******************************************");
USB_SERIAL.println(" h / help - Show this Menu");
USB_SERIAL.println("******************************************");
}
// Helper function 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 function 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);
}
}
//
void handleSerialInput(const String &input, uint16_t &throttle, bool &continuous_throttle)
{
if (input == "0")
{
// Stop motor
throttle = 0;
continuous_throttle = true; // kill motor for sure
dshot_result_t result = motor01.sendCommand(DSHOT_CMD_MOTOR_STOP);
printCommandResult(result, "Stop Motor");
}
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);
}
else if (input.startsWith("cmd "))
{
continuous_throttle = false;
// Send DShot command
int cmd_num = input.substring(4).toInt();
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));
}
else
{
USB_SERIAL.printf("Invalid command: %d (valid range: 0 - %d)\n", cmd_num, DSHOT_CMD_MAX);
}
}
else if (input == "h" || input == "help")
{
printMenu();
}
else
{
// Parse input throttle value
int throttle_value = input.toInt();
if (throttle_value >= DSHOT_THROTTLE_MIN && throttle_value <= DSHOT_THROTTLE_MAX)
{
throttle = throttle_value;
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.");
}
else
{
USB_SERIAL.printf("Invalid input: '%s'\n", input.c_str());
USB_SERIAL.printf("Valid throttle range: %d - %d\n", DSHOT_THROTTLE_MIN, DSHOT_THROTTLE_MAX);
}
}
}