From cfca2de4e92ee5ddb4d4203b55b0236d3e468bb2 Mon Sep 17 00:00:00 2001 From: Wastl Kraus Date: Fri, 5 Sep 2025 23:28:08 +0200 Subject: [PATCH 1/8] ...cosmetics --- DShotRMT.cpp | 26 +++++++++++++++----------- examples/dshot300/dshot300.ino | 19 ++++++++++--------- 2 files changed, 25 insertions(+), 20 deletions(-) diff --git a/DShotRMT.cpp b/DShotRMT.cpp index 823e67e..3998ae6 100644 --- a/DShotRMT.cpp +++ b/DShotRMT.cpp @@ -60,30 +60,34 @@ 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); - rmt_del_channel(_rmt_tx_channel); - _rmt_tx_channel = nullptr; + 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); - rmt_del_channel(_rmt_rx_channel); - _rmt_rx_channel = nullptr; + 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; } - // + // ...Buffer if (_rx_queue) { vQueueDelete(_rx_queue); @@ -91,7 +95,7 @@ DShotRMT::~DShotRMT() } } -// Initialize DShotRMT +// Init DShotRMT dshot_result_t DShotRMT::begin() { dshot_result_t result = {false, UNKNOWN_ERROR}; diff --git a/examples/dshot300/dshot300.ino b/examples/dshot300/dshot300.ino index 21f68a8..bc6524b 100644 --- a/examples/dshot300/dshot300.ino +++ b/examples/dshot300/dshot300.ino @@ -78,6 +78,9 @@ void loop() { motor01.printDShotInfo(); + USB_SERIAL.println(" "); + USB_SERIAL.println("Type 'help' to show Menu"); + // Get Motor RPM if bidirectional if (IS_BIDIRECTIONAL) { @@ -94,21 +97,21 @@ void loop() void printMenu() { USB_SERIAL.println(" "); - USB_SERIAL.println("******************************************"); - USB_SERIAL.println(" DShotRMT Demo "); - USB_SERIAL.println("******************************************"); + USB_SERIAL.println("*******************************************"); + USB_SERIAL.println(" DShotRMT Demo "); + USB_SERIAL.println("*******************************************"); USB_SERIAL.println(" - Set throttle (48 – 2047)"); USB_SERIAL.println(" 0 - Stop motor"); - USB_SERIAL.println("******************************************"); - USB_SERIAL.println(" cmd - Send DShot command (0-47)"); + USB_SERIAL.println("*******************************************"); + USB_SERIAL.println(" cmd - 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("******************************************"); + USB_SERIAL.println("*******************************************"); } // Helper to print command results @@ -191,8 +194,6 @@ void handleSerialInput(const String &input, uint16_t &throttle, bool &continuous 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 { From feb6f4ecac5c322f5b805c2c4369e42954786051 Mon Sep 17 00:00:00 2001 From: Wastl Kraus Date: Sun, 7 Sep 2025 13:48:48 +0200 Subject: [PATCH 2/8] ...update error handling --- DShotCommandManager.cpp | 20 +- DShotCommandManager.h | 6 +- DShotRMT.cpp | 185 ++++++++++++------- DShotRMT.h | 96 +++++----- examples/command_manager/command_manager.ino | 4 +- examples/dshot300/dshot300.ino | 48 ++--- 6 files changed, 190 insertions(+), 169 deletions(-) diff --git a/DShotCommandManager.cpp b/DShotCommandManager.cpp index 9391640..ac5cb17 100644 --- a/DShotCommandManager.cpp +++ b/DShotCommandManager.cpp @@ -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; } @@ -36,10 +36,10 @@ dshot_result_t DShotCommandManager::sendCommand(dshot_commands_t command, uint16 dshot_result_t DShotCommandManager::sendCommandWithDelay(dshot_commands_t command, uint16_t repeat_count, uint32_t delay_ms) { dshot_result_t result = {false, "Unknown error"}; - + 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; } @@ -64,12 +64,12 @@ dshot_result_t DShotCommandManager::sendCommandWithDelay(dshot_commands_t comman } } - // + // result.success = all_successful; 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 diff --git a/DShotCommandManager.h b/DShotCommandManager.h index aa47ff1..1c7dcff 100644 --- a/DShotCommandManager.h +++ b/DShotCommandManager.h @@ -12,13 +12,13 @@ #include #include -// 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(); diff --git a/DShotRMT.cpp b/DShotRMT.cpp index 3998ae6..d3dc31a 100644 --- a/DShotRMT.cpp +++ b/DShotRMT.cpp @@ -18,6 +18,36 @@ static constexpr dshot_timing_t DSHOT_TIMINGS[] = { {16, 8, 6, 2, 3, 5} // DSHOT1200 }; +// --- HELPERS --- +void printDShotResult(dshot_result_t &result, Stream &output) +{ + if (result.success) + { + output.printf("Staus: SUCCESS - %s\n", result.msg); + } + else + { + output.printf("Status: FAILED - %s\n", 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(" "); +} + // Constructor with GPIO number DShotRMT::DShotRMT(gpio_num_t gpio, dshot_mode_t mode, bool is_bidirectional) : _gpio(gpio), @@ -26,11 +56,8 @@ DShotRMT::DShotRMT(gpio_num_t gpio, dshot_mode_t mode, bool is_bidirectional) _frame_timer_us(0), _timing_config(DSHOT_TIMINGS[mode]), _last_transmission_time(0), - _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), @@ -98,46 +125,45 @@ DShotRMT::~DShotRMT() // Init DShotRMT dshot_result_t DShotRMT::begin() { - dshot_result_t result = {false, UNKNOWN_ERROR}; - uint64_t start_time = esp_timer_get_time(); + // Result container + dshot_result_t result = {false, INIT_FAILED}; // Init RX channel first if (_is_bidirectional) { - if (!_initRXChannel()) + if (!_initRXChannel().success) { - result.error_message = RX_INIT_FAILED; - _dshot_log(RX_INIT_FAILED); + result.msg = RX_INIT_FAILED; return result; } } // Init TX channel - if (!_initTXChannel()) + if (!_initTXChannel().success) { - result.error_message = TX_INIT_FAILED; - _dshot_log(TX_INIT_FAILED); + result.msg = TX_INIT_FAILED; return result; } // Init DShot encoder - if (_initDShotEncoder() != DSHOT_OK) + if (!_initDShotEncoder().success) { - result.error_message = ENCODER_INIT_FAILED; - _dshot_log(ENCODER_INIT_FAILED); + result.msg = ENCODER_INIT_FAILED; return result; } - uint64_t end_time = esp_timer_get_time(); result.success = true; - result.error_message = INIT_SUCCESS; + result.msg = INIT_SUCCESS; return result; } // Init RMT TX channel -bool DShotRMT::_initTXChannel() +dshot_result_t DShotRMT::_initTXChannel() { + // Result container + dshot_result_t result = {false, TX_INIT_FAILED}; + // Configure TX channel _tx_channel_config.gpio_num = _gpio; _tx_channel_config.clk_src = DSHOT_CLOCK_SRC_DEFAULT; @@ -152,21 +178,33 @@ 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 result; } - return (rmt_enable(_rmt_tx_channel) == DSHOT_OK); + // + if (rmt_enable(_rmt_tx_channel) != 0) + { + return result; + } + + result.success = true; + result.msg = TX_INIT_SUCCESS; + + return result; } // Init RMT RX channel -bool DShotRMT::_initRXChannel() +dshot_result_t DShotRMT::_initRXChannel() { + // Result container + dshot_result_t result = {false, RX_INIT_FAILED}; + // 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; + result.msg = RX_BUFFER_FAILED; + return result; } // Config RMT RX @@ -182,8 +220,7 @@ 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 result; } // Register RX callback @@ -191,48 +228,64 @@ bool DShotRMT::_initRXChannel() if (rmt_rx_register_event_callbacks(_rmt_rx_channel, &_rx_event_callbacks, _rx_queue) != DSHOT_OK) { - _dshot_log(RX_INIT_FAILED); - return DSHOT_ERROR; + result.msg = RX_BUFFER_FAILED; + return result; } - return (rmt_enable(_rmt_rx_channel) == DSHOT_OK); + // + if (rmt_enable(_rmt_rx_channel) != 0) + { + return result; + } + + result.success = true; + result.msg = RX_INIT_SUCCESS; + + return result; } // 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; + QueueHandle_t rx_buffer = (QueueHandle_t)user_data; BaseType_t xHigherPriorityTaskWoken = pdFALSE; // Copy callback data into RX buffer - xQueueGenericSendFromISR(rx_queue, edata, &xHigherPriorityTaskWoken, queueSEND_TO_BACK); + xQueueGenericSendFromISR(rx_buffer, edata, &xHigherPriorityTaskWoken, queueSEND_TO_BACK); return (xHigherPriorityTaskWoken == pdTRUE); } // Initialize DShot encoder -bool DShotRMT::_initDShotEncoder() +dshot_result_t DShotRMT::_initDShotEncoder() { + // Result container + dshot_result_t result = {false, ENCODER_INIT_FAILED}; + // Create copy encoder configuration rmt_copy_encoder_config_t encoder_config = {}; // Create encoder instance if (rmt_new_copy_encoder(&encoder_config, &_dshot_encoder) != DSHOT_OK) { - _dshot_log(ENCODER_INIT_FAILED); - return DSHOT_ERROR; + return result; } - return DSHOT_OK; + result.success = true; + result.msg = ENCODER_INIT_SUCCESS; + + return result; } // Send throttle value dshot_result_t DShotRMT::sendThrottle(uint16_t throttle) { - static uint16_t last_throttle = DSHOT_CMD_MOTOR_STOP; + // Result container dshot_result_t result = {false, UNKNOWN_ERROR}; + static uint16_t last_throttle = DSHOT_CMD_MOTOR_STOP; + // Special case: if throttle is 0, use sendCommand() instead if (throttle == 0) { @@ -242,8 +295,7 @@ dshot_result_t DShotRMT::sendThrottle(uint16_t throttle) // 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; + result.msg = THROTTLE_NOT_IN_RANGE; } // Always store the original throttle value @@ -259,63 +311,67 @@ dshot_result_t DShotRMT::sendThrottle(uint16_t throttle) // Send DShot command to ESC dshot_result_t DShotRMT::sendCommand(uint16_t command) { + // Result container 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; + result.msg = COMMAND_NOT_VALID; return result; } // 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, 0, 0, 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(); + 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; + 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; + result.msg = TELEMETRY_SUCCESS; return result; } // Get RPM from ESC (bidirectional mode only) - backward compatibility -uint16_t DShotRMT::getERPM() +uint16_t DShotRMT::_getERPM() { + static uint16_t last_erpm = 0; + + // Result container + dshot_telemetry_result_t result = {false, 0, 0, TELEMETRY_FAILED}; + // Check if bidirectional mode is enabled if (!_is_bidirectional) { - _dshot_log(BIDIR_NOT_ENABLED); - return _last_erpm; + return last_erpm; } // RMT RX event data @@ -327,11 +383,11 @@ uint16_t DShotRMT::getERPM() // 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); + last_erpm = _decodeDShotFrame(rx_data.received_symbols); } } - return _last_erpm; + return last_erpm; } // Build a complete DShot packet @@ -343,8 +399,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; } @@ -390,7 +444,6 @@ uint16_t DShotRMT::_calculateCRC(const uint16_t data) 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()) @@ -406,7 +459,7 @@ dshot_result_t DShotRMT::_sendDShotFrame(const dshot_packet_t &packet) if (rmt_receive(_rmt_rx_channel, rx_symbols, sizeof(rx_symbols), &_receive_config) != DSHOT_OK) { - result.error_message = RX_RMT_RECEIVER_ERROR; + result.msg = RECEIVER_FAILED; return result; } } @@ -426,8 +479,7 @@ 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); + result.msg = RECEIVER_FAILED; return result; } } @@ -435,8 +487,7 @@ dshot_result_t DShotRMT::_sendDShotFrame(const dshot_packet_t &packet) // 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); + result.msg = TRANSMISSION_FAILED; return result; } @@ -445,18 +496,16 @@ 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); + result.msg = RECEIVER_FAILED; return result; } } // Update timestamp and calculate execution time _timer_reset(); - uint64_t end_time = esp_timer_get_time(); result.success = true; - result.error_message = TRANSMISSION_SUCCESS; + result.msg = TRANSMISSION_SUCCESS; return result; } @@ -555,10 +604,10 @@ void DShotRMT::printDShotInfo(Stream &output) const // Current DShot mode output.printf("Current Mode: DSHOT%d\n", - _mode == DSHOT150 ? 150 : - _mode == DSHOT300 ? 300 : - _mode == DSHOT600 ? 600 : - _mode == DSHOT1200 ? 1200 : 0); + _mode == DSHOT150 ? 150 : + _mode == DSHOT300 ? 300 : + _mode == DSHOT600 ? 600 : + _mode == DSHOT1200 ? 1200 : 0); output.printf("Bidirectional: %s\n", _is_bidirectional ? "YES" : "NO"); diff --git a/DShotRMT.h b/DShotRMT.h index bdf40ee..fd81846 100644 --- a/DShotRMT.h +++ b/DShotRMT.h @@ -15,19 +15,20 @@ #include // 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_SWITCH_TIME = 30; // Time in us - add some padding to RX - TX switching +static constexpr auto DSHOT_NULL_PACKET = 0b0000000000000000; +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; // 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 +37,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 +47,7 @@ typedef enum DSHOT1200 } dshot_mode_t; -// DShot Packet Structure +// DShot Packet typedef struct { uint16_t throttle_value : 11; @@ -54,7 +55,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 +66,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 +111,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; @@ -141,7 +137,7 @@ public: return result.success; } - [[deprecated("Use getTelemetry() instead")]] + [[deprecated("Use getTelemetry() instead")]] uint32_t getMotorRPM(uint8_t magnet_count) { auto result = getTelemetry(magnet_count); @@ -158,7 +154,6 @@ private: // --- TIMING & STATE VARIABLES --- uint64_t _last_transmission_time; - uint16_t _last_erpm; uint16_t _parsed_packet; dshot_packet_t _packet; @@ -178,9 +173,9 @@ 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); @@ -192,6 +187,9 @@ private: bool IRAM_ATTR _encodeDShotFrame(const dshot_packet_t &packet, rmt_symbol_word_t *symbols); uint16_t _decodeDShotFrame(const rmt_symbol_word_t *symbols); + // --- HELPERS --- + uint16_t _getERPM(); + // --- TIMING CONTROL --- bool IRAM_ATTR _timer_signal(); bool _timer_reset(); @@ -204,27 +202,27 @@ private: // --- 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); } - // --- CONSTANTS & ERROR MESSAGES --- static constexpr bool DSHOT_OK = 0; static constexpr bool DSHOT_ERROR = 1; - static constexpr char *NEW_LINE = " "; + static constexpr char *NONE = ""; 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 *INIT_SUCCESS = "SignalGeneratorRMT initialized successfully"; + static constexpr char *INIT_FAILED = "SignalGeneratorRMT init failed!"; + static constexpr char *TX_INIT_SUCCESS = "TX RMT channel initialized successfully"; + static constexpr char *TX_INIT_FAILED = "TX RMT channel init failed!"; + static constexpr char *RX_INIT_SUCCESS = "RX RMT channel initialized successfully"; + static constexpr char *RX_INIT_FAILED = "RX RMT channel init failed!"; + static constexpr char *RX_BUFFER_FAILED = "RX RMT buffer init failed!"; + static constexpr char *ENCODER_INIT_SUCCESS = "RMT encoder initialized successfully"; + static constexpr char *ENCODER_INIT_FAILED = "RMT encoder init failed!"; + static constexpr char *TRANSMISSION_SUCCESS = "Transmission successfully"; + static constexpr char *TRANSMISSION_FAILED = "Transmission failed!"; + static constexpr char *RECEIVER_FAILED = "RMT receiver failed!"; + static constexpr char *THROTTLE_NOT_IN_RANGE = "Throttle not in range! (48 - 2047)"; + static constexpr char *COMMAND_NOT_VALID = "Command not valid! (0 - 47)"; + static constexpr char *BIDIR_NOT_ENABLED = "Bidirectional DShot not enabled!"; + static constexpr char *TELEMETRY_SUCCESS = "Valid Telemetric Frame received!"; + static constexpr char *TELEMETRY_FAILED = "No valid Telemetric Frame received!"; }; diff --git a/examples/command_manager/command_manager.ino b/examples/command_manager/command_manager.ino index a07cbe2..9b4e974 100644 --- a/examples/command_manager/command_manager.ino +++ b/examples/command_manager/command_manager.ino @@ -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); } } diff --git a/examples/dshot300/dshot300.ino b/examples/dshot300/dshot300.ino index bc6524b..dd92766 100644 --- a/examples/dshot300/dshot300.ino +++ b/examples/dshot300/dshot300.ino @@ -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,23 +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(" "); - USB_SERIAL.println("Type 'help' to show Menu"); // 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(); } } @@ -114,32 +115,6 @@ void printMenu() 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); - } -} - // void handleSerialInput(const String &input, uint16_t &throttle, bool &continuous_throttle) { @@ -149,17 +124,16 @@ void handleSerialInput(const String &input, uint16_t &throttle, bool &continuous throttle = 0; continuous_throttle = true; // kill motor for sure 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 ")) { @@ -171,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 { @@ -193,7 +167,7 @@ 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)); + printDShotResult(result); } else { From 682b26b5440a39d6c2de0999b5472e1070a1e0e4 Mon Sep 17 00:00:00 2001 From: Wastl Kraus Date: Sun, 7 Sep 2025 14:45:11 +0200 Subject: [PATCH 3/8] ...testing performance updates --- DShotRMT.cpp | 31 +++++++++++++++++++++++++------ DShotRMT.h | 47 ++++++++++++++++++++++++++--------------------- 2 files changed, 51 insertions(+), 27 deletions(-) diff --git a/DShotRMT.cpp b/DShotRMT.cpp index d3dc31a..e4c369a 100644 --- a/DShotRMT.cpp +++ b/DShotRMT.cpp @@ -152,6 +152,9 @@ dshot_result_t DShotRMT::begin() return result; } + // Bit positions precalculation + _preCalculateBitPositions(); + result.success = true; result.msg = INIT_SUCCESS; @@ -331,7 +334,7 @@ dshot_result_t DShotRMT::sendCommand(uint16_t command) dshot_telemetry_result_t DShotRMT::getTelemetry(uint16_t magnet_count) { // Result container - dshot_telemetry_result_t result = {false, 0, 0, TELEMETRY_FAILED}; + dshot_telemetry_result_t result = {false, NO_DSHOT_ERPM, NO_DSHOT_RPM, TELEMETRY_FAILED}; // Check if bidirectional mode is enabled if (!_is_bidirectional) @@ -349,6 +352,12 @@ dshot_telemetry_result_t DShotRMT::getTelemetry(uint16_t magnet_count) } // Calculate motor RPM + if (magnet_count < 1) + { + result.msg = INVALID_MAGNET_COUNT; + return result; + } + uint8_t pole_pairs = max(1, (magnet_count / 2)); uint32_t motor_rpm = (erpm / pole_pairs); @@ -440,6 +449,14 @@ uint16_t DShotRMT::_calculateCRC(const uint16_t data) 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) { @@ -516,12 +533,14 @@ 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; + const uint16_t level1 = _is_bidirectional ? 1 : 0; + + // Decode MSB + for (int i = 0; i < DSHOT_BITS_PER_FRAME; ++i) { + // Use precalculated bit positions - Performace optimized + int bit_position = _bitPositions[i]; - for (int i = 0; i < DSHOT_BITS_PER_FRAME; i++) - { - // Decode MSB - bool bit = (_parsed_packet >> (DSHOT_BITS_PER_FRAME - 1 - i)) & 0b0000000000000001; + 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; diff --git a/DShotRMT.h b/DShotRMT.h index fd81846..b224d9a 100644 --- a/DShotRMT.h +++ b/DShotRMT.h @@ -19,11 +19,13 @@ 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_SWITCH_TIME = 30; // Time in us - add some padding to RX - TX switching +static constexpr auto DSHOT_SWITCH_TIME = 30; // Additional time in us for bidir switching static constexpr auto DSHOT_NULL_PACKET = 0b0000000000000000; 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 NO_DSHOT_ERPM = 0; +static constexpr auto NO_DSHOT_RPM = 0; // RMT Configuration Constants constexpr auto DSHOT_CLOCK_SRC_DEFAULT = RMT_CLK_SRC_DEFAULT; @@ -152,10 +154,11 @@ private: uint32_t _frame_timer_us; const dshot_timing_t &_timing_config; - // --- TIMING & STATE VARIABLES --- + // --- TIMING & PACKET VARIABLES --- uint64_t _last_transmission_time; uint16_t _parsed_packet; dshot_packet_t _packet; + uint8_t _bitPositions[DSHOT_BITS_PER_FRAME]; // --- STATISTICS --- uint32_t _total_transmissions; @@ -181,6 +184,7 @@ private: 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); @@ -206,23 +210,24 @@ private: static constexpr bool DSHOT_OK = 0; static constexpr bool DSHOT_ERROR = 1; - static constexpr char *NONE = ""; - static constexpr char *UNKNOWN_ERROR = "Unknown Error!"; - static constexpr char *INIT_SUCCESS = "SignalGeneratorRMT initialized successfully"; - static constexpr char *INIT_FAILED = "SignalGeneratorRMT init failed!"; - static constexpr char *TX_INIT_SUCCESS = "TX RMT channel initialized successfully"; - static constexpr char *TX_INIT_FAILED = "TX RMT channel init failed!"; - static constexpr char *RX_INIT_SUCCESS = "RX RMT channel initialized successfully"; - static constexpr char *RX_INIT_FAILED = "RX RMT channel init failed!"; - static constexpr char *RX_BUFFER_FAILED = "RX RMT buffer init failed!"; - static constexpr char *ENCODER_INIT_SUCCESS = "RMT encoder initialized successfully"; - static constexpr char *ENCODER_INIT_FAILED = "RMT encoder init failed!"; - static constexpr char *TRANSMISSION_SUCCESS = "Transmission successfully"; - static constexpr char *TRANSMISSION_FAILED = "Transmission failed!"; - static constexpr char *RECEIVER_FAILED = "RMT receiver failed!"; - static constexpr char *THROTTLE_NOT_IN_RANGE = "Throttle not in range! (48 - 2047)"; - static constexpr char *COMMAND_NOT_VALID = "Command not valid! (0 - 47)"; - static constexpr char *BIDIR_NOT_ENABLED = "Bidirectional DShot not enabled!"; - static constexpr char *TELEMETRY_SUCCESS = "Valid Telemetric Frame received!"; - static constexpr char *TELEMETRY_FAILED = "No valid Telemetric Frame received!"; + 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!"; }; From 7c87661c0a3643c252fa3f1b6f25544435d84828 Mon Sep 17 00:00:00 2001 From: Wastl Kraus Date: Sun, 7 Sep 2025 15:19:52 +0200 Subject: [PATCH 4/8] ...prepare release --- .github/workflows/ci.yml | 27 ++-- DShotRMT.cpp | 12 +- DShotRMT.h | 6 +- README.md | 334 ++++++++++++++++++++------------------- library.properties | 2 +- 5 files changed, 192 insertions(+), 189 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 76f1123..2cfda7b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -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 diff --git a/DShotRMT.cpp b/DShotRMT.cpp index e4c369a..534bd33 100644 --- a/DShotRMT.cpp +++ b/DShotRMT.cpp @@ -58,6 +58,9 @@ DShotRMT::DShotRMT(gpio_num_t gpio, dshot_mode_t mode, bool is_bidirectional) _last_transmission_time(0), _parsed_packet(0), _packet{0}, + _bitPositions{0}, + _level0(_is_bidirectional ? 0 : 1), + _level1(_is_bidirectional ? 1 : 0), _rmt_tx_channel(nullptr), _rmt_rx_channel(nullptr), _dshot_encoder(nullptr), @@ -531,19 +534,16 @@ dshot_result_t DShotRMT::_sendDShotFrame(const dshot_packet_t &packet) bool IRAM_ATTR DShotRMT::_encodeDShotFrame(const dshot_packet_t &packet, rmt_symbol_word_t *symbols) { _parsed_packet = _parseDShotPacket(packet); - - const uint16_t level0 = _is_bidirectional ? 0 : 1; - const uint16_t level1 = _is_bidirectional ? 1 : 0; // Decode MSB 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; + 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; } diff --git a/DShotRMT.h b/DShotRMT.h index b224d9a..c43b230 100644 --- a/DShotRMT.h +++ b/DShotRMT.h @@ -159,10 +159,8 @@ private: uint16_t _parsed_packet; dshot_packet_t _packet; uint8_t _bitPositions[DSHOT_BITS_PER_FRAME]; - - // --- STATISTICS --- - uint32_t _total_transmissions; - uint32_t _failed_transmissions; + uint16_t _level0; + uint16_t _level1; // --- RMT HARDWARE HANDLES --- rmt_channel_handle_t _rmt_tx_channel; diff --git a/README.md b/README.md index c7c716a..8c3a75c 100644 --- a/README.md +++ b/README.md @@ -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 -// 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 -#include -// 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 + 1 - Stop Motor + 2 - Activate Beacon 1 + 3 - Set Normal Spin Direction + 4 - Set Reversed Spin Direction + 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 - Send DShot command (0 - 47) - throttle - Set throttle (48 - 2047) - throttle 0 - Stop sending throttle +Advanced Commands: + cmd - Send DShot command (0-47) + throttle - Set throttle (48-2047) + repeat cmd 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) diff --git a/library.properties b/library.properties index 0f27f35..19b7cda 100644 --- a/library.properties +++ b/library.properties @@ -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. From 44e5a5cffe6a199ed24158f182d0b65e905c43d5 Mon Sep 17 00:00:00 2001 From: Wastl Kraus Date: Sun, 7 Sep 2025 23:21:23 +0200 Subject: [PATCH 5/8] ...update callback ...performance optimized by direct rmt symbol processing - no queue --- DShotRMT.cpp | 148 ++++++++++++++++++--------------------------------- DShotRMT.h | 11 ++-- 2 files changed, 60 insertions(+), 99 deletions(-) diff --git a/DShotRMT.cpp b/DShotRMT.cpp index 534bd33..81916b0 100644 --- a/DShotRMT.cpp +++ b/DShotRMT.cpp @@ -53,8 +53,11 @@ 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), _parsed_packet(0), _packet{0}, @@ -67,8 +70,7 @@ DShotRMT::DShotRMT(gpio_num_t gpio, dshot_mode_t mode, bool is_bidirectional) _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; @@ -116,13 +118,6 @@ DShotRMT::~DShotRMT() rmt_del_encoder(_dshot_encoder); _dshot_encoder = nullptr; } - - // ...Buffer - if (_rx_queue) - { - vQueueDelete(_rx_queue); - _rx_queue = nullptr; - } } // Init DShotRMT @@ -188,7 +183,7 @@ dshot_result_t DShotRMT::_initTXChannel() } // - if (rmt_enable(_rmt_tx_channel) != 0) + if (rmt_enable(_rmt_tx_channel) != DSHOT_OK) { return result; } @@ -205,13 +200,8 @@ dshot_result_t DShotRMT::_initRXChannel() // Result container dshot_result_t result = {false, RX_INIT_FAILED}; - // Create a queue for RX callback data - _rx_queue = xQueueCreate(RMT_QUEUE_DEPTH, sizeof(rmt_rx_done_event_data_t)); - if (_rx_queue == nullptr) - { - result.msg = RX_BUFFER_FAILED; - return result; - } + // 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; @@ -229,17 +219,8 @@ dshot_result_t DShotRMT::_initRXChannel() return result; } - // 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) - { - result.msg = RX_BUFFER_FAILED; - return result; - } - // - if (rmt_enable(_rmt_rx_channel) != 0) + if (rmt_enable(_rmt_rx_channel) != DSHOT_OK) { return result; } @@ -253,14 +234,25 @@ dshot_result_t DShotRMT::_initRXChannel() // 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_buffer = (QueueHandle_t)user_data; - BaseType_t xHigherPriorityTaskWoken = pdFALSE; + DShotRMT *instance = static_cast(user_data); - // Copy callback data into RX buffer - xQueueGenericSendFromISR(rx_buffer, edata, &xHigherPriorityTaskWoken, queueSEND_TO_BACK); + // Minimale ISR-Verarbeitung: Nur bei gültigen Daten + if (edata && edata->num_symbols >= GCR_BITS_PER_FRAME && + edata->num_symbols <= GCR_BITS_PER_FRAME) + { - return (xHigherPriorityTaskWoken == pdTRUE); + // Direkte Dekodierung in der ISR (schnell!) + uint16_t erpm = instance->_decodeDShotFrame(edata->received_symbols); + + if (erpm != DSHOT_NULL_PACKET) + { + // Atomic writes - thread-safe ohne Mutex + instance->_last_erpm_atomic = erpm; + instance->_telemetry_ready_flag = true; + } + } + + return false; } // Initialize DShot encoder @@ -290,8 +282,6 @@ dshot_result_t DShotRMT::sendThrottle(uint16_t throttle) // Result container dshot_result_t result = {false, UNKNOWN_ERROR}; - static uint16_t last_throttle = DSHOT_CMD_MOTOR_STOP; - // Special case: if throttle is 0, use sendCommand() instead if (throttle == 0) { @@ -299,13 +289,13 @@ dshot_result_t DShotRMT::sendThrottle(uint16_t throttle) } // 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) + if ((throttle < DSHOT_THROTTLE_MIN || throttle > DSHOT_THROTTLE_MAX) && throttle != _last_throttle) { result.msg = 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); @@ -346,60 +336,25 @@ dshot_telemetry_result_t DShotRMT::getTelemetry(uint16_t magnet_count) return result; } - // Get eRPM - uint16_t erpm = _getERPM(); - - if (erpm == DSHOT_NULL_PACKET) + if (_telemetry_ready_flag) { - return result; - } + _telemetry_ready_flag = false; - // Calculate motor RPM - if (magnet_count < 1) - { - result.msg = INVALID_MAGNET_COUNT; - return result; - } - - uint8_t pole_pairs = max(1, (magnet_count / 2)); - uint32_t motor_rpm = (erpm / pole_pairs); + uint16_t erpm = _last_erpm_atomic; - result.success = true; - result.erpm = erpm; - result.motor_rpm = motor_rpm; - result.msg = TELEMETRY_SUCCESS; - - return result; -} - -// Get RPM from ESC (bidirectional mode only) - backward compatibility -uint16_t DShotRMT::_getERPM() -{ - static uint16_t last_erpm = 0; - - // Result container - dshot_telemetry_result_t result = {false, 0, 0, TELEMETRY_FAILED}; - - // Check if bidirectional mode is enabled - if (!_is_bidirectional) - { - 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) + if (erpm != DSHOT_NULL_PACKET && magnet_count >= 1) { - last_erpm = _decodeDShotFrame(rx_data.received_symbols); + 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.msg = TELEMETRY_SUCCESS; } } - return last_erpm; + return result; } // Build a complete DShot packet @@ -455,9 +410,10 @@ uint16_t DShotRMT::_calculateCRC(const uint16_t data) // Per calculate bits - Performance optimized void DShotRMT::_preCalculateBitPositions() { - for (int i = 0; i < DSHOT_BITS_PER_FRAME; ++i) { + for (int i = 0; i < DSHOT_BITS_PER_FRAME; ++i) + { _bitPositions[i] = DSHOT_BITS_PER_FRAME - 1 - i; - } + } } // Transmit DShot packet via RMT @@ -468,6 +424,7 @@ dshot_result_t DShotRMT::_sendDShotFrame(const dshot_packet_t &packet) // Check timing requirements if (!_timer_signal()) { + result.msg = TIMING_CORRECTION; return result; } @@ -534,13 +491,14 @@ dshot_result_t DShotRMT::_sendDShotFrame(const dshot_packet_t &packet) bool IRAM_ATTR DShotRMT::_encodeDShotFrame(const dshot_packet_t &packet, rmt_symbol_word_t *symbols) { _parsed_packet = _parseDShotPacket(packet); - + // Decode MSB - for (int i = 0; i < DSHOT_BITS_PER_FRAME; ++i) { + 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; + 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; @@ -622,11 +580,11 @@ void DShotRMT::printDShotInfo(Stream &output) const output.println(" === DShot Signal Info === "); // Current DShot mode - output.printf("Current Mode: DSHOT%d\n", - _mode == DSHOT150 ? 150 : - _mode == DSHOT300 ? 300 : - _mode == DSHOT600 ? 600 : - _mode == DSHOT1200 ? 1200 : 0); + output.printf("Current Mode: DSHOT%d\n", + _mode == DSHOT150 ? 150 : + _mode == DSHOT300 ? 300 : + _mode == DSHOT600 ? 600 : + _mode == DSHOT1200 ? 1200 : 0); output.printf("Bidirectional: %s\n", _is_bidirectional ? "YES" : "NO"); @@ -637,7 +595,7 @@ void DShotRMT::printDShotInfo(Stream &output) const 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) { diff --git a/DShotRMT.h b/DShotRMT.h index c43b230..0443597 100644 --- a/DShotRMT.h +++ b/DShotRMT.h @@ -24,6 +24,8 @@ static constexpr auto DSHOT_NULL_PACKET = 0b0000000000000000; 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; @@ -153,6 +155,7 @@ private: bool _is_bidirectional; uint32_t _frame_timer_us; const dshot_timing_t &_timing_config; + uint16_t _last_throttle; // --- TIMING & PACKET VARIABLES --- uint64_t _last_transmission_time; @@ -189,16 +192,15 @@ private: bool IRAM_ATTR _encodeDShotFrame(const dshot_packet_t &packet, rmt_symbol_word_t *symbols); uint16_t _decodeDShotFrame(const rmt_symbol_word_t *symbols); - // --- HELPERS --- - uint16_t _getERPM(); - // --- TIMING CONTROL --- bool IRAM_ATTR _timer_signal(); 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 --- @@ -228,4 +230,5 @@ private: 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!"; }; From 16ab6a6ef2efe75554c1d5b9082963381bc3f558 Mon Sep 17 00:00:00 2001 From: Wastl Kraus Date: Mon, 8 Sep 2025 09:05:54 +0200 Subject: [PATCH 6/8] ...readability adaptions --- DShotRMT.cpp | 107 +++++++++++++++------------------ DShotRMT.h | 5 +- examples/dshot300/dshot300.ino | 4 +- 3 files changed, 54 insertions(+), 62 deletions(-) diff --git a/DShotRMT.cpp b/DShotRMT.cpp index 81916b0..d164016 100644 --- a/DShotRMT.cpp +++ b/DShotRMT.cpp @@ -18,36 +18,6 @@ static constexpr dshot_timing_t DSHOT_TIMINGS[] = { {16, 8, 6, 2, 3, 5} // DSHOT1200 }; -// --- HELPERS --- -void printDShotResult(dshot_result_t &result, Stream &output) -{ - if (result.success) - { - output.printf("Staus: SUCCESS - %s\n", result.msg); - } - else - { - output.printf("Status: FAILED - %s\n", 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(" "); -} - // Constructor with GPIO number DShotRMT::DShotRMT(gpio_num_t gpio, dshot_mode_t mode, bool is_bidirectional) : _gpio(gpio), @@ -70,7 +40,8 @@ DShotRMT::DShotRMT(gpio_num_t gpio, dshot_mode_t mode, bool is_bidirectional) _tx_channel_config{}, _rx_channel_config{}, _transmit_config{}, - _receive_config{} + _receive_config{}, + _result{false, UNKNOWN_ERROR} { // Calculate frame timing including switch/pause time _frame_timer_us = _timing_config.frame_length_us + DSHOT_SWITCH_TIME; @@ -123,40 +94,37 @@ DShotRMT::~DShotRMT() // Init DShotRMT dshot_result_t DShotRMT::begin() { - // Result container - dshot_result_t result = {false, INIT_FAILED}; - // Init RX channel first if (_is_bidirectional) { if (!_initRXChannel().success) { - result.msg = RX_INIT_FAILED; - return result; + _result.msg = RX_INIT_FAILED; + return _result; } } // Init TX channel if (!_initTXChannel().success) { - result.msg = TX_INIT_FAILED; - return result; + _result.msg = TX_INIT_FAILED; + return _result; } // Init DShot encoder if (!_initDShotEncoder().success) { - result.msg = ENCODER_INIT_FAILED; - return result; + _result.msg = ENCODER_INIT_FAILED; + return _result; } // Bit positions precalculation _preCalculateBitPositions(); - result.success = true; - result.msg = INIT_SUCCESS; + _result.success = true; + _result.msg = INIT_SUCCESS; - return result; + return _result; } // Init RMT TX channel @@ -258,30 +226,24 @@ bool IRAM_ATTR DShotRMT::_rmt_rx_done_callback(rmt_channel_handle_t rmt_rx_chann // Initialize DShot encoder dshot_result_t DShotRMT::_initDShotEncoder() { - // Result container - dshot_result_t result = {false, ENCODER_INIT_FAILED}; - // Create copy encoder configuration rmt_copy_encoder_config_t encoder_config = {}; // Create encoder instance if (rmt_new_copy_encoder(&encoder_config, &_dshot_encoder) != DSHOT_OK) { - return result; + return _result; } - result.success = true; - result.msg = ENCODER_INIT_SUCCESS; + _result.success = true; + _result.msg = ENCODER_INIT_SUCCESS; - return result; + return _result; } // Send throttle value dshot_result_t DShotRMT::sendThrottle(uint16_t throttle) { - // Result container - dshot_result_t result = {false, UNKNOWN_ERROR}; - // Special case: if throttle is 0, use sendCommand() instead if (throttle == 0) { @@ -291,7 +253,7 @@ dshot_result_t DShotRMT::sendThrottle(uint16_t throttle) // 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) { - result.msg = THROTTLE_NOT_IN_RANGE; + _result.msg = THROTTLE_NOT_IN_RANGE; } // Always store the original throttle value @@ -307,14 +269,11 @@ dshot_result_t DShotRMT::sendThrottle(uint16_t throttle) // Send DShot command to ESC dshot_result_t DShotRMT::sendCommand(uint16_t command) { - // Result container - dshot_result_t result = {false, UNKNOWN_ERROR}; - // Validate command is within DShot specification range if (command < DSHOT_CMD_MOTOR_STOP || command > DSHOT_CMD_MAX) { - result.msg = COMMAND_NOT_VALID; - return result; + _result.msg = COMMAND_NOT_VALID; + return _result; } // Build packet and transmit @@ -622,3 +581,33 @@ 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) +{ + if (result.success) + { + output.printf("Staus: SUCCESS - %s\n", result.msg); + } + else + { + output.printf("Status: FAILED - %s\n", 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(" "); +} diff --git a/DShotRMT.h b/DShotRMT.h index 0443597..7c14853 100644 --- a/DShotRMT.h +++ b/DShotRMT.h @@ -203,8 +203,11 @@ private: 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 RESULT HANDLER --- + dshot_result_t _result; + // --- DSHOT DEFAULTS --- - static constexpr auto DSHOT_TELEMETRY_INVALID = (0xffff); + static constexpr auto const DSHOT_TELEMETRY_INVALID = (0xffff); // --- CONSTANTS & ERROR MESSAGES --- static constexpr bool DSHOT_OK = 0; diff --git a/examples/dshot300/dshot300.ino b/examples/dshot300/dshot300.ino index dd92766..8e9406a 100644 --- a/examples/dshot300/dshot300.ino +++ b/examples/dshot300/dshot300.ino @@ -122,7 +122,7 @@ 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); printDShotResult(result); } @@ -171,7 +171,7 @@ void handleSerialInput(const String &input, uint16_t &throttle, bool &continuous } else { - USB_SERIAL.printf("Invalid input: '%s'\n", input.c_str()); + USB_SERIAL.printf("Invalid input: '%s'\n", input); USB_SERIAL.printf("Valid throttle range: %d - %d\n", DSHOT_THROTTLE_MIN, DSHOT_THROTTLE_MAX); } } From 0e77a050393205dc02729b5511341d33d8ffa459 Mon Sep 17 00:00:00 2001 From: Wastl Kraus Date: Mon, 8 Sep 2025 14:40:32 +0200 Subject: [PATCH 7/8] ...update result --- DShotRMT.cpp | 70 ++++++++++++++++++++++++++-------------------------- DShotRMT.h | 6 +++-- 2 files changed, 39 insertions(+), 37 deletions(-) diff --git a/DShotRMT.cpp b/DShotRMT.cpp index d164016..8abc326 100644 --- a/DShotRMT.cpp +++ b/DShotRMT.cpp @@ -44,7 +44,7 @@ DShotRMT::DShotRMT(gpio_num_t gpio, dshot_mode_t mode, bool is_bidirectional) _result{false, UNKNOWN_ERROR} { // 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) @@ -130,8 +130,7 @@ dshot_result_t DShotRMT::begin() // Init RMT TX channel dshot_result_t DShotRMT::_initTXChannel() { - // Result container - dshot_result_t result = {false, TX_INIT_FAILED}; + _result = {false, TX_INIT_FAILED}; // Configure TX channel _tx_channel_config.gpio_num = _gpio; @@ -147,26 +146,25 @@ dshot_result_t DShotRMT::_initTXChannel() // Create RMT TX channel if (rmt_new_tx_channel(&_tx_channel_config, &_rmt_tx_channel) != DSHOT_OK) { - return result; + return _result; } // if (rmt_enable(_rmt_tx_channel) != DSHOT_OK) { - return result; + return _result; } - result.success = true; - result.msg = TX_INIT_SUCCESS; + _result.success = true; + _result.msg = TX_INIT_SUCCESS; - return result; + return _result; } // Init RMT RX channel dshot_result_t DShotRMT::_initRXChannel() { - // Result container - dshot_result_t result = {false, RX_INIT_FAILED}; + _result = {false, RX_INIT_FAILED}; // Direct RMT symbol processing - Performance optimized _rx_event_callbacks.on_recv_done = _rmt_rx_done_callback; @@ -184,19 +182,19 @@ dshot_result_t DShotRMT::_initRXChannel() // Create RMT RX channel if (rmt_new_rx_channel(&_rx_channel_config, &_rmt_rx_channel) != DSHOT_OK) { - return result; + return _result; } // if (rmt_enable(_rmt_rx_channel) != DSHOT_OK) { - return result; + return _result; } - result.success = true; - result.msg = RX_INIT_SUCCESS; + _result.success = true; + _result.msg = RX_INIT_SUCCESS; - return result; + return _result; } // Callback for RMT RX @@ -226,6 +224,8 @@ bool IRAM_ATTR DShotRMT::_rmt_rx_done_callback(rmt_channel_handle_t rmt_rx_chann // Initialize DShot encoder dshot_result_t DShotRMT::_initDShotEncoder() { + _result = {false, ENCODER_INIT_FAILED}; + // Create copy encoder configuration rmt_copy_encoder_config_t encoder_config = {}; @@ -355,12 +355,12 @@ 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; @@ -378,13 +378,13 @@ void DShotRMT::_preCalculateBitPositions() // Transmit DShot packet via RMT dshot_result_t DShotRMT::_sendDShotFrame(const dshot_packet_t &packet) { - dshot_result_t result = {false, UNKNOWN_ERROR}; - + _result = {false, UNKNOWN_ERROR}; + // Check timing requirements if (!_timer_signal()) { - result.msg = TIMING_CORRECTION; - return result; + _result.msg = TIMING_CORRECTION; + return _result; } // Enable RMT RX before RMT TX @@ -395,8 +395,8 @@ dshot_result_t DShotRMT::_sendDShotFrame(const dshot_packet_t &packet) if (rmt_receive(_rmt_rx_channel, rx_symbols, sizeof(rx_symbols), &_receive_config) != DSHOT_OK) { - result.msg = RECEIVER_FAILED; - return result; + _result.msg = RECEIVER_FAILED; + return _result; } } @@ -415,16 +415,16 @@ dshot_result_t DShotRMT::_sendDShotFrame(const dshot_packet_t &packet) // Disable RMT RX for sending if (rmt_disable(_rmt_rx_channel) != DSHOT_OK) { - result.msg = RECEIVER_FAILED; - return result; + _result.msg = RECEIVER_FAILED; + return _result; } } // Perform RMT transmission if (rmt_transmit(_rmt_tx_channel, _dshot_encoder, tx_symbols, tx_size_bytes, &_transmit_config) != DSHOT_OK) { - result.msg = TRANSMISSION_FAILED; - return result; + _result.msg = TRANSMISSION_FAILED; + return _result; } // Re-enable RMT RX @@ -432,18 +432,18 @@ dshot_result_t DShotRMT::_sendDShotFrame(const dshot_packet_t &packet) { if (rmt_enable(_rmt_rx_channel) != DSHOT_OK) { - result.msg = RECEIVER_FAILED; - return result; + _result.msg = RECEIVER_FAILED; + return _result; } } // Update timestamp and calculate execution time _timer_reset(); - result.success = true; - result.msg = TRANSMISSION_SUCCESS; + _result.success = true; + _result.msg = TRANSMISSION_SUCCESS; - return result; + return _result; } // Encode DShot packet into RMT symbol format (placed in IRAM for performance) @@ -486,13 +486,13 @@ 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. if (!(received_data & (1 << 11))) @@ -511,7 +511,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 diff --git a/DShotRMT.h b/DShotRMT.h index 7c14853..81651d3 100644 --- a/DShotRMT.h +++ b/DShotRMT.h @@ -19,8 +19,10 @@ 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_SWITCH_TIME = 30; // Additional time in us for bidir switching +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; @@ -30,7 +32,7 @@ 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_CLOCK_SRC_DEFAULT = RMT_CLK_SRC_APB; constexpr auto DSHOT_RMT_RESOLUTION = 10 * 1000 * 1000; // 10 MHz resolution constexpr auto RMT_BUFFER_SIZE = DSHOT_BITS_PER_FRAME; constexpr auto RMT_BUFFER_SYMBOLS = 64; From eaab163836ed4517c67caa812e128fe98228bb4a Mon Sep 17 00:00:00 2001 From: Wastl Kraus Date: Mon, 8 Sep 2025 21:10:15 +0200 Subject: [PATCH 8/8] ...simplification ...simplification: error handling --- DShotRMT.cpp | 113 ++++++++++----------------------- DShotRMT.h | 5 +- examples/dshot300/dshot300.ino | 1 + 3 files changed, 37 insertions(+), 82 deletions(-) diff --git a/DShotRMT.cpp b/DShotRMT.cpp index 8abc326..edcbd13 100644 --- a/DShotRMT.cpp +++ b/DShotRMT.cpp @@ -40,8 +40,7 @@ DShotRMT::DShotRMT(gpio_num_t gpio, dshot_mode_t mode, bool is_bidirectional) _tx_channel_config{}, _rx_channel_config{}, _transmit_config{}, - _receive_config{}, - _result{false, UNKNOWN_ERROR} + _receive_config{} { // Calculate frame timing including switch/pause time _frame_timer_us = _timing_config.frame_length_us + DSHOT_PAUSE_US; @@ -99,39 +98,31 @@ dshot_result_t DShotRMT::begin() { if (!_initRXChannel().success) { - _result.msg = RX_INIT_FAILED; - return _result; + return {false, RX_INIT_FAILED}; } } // Init TX channel if (!_initTXChannel().success) { - _result.msg = TX_INIT_FAILED; - return _result; + return {false, TX_INIT_FAILED}; } // Init DShot encoder if (!_initDShotEncoder().success) { - _result.msg = ENCODER_INIT_FAILED; - return _result; + return {false, ENCODER_INIT_FAILED}; } // Bit positions precalculation _preCalculateBitPositions(); - _result.success = true; - _result.msg = INIT_SUCCESS; - - return _result; + return {true, INIT_SUCCESS}; } // Init RMT TX channel dshot_result_t DShotRMT::_initTXChannel() { - _result = {false, TX_INIT_FAILED}; - // Configure TX channel _tx_channel_config.gpio_num = _gpio; _tx_channel_config.clk_src = DSHOT_CLOCK_SRC_DEFAULT; @@ -146,25 +137,21 @@ dshot_result_t DShotRMT::_initTXChannel() // Create RMT TX channel if (rmt_new_tx_channel(&_tx_channel_config, &_rmt_tx_channel) != DSHOT_OK) { - return _result; + return {false, TX_INIT_FAILED}; } // if (rmt_enable(_rmt_tx_channel) != DSHOT_OK) { - return _result; + return {false, TX_INIT_FAILED}; } - _result.success = true; - _result.msg = TX_INIT_SUCCESS; - - return _result; + return {true, TX_INIT_SUCCESS}; } // Init RMT RX channel dshot_result_t DShotRMT::_initRXChannel() { - _result = {false, RX_INIT_FAILED}; // Direct RMT symbol processing - Performance optimized _rx_event_callbacks.on_recv_done = _rmt_rx_done_callback; @@ -182,19 +169,16 @@ dshot_result_t DShotRMT::_initRXChannel() // Create RMT RX channel if (rmt_new_rx_channel(&_rx_channel_config, &_rmt_rx_channel) != DSHOT_OK) { - return _result; + return {false, RX_INIT_FAILED}; } // if (rmt_enable(_rmt_rx_channel) != DSHOT_OK) { - return _result; + return {false, RX_INIT_FAILED}; } - _result.success = true; - _result.msg = RX_INIT_SUCCESS; - - return _result; + return {true, RX_INIT_SUCCESS}; } // Callback for RMT RX @@ -202,17 +186,16 @@ bool IRAM_ATTR DShotRMT::_rmt_rx_done_callback(rmt_channel_handle_t rmt_rx_chann { DShotRMT *instance = static_cast(user_data); - // Minimale ISR-Verarbeitung: Nur bei gültigen Daten - if (edata && edata->num_symbols >= GCR_BITS_PER_FRAME && - edata->num_symbols <= GCR_BITS_PER_FRAME) + // ISR check for valid data + if (edata && edata->num_symbols >= GCR_BITS_PER_FRAME && edata->num_symbols <= GCR_BITS_PER_FRAME) { - // Direkte Dekodierung in der ISR (schnell!) + // Direct decoding uint16_t erpm = instance->_decodeDShotFrame(edata->received_symbols); if (erpm != DSHOT_NULL_PACKET) { - // Atomic writes - thread-safe ohne Mutex + // Atomic writes - thread-safe instance->_last_erpm_atomic = erpm; instance->_telemetry_ready_flag = true; } @@ -224,21 +207,16 @@ bool IRAM_ATTR DShotRMT::_rmt_rx_done_callback(rmt_channel_handle_t rmt_rx_chann // Initialize DShot encoder dshot_result_t DShotRMT::_initDShotEncoder() { - _result = {false, ENCODER_INIT_FAILED}; - // Create copy encoder configuration rmt_copy_encoder_config_t encoder_config = {}; // Create encoder instance if (rmt_new_copy_encoder(&encoder_config, &_dshot_encoder) != DSHOT_OK) { - return _result; + return {false, ENCODER_INIT_FAILED}; } - _result.success = true; - _result.msg = ENCODER_INIT_SUCCESS; - - return _result; + return {true, TX_INIT_SUCCESS}; } // Send throttle value @@ -250,17 +228,12 @@ dshot_result_t DShotRMT::sendThrottle(uint16_t throttle) 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) - { - _result.msg = THROTTLE_NOT_IN_RANGE; - } - // Always store the original throttle value _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); @@ -272,8 +245,7 @@ dshot_result_t DShotRMT::sendCommand(uint16_t command) // Validate command is within DShot specification range if (command < DSHOT_CMD_MOTOR_STOP || command > DSHOT_CMD_MAX) { - _result.msg = COMMAND_NOT_VALID; - return _result; + return {false, COMMAND_NOT_VALID}; } // Build packet and transmit @@ -295,12 +267,14 @@ dshot_telemetry_result_t DShotRMT::getTelemetry(uint16_t magnet_count) return result; } + // if (_telemetry_ready_flag) { _telemetry_ready_flag = false; 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)); @@ -378,25 +352,24 @@ void DShotRMT::_preCalculateBitPositions() // Transmit DShot packet via RMT dshot_result_t DShotRMT::_sendDShotFrame(const dshot_packet_t &packet) { - _result = {false, UNKNOWN_ERROR}; - // Check timing requirements if (!_timer_signal()) { - _result.msg = TIMING_CORRECTION; - 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.msg = RECEIVER_FAILED; - return _result; + return {false, RECEIVER_FAILED}; } } @@ -415,16 +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.msg = RECEIVER_FAILED; - 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.msg = TRANSMISSION_FAILED; - return _result; + return {false, TRANSMISSION_FAILED}; } // Re-enable RMT RX @@ -432,18 +403,14 @@ dshot_result_t DShotRMT::_sendDShotFrame(const dshot_packet_t &packet) { if (rmt_enable(_rmt_rx_channel) != DSHOT_OK) { - _result.msg = RECEIVER_FAILED; - return _result; + return {false, RECEIVER_FAILED}; } } // Update timestamp and calculate execution time _timer_reset(); - _result.success = true; - _result.msg = TRANSMISSION_SUCCESS; - - return _result; + return {true, TRANSMISSION_SUCCESS}; } // Encode DShot packet into RMT symbol format (placed in IRAM for performance) @@ -494,7 +461,7 @@ uint16_t DShotRMT::_decodeDShotFrame(const rmt_symbol_word_t *symbols) // Masking CRC 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; @@ -529,6 +496,7 @@ bool IRAM_ATTR DShotRMT::_timer_signal() bool DShotRMT::_timer_reset() { _last_transmission_time = esp_timer_get_time(); + return DSHOT_OK; } @@ -547,9 +515,6 @@ 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: "); @@ -585,15 +550,7 @@ void DShotRMT::printCpuInfo(Stream &output) const // --- HELPERS --- void printDShotResult(dshot_result_t &result, Stream &output) { - if (result.success) - { - output.printf("Staus: SUCCESS - %s\n", result.msg); - } - else - { - output.printf("Status: FAILED - %s\n", result.msg); - } - + output.printf("Status: %s - %s\n", result.success ? "SUCCESS" : "FAILED", result.msg); output.println(" "); } @@ -602,7 +559,7 @@ 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); + output.printf("Telemetry: eRPM=%u, Motor RPM=%u\n", result.erpm, result.motor_rpm); } else { diff --git a/DShotRMT.h b/DShotRMT.h index 81651d3..c3aabe0 100644 --- a/DShotRMT.h +++ b/DShotRMT.h @@ -32,7 +32,7 @@ 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_APB; +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 RMT_BUFFER_SYMBOLS = 64; @@ -205,9 +205,6 @@ private: 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 RESULT HANDLER --- - dshot_result_t _result; - // --- DSHOT DEFAULTS --- static constexpr auto const DSHOT_TELEMETRY_INVALID = (0xffff); diff --git a/examples/dshot300/dshot300.ino b/examples/dshot300/dshot300.ino index 8e9406a..4c7d8ff 100644 --- a/examples/dshot300/dshot300.ino +++ b/examples/dshot300/dshot300.ino @@ -171,6 +171,7 @@ void handleSerialInput(const String &input, uint16_t &throttle, bool &continuous } else { + 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); }