From d9d8dce251d8ae43d59f8049b3b85929050d9a7f Mon Sep 17 00:00:00 2001 From: Wastl Kraus Date: Wed, 3 Sep 2025 11:16:14 +0200 Subject: [PATCH] ...hotfix: memory consumption ...update callback ...performance optimizations ... fix memory consumption ...update timers ...clean up ...update GCR logic ...some more memory testing ...optimize encoder logic --- DShotRMT.cpp | 128 ++++++++++++++++++++++++++------------------------- DShotRMT.h | 37 +++++++-------- 2 files changed, 81 insertions(+), 84 deletions(-) diff --git a/DShotRMT.cpp b/DShotRMT.cpp index 8ea1493..94580d5 100644 --- a/DShotRMT.cpp +++ b/DShotRMT.cpp @@ -7,7 +7,6 @@ */ #include "DShotRMT.h" -#include // Timing parameters for each DShot mode // Format: {frame_length_us, ticks_per_bit, ticks_one_high, ticks_one_low, ticks_zero_high, ticks_zero_low} @@ -19,19 +18,24 @@ static constexpr dshot_timing_t DSHOT_TIMINGS[] = { {16, 8, 6, 2, 3, 5} // DSHOT1200 }; -// Primary constructor with GPIO number +// Constructor with GPIO number DShotRMT::DShotRMT(gpio_num_t gpio, dshot_mode_t mode, bool is_bidirectional) : _gpio(gpio), _mode(mode), _is_bidirectional(is_bidirectional), + _frame_timer_us(0), _timing_config(DSHOT_TIMINGS[mode]), - _rmt_tx_channel(nullptr), - _rmt_rx_channel(nullptr), - _dshot_encoder(nullptr), + _last_transmission_time(0), _last_erpm(0), _parsed_packet(0), _packet{0}, - _last_transmission_time(0), + _rmt_tx_channel(nullptr), + _rmt_rx_channel(nullptr), + _dshot_encoder(nullptr), + _tx_channel_config{}, + _rx_channel_config{}, + _transmit_config{}, + _receive_config{}, _rx_queue(nullptr) { // Calculate frame timing including switch/pause time @@ -84,14 +88,14 @@ DShotRMT::~DShotRMT() // Initialize DShotRMT uint16_t DShotRMT::begin() { - // Initialize TX channel + // Init TX channel if (!_initTXChannel()) { _dshot_log(TX_INIT_FAILED); return DSHOT_ERROR; } - // Initialize RX channel only if bidirectional mode is enabled + // Init RX channel if (_is_bidirectional) { if (!_initRXChannel()) @@ -101,7 +105,7 @@ uint16_t DShotRMT::begin() } } - // Initialize DShot encoder + // Init DShot encoder if (_initDShotEncoder() != DSHOT_OK) { _dshot_log(ENCODER_INIT_FAILED); @@ -111,7 +115,7 @@ uint16_t DShotRMT::begin() return DSHOT_OK; } -// Initialize RMT TX channel +// Init RMT TX channel bool DShotRMT::_initTXChannel() { // Configure TX channel @@ -121,7 +125,7 @@ bool DShotRMT::_initTXChannel() _tx_channel_config.mem_block_symbols = RMT_BUFFER_SYMBOLS; _tx_channel_config.trans_queue_depth = RMT_QUEUE_DEPTH; - // Configure transmission + // Config RMT TX _transmit_config.loop_count = 0; // No automatic loops - real-time calculation _transmit_config.flags.eot_level = _is_bidirectional ? 1 : 0; // Telemetric Bit used as bidir flag @@ -135,23 +139,23 @@ bool DShotRMT::_initTXChannel() return (rmt_enable(_rmt_tx_channel) == DSHOT_OK); } -// Initialize RMT RX channel +// Init RMT RX channel bool DShotRMT::_initRXChannel() { - // Create a queue to receive data from the RX callback + // 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; } - // Configure RX channel parameters + // Config RMT RX _rx_channel_config.gpio_num = _gpio; _rx_channel_config.clk_src = DSHOT_CLOCK_SRC_DEFAULT; _rx_channel_config.resolution_hz = DSHOT_RMT_RESOLUTION; _rx_channel_config.mem_block_symbols = RMT_BUFFER_SYMBOLS; - // Configure reception parameters + // Config RMT RX parameters _receive_config.signal_range_min_ns = DSHOT_PULSE_MIN; _receive_config.signal_range_max_ns = DSHOT_PULSE_MAX; @@ -162,9 +166,10 @@ bool DShotRMT::_initRXChannel() return DSHOT_ERROR; } - // Register callback for reception - _rx_event_cbs.on_recv_done = _rmt_rx_done_callback; - if (rmt_rx_register_event_callbacks(_rmt_rx_channel, &_rx_event_cbs, _rx_queue) != DSHOT_OK) + // 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) { _dshot_log(RX_INIT_FAILED); return DSHOT_ERROR; @@ -174,14 +179,14 @@ bool DShotRMT::_initRXChannel() } // Callback for RMT RX -bool IRAM_ATTR DShotRMT::_rmt_rx_done_callback(rmt_channel_handle_t rx_chan, const rmt_rx_done_event_data_t *edata, void *user_data) +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) { - // Get the queue handle + // Init RX buffer QueueHandle_t rx_queue = (QueueHandle_t)user_data; BaseType_t xHigherPriorityTaskWoken = pdFALSE; - // Send the event data to the queue - xQueueSendFromISR(rx_queue, edata, &xHigherPriorityTaskWoken); + // Copy callback data into RX buffer + xQueueGenericSendFromISR(rx_queue, edata, &xHigherPriorityTaskWoken, queueSEND_TO_BACK); return (xHigherPriorityTaskWoken == pdTRUE); } @@ -340,20 +345,26 @@ uint16_t DShotRMT::_sendDShotFrame(const dshot_packet_t &packet) // Enable RMT RX before RMT TX if (_is_bidirectional) { - rmt_receive(_rmt_rx_channel, _rx_symbols, sizeof(_rx_symbols), &_receive_config); + // Performance reasons + rmt_symbol_word_t rx_symbols[DSHOT_BITS_PER_FRAME]; + + rmt_receive(_rmt_rx_channel, rx_symbols, sizeof(rx_symbols), &_receive_config); // Disable RMT RX for sending rmt_disable(_rmt_rx_channel); } + // Local for performance + rmt_symbol_word_t tx_symbols[DSHOT_BITS_PER_FRAME]; + // Encode DShot packet into RMT symbols - _encodeDShotFrame(packet, _tx_symbols); + _encodeDShotFrame(packet, tx_symbols); // Calculate transmission data size size_t tx_size_bytes = DSHOT_BITS_PER_FRAME * sizeof(rmt_symbol_word_t); // Perform RMT transmission - uint16_t result = rmt_transmit(_rmt_tx_channel, _dshot_encoder, _tx_symbols, tx_size_bytes, &_transmit_config); + uint16_t result = rmt_transmit(_rmt_tx_channel, _dshot_encoder, tx_symbols, tx_size_bytes, &_transmit_config); if (result != DSHOT_OK) { @@ -377,31 +388,19 @@ uint16_t DShotRMT::_sendDShotFrame(const dshot_packet_t &packet) // Encode DShot packet into RMT symbol format (placed in IRAM for performance) bool IRAM_ATTR DShotRMT::_encodeDShotFrame(const dshot_packet_t &packet, rmt_symbol_word_t *symbols) { - // Parse packet to 16-bit format _parsed_packet = _parseDShotPacket(packet); - // Convert each bit to RMT symbol + const uint16_t level0 = _is_bidirectional ? 0 : 1; + const uint16_t level1 = _is_bidirectional ? 1 : 0; + for (int i = 0; i < DSHOT_BITS_PER_FRAME; i++) { - // Extract bit from packet + // Decode MSB bool bit = (_parsed_packet >> (DSHOT_BITS_PER_FRAME - 1 - i)) & 0b0000000000000001; - - if (_is_bidirectional) - { - // Bidirectional DShot uses inverted levels - Idle HIGH - symbols[i].level0 = 0; - symbols[i].duration0 = bit ? _timing_config.ticks_one_high : _timing_config.ticks_zero_high; - symbols[i].level1 = 1; - symbols[i].duration1 = bit ? _timing_config.ticks_one_low : _timing_config.ticks_zero_low; - } - else - { - // Standard DShot levels - Idle LOW - symbols[i].level0 = 1; - symbols[i].duration0 = bit ? _timing_config.ticks_one_high : _timing_config.ticks_zero_high; - symbols[i].level1 = 0; - symbols[i].duration1 = bit ? _timing_config.ticks_one_low : _timing_config.ticks_zero_low; - } + symbols[i].level0 = level0; + symbols[i].duration0 = bit ? _timing_config.ticks_one_high : _timing_config.ticks_zero_high; + symbols[i].level1 = level1; + symbols[i].duration1 = bit ? _timing_config.ticks_one_low : _timing_config.ticks_zero_low; } return DSHOT_OK; @@ -410,30 +409,33 @@ bool IRAM_ATTR DShotRMT::_encodeDShotFrame(const dshot_packet_t &packet, rmt_sym // Decode received RMT symbols uint16_t DShotRMT::_decodeDShotFrame(const rmt_symbol_word_t *symbols) { - // DShot answer is GCR encoded? + // DShot answer is GCR encoded. // GCR decoding: bit_N = gcr_bit_N ^ gcr_bit_(N-1) uint32_t raw_gcr_data = 0; - // Reconstruct the raw GCR frame from RMT symbols - for (size_t i = 0; i < DSHOT_BITS_PER_FRAME; ++i) + // Based on DShot bidirectional protocol, idle state is high, + // so the first duration is a low pulse. + // Bit 1: long low pulse, short high pulse + // Bit 0: short low pulse, long high pulse + for (size_t i = 0; i < GCR_BITS_PER_FRAME; ++i) { - // Based on DShot bidirectional protocol, idle state is high, so first duration is low pulse. - // Bit 1: long low pulse, short high pulse - // Bit 0: short low pulse, long high pulse + // Check which duration is longer to determine if it's a '1' bit bool bit_is_one = symbols[i].duration0 > symbols[i].duration1; raw_gcr_data = (raw_gcr_data << 1) | bit_is_one; } + // Extract the 10-bit data from the GCR frame + uint16_t gcr_data = (raw_gcr_data >> 5) & 0b0000001111111111; // Mask for 10 bits + // GCR decoding over the "throttle" bits - // GCR encoding rule is: bit_n = gcr_bit_n ^ gcr_bit_(n-1) - uint16_t gcr_data = (raw_gcr_data >> 5) & 0b000000001111111111; uint16_t received_data = gcr_data ^ (gcr_data >> 1); - // Extract CRC from gcr answer - uint16_t received_crc = raw_gcr_data & 0b0000000000001111; + // Extract CRC from gcr answer (4 bits) + uint16_t received_crc = raw_gcr_data & 0b0000000000001111; // Mask for 4 bits // Calculate expected CRC using the new, centralized function - uint16_t data_for_crc = (received_data << 1) | 1; // Telemetry request bit is always 1 for bidirectional + // Telemetry request bit is always 1 for bidirectional + uint16_t data_for_crc = (received_data << 1) | 1; uint16_t calculated_crc = _calculateCRC(data_for_crc); // Validate CRC @@ -450,10 +452,10 @@ uint16_t DShotRMT::_decodeDShotFrame(const rmt_symbol_word_t *symbols) // Check if enough time has passed for next transmission bool IRAM_ATTR DShotRMT::_timer_signal() { - uint32_t current_time = micros(); + uint64_t current_time = esp_timer_get_time(); // Handle potential overflow - uint32_t elapsed = current_time - _last_transmission_time; + uint64_t elapsed = current_time - _last_transmission_time; return elapsed >= _frame_timer_us; } @@ -461,7 +463,7 @@ bool IRAM_ATTR DShotRMT::_timer_signal() // Reset transmission timer to current time bool DShotRMT::_timer_reset() { - _last_transmission_time = micros(); + _last_transmission_time = esp_timer_get_time(); return DSHOT_OK; } @@ -473,10 +475,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 c2afabc..475f5e5 100644 --- a/DShotRMT.h +++ b/DShotRMT.h @@ -13,8 +13,6 @@ #include #include #include -#include -#include // DShot Protocol Constants constexpr auto DSHOT_THROTTLE_FAILSAFE = 0; @@ -29,6 +27,7 @@ constexpr auto DSHOT_RX_TIMEOUT_MS = 2; constexpr auto DSHOT_CLOCK_SRC_DEFAULT = RMT_CLK_SRC_DEFAULT; constexpr auto DSHOT_RMT_RESOLUTION = 10 * 1000 * 1000; // 10 MHz resolution constexpr auto RMT_BUFFER_SIZE = DSHOT_BITS_PER_FRAME; +constexpr auto GCR_BITS_PER_FRAME = 20; constexpr auto RMT_BUFFER_SYMBOLS = 64; constexpr auto RMT_QUEUE_DEPTH = 1; @@ -70,7 +69,7 @@ typedef struct class DShotRMT { public: - // Primary constructor with GPIO enum + // Constructor with GPIO enum explicit DShotRMT(gpio_num_t gpio = GPIO_NUM_16, dshot_mode_t mode = DSHOT300, bool is_bidirectional = false); @@ -121,7 +120,7 @@ private: const dshot_timing_t &_timing_config; // --- TIMING & STATE VARIABLES --- - uint32_t _last_transmission_time; + uint64_t _last_transmission_time; uint16_t _last_erpm; uint16_t _parsed_packet; dshot_packet_t _packet; @@ -137,10 +136,6 @@ private: rmt_transmit_config_t _transmit_config; rmt_receive_config_t _receive_config; - // --- RMT DATA BUFFERS --- - rmt_symbol_word_t _tx_symbols[RMT_BUFFER_SYMBOLS]; - rmt_symbol_word_t _rx_symbols[RMT_BUFFER_SYMBOLS]; - // --- INITS --- bool _initTXChannel(); bool _initRXChannel(); @@ -162,24 +157,24 @@ private: // -- CALLBACKS --- QueueHandle_t _rx_queue; - rmt_rx_event_callbacks_t _rx_event_cbs; - static bool IRAM_ATTR _rmt_rx_done_callback(rmt_channel_handle_t rx_chan, const rmt_rx_done_event_data_t *edata, void *user_data); + rmt_rx_event_callbacks_t _rx_event_callbacks; + static bool IRAM_ATTR _rmt_rx_done_callback(rmt_channel_handle_t rmt_rx_channel, const rmt_rx_done_event_data_t *edata, void *user_data); // --- ERROR HANDLING & LOGGING --- - void _dshot_log(std::string_view msg) { std::cerr << msg << '\n'; } + void _dshot_log(const char *msg, Stream &output = Serial) { output.println(msg); } // --- CONSTANTS & ERROR MESSAGES --- static constexpr uint16_t DSHOT_OK = 0; static constexpr uint16_t DSHOT_ERROR = 1; - static constexpr std::string_view NEW_LINE = " "; - static constexpr std::string_view TX_INIT_FAILED = "Failed to initialize TX channel!"; - static constexpr std::string_view RX_INIT_FAILED = "Failed to initialize RX channel!"; - static constexpr std::string_view ENCODER_INIT_FAILED = "Failed to initialize DShot encoder!"; - static constexpr std::string_view CRC_CHECK_FAILED = "RX CRC Check failed!"; - static constexpr std::string_view THROTTLE_NOT_IN_RANGE = "Throttle value not in range (48 - 2047)!"; - static constexpr std::string_view COMMAND_NOT_VALID = "Not a valid DShot Command (0 - 47)!"; - static constexpr std::string_view BIDIR_NOT_ENABLED = "Bidirectional DShot support not enabled!"; - static constexpr std::string_view RX_RMT_RECEIVER_ERROR = "RX RMT receiver error!"; - static constexpr std::string_view PACKET_BUILD_ERROR = "Value too big for DShot Packet!"; + static constexpr char *NEW_LINE = " "; + 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!"; };