diff --git a/DShotRMT.cpp b/DShotRMT.cpp index e460863..33cd41c 100644 --- a/DShotRMT.cpp +++ b/DShotRMT.cpp @@ -9,16 +9,16 @@ #include "DShotRMT.h" // --- DShot Timings --- -// frame_length_us, ticks_per_bit, ticks_one_high, ticks_zero_high, ticks_zero_low, ticks_one_low +// frame_length_us, ticks_per_bit, ticks_one_high, ticks_one_low, ticks_zero_high, ticks_zero_low constexpr dshot_timing_t DSHOT_TIMINGS[] = { {0, 0, 0, 0, 0, 0}, // DSHOT_OFF - {128, 64, 48, 24, 40, 16}, // DSHOT150 - {64, 32, 24, 12, 20, 8}, // DSHOT300 - {32, 16, 12, 6, 10, 4}, // DSHOT600 - {16, 8, 6, 3, 5, 2} // DSHOT1200 + {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 }; -// +// --- DShot Config --- DShotRMT::DShotRMT(gpio_num_t gpio, dshot_mode_t mode, bool is_bidirectional): _gpio(gpio), _mode(mode), @@ -28,20 +28,30 @@ DShotRMT::DShotRMT(gpio_num_t gpio, dshot_mode_t mode, bool is_bidirectional): _rmt_rx_channel(nullptr), _dshot_encoder(nullptr), _last_erpm(0), - _last_transmission_time(0), - _current_packet(0) + _current_packet(0), + _packet{0}, + _last_transmission_time(0) { // Double up frame time for bidirectional mode if (_is_bidirectional) { - _frame_time_us = (_timing_config.frame_length_us << 1) + DSHOT_SWITCH_TIME; + _frame_timer_us = (_timing_config.frame_length_us << 1) + DSHOT_SWITCH_TIME; } // Calculate frame time including switch time - _frame_time_us = _timing_config.frame_length_us + DSHOT_SWITCH_TIME; + _frame_timer_us = _timing_config.frame_length_us + DSHOT_SWITCH_TIME; } -// Init DShotRMT +// Easy Constructor +DShotRMT::DShotRMT(uint16_t pin_nr, dshot_mode_t mode, bool is_bidirectional): + DShotRMT((gpio_num_t)pin_nr, + mode, + is_bidirectional) +{ + // ...just to accept pin numbers and GPIO_NUMs +} + +// Setup and configure DShotRMT bool DShotRMT::begin() { // Init TX Channel @@ -69,36 +79,43 @@ bool DShotRMT::begin() return DSHOT_OK; } -// +// Deprecated, use "sendThrottle()"" instead bool DShotRMT::setThrottle(uint16_t throttle) { - // Precheck throttle value - if (throttle < DSHOT_THROTTLE_MIN || throttle > DSHOT_THROTTLE_MAX) - { - Serial.println(DSHOT_MSG_06); - return DSHOT_ERROR; - } - - // - dshot_packet_t packet = _buildDShotPacket(throttle); - - return (_sendDShotFrame(packet)); + return sendThrottle(throttle); } -// +// Sends a valid throttle value +bool DShotRMT::sendThrottle(uint16_t throttle) +{ + // Make sure throttle value is valid by force + auto value = constrain(throttle, DSHOT_THROTTLE_MIN, DSHOT_THROTTLE_MAX); + + // Converts throttle value to dshot packet RMT symbols + _packet = _buildDShotPacket(value); + + // Actually send the RMT symbols + return (_sendDShotFrame(_packet)); +} + +// Deprecated, use "sendCommand()"" instead bool DShotRMT::sendDShotCommand(uint16_t command) { - // Precheck command value + return sendCommand(command); +} + +bool DShotRMT::sendCommand(uint16_t command) +{ + // Check for valid command if (command < DSHOT_CMD_MOTOR_STOP || command > DSHOT_CMD_MAX) { Serial.println(DSHOT_MSG_07); return DSHOT_ERROR; } - // - dshot_packet_t packet = _buildDShotPacket(command); + _packet = _buildDShotPacket(command); - return (_sendDShotFrame(packet)); + return (_sendDShotFrame(_packet)); } // @@ -118,7 +135,7 @@ uint16_t DShotRMT::getERPM() return _last_erpm; } - // Decode the response + // Decodes the response uint16_t new_erpm = _decodeDShotFrame(_rx_symbols); if (new_erpm != 0) { @@ -145,7 +162,7 @@ bool DShotRMT::_initTXChannel() _tx_channel_config.mem_block_symbols = DSHOT_SYMBOLS_SIZE; _tx_channel_config.trans_queue_depth = TX_BUFFER_SIZE; - // + // No loops, real time calculation for each frame _transmit_config.loop_count = 0; // ...it's a trap @@ -169,8 +186,9 @@ bool DShotRMT::_initRXChannel() _rx_channel_config.resolution_hz = DSHOT_RMT_RESOLUTION; _rx_channel_config.mem_block_symbols = DSHOT_SYMBOLS_SIZE; - _receive_config.signal_range_min_ns = 300; - _receive_config.signal_range_max_ns = 5000; + // TODO: need to figure out + _receive_config.signal_range_min_ns = 2; + _receive_config.signal_range_max_ns = 128; // Creates and activates RMT TX Channel if (rmt_new_rx_channel(&_rx_channel_config, &_rmt_rx_channel) != DSHOT_OK) @@ -192,14 +210,14 @@ bool DShotRMT::_initDShotEncoder() // Use RMT to transmit a prepared DShot packet and returns it bool DShotRMT::_sendDShotFrame(const dshot_packet_t &packet) { - // Exclude calculation from timing is more stable + // Excludes calculation from timing is more stable rmt_symbol_word_t tx_symbols[DSHOT_BITS_PER_FRAME]; _encodeDShotFrame(packet, tx_symbols); // Checking timer signal if (_timer_signal()) { - // Trigger RMT Transmit + // Triggers RMT Transmit rmt_transmit(_rmt_tx_channel, _dshot_encoder, tx_symbols, DSHOT_SYMBOLS_SIZE, &_transmit_config); // Time Stamp @@ -215,7 +233,7 @@ uint16_t DShotRMT::_calculateCRC(const dshot_packet_t &packet) uint16_t data = (packet.throttle_value << 1) | packet.telemetric_request; uint16_t crc = (data ^ (data >> 4) ^ (data >> 8)) & 0b0000000000001111; - // Invert CRC for bidirectional DShot + // Inverts CRC for bidirectional DShot if (_is_bidirectional) { crc = (~crc) & 0b0000000000001111; @@ -237,9 +255,9 @@ dshot_packet_t DShotRMT::_buildDShotPacket(const uint16_t value) // DShot Frame Container dshot_packet_t packet = {}; - // Create DShot packet + // Creates DShot packet packet.throttle_value = value; - packet.telemetric_request = 0; + packet.telemetric_request = 1; // needed to get the motor spinning packet.checksum = _calculateCRC(packet); // @@ -252,10 +270,10 @@ bool IRAM_ATTR DShotRMT::_encodeDShotFrame(const dshot_packet_t &packet, rmt_sym // Parse actual packet into buffer _current_packet = _parseDShotPacket(packet); - // Convert the parsed dshot frame to rmt_tx data + // Converts the parsed dshot frame to rmt_tx data for (int i = 0; i < DSHOT_BITS_PER_FRAME; i++) { - // Encode RMT symbols bitwise (MSB first) - tricky + // Encoded RMT symbols bitwise (MSB first) - tricky bool bit = (_current_packet >> (DSHOT_BITS_PER_FRAME - 1 - i)) & 0b0000000000000001; if (_is_bidirectional) { @@ -280,25 +298,25 @@ uint16_t DShotRMT::_decodeDShotFrame(const rmt_symbol_word_t *symbols) { uint16_t received_frame = 0; - // Decode each symbol to reconstruct the frame + // Decodes each symbol to reconstruct the frame for (size_t i = 0; i < DSHOT_BITS_PER_FRAME; ++i) { bool bit = symbols[i].duration0 < symbols[i].duration1; received_frame = (received_frame << 1) | bit; } - // Extract payload and CRC + // Extracts payload and CRC uint16_t data = received_frame >> 4; uint16_t received_crc = received_frame & 0b0000000000001111; - // Calculate CRC for received frame + // Calculates CRC for received frame uint16_t calculated_crc = (data ^ (data >> 4) ^ (data >> 8)) & 0b0000000000001111; if (_is_bidirectional) { calculated_crc = (~calculated_crc) & 0b0000000000001111; } - // Compare CRC + // Compares CRC if (received_crc != calculated_crc) { Serial.println(DSHOT_MSG_04); @@ -312,7 +330,7 @@ uint16_t DShotRMT::_decodeDShotFrame(const rmt_symbol_word_t *symbols) // Timer triggered bool DShotRMT::_timer_signal() { - return (micros() - _last_transmission_time) >= _frame_time_us; + return (micros() - _last_transmission_time) >= _frame_timer_us; } // Updates timestamp diff --git a/DShotRMT.h b/DShotRMT.h index 6fb1290..7e8dd25 100644 --- a/DShotRMT.h +++ b/DShotRMT.h @@ -54,29 +54,31 @@ typedef struct dshot_timing_s uint16_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; - uint16_t ticks_one_low; } dshot_timing_t; -// --- DShot Timing Config --- extern const dshot_timing_t DSHOT_TIMINGS[]; -// +// --- DShotRMT Class --- class DShotRMT { public: - // + // --- DShot Config --- DShotRMT(gpio_num_t gpio, dshot_mode_t mode = DSHOT300, bool is_bidirectional = false); + DShotRMT(uint16_t pin_nr, dshot_mode_t mode, bool is_bidirectional); // --- Init RMT Module --- bool begin(); // Sets the throttle value and transmits - bool setThrottle(uint16_t throttle); + bool setThrottle(uint16_t throttle); // deprecated + bool sendThrottle(uint16_t throttle); // Sends a DShot Command - bool sendDShotCommand(uint16_t command); + bool sendDShotCommand(uint16_t command); // deprecated + bool sendCommand(uint16_t command); // Gets eRPM from ESC telemetry uint16_t getERPM(); @@ -88,7 +90,7 @@ public: uint16_t getGPIO() const { return _gpio; } // Returns "raw" Dshot packet sent by RMT - uint16_t getDShotPacket() { return _current_packet; } + uint16_t getDShotPacket() const { return _current_packet; } // bool is_bidirectional() const { return _is_bidirectional; } @@ -98,7 +100,7 @@ private: gpio_num_t _gpio; dshot_mode_t _mode; bool _is_bidirectional; - uint16_t _frame_time_us; + uint16_t _frame_timer_us; // --- DShot Timings --- const dshot_timing_t &_timing_config; @@ -117,9 +119,10 @@ private: // --- Buffers --- uint16_t _last_erpm; uint16_t _current_packet; + dshot_packet_t _packet; unsigned long _last_transmission_time; - // + // ---Helpers --- bool _initTXChannel(); bool _initRXChannel(); bool _initDShotEncoder(); @@ -131,10 +134,11 @@ private: bool IRAM_ATTR _encodeDShotFrame(const dshot_packet_t &packet, rmt_symbol_word_t *symbols); uint16_t _decodeDShotFrame(const rmt_symbol_word_t *symbols); + // --- Simple Timer --- bool _timer_signal(); bool _timer_reset(); - // Error Handling + // --- Error Handling --- static constexpr bool DSHOT_OK = 0; static constexpr bool DSHOT_ERROR = 1; static constexpr char *DSHOT_MSG_01 = "Failed to initialize TX channel!"; diff --git a/README.md b/README.md index 1fd1186..d7c1a5b 100644 --- a/README.md +++ b/README.md @@ -12,9 +12,9 @@ Supports all standard DShot modes (150, 300, 600) and features continuous frame ## 🚀 Features -- **All DShot Modes:** DSHOT150, DSHOT300 (default), DSHOT600 -- **BiDirectional DShot:** Experimental support for telemetry and RPM feedback -- **Continuous Frames:** Hardware-timed, CPU-independent signal generation +- **All DShot Modes:** DSHOT150, DSHOT300 (default), DSHOT600, (DSHOT1200) +- **BiDirectional DShot:** Experimental support for RPM feedback +- **Continuous Frames:** Independent timed, Hardware signal generation - **Configurable Pause:** Ensures ESCs can reliably detect frame boundaries - **Simple API:** Easy integration into your Arduino or ESP-IDF project @@ -33,25 +33,7 @@ git clone https://github.com/derdoktor667/DShotRMT.git ## ⚡ Quick Start ```cpp -#include - -constexpr gpio_num_t MOTOR_PIN = GPIO_NUM_17; -constexpr dshot_mode_t MODE = DSHOT300; -constexpr bool BIDIRECTIONAL = true; - -DShotRMT motor(MOTOR_PIN, MODE, BIDIRECTIONAL); - -void setup() { - Serial.begin(115200); - motor.begin(); - motor.setThrottle(1000); // Set throttle value (48–2047) -} - -void loop() { - // Optionally read RPM if bidirectional mode is enabled - uint32_t rpm = motor.getMotorRPM(14); // 14 magnets - Serial.println(rpm); -} +Use "dshot300.ino" example sketch ``` --- @@ -104,7 +86,8 @@ crc = (~(value ^ (value >> 4) ^ (value >> 8))) & 0x0F; ## 🛠️ ESP32 RMT Peripheral The RMT (Remote Control) peripheral generates accurate, hardware-timed signals for controlling external devices. -Perfect for DShot: +Perfect for DShot: +- Utilizes latest ESP-IDF APIs - Hardware-timed pulses - CPU-independent - Loop mode with inter-frame pause @@ -116,11 +99,9 @@ Perfect for DShot: - `DShotRMT(gpio_num_t gpio, dshot_mode_t mode, bool isBidirectional)` - `void begin()` -- `void setThrottle(uint16_t throttle)` -- `uint32_t getERPM()` -- `uint32_t getMotorRPM(uint8_t magnet_count)` +- `void sendThrottle(uint16_t throttle)` -See [examples/dshot300/dshot300.ino](examples/dshot300/dshot300.ino) for a full demo. +See [examples/dshot300/dshot300.ino](examples/dshot300/dshot300.ino) for a more informations. --- diff --git a/examples/dshot300/dshot300.ino b/examples/dshot300/dshot300.ino index c11ce22..4ab2212 100644 --- a/examples/dshot300/dshot300.ino +++ b/examples/dshot300/dshot300.ino @@ -10,31 +10,27 @@ #include // USB serial port settings -static constexpr HardwareSerial &USB_SERIAL = Serial0; -static constexpr uint32_t USB_SERIAL_BAUD = 115200; +static constexpr auto &USB_SERIAL = Serial0; +static constexpr auto USB_SERIAL_BAUD = 115200; // Motor configuration -static constexpr gpio_num_t MOTOR01_PIN = GPIO_NUM_17; +// Pin number or GPIO_PIN +// static constexpr gpio_num_t MOTOR01_PIN = GPIO_NUM_17; +static constexpr auto MOTOR01_PIN = 17; + +// Supported: DSHOT150, DSHOT300, DSHOT600, (DSHOT1200) static constexpr dshot_mode_t DSHOT_MODE = DSHOT300; // BiDirectional DShot Support (default: false) static constexpr bool IS_BIDIRECTIONAL = false; // Motor magnet count for RPM calculation -static constexpr uint8_t MOTOR01_MAGNET_COUNT = 14; +static constexpr auto MOTOR01_MAGNET_COUNT = 14; -// Setup Motor Pin, DShot Mode and optional BiDirectional Support +// +// Create the motor instance DShotRMT motor01(MOTOR01_PIN, DSHOT_MODE, IS_BIDIRECTIONAL); -// Prints RPM and throttle every 2 seconds if BiDirectional is enabled -void printRPMPeriodically(uint16_t throttle); - -// Reads throttle value from serial input -uint16_t readSerialThrottle(); - -// Prints out the dshot packet bitwise (Debug) -void printPacket(); - // void setup() { @@ -45,89 +41,69 @@ void setup() motor01.begin(); // Arm ESC with minimum throttle - motor01.setThrottle(DSHOT_THROTTLE_MIN); + // motor01.sendThrottle(DSHOT_THROTTLE_MIN); - USB_SERIAL.println("**********************"); - USB_SERIAL.println("DShotRMT Demo started."); + USB_SERIAL.println("***********************************"); + USB_SERIAL.println(" === DShotRMT Demo started. === "); USB_SERIAL.println("Enter a throttle value (48 – 2047):"); } // void loop() { - // Read value input from Serial - uint16_t throttle_input = readSerialThrottle(); + static auto throttle = DSHOT_THROTTLE_MIN; + + if (USB_SERIAL.available() > NULL) + { + auto new_throttle = (USB_SERIAL.readStringUntil('\n').toInt()); - // Send the value to the ESC - motor01.setThrottle(throttle_input); + USB_SERIAL.println("*********************"); + USB_SERIAL.print("Throttle set to: "); + USB_SERIAL.println(new_throttle); - // Print RPM if BiDirectional DShot is enabled + // + throttle = new_throttle; + } + + // Sends the value to the ESC + motor01.sendThrottle(throttle); + + // Prints out RPM if BiDirectional DShot is enabled every 2 seconds + // printRPMPeriodically(2000); + + // Debug: Prints out "raw" DShot packet every 2 seconds + print_RMT_packet(2000); +} + +// Prints RPM every ms +void printRPMPeriodically(auto timer_ms) +{ if (IS_BIDIRECTIONAL) { - printRPMPeriodically(throttle_input); - } + static unsigned long last_print_time = 0; - // Prints out "raw" DShot packet - // printPacket(); + if (millis() - last_print_time >= timer_ms) + { + auto rpm = motor01.getMotorRPM(MOTOR01_MAGNET_COUNT); + + USB_SERIAL.print("RPM: "); + USB_SERIAL.println(rpm); + + last_print_time = millis(); + } + } } -// Reads throttle value from serial input -uint16_t readSerialThrottle() -{ - static uint16_t last_throttle = DSHOT_THROTTLE_MIN; - - if (USB_SERIAL.available() > 0) - { - String input = USB_SERIAL.readStringUntil('\n'); - int throttle_input = input.toInt(); - - if (throttle_input < DSHOT_THROTTLE_MIN || throttle_input > DSHOT_THROTTLE_MAX) - { - USB_SERIAL.println("Invalid input. Please enter a value between 48 and 2047."); - return last_throttle; - } - else - { - last_throttle = throttle_input; - USB_SERIAL.print("Throttle set to: "); - USB_SERIAL.println(last_throttle); - } - - USB_SERIAL.println("*********************************"); - USB_SERIAL.println("Enter a throttle value (48 – 2047):"); - } - - return last_throttle; -} - -// Prints RPM and throttle every 2 seconds -void printRPMPeriodically(uint16_t throttle) +// Prints "raw" packet every ms +void print_RMT_packet(auto timer_ms) { static unsigned long last_print_time = 0; - if (millis() - last_print_time >= 2000) + if (millis() - last_print_time >= timer_ms) { - uint32_t rpm = motor01.getMotorRPM(MOTOR01_MAGNET_COUNT); - - USB_SERIAL.print("Throttle: "); - USB_SERIAL.print(throttle); - USB_SERIAL.print(" | RPM: "); - USB_SERIAL.println(rpm); - - last_print_time = millis(); - } -} - -// -void printPacket() -{ - static unsigned long last_print_time = 0; - - if (millis() - last_print_time >= 2000) - { - // Prints out actual DShot Packet bitwise - uint16_t packet = motor01.getDShotPacket(); + auto packet = motor01.getDShotPacket(); + // Print bit by bit for (int i = 15; i >= 0; --i) { if ((packet >> i) & 1) diff --git a/keywords.txt b/keywords.txt new file mode 100644 index 0000000..67b4d0f --- /dev/null +++ b/keywords.txt @@ -0,0 +1,49 @@ +####################################### +# Syntax Coloring Map For DShotRMT +####################################### + +####################################### +# Datatypes (KEYWORD1) +####################################### + +DShotRMT KEYWORD1 +dshot_mode_t KEYWORD1 +dshot_packet_t KEYWORD1 +dshot_timing_t KEYWORD1 +dshotCommands_e KEYWORD1 +dshotCommandType_e KEYWORD1 + +####################################### +# Methods and Functions (KEYWORD2) +####################################### + +begin KEYWORD2 +setThrottle KEYWORD2 +sendThrottle KEYWORD2 +sendDShotCommand KEYWORD2 +sendCommand KEYWORD2 +getERPM KEYWORD2 +getMotorRPM KEYWORD2 +getGPIO KEYWORD2 +getDShotPacket KEYWORD2 +is_bidirectional KEYWORD2 + +####################################### +# Constants (LITERAL1) +####################################### + +DSHOT_OFF LITERAL1 +DSHOT150 LITERAL1 +DSHOT300 LITERAL1 +DSHOT600 LITERAL1 +DSHOT1200 LITERAL1 + +DSHOT_THROTTLE_FAILSAFE LITERAL1 +DSHOT_THROTTLE_MIN LITERAL1 +DSHOT_THROTTLE_MAX LITERAL1 + +DSHOT_CMD_MOTOR_STOP LITERAL1 +DSHOT_CMD_MAX LITERAL1 + +DSHOT_CMD_TYPE_INLINE LITERAL1 +DSHOT_CMD_TYPE_BLOCKING LITERAL1 \ No newline at end of file diff --git a/library.properties b/library.properties index 4238997..e526f8f 100644 --- a/library.properties +++ b/library.properties @@ -1,5 +1,5 @@ name=DShotRMT -version=0.5.0 +version=0.6.0 author=derdoktor667 maintainer=derdoktor667 sentence=DShotRMT Library supporting all DShot Types and speeds. Tested with BlHeli_S.