From 8d0781254895f10f49438239a72d668388e455ed Mon Sep 17 00:00:00 2001 From: Wastl Kraus Date: Fri, 12 Sep 2025 23:14:34 +0200 Subject: [PATCH] RMT timing rewritten --- src/DShotRMT.cpp | 73 ++++++++++++++++++++++++++---------------------- src/DShotRMT.h | 72 +++++++++++++++++++++++++---------------------- 2 files changed, 79 insertions(+), 66 deletions(-) diff --git a/src/DShotRMT.cpp b/src/DShotRMT.cpp index d64cdec..7575a90 100644 --- a/src/DShotRMT.cpp +++ b/src/DShotRMT.cpp @@ -8,14 +8,28 @@ #include "DShotRMT.h" +// --- HELPERS --- +void printDShotResult(dshot_result_t &result, Stream &output) +{ + output.printf("Status: %s - %s", result.success ? "SUCCESS" : "FAILED", result.msg); + + // Print telemetry data if available + if (result.success && (result.erpm > 0 || result.motor_rpm > 0)) + { + output.printf(" | eRPM: %u, Motor RPM: %u", result.erpm, result.motor_rpm); + } + + output.println(); +} + // 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} -static constexpr dshot_timing_t DSHOT_TIMINGS[] = { - {0, 0, 0, 0, 0, 0}, // DSHOT_OFF - {128, 64, 48, 16, 24, 40}, // DSHOT150 - {64, 32, 24, 8, 12, 20}, // DSHOT300 - {32, 16, 12, 4, 6, 10}, // DSHOT600 - {16, 8, 6, 2, 3, 5} // DSHOT1200 +// Format: {frame_length_ticks, ticks_per_bit, t1h_ticks, t1l_ticks, t0h_ticks, t0l_ticks} +static constexpr dshot_timing_us_t DSHOT_TIMING_US[] = { + {0.00, 0.00}, + {6.67, 5.00}, + {3.33, 2.50}, + {1.67, 1.25}, + {0.83, 0.67} }; // Constructor with GPIO number @@ -26,9 +40,10 @@ DShotRMT::DShotRMT(gpio_num_t gpio, dshot_mode_t mode, bool is_bidirectional) _last_erpm_atomic(0), _telemetry_ready_flag(false), _frame_timer_us(0), - _timing_config(DSHOT_TIMINGS[mode]), + _dshot_timing(DSHOT_TIMING_US[mode]), + _rmt_ticks{0}, _last_throttle(DSHOT_CMD_MOTOR_STOP), - _last_transmission_time(0), + _last_transmission_time_us(0), _parsed_packet(0), _packet{0}, _bitPositions{0}, @@ -42,8 +57,15 @@ DShotRMT::DShotRMT(gpio_num_t gpio, dshot_mode_t mode, bool is_bidirectional) _transmit_config{}, _receive_config{} { - // Calculate frame timing including switch/pause time - _frame_timer_us = _timing_config.frame_length_us + DSHOT_PAUSE_US; + // Convert DShot timings (us) to RMT ticks + _rmt_ticks.ticks_per_bit = static_cast(_dshot_timing.bit_length_us * RMT_TICKS_PER_US); + _rmt_ticks.t1h_ticks = static_cast(_dshot_timing.t1h_lenght_us * RMT_TICKS_PER_US); + _rmt_ticks.t0h_ticks = _rmt_ticks.t1h_ticks >> 1; // High time for a 1 is always double that of a 0 + _rmt_ticks.t1l_ticks = _rmt_ticks.ticks_per_bit - _rmt_ticks.t1h_ticks; + _rmt_ticks.t0l_ticks = _rmt_ticks.ticks_per_bit - _rmt_ticks.t0h_ticks; + + // Pause between frames is frame time in us, some padding and about 30 us is added by hardware + _frame_timer_us = (static_cast(_dshot_timing.bit_length_us * DSHOT_BITS_PER_FRAME) << 1) + DSHOT_PADDING_US; // Double frame time for bidirectional mode (includes response time) if (_is_bidirectional) @@ -54,7 +76,7 @@ DShotRMT::DShotRMT(gpio_num_t gpio, dshot_mode_t mode, bool is_bidirectional) // Constructor using pin number DShotRMT::DShotRMT(uint16_t pin_nr, dshot_mode_t mode, bool is_bidirectional) - : DShotRMT((gpio_num_t)pin_nr, mode, is_bidirectional) + : DShotRMT(static_cast(pin_nr), mode, is_bidirectional) { // Delegates to primary constructor with type cast } @@ -152,7 +174,6 @@ dshot_result_t DShotRMT::_initTXChannel() // Init RMT RX channel dshot_result_t DShotRMT::_initRXChannel() { - // Direct RMT symbol processing - Performance optimized _rx_event_callbacks.on_recv_done = _rmt_rx_done_callback; @@ -258,7 +279,7 @@ dshot_result_t DShotRMT::sendCommand(uint16_t command) dshot_result_t DShotRMT::getTelemetry(uint16_t magnet_count) { // Result container with unified structure - dshot_result_t result = {false, TELEMETRY_FAILED, NO_DSHOT_ERPM, NO_DSHOT_RPM}; + dshot_result_t result = {false, TELEMETRY_FAILED, NO_DSHOT_TELEMETRY, NO_DSHOT_TELEMETRY}; // Check if bidirectional mode is enabled if (!_is_bidirectional) @@ -277,7 +298,7 @@ dshot_result_t DShotRMT::getTelemetry(uint16_t magnet_count) // if (erpm != DSHOT_NULL_PACKET && magnet_count >= 1) { - uint8_t pole_pairs = max(MIN_POLE_PAIRS, (magnet_count / MAGNETS_PER_POLE_PAIR)); + uint8_t pole_pairs = max(POLE_PAIRS_MIN, (magnet_count / MAGNETS_PER_POLE_PAIR)); uint32_t motor_rpm = (erpm / pole_pairs); result.success = true; @@ -426,9 +447,9 @@ bool DShotRMT::_encodeDShotFrame(const dshot_packet_t &packet, rmt_symbol_word_t 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].duration0 = bit ? _rmt_ticks.t1h_ticks : _rmt_ticks.t0h_ticks; symbols[i].level1 = _level1; - symbols[i].duration1 = bit ? _timing_config.ticks_one_low : _timing_config.ticks_zero_low; + symbols[i].duration1 = bit ? _rmt_ticks.t1l_ticks : _rmt_ticks.t0l_ticks; } return DSHOT_OK; @@ -487,7 +508,7 @@ bool DShotRMT::_timer_signal() uint64_t current_time = esp_timer_get_time(); // Handle potential overflow - uint64_t elapsed = current_time - _last_transmission_time; + uint64_t elapsed = current_time - _last_transmission_time_us; return elapsed >= _frame_timer_us; } @@ -495,7 +516,7 @@ bool DShotRMT::_timer_signal() // Reset transmission timer to current time bool DShotRMT::_timer_reset() { - _last_transmission_time = esp_timer_get_time(); + _last_transmission_time_us = esp_timer_get_time(); return DSHOT_OK; } @@ -546,17 +567,3 @@ void DShotRMT::printCpuInfo(Stream &output) const output.printf("XTAL Freq = %lu MHz\n", getXtalFrequencyMhz()); output.printf("APB Freq = %lu Hz\n", getApbFrequency()); } - -// --- HELPERS --- -void printDShotResult(dshot_result_t &result, Stream &output) -{ - output.printf("Status: %s - %s", result.success ? "SUCCESS" : "FAILED", result.msg); - - // Print telemetry data if available - if (result.success && (result.erpm > 0 || result.motor_rpm > 0)) - { - output.printf(" | eRPM: %u, Motor RPM: %u", result.erpm, result.motor_rpm); - } - - output.println(); -} diff --git a/src/DShotRMT.h b/src/DShotRMT.h index 3a4d466..43eab78 100644 --- a/src/DShotRMT.h +++ b/src/DShotRMT.h @@ -20,29 +20,7 @@ static constexpr auto DSHOT_THROTTLE_FAILSAFE = 0; static constexpr auto DSHOT_THROTTLE_MIN = 48; static constexpr auto DSHOT_THROTTLE_MAX = 2047; static constexpr auto DSHOT_BITS_PER_FRAME = 16; -static constexpr auto DSHOT_PAUSE_US = 30; // Additional frame pause time -static constexpr auto DSHOT_NULL_PACKET = 0b0000000000000000; -static constexpr auto DSHOT_FULL_PACKET = 0b1111111111111111; -static constexpr auto DSHOT_CRC_MASK = 0b0000000000001111; -static constexpr auto DSHOT_RX_TIMEOUT_MS = 2; // Never reached, just a timeeout -static constexpr auto GCR_BITS_PER_FRAME = 21; // Number of GCR bits in a DShot answer frame (1 start + 16 data + 4 CRC) static constexpr auto DEFAULT_MOTOR_MAGNET_COUNT = 14; -static constexpr auto MAGNETS_PER_POLE_PAIR = 2; -static constexpr auto MIN_POLE_PAIRS = 1; -static constexpr auto NO_DSHOT_ERPM = 0; -static constexpr auto NO_DSHOT_RPM = 0; - -// RMT Configuration Constants -constexpr auto DSHOT_CLOCK_SRC_DEFAULT = RMT_CLK_SRC_DEFAULT; -constexpr auto DSHOT_RMT_RESOLUTION = 10 * 1000 * 1000; // 10 MHz resolution -constexpr auto RMT_BUFFER_SIZE = DSHOT_BITS_PER_FRAME; -constexpr auto RMT_BUFFER_SYMBOLS = 64; -constexpr auto RMT_QUEUE_DEPTH = 1; - -// Smallest pulse for DShot1200 is 2us. Largest for DShot150 is 40us. -// The range is set from 3us (3000ns) to 60us (60000ns) to be safe across all modes. -constexpr uint32_t DSHOT_PULSE_MIN = 3000; -constexpr uint32_t DSHOT_PULSE_MAX = 60000; // DShot Modes typedef enum @@ -62,16 +40,22 @@ typedef struct uint16_t checksum : 4; } dshot_packet_t; -// DShot Timing Configuration +// DShot Timings +typedef struct +{ + double bit_length_us; + double t1h_lenght_us; +} dshot_timing_us_t; + +// RMT Ticks Configuration typedef struct { - uint32_t frame_length_us; uint16_t ticks_per_bit; - uint16_t ticks_one_high; - uint16_t ticks_one_low; - uint16_t ticks_zero_high; - uint16_t ticks_zero_low; -} dshot_timing_t; + uint16_t t1h_ticks; + uint16_t t1l_ticks; + uint16_t t0h_ticks; + uint16_t t0l_ticks; +} rmt_ticks_t; // Unified DShot result structure typedef struct @@ -149,11 +133,12 @@ private: dshot_mode_t _mode; bool _is_bidirectional; uint32_t _frame_timer_us; - const dshot_timing_t &_timing_config; + rmt_ticks_t _rmt_ticks; + const dshot_timing_us_t &_dshot_timing; uint16_t _last_throttle; // --- TIMING & PACKET VARIABLES --- - uint64_t _last_transmission_time; + uint64_t _last_transmission_time_us; uint16_t _parsed_packet; dshot_packet_t _packet; uint8_t _bitPositions[DSHOT_BITS_PER_FRAME]; @@ -193,13 +178,12 @@ private: // -- CALLBACKS --- rmt_rx_event_callbacks_t _rx_event_callbacks; - volatile rmt_symbol_word_t _rx_symbols_direct[GCR_BITS_PER_FRAME]; volatile uint16_t _last_erpm_atomic; volatile bool _telemetry_ready_flag; static bool IRAM_ATTR _rmt_rx_done_callback(rmt_channel_handle_t rmt_rx_channel, const rmt_rx_done_event_data_t *edata, void *user_data); // --- DSHOT DEFAULTS --- - static constexpr auto const DSHOT_TELEMETRY_INVALID = (0xffff); + static constexpr auto const DSHOT_TELEMETRY_INVALID = 0b1111111111111111; // --- CONSTANTS & ERROR MESSAGES --- static constexpr bool DSHOT_OK = 0; @@ -226,4 +210,26 @@ private: 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!"; + + // Configuration Constants + static constexpr auto const DSHOT_NULL_PACKET = 0b0000000000000000; + static constexpr auto const DSHOT_FULL_PACKET = 0b1111111111111111; + static constexpr auto const DSHOT_CRC_MASK = 0b0000000000001111; + static constexpr auto const DSHOT_CLOCK_SRC_DEFAULT = RMT_CLK_SRC_DEFAULT; + static constexpr auto const DSHOT_RMT_RESOLUTION = 8 * 1000 * 1000; // 8 MHz resolution + static constexpr auto const RMT_TICKS_PER_US = DSHOT_RMT_RESOLUTION / (1 * 1000 * 1000); // RMT Ticks per microsecond, based on the RMT resolution in MHz + static constexpr auto const RMT_BUFFER_SIZE = DSHOT_BITS_PER_FRAME; + static constexpr auto const DSHOT_RX_TIMEOUT_MS = 2; // Never reached + static constexpr auto const DSHOT_PADDING_US = 3; + static constexpr auto const RMT_BUFFER_SYMBOLS = 64; + static constexpr auto const RMT_QUEUE_DEPTH = 1; + static constexpr auto const GCR_BITS_PER_FRAME = 21; // Number of GCR bits in a DShot answer frame (1 start + 16 data + 4 CRC) + static constexpr auto const POLE_PAIRS_MIN = 1; + static constexpr auto const MAGNETS_PER_POLE_PAIR = 2; + static constexpr auto const NO_DSHOT_TELEMETRY = 0; + + // Smallest pulse for DShot1200 is 2us. Largest for DShot150 is 40us. + // The range is set from 3us (3000ns) to 60us (60000ns) to be safe across all modes. + static constexpr auto const DSHOT_PULSE_MIN = 3000; + static constexpr auto const DSHOT_PULSE_MAX = 60000; };