diff --git a/examples/dshot300/dshot300.ino b/examples/dshot300/dshot300.ino index d13533c..70fa73a 100644 --- a/examples/dshot300/dshot300.ino +++ b/examples/dshot300/dshot300.ino @@ -7,7 +7,8 @@ */ #include -#include +#include "DShotRMT.h" +#include "DShotRMT_Utils.h" // Include utility functions // USB serial port settings static constexpr auto &USB_SERIAL = Serial0; @@ -18,7 +19,7 @@ static constexpr gpio_num_t MOTOR01_PIN = GPIO_NUM_27; // static constexpr auto MOTOR01_PIN = 17; // Supported: DSHOT150, DSHOT300, DSHOT600, (DSHOT1200) -static constexpr dshot_mode_t DSHOT_MODE = DSHOT300; +static constexpr dshot_mode_t DSHOT_MODE = dshot_mode_t::DSHOT300; // BiDirectional DShot Support (default: false) // Note: Bidirectional DShot is currently not officially supported @@ -41,7 +42,7 @@ void setup() motor01.begin(); // Print CPU Info - motor01.printCpuInfo(); + printCpuInfo(USB_SERIAL); // printMenu(); @@ -51,7 +52,7 @@ void setup() void loop() { // Safety first - static uint16_t throttle = DSHOT_CMD_MOTOR_STOP; + static uint16_t throttle = static_cast(dshotCommands_e::DSHOT_CMD_MOTOR_STOP); // Initialize the esc with "0" static bool continuous_throttle = true; @@ -80,7 +81,7 @@ void loop() // Print motor stats every 3 seconds in continuous mode if (continuous_throttle && (esp_timer_get_time() - last_stats_print >= 3000000)) { - motor01.printDShotInfo(); + printDShotInfo(motor01, USB_SERIAL); USB_SERIAL.println(" "); @@ -127,12 +128,12 @@ void handleSerialInput(const String &input, uint16_t &throttle, bool &continuous // Stop motor throttle = 0; continuous_throttle = true; - dshot_result_t result = motor01.sendCommand(DSHOT_CMD_MOTOR_STOP); + dshot_result_t result = motor01.sendCommand(static_cast(dshotCommands_e::DSHOT_CMD_MOTOR_STOP)); printDShotResult(result); } else if (input == "info") { - motor01.printDShotInfo(); + printDShotInfo(motor01, USB_SERIAL); } else if (input == "rpm" && IS_BIDIRECTIONAL) { @@ -146,14 +147,14 @@ void handleSerialInput(const String &input, uint16_t &throttle, bool &continuous // Send DShot command int cmd_num = input.substring(4).toInt(); - if (cmd_num >= DSHOT_CMD_MOTOR_STOP && cmd_num <= DSHOT_CMD_MAX) + if (cmd_num >= static_cast(dshotCommands_e::DSHOT_CMD_MOTOR_STOP) && cmd_num <= static_cast(dshotCommands_e::DSHOT_CMD_MAX)) { dshot_result_t result = motor01.sendCommand(cmd_num); printDShotResult(result); } else { - USB_SERIAL.printf("Invalid command: %d (valid range: 0 - %d)\n", cmd_num, DSHOT_CMD_MAX); + USB_SERIAL.printf("Invalid command: %d (valid range: 0 - %d)\n", cmd_num, static_cast(dshotCommands_e::DSHOT_CMD_MAX)); } } else if (input == "h" || input == "help") diff --git a/library.properties b/library.properties index 7ee1a53..ed727ea 100644 --- a/library.properties +++ b/library.properties @@ -1,5 +1,5 @@ name=DShotRMT -version=0.8.3 +version=0.8.5 author=Wastl Kraus maintainer=Wastl Kraus license=MIT diff --git a/src/DShotRMT.cpp b/src/DShotRMT.cpp index dbea4fa..0ac1e2b 100644 --- a/src/DShotRMT.cpp +++ b/src/DShotRMT.cpp @@ -8,29 +8,6 @@ #include -// Timing parameters for each DShot mode -// Format: {bit_length_us, t1h_length_us} -static constexpr dshot_timing_us_t DSHOT_TIMING_US[] = { - {0.00, 0.00}, // DSHOT_OFF - {6.67, 5.00}, // DSHOT150 - {3.33, 2.50}, // DSHOT300 - {1.67, 1.25}, // DSHOT600 - {0.83, 0.67}}; // DSHOT1200 - -// Helper function to print DShot results and telemetry -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(); -} - // Constructors & Destructor // Constructor with GPIO number DShotRMT::DShotRMT(gpio_num_t gpio, dshot_mode_t mode, bool is_bidirectional, uint16_t magnet_count) @@ -38,15 +15,14 @@ DShotRMT::DShotRMT(gpio_num_t gpio, dshot_mode_t mode, bool is_bidirectional, ui _mode(mode), _is_bidirectional(is_bidirectional), _motor_magnet_count(magnet_count), - _dshot_timing(DSHOT_TIMING_US[mode]), + _dshot_timing(DSHOT_TIMING_US[static_cast(mode)]), _frame_timer_us(0), _rmt_ticks{0}, - _last_throttle(DSHOT_CMD_MOTOR_STOP), + _last_throttle(static_cast(dshotCommands_e::DSHOT_CMD_MOTOR_STOP)), _last_transmission_time_us(0), _last_command_timestamp(0), _encoded_frame_value(0), _packet{0}, - _bitPositions{0}, _level0(1), // DShot standard: signal is idle-low, so pulses start by going HIGH _level1(0), // DShot standard: signal returns to LOW after the high pulse _rmt_tx_channel(nullptr), @@ -62,7 +38,6 @@ DShotRMT::DShotRMT(gpio_num_t gpio, dshot_mode_t mode, bool is_bidirectional, ui { // Pre-calculate timing and bit positions for performance _preCalculateRMTTicks(); - _preCalculateBitPositions(); // Activate internal pullup resistor // gpio_set_pull_mode(_gpio, GPIO_PULLUP_ONLY); @@ -112,7 +87,7 @@ dshot_result_t DShotRMT::begin() { if (!_initTXChannel().success) { - return {false, TX_INIT_FAILED}; + return {false, dshot_msg_code_t::DSHOT_ERROR_TX_INIT_FAILED}; } if (_is_bidirectional) @@ -123,7 +98,7 @@ dshot_result_t DShotRMT::begin() rmt_disable(_rmt_tx_channel); rmt_del_channel(_rmt_tx_channel); _rmt_tx_channel = nullptr; - return {false, RX_INIT_FAILED}; + return {false, dshot_msg_code_t::DSHOT_ERROR_RX_INIT_FAILED}; } } @@ -140,10 +115,10 @@ dshot_result_t DShotRMT::begin() _rmt_rx_channel = nullptr; } - return {false, ENCODER_INIT_FAILED}; + return {false, dshot_msg_code_t::DSHOT_ERROR_ENCODER_INIT_FAILED}; } - return {true, INIT_SUCCESS}; + return {true, dshot_msg_code_t::DSHOT_ERROR_INIT_SUCCESS}; } // Send throttle value @@ -152,7 +127,7 @@ dshot_result_t DShotRMT::sendThrottle(uint16_t throttle) // A throttle value of 0 is a disarm command if (throttle == 0) { - return sendCommand(DSHOT_CMD_MOTOR_STOP); + return sendCommand(static_cast(dshotCommands_e::DSHOT_CMD_MOTOR_STOP)); } // Constrain throttle to the valid DShot range @@ -167,7 +142,7 @@ dshot_result_t DShotRMT::sendThrottlePercent(float percent) { if (percent < 0.0f || percent > 100.0f) { - return {false, PERCENT_NOT_IN_RANGE}; + return {false, dshot_msg_code_t::DSHOT_ERROR_PERCENT_NOT_IN_RANGE}; } // Map percent to DShot throttle range @@ -179,9 +154,9 @@ dshot_result_t DShotRMT::sendThrottlePercent(float percent) // Send DShot command to ESC dshot_result_t DShotRMT::sendCommand(uint16_t command) { - if (command > DSHOT_CMD_MAX) + if (command > static_cast(dshotCommands_e::DSHOT_CMD_MAX)) { - return {false, COMMAND_NOT_VALID}; + return {false, dshot_msg_code_t::DSHOT_ERROR_COMMAND_NOT_VALID}; } _packet = _buildDShotPacket(command); @@ -189,13 +164,14 @@ dshot_result_t DShotRMT::sendCommand(uint16_t command) } // Send full DShot commands for setup etc -dshot_result_t DShotRMT::sendCommand(dshot_commands_t dshot_command, uint16_t repeat_count, uint16_t delay_us) +// This is a blocking function that uses delayMicroseconds for repetitions. +dshot_result_t DShotRMT::sendCommand(dshotCommands_e dshot_command, uint16_t repeat_count, uint16_t delay_us) { - dshot_result_t result = {false, UNKNOWN_ERROR}; + dshot_result_t result = {false, dshot_msg_code_t::DSHOT_ERROR_UNKNOWN}; if (!_isValidCommand(dshot_command)) { - result.msg = INVALID_COMMAND; + result.error_code = dshot_msg_code_t::DSHOT_ERROR_INVALID_COMMAND; return result; } @@ -209,7 +185,7 @@ dshot_result_t DShotRMT::sendCommand(dshot_commands_t dshot_command, uint16_t re if (!single_result.success) { all_successful = false; - result.msg = single_result.msg; + result.error_code = single_result.error_code; break; } @@ -225,7 +201,7 @@ dshot_result_t DShotRMT::sendCommand(dshot_commands_t dshot_command, uint16_t re if (result.success) { - result.msg = COMMAND_SUCCESS; + result.error_code = dshot_msg_code_t::DSHOT_ERROR_COMMAND_SUCCESS; } return result; @@ -234,11 +210,11 @@ dshot_result_t DShotRMT::sendCommand(dshot_commands_t dshot_command, uint16_t re // Get telemetry data dshot_result_t DShotRMT::getTelemetry(uint16_t magnet_count) { - dshot_result_t result = {false, TELEMETRY_FAILED, NO_DSHOT_TELEMETRY, NO_DSHOT_TELEMETRY}; + dshot_result_t result = {false, dshot_msg_code_t::DSHOT_ERROR_TELEMETRY_FAILED, NO_DSHOT_TELEMETRY, NO_DSHOT_TELEMETRY}; if (!_is_bidirectional) { - result.msg = BIDIR_NOT_ENABLED; + result.error_code = dshot_msg_code_t::DSHOT_ERROR_BIDIR_NOT_ENABLED; return result; } @@ -260,7 +236,7 @@ dshot_result_t DShotRMT::getTelemetry(uint16_t magnet_count) result.success = true; result.erpm = erpm; result.motor_rpm = motor_rpm; - result.msg = TELEMETRY_SUCCESS; + result.error_code = dshot_msg_code_t::DSHOT_ERROR_TELEMETRY_SUCCESS; } } @@ -271,66 +247,30 @@ dshot_result_t DShotRMT::getTelemetry(uint16_t magnet_count) dshot_result_t DShotRMT::setMotorSpinDirection(bool reversed) { // Use command as a yes / no switch - dshot_commands_t command = reversed ? DSHOT_CMD_SPIN_DIRECTION_REVERSED : DSHOT_CMD_SPIN_DIRECTION_NORMAL; + dshotCommands_e command = reversed ? dshotCommands_e::DSHOT_CMD_SPIN_DIRECTION_REVERSED : dshotCommands_e::DSHOT_CMD_SPIN_DIRECTION_NORMAL; return sendCommand(command, SETTINGS_COMMAND_REPEATS, SETTINGS_COMMAND_DELAY_US); } dshot_result_t DShotRMT::getESCInfo() { - return sendCommand(DSHOT_CMD_ESC_INFO); + return sendCommand(static_cast(dshotCommands_e::DSHOT_CMD_ESC_INFO)); } // Use with caution dshot_result_t DShotRMT::saveESCSettings() { - return sendCommand(DSHOT_CMD_SAVE_SETTINGS, SETTINGS_COMMAND_REPEATS, SETTINGS_COMMAND_DELAY_US); -} - -// Public Info & Debug Functions -void DShotRMT::setMotorMagnetCount(uint16_t magnet_count) -{ - _motor_magnet_count = magnet_count; -} - -void DShotRMT::printDShotInfo(Stream &output) const -{ - output.println("\n === DShot Signal Info === "); - 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"); - output.printf("Current Packet: "); - - for (int i = DSHOT_BITS_PER_FRAME - 1; i >= 0; --i) - { - output.print((_encoded_frame_value >> i) & 1); - } - - output.printf("\nCurrent Value: %u\n", _packet.throttle_value); -} - -// -void DShotRMT::printCpuInfo(Stream &output) const -{ - output.println("\n === CPU Info === "); - output.printf("Chip Model: %s\n", ESP.getChipModel()); - output.printf("Chip Revision: %d\n", ESP.getChipRevision()); - output.printf("CPU Freq = %lu MHz\n", ESP.getCpuFreqMHz()); - output.printf("XTAL Freq = %lu MHz\n", getXtalFrequencyMhz()); - output.printf("APB Freq = %lu Hz\n", getApbFrequency()); + return sendCommand(dshotCommands_e::DSHOT_CMD_SAVE_SETTINGS, SETTINGS_COMMAND_REPEATS, SETTINGS_COMMAND_DELAY_US); } // Simple check -bool DShotRMT::_isValidCommand(dshot_commands_t command) +bool DShotRMT::_isValidCommand(dshotCommands_e command) { - return (command >= DSHOT_CMD_MOTOR_STOP && command <= DSHOT_CMD_MAX); + return (static_cast(command) >= static_cast(dshotCommands_e::DSHOT_CMD_MOTOR_STOP) && static_cast(command) <= static_cast(dshotCommands_e::DSHOT_CMD_MAX)); } // -dshot_result_t DShotRMT::_executeCommand(dshot_commands_t command) +dshot_result_t DShotRMT::_executeCommand(dshotCommands_e command) { uint64_t start_time = esp_timer_get_time(); @@ -357,14 +297,14 @@ dshot_result_t DShotRMT::_initTXChannel() if (rmt_new_tx_channel(&_tx_channel_config, &_rmt_tx_channel) != DSHOT_OK) { - return {false, TX_INIT_FAILED}; + return {false, dshot_msg_code_t::DSHOT_ERROR_TX_INIT_FAILED}; } if (rmt_enable(_rmt_tx_channel) != DSHOT_OK) { - return {false, TX_INIT_FAILED}; + return {false, dshot_msg_code_t::DSHOT_ERROR_TX_INIT_FAILED}; } - return {true, TX_INIT_SUCCESS}; + return {true, dshot_msg_code_t::DSHOT_ERROR_TX_INIT_SUCCESS}; } dshot_result_t DShotRMT::_initRXChannel() @@ -372,7 +312,7 @@ dshot_result_t DShotRMT::_initRXChannel() // Double check if bidirectional mode is enabled if (!_is_bidirectional) { - return {true, NONE}; + return {true, dshot_msg_code_t::DSHOT_ERROR_NONE}; } _rx_channel_config.gpio_num = _gpio; @@ -386,19 +326,19 @@ dshot_result_t DShotRMT::_initRXChannel() if (rmt_new_rx_channel(&_rx_channel_config, &_rmt_rx_channel) != DSHOT_OK) { - return {false, RX_INIT_FAILED}; + return {false, dshot_msg_code_t::DSHOT_ERROR_RX_INIT_FAILED}; } // Register the callback function that will be triggered when a frame is received _rx_event_callbacks.on_recv_done = _on_rx_done; if (rmt_rx_register_event_callbacks(_rmt_rx_channel, &_rx_event_callbacks, this) != DSHOT_OK) { - return {false, CALLBACK_REGISTERING_FAILED}; + return {false, dshot_msg_code_t::DSHOT_ERROR_CALLBACK_REGISTERING_FAILED}; } if (rmt_enable(_rmt_rx_channel) != DSHOT_OK) { - return {false, RX_INIT_FAILED}; + return {false, dshot_msg_code_t::DSHOT_ERROR_RX_INIT_FAILED}; } // Start the receiver to wait for incoming telemetry data @@ -406,10 +346,10 @@ dshot_result_t DShotRMT::_initRXChannel() size_t rx_size_bytes = GCR_BITS_PER_FRAME * sizeof(rmt_symbol_word_t); if (rmt_receive(_rmt_rx_channel, rx_symbols, rx_size_bytes, &_rmt_rx_config) != DSHOT_OK) { - return {false, RECEIVER_FAILED}; + return {false, dshot_msg_code_t::DSHOT_ERROR_RECEIVER_FAILED}; } - return {true, RX_INIT_SUCCESS}; + return {true, dshot_msg_code_t::DSHOT_ERROR_RX_INIT_SUCCESS}; } dshot_result_t DShotRMT::_initDShotEncoder() @@ -418,10 +358,10 @@ dshot_result_t DShotRMT::_initDShotEncoder() if (rmt_new_copy_encoder(&encoder_config, &_dshot_encoder) != DSHOT_OK) { - return {false, ENCODER_INIT_FAILED}; + return {false, dshot_msg_code_t::DSHOT_ERROR_ENCODER_INIT_FAILED}; } - return {true, ENCODER_INIT_SUCCESS}; + return {true, dshot_msg_code_t::DSHOT_ERROR_ENCODER_INIT_SUCCESS}; } // Private Packet Management Functions @@ -439,7 +379,7 @@ dshot_packet_t DShotRMT::_buildDShotPacket(const uint16_t &value) return packet; } -uint16_t DShotRMT::_parseDShotPacket(const dshot_packet_t &packet) +uint16_t DShotRMT::_buildDShotFrameValue(const dshot_packet_t &packet) { // Combine throttle, telemetry bit, and CRC into a single 16-bit frame uint16_t data_and_telemetry = (packet.throttle_value << 1) | packet.telemetric_request; @@ -479,22 +419,13 @@ void DShotRMT::_preCalculateRMTTicks() } } -void DShotRMT::_preCalculateBitPositions() -{ - // Pre-calculate bit positions to avoid redundant calculations in the encoding loop - for (int i = 0; i < DSHOT_BITS_PER_FRAME; ++i) - { - _bitPositions[i] = DSHOT_BITS_PER_FRAME - 1 - i; - } -} - // Private Frame Processing Functions dshot_result_t DShotRMT::_sendDShotFrame(const dshot_packet_t &packet) { // Ensure enough time has passed since the last transmission if (!_timer_signal()) { - return {true, NONE}; + return {true, dshot_msg_code_t::DSHOT_ERROR_NONE}; } rmt_symbol_word_t tx_symbols[DSHOT_BITS_PER_FRAME]; @@ -509,22 +440,22 @@ dshot_result_t DShotRMT::_sendDShotFrame(const dshot_packet_t &packet) if (rmt_transmit(_rmt_tx_channel, _dshot_encoder, tx_symbols, tx_size_bytes, &_rmt_tx_config) != DSHOT_OK) { - return {false, TRANSMISSION_FAILED}; + return {false, dshot_msg_code_t::DSHOT_ERROR_TRANSMISSION_FAILED}; } _timer_reset(); // Reset the timer for the next frame - return {true, TRANSMISSION_SUCCESS}; + return {true, dshot_msg_code_t::DSHOT_ERROR_TRANSMISSION_SUCCESS}; } // This function needs to be fast, as it generates the RMT symbols just before sending dshot_result_t IRAM_ATTR DShotRMT::_encodeDShotFrame(const dshot_packet_t &packet, rmt_symbol_word_t *symbols) { - _encoded_frame_value = _parseDShotPacket(packet); + _encoded_frame_value = _buildDShotFrameValue(packet); for (int i = 0; i < DSHOT_BITS_PER_FRAME; ++i) { - int bit_position = _bitPositions[i]; + int bit_position = DSHOT_BITS_PER_FRAME - 1 - i; bool bit = (_encoded_frame_value >> bit_position) & 1; // A '1' bit has a longer high-time, a '0' bit has a shorter high-time @@ -534,7 +465,7 @@ dshot_result_t IRAM_ATTR DShotRMT::_encodeDShotFrame(const dshot_packet_t &packe symbols[i].duration1 = bit ? _rmt_ticks.t1l_ticks : _rmt_ticks.t0l_ticks; } - return {true, ENCODING_SUCCESS}; + return {true, dshot_msg_code_t::DSHOT_ERROR_ENCODING_SUCCESS}; } // Placed in IRAM for high performance, as it's called from an ISR context @@ -557,11 +488,11 @@ uint16_t IRAM_ATTR DShotRMT::_decodeDShotFrame(const rmt_symbol_word_t *symbols) uint16_t data_and_crc = (decoded_frame & DSHOT_FULL_PACKET); // Step 4: Extract data and CRC from the 16-bit frame - uint16_t received_data = data_and_crc >> 4; + uint16_t received_data = data_and_crc >> DSHOT_CRC_BIT_SHIFT; uint16_t received_crc = data_and_crc & DSHOT_CRC_MASK; // Step 5: A valid response must have the telemetry request bit set to 1. This is a sanity check. - if (!((received_data >> 11) & 1)) + if (!((received_data >> DSHOT_TELEMETRY_BIT_POSITION) & 1)) { return DSHOT_NULL_PACKET; } @@ -612,4 +543,4 @@ bool IRAM_ATTR DShotRMT::_on_rx_done(rmt_channel_handle_t rmt_rx_channel, const } return false; -} +} \ No newline at end of file diff --git a/src/DShotRMT.h b/src/DShotRMT.h index 76370ab..ebda5d0 100644 --- a/src/DShotRMT.h +++ b/src/DShotRMT.h @@ -9,101 +9,146 @@ #pragma once #include -#include +#include "dshot_definitions.h" #include #include #include #include -// DShot Protocol Constants & Types -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 DEFAULT_MOTOR_MAGNET_COUNT = 14; - -// Custom status codes -static constexpr auto DSHOT_OK = 0; -static constexpr auto DSHOT_ERROR = 1; - -// DShot Modes -typedef enum dshot_mode -{ - DSHOT_OFF, - DSHOT150, - DSHOT300, - DSHOT600, - DSHOT1200 -} dshot_mode_t; - -// DShot Packet Structure -typedef struct dshot_packet -{ - uint16_t throttle_value : 11; - bool telemetric_request : 1; - uint16_t checksum : 4; -} dshot_packet_t; - -// DShot Timing Configuration -typedef struct dshot_timing -{ - double bit_length_us; - double t1h_lenght_us; -} dshot_timing_us_t; - -// RMT Timing Configuration -typedef struct rmt_ticks -{ - uint16_t bit_length_ticks; - 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 dshot_result -{ - bool success; - const char *msg; - uint16_t erpm; - uint16_t motor_rpm; -} dshot_result_t; - -// Command Type Alias -typedef dshotCommands_e dshot_commands_t; - -// Helper Functions -void printDShotResult(dshot_result_t &result, Stream &output = Serial); - -// -// DShotRMT Main Class +/** + * @brief DShotRMT Main Class for DShot signal generation and reception. + * + * This class provides an interface to generate DShot signals for Electronic Speed Controllers (ESCs) + * and to receive telemetry data using the ESP32's RMT peripheral. + */ class DShotRMT { public: - // Constructors & Destructor - explicit DShotRMT(gpio_num_t gpio = GPIO_NUM_16, dshot_mode_t mode = DSHOT300, bool is_bidirectional = false, uint16_t magnet_count = DEFAULT_MOTOR_MAGNET_COUNT); + /** + * @brief Constructor for DShotRMT with GPIO number. + * @param gpio The GPIO pin number to use for DShot communication. + * @param mode The DShot mode (e.g., DSHOT150, DSHOT300, DSHOT600). + * @param is_bidirectional True if bidirectional DShot is enabled, false otherwise. + * @param magnet_count The number of magnets in the motor for RPM calculation. + */ + explicit DShotRMT(gpio_num_t gpio = GPIO_NUM_16, dshot_mode_t mode = dshot_mode_t::DSHOT300, bool is_bidirectional = false, uint16_t magnet_count = DEFAULT_MOTOR_MAGNET_COUNT); + + /** + * @brief Constructor for DShotRMT with Arduino pin number. + * @param pin_nr The Arduino pin number to use for DShot communication. + * @param mode The DShot mode (e.g., DSHOT150, DSHOT300, DSHOT600). + * @param is_bidirectional True if bidirectional DShot is enabled, false otherwise. + * @param magnet_count The number of magnets in the motor for RPM calculation. + */ DShotRMT(uint16_t pin_nr, dshot_mode_t mode, bool is_bidirectional, uint16_t magnet_count = DEFAULT_MOTOR_MAGNET_COUNT); + /** + * @brief Destructor for DShotRMT. + * Cleans up RMT channels and encoder resources. + */ ~DShotRMT(); // Public Core Functions + /** + * @brief Initializes the DShot RMT channels and encoder. + * @return dshot_result_t indicating success or failure of the initialization. + */ dshot_result_t begin(); + + /** + * @brief Sends a DShot throttle value to the ESC. + * @param throttle The throttle value (48-2047). A value of 0 sends a motor stop command. + * @return dshot_result_t indicating success or failure of the transmission. + */ dshot_result_t sendThrottle(uint16_t throttle); + + /** + * @brief Sends a DShot throttle value as a percentage to the ESC. + * @param percent The throttle percentage (0.0f - 100.0f). + * @return dshot_result_t indicating success or failure of the transmission. + */ dshot_result_t sendThrottlePercent(float percent); + + /** + * @brief Sends a single DShot command to the ESC. + * @param command The DShot command value (0-47). + * @return dshot_result_t indicating success or failure of the transmission. + */ dshot_result_t sendCommand(uint16_t command); - dshot_result_t sendCommand(dshot_commands_t dshot_command, uint16_t repeat_count = DEFAULT_CMD_REPEAT_COUNT, uint16_t delay_us = DEFAULT_CMD_DELAY_US); + + /** + * @brief Sends a DShot command multiple times with a delay between repetitions. This is a blocking function. + * @param dshot_command The DShot command to send. + * @param repeat_count The number of times to repeat the command. + * @param delay_us The delay in microseconds between repetitions. + * @return dshot_result_t indicating success or failure of the transmission. + */ + dshot_result_t sendCommand(dshotCommands_e dshot_command, uint16_t repeat_count = DEFAULT_CMD_REPEAT_COUNT, uint16_t delay_us = DEFAULT_CMD_DELAY_US); + + /** + * @brief Retrieves telemetry data from the ESC. + * @param magnet_count The number of magnets in the motor. If 0, uses the stored motor_magnet_count. + * @return dshot_result_t containing telemetry data (eRPM, motor RPM) if successful. + */ dshot_result_t getTelemetry(uint16_t magnet_count = 0); + + /** + * @brief Sends a command to the ESC to request ESC information. + * @return dshot_result_t indicating success or failure of the command transmission. + */ dshot_result_t getESCInfo(); + + /** + * @brief Sets the motor spin direction. + * @param reversed True for reversed direction, false for normal. + * @return dshot_result_t indicating success or failure of the command transmission. + */ dshot_result_t setMotorSpinDirection(bool reversed); + + /** + * @brief Sends a command to the ESC to save its current settings. + * Use with caution as this writes to ESC's non-volatile memory. + * @return dshot_result_t indicating success or failure of the command transmission. + */ dshot_result_t saveESCSettings(); // Public Utility & Info Functions + /** + * @brief Sets the motor magnet count for RPM calculation. + * @param magnet_count The number of magnets in the motor. + */ void setMotorMagnetCount(uint16_t magnet_count); - void printDShotInfo(Stream &output = Serial) const; - void printCpuInfo(Stream &output = Serial) const; + + /** + * @brief Gets the current DShot mode. + * @return The current dshot_mode_t. + */ + dshot_mode_t getMode() const { return _mode; } + + /** + * @brief Checks if bidirectional DShot is enabled. + * @return True if bidirectional DShot is enabled, false otherwise. + */ + bool isBidirectional() const { return _is_bidirectional; } + + /** + * @brief Gets the last encoded DShot frame value. + * @return The 16-bit encoded DShot frame value. + */ + uint16_t getEncodedFrameValue() const { return _encoded_frame_value; } + + /** + * @brief Gets the last transmitted throttle value. + * @return The last transmitted throttle value. + */ + uint16_t getThrottleValue() const { return _packet.throttle_value; } // Deprecated Methods + /** + * @brief Deprecated. Use sendThrottle() instead. + * @param throttle The throttle value. + * @return True on success, false on failure. + */ [[deprecated("Use sendThrottle() instead")]] bool setThrottle(uint16_t throttle) { @@ -111,6 +156,11 @@ public: return result.success; } + /** + * @brief Deprecated. Use sendCommand() instead. + * @param command The DShot command. + * @return True on success, false on failure. + */ [[deprecated("Use sendCommand() instead")]] bool sendDShotCommand(uint16_t command) { @@ -118,6 +168,11 @@ public: return result.success; } + /** + * @brief Deprecated. Use getTelemetry() instead. + * @param magnet_count The number of magnets in the motor. + * @return The motor RPM. + */ [[deprecated("Use getTelemetry() instead")]] uint32_t getMotorRPM(uint8_t magnet_count) { @@ -126,55 +181,9 @@ public: } private: - // 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 - static constexpr auto const DSHOT_RX_TIMEOUT_MS = 2; - static constexpr auto const DSHOT_PADDING_US = 20; // Add to pause between frames for compatibility - static constexpr auto const RMT_BUFFER_SYMBOLS = 128; - 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 - 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; - static constexpr auto const DSHOT_PULSE_MIN = 800; // 0.8us minimum pulse - static constexpr auto const DSHOT_PULSE_MAX = 8000; // 8.0us maximum pulse - static constexpr auto const DSHOT_TELEMETRY_INVALID = DSHOT_THROTTLE_MAX; - - // Error Messages - 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 *ENCODER_INIT_SUCCESS = "RMT encoder initialized successfully"; - static constexpr char const *ENCODER_INIT_FAILED = "RMT encoder init failed!"; - static constexpr char const *ENCODING_SUCCESS = "Packet encoded successfully"; - 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 *PERCENT_NOT_IN_RANGE = "Percent not in range! (0.0 - 100.0)"; - 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!"; - static constexpr char const *TIMING_CORRECTION = "Timing correction!"; - static constexpr char const *CALLBACK_REGISTERING_FAILED = "RMT RX Callback registering failed!"; - static constexpr char const *INVALID_COMMAND = "Invalid command!"; - static constexpr char const *COMMAND_SUCCESS = "DShot command sent successfully"; - // --- UTILITY METHODS --- - bool _isValidCommand(dshot_commands_t command); - dshot_result_t _executeCommand(dshot_commands_t command); + bool _isValidCommand(dshotCommands_e command); + dshot_result_t _executeCommand(dshotCommands_e command); // Core Configuration Variables gpio_num_t _gpio; @@ -188,12 +197,11 @@ private: rmt_ticks_t _rmt_ticks; uint16_t _last_throttle; uint64_t _last_transmission_time_us; - uint64_t _last_command_timestamp; - uint16_t _encoded_frame_value; - dshot_packet_t _packet; - uint8_t _bitPositions[DSHOT_BITS_PER_FRAME]; - uint16_t _level0; // Signal level for the first part of a pulse (always HIGH for DShot) - uint16_t _level1; // Signal level for the second part of a pulse (always LOW for DShot) + uint64_t _last_command_timestamp; + uint16_t _encoded_frame_value; + dshot_packet_t _packet; + uint16_t _level0; // DShot protocol: Signal is idle-low, so pulses start by going HIGH. + uint16_t _level1; // DShot protocol: Signal returns to LOW after the high pulse. // RMT Hardware Handles rmt_channel_handle_t _rmt_tx_channel; @@ -218,10 +226,9 @@ private: // Private Packet Management Functions dshot_packet_t _buildDShotPacket(const uint16_t &value); - uint16_t _parseDShotPacket(const dshot_packet_t &packet); + uint16_t _buildDShotFrameValue(const dshot_packet_t &packet); uint16_t _calculateCRC(const uint16_t &data); void _preCalculateRMTTicks(); - void _preCalculateBitPositions(); // Private Frame Processing Functions dshot_result_t _sendDShotFrame(const dshot_packet_t &packet); @@ -234,10 +241,4 @@ private: // Static Callback Functions static bool _on_rx_done(rmt_channel_handle_t rmt_rx_channel, const rmt_rx_done_event_data_t *edata, void *user_data); - - // Command Constants - static constexpr auto DEFAULT_CMD_DELAY_US = 10; - static constexpr auto DEFAULT_CMD_REPEAT_COUNT = 1; - static constexpr auto SETTINGS_COMMAND_REPEATS = 10; // Settings commands need 10 repeats - static constexpr auto SETTINGS_COMMAND_DELAY_US = 5; -}; +}; \ No newline at end of file diff --git a/src/DShotRMT_Utils.cpp b/src/DShotRMT_Utils.cpp new file mode 100644 index 0000000..08681c8 --- /dev/null +++ b/src/DShotRMT_Utils.cpp @@ -0,0 +1,30 @@ +#include "DShotRMT_Utils.h" +#include "DShotRMT.h" // Include DShotRMT.h for DShotRMT class definition + +void printDShotInfo(const DShotRMT &dshot_rmt, Stream &output) +{ + output.println("\n === DShot Signal Info === "); + output.printf("Current Mode: DSHOT%d\n", dshot_rmt.getMode() == dshot_mode_t::DSHOT150 ? 150 : + dshot_rmt.getMode() == dshot_mode_t::DSHOT300 ? 300 : + dshot_rmt.getMode() == dshot_mode_t::DSHOT600 ? 600 : + dshot_rmt.getMode() == dshot_mode_t::DSHOT1200 ? 1200 : 0); + output.printf("Bidirectional: %s\n", dshot_rmt.isBidirectional() ? "YES" : "NO"); + output.printf("Current Packet: "); + + for (int i = DSHOT_BITS_PER_FRAME - 1; i >= 0; --i) + { + output.print((dshot_rmt.getEncodedFrameValue() >> i) & 1); + } + + output.printf("\nCurrent Value: %u\n", dshot_rmt.getThrottleValue()); +} + +void printCpuInfo(Stream &output) +{ + output.println("\n === CPU Info === "); + output.printf("Chip Model: %s\n", ESP.getChipModel()); + output.printf("Chip Revision: %d\n", ESP.getChipRevision()); + output.printf("CPU Freq = %lu MHz\n", ESP.getCpuFreqMHz()); + output.printf("XTAL Freq = %lu MHz\n", getXtalFrequencyMhz()); + output.printf("APB Freq = %lu Hz\n", getApbFrequency()); +} diff --git a/src/DShotRMT_Utils.h b/src/DShotRMT_Utils.h new file mode 100644 index 0000000..97983ca --- /dev/null +++ b/src/DShotRMT_Utils.h @@ -0,0 +1,21 @@ +#pragma once + +#include +#include "DShotRMT.h" // Include DShotRMT.h for DShotRMT class definition + +/** + * @brief Utility functions for printing DShot and CPU information + */ + +/** + * @brief Prints detailed DShot signal information for a given DShotRMT instance. + * @param dshot_rmt The DShotRMT instance to get information from. + * @param output The output stream (e.g., Serial) to print to. Defaults to Serial. + */ +void printDShotInfo(const DShotRMT &dshot_rmt, Stream &output = Serial); + +/** + * @brief Prints detailed CPU information. + * @param output The output stream (e.g., Serial) to print to. Defaults to Serial. + */ +void printCpuInfo(Stream &output = Serial); diff --git a/src/dshot_commands.h b/src/dshot_commands.h deleted file mode 100644 index 702a065..0000000 --- a/src/dshot_commands.h +++ /dev/null @@ -1,68 +0,0 @@ -/* - * This file is part of Cleanflight and Betaflight. - * - * Cleanflight and Betaflight are free software. You can redistribute - * this software and/or modify this software under the terms of the - * GNU General Public License as published by the Free Software - * Foundation, either version 3 of the License, or (at your option) - * any later version. - * - * Cleanflight and Betaflight are distributed in the hope that they - * will be useful, but WITHOUT ANY WARRANTY; without even the implied - * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - * See the GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this software. - * - * If not, see . - */ - -#pragma once - -/* - DshotSettingRequest (KISS24). Spin direction, 3d and save Settings require 10 requests.. and the TLM Byte must always be high if 1-47 are used to send settings - - 3D Mode: - 0 = stop - 48 (low) - 1047 (high) -> negative direction - 1048 (low) - 2047 (high) -> positive direction - */ - -typedef enum -{ - DSHOT_CMD_MOTOR_STOP = 0, - DSHOT_CMD_BEACON1, - DSHOT_CMD_BEACON2, - DSHOT_CMD_BEACON3, - DSHOT_CMD_BEACON4, - DSHOT_CMD_BEACON5, - DSHOT_CMD_ESC_INFO, // V2 includes settings - DSHOT_CMD_SPIN_DIRECTION_1, - DSHOT_CMD_SPIN_DIRECTION_2, - DSHOT_CMD_3D_MODE_OFF, - DSHOT_CMD_3D_MODE_ON, - DSHOT_CMD_SETTINGS_REQUEST, // Currently not implemented - DSHOT_CMD_SAVE_SETTINGS, - DSHOT_CMD_EXTENDED_TELEMETRY_ENABLE, - DSHOT_CMD_EXTENDED_TELEMETRY_DISABLE, - DSHOT_CMD_SPIN_DIRECTION_NORMAL = 20, - DSHOT_CMD_SPIN_DIRECTION_REVERSED = 21, - DSHOT_CMD_LED0_ON, // BLHeli32 only - DSHOT_CMD_LED1_ON, // BLHeli32 only - DSHOT_CMD_LED2_ON, // BLHeli32 only - DSHOT_CMD_LED3_ON, // BLHeli32 only - DSHOT_CMD_LED0_OFF, // BLHeli32 only - DSHOT_CMD_LED1_OFF, // BLHeli32 only - DSHOT_CMD_LED2_OFF, // BLHeli32 only - DSHOT_CMD_LED3_OFF, // BLHeli32 only - DSHOT_CMD_AUDIO_STREAM_MODE_ON_OFF = 30, // KISS audio Stream mode on/Off - DSHOT_CMD_SILENT_MODE_ON_OFF = 31, // KISS silent Mode on/Off - DSHOT_CMD_MAX = 47 -} dshotCommands_e; - -typedef enum -{ - DSHOT_CMD_TYPE_INLINE = 0, // dshot commands sent inline with motor signal (motors must be enabled) - DSHOT_CMD_TYPE_BLOCKING // dshot commands sent in blocking method (motors must be disabled) -} dshotCommandType_e; diff --git a/src/dshot_definitions.h b/src/dshot_definitions.h new file mode 100644 index 0000000..e8ed232 --- /dev/null +++ b/src/dshot_definitions.h @@ -0,0 +1,267 @@ +#pragma once + +#include +#include // Added for gpio_num_t +#include +#include +#include // Added for std::atomic + +/** + * @brief DShot Modes + * Defines the available DShot communication speeds. + */ +enum class dshot_mode_t +{ + DSHOT_OFF, + DSHOT150, + DSHOT300, + DSHOT600, + DSHOT1200 +}; + +/** + * @brief DShot Packet Structure + * Represents the 16-bit DShot data packet sent to the ESC. + */ +typedef struct dshot_packet +{ + uint16_t throttle_value : 11; ///< 11-bit throttle value or command. + bool telemetric_request : 1; ///< 1-bit telemetry request flag. + uint16_t checksum : 4; ///< 4-bit CRC checksum. +} dshot_packet_t; + +/** + * @brief DShot Timing Configuration + * Defines the bit length and high time for a '1' bit in microseconds for each DShot mode. + */ +typedef struct dshot_timing +{ + double bit_length_us; ///< Total duration of one bit in microseconds. + double t1h_lenght_us; ///< High time duration for a '1' bit in microseconds. +} dshot_timing_us_t; + +/** + * @brief RMT Timing Configuration + * Stores pre-calculated timing values in RMT ticks for efficient signal generation. + */ +typedef struct rmt_ticks +{ + uint16_t bit_length_ticks; ///< Total duration of one bit in RMT ticks. + uint16_t t1h_ticks; ///< High time duration for a '1' bit in RMT ticks. + uint16_t t1l_ticks; ///< Low time duration for a '1' bit in RMT ticks. + uint16_t t0h_ticks; ///< High time duration for a '0' bit in RMT ticks. + uint16_t t0l_ticks; ///< Low time duration for a '0' bit in RMT ticks. +} rmt_ticks_t; + +/** + * @brief DShot Error Codes + * Enum class for specific error and success codes returned by DShotRMT functions. + */ +enum class dshot_msg_code_t +{ + DSHOT_ERROR_NONE = 0, + DSHOT_ERROR_UNKNOWN, + DSHOT_ERROR_TX_INIT_FAILED, + DSHOT_ERROR_RX_INIT_FAILED, + DSHOT_ERROR_ENCODER_INIT_FAILED, + DSHOT_ERROR_CALLBACK_REGISTERING_FAILED, + DSHOT_ERROR_RECEIVER_FAILED, + DSHOT_ERROR_TRANSMISSION_FAILED, + DSHOT_ERROR_THROTTLE_NOT_IN_RANGE, + DSHOT_ERROR_PERCENT_NOT_IN_RANGE, + DSHOT_ERROR_COMMAND_NOT_VALID, + DSHOT_ERROR_BIDIR_NOT_ENABLED, + DSHOT_ERROR_TELEMETRY_FAILED, + DSHOT_ERROR_INVALID_MAGNET_COUNT, + DSHOT_ERROR_INVALID_COMMAND, + DSHOT_ERROR_TIMING_CORRECTION, + DSHOT_ERROR_INIT_FAILED, + DSHOT_ERROR_INIT_SUCCESS, + DSHOT_ERROR_TX_INIT_SUCCESS, + DSHOT_ERROR_RX_INIT_SUCCESS, + DSHOT_ERROR_ENCODER_INIT_SUCCESS, + DSHOT_ERROR_ENCODING_SUCCESS, + DSHOT_ERROR_TRANSMISSION_SUCCESS, + DSHOT_ERROR_TELEMETRY_SUCCESS, + DSHOT_ERROR_COMMAND_SUCCESS +}; + +/** + * @brief Unified DShot Result Structure + * Contains the success status, an error code, and optional telemetry data. + */ +typedef struct dshot_result +{ + bool success; + dshot_msg_code_t error_code; ///< Specific error or success code. + uint16_t erpm; ///< Electrical RPM (eRPM) if telemetry is successful. + uint16_t motor_rpm; ///< Motor RPM if telemetry is successful and magnet count is known. +} dshot_result_t; + +/** + * @brief DShot Commands + * Enum class for standard DShot commands that can be sent to an ESC. + */ +enum class dshotCommands_e +{ + DSHOT_CMD_MOTOR_STOP = 0, + DSHOT_CMD_BEACON1, + DSHOT_CMD_BEACON2, + DSHOT_CMD_BEACON3, + DSHOT_CMD_BEACON4, + DSHOT_CMD_BEACON5, + DSHOT_CMD_ESC_INFO, // V2 includes settings + DSHOT_CMD_SPIN_DIRECTION_1, + DSHOT_CMD_SPIN_DIRECTION_2, + DSHOT_CMD_3D_MODE_OFF, + DSHOT_CMD_3D_MODE_ON, + DSHOT_CMD_SETTINGS_REQUEST, // Currently not implemented + DSHOT_CMD_SAVE_SETTINGS, + DSHOT_CMD_EXTENDED_TELEMETRY_ENABLE, + DSHOT_CMD_EXTENDED_TELEMETRY_DISABLE, + DSHOT_CMD_SPIN_DIRECTION_NORMAL = 20, + DSHOT_CMD_SPIN_DIRECTION_REVERSED = 21, + DSHOT_CMD_LED0_ON, // BLHeli32 only + DSHOT_CMD_LED1_ON, // BLHeli32 only + DSHOT_CMD_LED2_ON, // BLHeli32 only + DSHOT_CMD_LED3_ON, // BLHeli32 only + DSHOT_CMD_LED0_OFF, // BLHeli32 only + DSHOT_CMD_LED1_OFF, // BLHeli32 only + DSHOT_CMD_LED2_OFF, // BLHeli32 only + DSHOT_CMD_LED3_OFF, // BLHeli32 only + DSHOT_CMD_AUDIO_STREAM_MODE_ON_OFF = 30, // KISS audio Stream mode on/Off + DSHOT_CMD_SILENT_MODE_ON_OFF = 31, // KISS silent Mode on/Off + DSHOT_CMD_MAX = 47 +}; + +/** + * @brief DShot Command Type Enum + * Defines how DShot commands are sent. + */ +enum class dshotCommandType_e +{ + DSHOT_CMD_TYPE_INLINE = 0, ///< Commands sent inline with motor signal (motors must be enabled). + DSHOT_CMD_TYPE_BLOCKING ///< Commands sent in blocking method (motors must be disabled). +}; + +// DShot Protocol Constants +const uint16_t DSHOT_THROTTLE_FAILSAFE = 0; +const uint16_t DSHOT_THROTTLE_MIN = 48; +const uint16_t DSHOT_THROTTLE_MAX = 2047; +const uint16_t DSHOT_BITS_PER_FRAME = 16; +const uint16_t DEFAULT_MOTOR_MAGNET_COUNT = 14; + +// Custom status codes +const int DSHOT_OK = 0; +const int DSHOT_ERROR = 1; + +// Configuration Constants (from DShotRMT.h private section) +const uint16_t DSHOT_NULL_PACKET = 0b0000000000000000; +const uint16_t DSHOT_FULL_PACKET = 0b1111111111111111; +const uint16_t DSHOT_CRC_MASK = 0b0000000000001111; +const rmt_clock_source_t DSHOT_CLOCK_SRC_DEFAULT = RMT_CLK_SRC_DEFAULT; +const uint32_t DSHOT_RMT_RESOLUTION = 8 * 1000 * 1000; // 8 MHz resolution +const uint16_t RMT_TICKS_PER_US = DSHOT_RMT_RESOLUTION / (1 * 1000 * 1000); // RMT Ticks per microsecond +const uint16_t DSHOT_RX_TIMEOUT_MS = 2; +const uint16_t DSHOT_PADDING_US = 20; // Add to pause between frames for compatibility +const uint16_t RMT_BUFFER_SYMBOLS = 64; +const uint16_t RMT_QUEUE_DEPTH = 1; +const uint16_t GCR_BITS_PER_FRAME = 21; // Number of GCR bits in a DShot answer frame +const uint16_t POLE_PAIRS_MIN = 1; +const uint16_t MAGNETS_PER_POLE_PAIR = 2; +const uint16_t NO_DSHOT_TELEMETRY = 0; +const uint16_t DSHOT_PULSE_MIN = 800; // 0.8us minimum pulse +const uint16_t DSHOT_PULSE_MAX = 8000; // 8.0us maximum pulse +const uint16_t DSHOT_TELEMETRY_INVALID = DSHOT_THROTTLE_MAX; +const uint16_t DSHOT_TELEMETRY_BIT_POSITION = 11; // Bit position of the telemetry request flag in the DShot frame +const uint16_t DSHOT_CRC_BIT_SHIFT = 4; // Number of bits to shift to extract data from data_and_crc + +// Command Constants (from DShotRMT.h private section) +const uint16_t DEFAULT_CMD_DELAY_US = 10; +const uint16_t DEFAULT_CMD_REPEAT_COUNT = 1; +const uint16_t SETTINGS_COMMAND_REPEATS = 10; // Settings commands need 10 repeats +const uint16_t SETTINGS_COMMAND_DELAY_US = 5; + +// Timing parameters for each DShot mode +// Format: {bit_length_us, t1h_length_us} +const dshot_timing_us_t DSHOT_TIMING_US[] = { + {0.00, 0.00}, // DSHOT_OFF + {6.67, 5.00}, // DSHOT150 + {3.33, 2.50}, // DSHOT300 + {1.67, 1.25}, // DSHOT600 + {0.83, 0.67}}; // DSHOT1200 + +// Error Messages +const char *const NONE = ""; +const char *const UNKNOWN_ERROR = "Unknown Error!"; +const char *const INIT_SUCCESS = "SignalGeneratorRMT initialized successfully"; +const char *const INIT_FAILED = "SignalGeneratorRMT init failed!"; +const char *const TX_INIT_SUCCESS = "TX RMT channel initialized successfully"; +const char *const TX_INIT_FAILED = "TX RMT channel init failed!"; +const char *const RX_INIT_SUCCESS = "RX RMT channel initialized successfully"; +const char *const RX_INIT_FAILED = "RX RMT channel init failed!"; +const char *const ENCODER_INIT_SUCCESS = "RMT encoder initialized successfully"; +const char *const ENCODER_INIT_FAILED = "RMT encoder init failed!"; +const char *const ENCODING_SUCCESS = "Packet encoded successfully"; +const char *const TRANSMISSION_SUCCESS = "Transmission successfully"; +const char *const TRANSMISSION_FAILED = "Transmission failed!"; +const char *const RECEIVER_FAILED = "RMT receiver failed!"; +const char *const THROTTLE_NOT_IN_RANGE = "Throttle not in range! (48 - 2047)"; +const char *const PERCENT_NOT_IN_RANGE = "Percent not in range! (0.0 - 100.0)"; +const char *const COMMAND_NOT_VALID = "Command not valid! (0 - 47)"; +const char *const BIDIR_NOT_ENABLED = "Bidirectional DShot not enabled!"; +const char *const TELEMETRY_SUCCESS = "Valid Telemetric Frame received!"; +const char *const TELEMETRY_FAILED = "No valid Telemetric Frame received!"; +const char *const INVALID_MAGNET_COUNT = "Invalid motor magnet count!"; +const char *const TIMING_CORRECTION = "Timing correction!"; +const char *const CALLBACK_REGISTERING_FAILED = "RMT RX Callback registering failed!"; +const char *const INVALID_COMMAND = "Invalid command!"; +const char *const COMMAND_SUCCESS = "DShot command sent successfully"; + +// Helper Functions +/** + * @brief Prints the result of a DShot operation to the specified output stream. + * @param result The dshot_result_t structure containing the operation's outcome. + * @param output The output stream (e.g., Serial) to print to. Defaults to Serial. + */ +inline void printDShotResult(dshot_result_t &result, Stream &output = Serial) +{ + const char *msg_str; + switch (result.error_code) + { + case dshot_msg_code_t::DSHOT_ERROR_NONE: msg_str = "None"; break; + case dshot_msg_code_t::DSHOT_ERROR_UNKNOWN: msg_str = "Unknown Error!"; break; + case dshot_msg_code_t::DSHOT_ERROR_TX_INIT_FAILED: msg_str = "TX RMT channel init failed!"; break; + case dshot_msg_code_t::DSHOT_ERROR_RX_INIT_FAILED: msg_str = "RX RMT channel init failed!"; break; + case dshot_msg_code_t::DSHOT_ERROR_ENCODER_INIT_FAILED: msg_str = "RMT encoder init failed!"; break; + case dshot_msg_code_t::DSHOT_ERROR_CALLBACK_REGISTERING_FAILED: msg_str = "RMT RX Callback registering failed!"; break; + case dshot_msg_code_t::DSHOT_ERROR_RECEIVER_FAILED: msg_str = "RMT receiver failed!"; break; + case dshot_msg_code_t::DSHOT_ERROR_TRANSMISSION_FAILED: msg_str = "Transmission failed!"; break; + case dshot_msg_code_t::DSHOT_ERROR_THROTTLE_NOT_IN_RANGE: msg_str = "Throttle not in range! (48 - 2047)"; break; + case dshot_msg_code_t::DSHOT_ERROR_PERCENT_NOT_IN_RANGE: msg_str = "Percent not in range! (0.0 - 100.0)"; break; + case dshot_msg_code_t::DSHOT_ERROR_COMMAND_NOT_VALID: msg_str = "Command not valid! (0 - 47)"; break; + case dshot_msg_code_t::DSHOT_ERROR_BIDIR_NOT_ENABLED: msg_str = "Bidirectional DShot not enabled!"; break; + case dshot_msg_code_t::DSHOT_ERROR_TELEMETRY_FAILED: msg_str = "No valid Telemetric Frame received!"; break; + case dshot_msg_code_t::DSHOT_ERROR_INVALID_MAGNET_COUNT: msg_str = "Invalid motor magnet count!"; break; + case dshot_msg_code_t::DSHOT_ERROR_INVALID_COMMAND: msg_str = "Invalid command!"; break; + case dshot_msg_code_t::DSHOT_ERROR_TIMING_CORRECTION: msg_str = "Timing correction!"; break; + case dshot_msg_code_t::DSHOT_ERROR_INIT_FAILED: msg_str = "SignalGeneratorRMT init failed!"; break; + case dshot_msg_code_t::DSHOT_ERROR_INIT_SUCCESS: msg_str = "SignalGeneratorRMT initialized successfully"; break; + case dshot_msg_code_t::DSHOT_ERROR_TX_INIT_SUCCESS: msg_str = "TX RMT channel initialized successfully"; break; + case dshot_msg_code_t::DSHOT_ERROR_RX_INIT_SUCCESS: msg_str = "RX RMT channel initialized successfully"; break; + case dshot_msg_code_t::DSHOT_ERROR_ENCODING_SUCCESS: msg_str = "Packet encoded successfully"; break; + case dshot_msg_code_t::DSHOT_ERROR_TRANSMISSION_SUCCESS: msg_str = "Transmission successfully"; break; + case dshot_msg_code_t::DSHOT_ERROR_TELEMETRY_SUCCESS: msg_str = "Valid Telemetric Frame received!"; break; + case dshot_msg_code_t::DSHOT_ERROR_COMMAND_SUCCESS: msg_str = "DShot command sent successfully"; break; + default: msg_str = "Unhandled Error Code!"; break; + } + output.printf("Status: %s - %s", result.success ? "SUCCESS" : "FAILED", msg_str); + + // 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(); +} \ No newline at end of file