diff --git a/README.md b/README.md index c3421a6..c5f7b46 100644 --- a/README.md +++ b/README.md @@ -108,8 +108,9 @@ The main class is `DShotRMT`. Here are the most important methods: - `begin()`: Initializes the DShot RMT channels and encoder. - `sendThrottlePercent(float percent)`: Sends a throttle value as a percentage (0.0-100.0) to the ESC. - `sendThrottle(uint16_t throttle)`: Sends a raw throttle value (48-2047) to the ESC. A value of 0 sends a motor stop command. -- `sendCommand(uint16_t command)`: Sends a single DShot command (0-47) to the ESC. -- `sendCommand(dshotCommands_e dshot_command, uint16_t repeat_count = DEFAULT_CMD_REPEAT_COUNT, uint16_t delay_us = DEFAULT_CMD_DELAY_US)`: Sends a DShot command multiple times with a delay between repetitions. This is a blocking function. +- `sendCommand(dshotCommands_e command)`: Sends a DShot command (0-47) to the ESC. Automatically handles repetitions and delays for specific commands (e.g., `DSHOT_CMD_SAVE_SETTINGS`). +- `sendCommand(dshotCommands_e command, uint16_t repeat_count, uint16_t delay_us)`: Sends a DShot command (0-47) to the ESC with a specified repeat count and delay. This is a blocking function. +- `sendCommand(uint16_t command_value)`: Sends a DShot command (0-47) to the ESC by accepting an integer value. It validates the input and then calls `sendCommand(dshotCommands_e command)`. - `getTelemetry(uint16_t magnet_count = 0)`: Retrieves telemetry data from the ESC. If `magnet_count` is 0, uses the stored motor magnet count. - `getESCInfo()`: Sends a command to the ESC to request ESC information. - `setMotorSpinDirection(bool reversed)`: Sets the motor spin direction. `true` for reversed, `false` for normal. diff --git a/src/DShotRMT.cpp b/src/DShotRMT.cpp index 91896aa..52fcf2a 100644 --- a/src/DShotRMT.cpp +++ b/src/DShotRMT.cpp @@ -14,7 +14,7 @@ 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[static_cast(mode)]) + _dshot_timing(DSHOT_TIMING_US[_mode]) { // Pre-calculate timing and bit positions for performance _preCalculateRMTTicks(); @@ -61,24 +61,27 @@ DShotRMT::~DShotRMT() // Initialize DShotRMT dshot_result_t DShotRMT::begin() { - if (!_initTXChannel().success) + dshot_result_t result = init_rmt_tx_channel(_gpio, &_rmt_tx_channel, _is_bidirectional); + if (!result.success) { - return {false, dshot_msg_code_t::DSHOT_TX_INIT_FAILED}; + return result; } if (_is_bidirectional) { - if (!_initRXChannel().success) + result = init_rmt_rx_channel(_gpio, &_rmt_rx_channel, &_rx_event_callbacks, this); + if (!result.success) { // Cleanup previously allocated TX channel on failure rmt_disable(_rmt_tx_channel); rmt_del_channel(_rmt_tx_channel); _rmt_tx_channel = nullptr; - return {false, dshot_msg_code_t::DSHOT_RX_INIT_FAILED}; + return result; } } - if (!_initDShotEncoder().success) + result = init_dshot_encoder(&_dshot_encoder, _rmt_ticks, _pulse_level, _idle_level); + if (!result.success) { // Cleanup previously allocated channels on failure rmt_disable(_rmt_tx_channel); @@ -91,8 +94,7 @@ dshot_result_t DShotRMT::begin() rmt_del_channel(_rmt_rx_channel); _rmt_rx_channel = nullptr; } - - return {false, dshot_msg_code_t::DSHOT_ENCODER_INIT_FAILED}; + return result; } return {true, dshot_msg_code_t::DSHOT_INIT_SUCCESS}; @@ -127,21 +129,45 @@ dshot_result_t DShotRMT::sendThrottlePercent(float percent) return sendThrottle(throttle); } -// Send DShot command to ESC -dshot_result_t DShotRMT::sendCommand(dshotCommands_e command) +// Sends a DShot command (0-47) to the ESC by accepting an integer value. +dshot_result_t DShotRMT::sendCommand(uint16_t command_value) { - _packet = _buildDShotPacket(static_cast(command)); - return _sendDShotFrame(_packet); + // Validate the integer command value before casting + if (command_value < DSHOT_CMD_MOTOR_STOP || command_value > DSHOT_CMD_MAX) + { + return {false, dshot_msg_code_t::DSHOT_COMMAND_NOT_VALID}; + } + return sendCommand(static_cast(command_value)); } +// Sends a DShot command (0-47) to the ESC. +dshot_result_t DShotRMT::sendCommand(dshotCommands_e command) +{ + uint16_t repeat_count = DEFAULT_CMD_REPEAT_COUNT; + uint16_t delay_us = DEFAULT_CMD_DELAY_US; -// Send full DShot commands for setup etc -// This is a blocking function that uses delayMicroseconds for repetitions. -dshot_result_t DShotRMT::_sendCommandInternal(dshotCommands_e dshot_command, uint16_t repeat_count, uint16_t delay_us) + switch (command) + { + case DSHOT_CMD_SAVE_SETTINGS: + case DSHOT_CMD_SPIN_DIRECTION_NORMAL: + case DSHOT_CMD_SPIN_DIRECTION_REVERSED: + repeat_count = SETTINGS_COMMAND_REPEATS; + delay_us = SETTINGS_COMMAND_DELAY_US; + break; + default: + // For other commands, use default repeat and delay + break; + } + + return sendCommand(command, repeat_count, delay_us); +} + +// Sends a DShot command (0-47) to the ESC with a specified repeat count and delay. +dshot_result_t DShotRMT::sendCommand(dshotCommands_e command, uint16_t repeat_count, uint16_t delay_us) { dshot_result_t result = {false, dshot_msg_code_t::DSHOT_UNKNOWN, NO_DSHOT_TELEMETRY, NO_DSHOT_TELEMETRY}; - if (!_isValidCommand(dshot_command)) + if (!_isValidCommand(command)) { result.result_code = dshot_msg_code_t::DSHOT_INVALID_COMMAND; return result; @@ -152,7 +178,7 @@ dshot_result_t DShotRMT::_sendCommandInternal(dshotCommands_e dshot_command, uin // Send command multiple times with delay for (uint16_t i = 0; i < repeat_count; i++) { - dshot_result_t single_result = _executeCommand(dshot_command); + dshot_result_t single_result = _executeCommand(command); if (!single_result.success) { @@ -168,7 +194,6 @@ dshot_result_t DShotRMT::_sendCommandInternal(dshotCommands_e dshot_command, uin } } - // result.success = all_successful; if (result.success) @@ -221,15 +246,13 @@ dshot_result_t DShotRMT::setMotorSpinDirection(bool reversed) // Use command as a yes / no switch dshotCommands_e command = reversed ? dshotCommands_e::DSHOT_CMD_SPIN_DIRECTION_REVERSED : dshotCommands_e::DSHOT_CMD_SPIN_DIRECTION_NORMAL; - return _sendCommandInternal(command, SETTINGS_COMMAND_REPEATS, SETTINGS_COMMAND_DELAY_US); + return sendCommand(command, SETTINGS_COMMAND_REPEATS, SETTINGS_COMMAND_DELAY_US); } - - // Use with caution dshot_result_t DShotRMT::saveESCSettings() { - return _sendCommandInternal(dshotCommands_e::DSHOT_CMD_SAVE_SETTINGS, SETTINGS_COMMAND_REPEATS, SETTINGS_COMMAND_DELAY_US); + return sendCommand(dshotCommands_e::DSHOT_CMD_SAVE_SETTINGS, SETTINGS_COMMAND_REPEATS, SETTINGS_COMMAND_DELAY_US); } // Simple check @@ -252,103 +275,7 @@ dshot_result_t DShotRMT::_executeCommand(dshotCommands_e command) return result; } -// Private Initialization Functions -dshot_result_t DShotRMT::_initTXChannel() -{ - _tx_channel_config.gpio_num = _gpio; - _tx_channel_config.clk_src = DSHOT_CLOCK_SRC_DEFAULT; - _tx_channel_config.resolution_hz = DSHOT_RMT_RESOLUTION; - _tx_channel_config.mem_block_symbols = RMT_BUFFER_SYMBOLS; - _tx_channel_config.trans_queue_depth = RMT_QUEUE_DEPTH; - _rmt_tx_config.loop_count = 0; // No automatic loops - real-time calculation - _rmt_tx_config.flags.eot_level = _is_bidirectional ? 1 : 0; - - if (rmt_new_tx_channel(&_tx_channel_config, &_rmt_tx_channel) != DSHOT_OK) - { - return {false, dshot_msg_code_t::DSHOT_TX_INIT_FAILED}; - } - - if (rmt_enable(_rmt_tx_channel) != DSHOT_OK) - { - return {false, dshot_msg_code_t::DSHOT_TX_INIT_FAILED}; - } - - return {true, dshot_msg_code_t::DSHOT_TX_INIT_SUCCESS}; -} - -dshot_result_t DShotRMT::_initRXChannel() -{ - // Double check if bidirectional mode is enabled - if (!_is_bidirectional) - { - return {true, dshot_msg_code_t::DSHOT_NONE}; - } - - _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; - - // Filter for pulses that are within a reasonable range for DShot telemetry - _rmt_rx_config.signal_range_min_ns = DSHOT_PULSE_MIN_NS; - _rmt_rx_config.signal_range_max_ns = DSHOT_PULSE_MAX_NS; - - if (rmt_new_rx_channel(&_rx_channel_config, &_rmt_rx_channel) != DSHOT_OK) - { - return {false, dshot_msg_code_t::DSHOT_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, dshot_msg_code_t::DSHOT_CALLBACK_REGISTERING_FAILED}; - } - - if (rmt_enable(_rmt_rx_channel) != DSHOT_OK) - { - return {false, dshot_msg_code_t::DSHOT_RX_INIT_FAILED}; - } - - // Start the receiver to wait for incoming telemetry data - rmt_symbol_word_t rx_symbols[GCR_BITS_PER_FRAME]; - 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, dshot_msg_code_t::DSHOT_RECEIVER_FAILED}; - } - - return {true, dshot_msg_code_t::DSHOT_RX_INIT_SUCCESS}; -} - -// -dshot_result_t DShotRMT::_initDShotEncoder() -{ - rmt_bytes_encoder_config_t encoder_config = { - .bit0 = { - .duration0 = _rmt_ticks.t0h_ticks, - .level0 = _pulse_level, - .duration1 = _rmt_ticks.t0l_ticks, - .level1 = _idle_level, - }, - .bit1 = { - .duration0 = _rmt_ticks.t1h_ticks, - .level0 = _pulse_level, - .duration1 = _rmt_ticks.t1l_ticks, - .level1 = _idle_level, - }, - .flags = { - .msb_first = 1 // DShot is MSB first - }}; - - if (rmt_new_bytes_encoder(&encoder_config, &_dshot_encoder) != DSHOT_OK) - { - return {false, dshot_msg_code_t::DSHOT_ENCODER_INIT_FAILED}; - } - - return {true, dshot_msg_code_t::DSHOT_ENCODER_INIT_SUCCESS}; -} // Private Packet Management Functions dshot_packet_t DShotRMT::_buildDShotPacket(const uint16_t &value) const @@ -422,7 +349,11 @@ dshot_result_t DShotRMT::_sendDShotFrame(const dshot_packet_t &packet) // The DShot frame is 16 bits, which is 2 bytes size_t tx_size_bytes = sizeof(swapped_value); - if (rmt_transmit(_rmt_tx_channel, _dshot_encoder, &swapped_value, tx_size_bytes, &_rmt_tx_config) != DSHOT_OK) + rmt_transmit_config_t tx_config = {}; // Initialize all members to zero + tx_config.loop_count = 0; // No automatic loops - real-time calculation + tx_config.flags.eot_level = _is_bidirectional ? 1 : 0; + + if (rmt_transmit(_rmt_tx_channel, _dshot_encoder, &swapped_value, tx_size_bytes, &tx_config) != DSHOT_OK) { return {false, dshot_msg_code_t::DSHOT_TRANSMISSION_FAILED}; } diff --git a/src/DShotRMT.h b/src/DShotRMT.h index bffad6b..aee173b 100644 --- a/src/DShotRMT.h +++ b/src/DShotRMT.h @@ -15,6 +15,7 @@ #include "dshot_definitions.h" #include +#include "dshot_config.h" // DShotRMT Class Definition class DShotRMT @@ -41,6 +42,10 @@ public: // Sends a DShot command (0-47) to the ESC. dshot_result_t sendCommand(dshotCommands_e command); + // Sends a DShot command (0-47) to the ESC with a specified repeat count and delay. + dshot_result_t sendCommand(dshotCommands_e command, uint16_t repeat_count, uint16_t delay_us); + // Sends a DShot command (0-47) to the ESC by accepting an integer value. + dshot_result_t sendCommand(uint16_t command_value); // Retrieves telemetry data from the ESC. dshot_result_t getTelemetry(); @@ -71,19 +76,11 @@ public: const char *getDShotMsg(dshot_result_t &result) const { return (_get_result_code_str(result.result_code)); } // Deprecated Methods - // Deprecated. Use sendThrottle() instead. - [[deprecated("Use sendThrottle() instead")]] - dshot_result_t sendValue(uint16_t value) { return sendThrottle(value); } - - // Deprecated. Use sendCommand() instead. - [[deprecated("Use sendCommand() instead")]] - dshot_result_t sendCommand(uint16_t command) { return sendCommand(static_cast(command)); } private: // --- UTILITY METHODS --- bool _isValidCommand(dshotCommands_e command) const; dshot_result_t _executeCommand(dshotCommands_e command); - dshot_result_t _sendCommandInternal(dshotCommands_e dshot_command, uint16_t repeat_count, uint16_t delay_us); // Core Configuration Variables gpio_num_t _gpio; @@ -108,22 +105,13 @@ private: rmt_channel_handle_t _rmt_rx_channel = nullptr; rmt_encoder_handle_t _dshot_encoder = nullptr; - // RMT Configuration Structures - rmt_tx_channel_config_t _tx_channel_config{}; - rmt_rx_channel_config_t _rx_channel_config{}; - rmt_transmit_config_t _rmt_tx_config{}; - rmt_receive_config_t _rmt_rx_config{}; + // Bidirectional / Telemetry Variables rmt_rx_event_callbacks_t _rx_event_callbacks{}; std::atomic _last_erpm_atomic{0}; std::atomic _telemetry_ready_flag_atomic{false}; - // Private Initialization Functions - dshot_result_t _initTXChannel(); - dshot_result_t _initRXChannel(); - dshot_result_t _initDShotEncoder(); - // Private Packet Management Functions dshot_packet_t _buildDShotPacket(const uint16_t &value) const; uint16_t _buildDShotFrameValue(const dshot_packet_t &packet) const; @@ -143,4 +131,4 @@ private: }; // Include utility functions after class definition -#include "dshot_utils.h" +#include "dshot_utils.h" \ No newline at end of file diff --git a/src/dshot_config.cpp b/src/dshot_config.cpp new file mode 100644 index 0000000..54024a7 --- /dev/null +++ b/src/dshot_config.cpp @@ -0,0 +1,98 @@ +#include "dshot_config.h" + +// Function to initialize the RMT TX channel +dshot_result_t init_rmt_tx_channel(gpio_num_t gpio, rmt_channel_handle_t *out_channel, bool is_bidirectional) +{ + rmt_tx_channel_config_t tx_channel_config = { + .gpio_num = gpio, + .clk_src = DSHOT_CLOCK_SRC_DEFAULT, + .resolution_hz = DSHOT_RMT_RESOLUTION, + .mem_block_symbols = RMT_BUFFER_SYMBOLS, + .trans_queue_depth = RMT_QUEUE_DEPTH, + }; + + rmt_transmit_config_t rmt_tx_config = {}; // Initialize all members to zero + rmt_tx_config.loop_count = 0; // No automatic loops - real-time calculation + rmt_tx_config.flags.eot_level = is_bidirectional ? 1 : 0; + + if (rmt_new_tx_channel(&tx_channel_config, out_channel) != DSHOT_OK) + { + return {false, dshot_msg_code_t::DSHOT_TX_INIT_FAILED}; + } + + if (rmt_enable(*out_channel) != DSHOT_OK) + { + return {false, dshot_msg_code_t::DSHOT_TX_INIT_FAILED}; + } + + return {true, dshot_msg_code_t::DSHOT_TX_INIT_SUCCESS}; +} + +// Function to initialize the RMT RX channel +dshot_result_t init_rmt_rx_channel(gpio_num_t gpio, rmt_channel_handle_t *out_channel, rmt_rx_event_callbacks_t *rx_event_callbacks, void *user_data) +{ + rmt_rx_channel_config_t rx_channel_config = { + .gpio_num = gpio, + .clk_src = DSHOT_CLOCK_SRC_DEFAULT, + .resolution_hz = DSHOT_RMT_RESOLUTION, + .mem_block_symbols = RMT_BUFFER_SYMBOLS, + }; + + rmt_receive_config_t rmt_rx_config = { + .signal_range_min_ns = DSHOT_PULSE_MIN_NS, + .signal_range_max_ns = DSHOT_PULSE_MAX_NS, + }; + + if (rmt_new_rx_channel(&rx_channel_config, out_channel) != DSHOT_OK) + { + return {false, dshot_msg_code_t::DSHOT_RX_INIT_FAILED}; + } + + if (rmt_rx_register_event_callbacks(*out_channel, rx_event_callbacks, user_data) != DSHOT_OK) + { + return {false, dshot_msg_code_t::DSHOT_CALLBACK_REGISTERING_FAILED}; + } + + if (rmt_enable(*out_channel) != DSHOT_OK) + { + return {false, dshot_msg_code_t::DSHOT_RX_INIT_FAILED}; + } + + // Start the receiver to wait for incoming telemetry data + rmt_symbol_word_t rx_symbols[GCR_BITS_PER_FRAME]; + size_t rx_size_bytes = GCR_BITS_PER_FRAME * sizeof(rmt_symbol_word_t); + if (rmt_receive(*out_channel, rx_symbols, rx_size_bytes, &rmt_rx_config) != DSHOT_OK) + { + return {false, dshot_msg_code_t::DSHOT_RECEIVER_FAILED}; + } + + return {true, dshot_msg_code_t::DSHOT_RX_INIT_SUCCESS}; +} + +// Function to initialize the DShot RMT encoder +dshot_result_t init_dshot_encoder(rmt_encoder_handle_t *out_encoder, const rmt_ticks_t &rmt_ticks, uint16_t pulse_level, uint16_t idle_level) +{ + rmt_bytes_encoder_config_t encoder_config = { + .bit0 = { + .duration0 = rmt_ticks.t0h_ticks, + .level0 = pulse_level, + .duration1 = rmt_ticks.t0l_ticks, + .level1 = idle_level, + }, + .bit1 = { + .duration0 = rmt_ticks.t1h_ticks, + .level0 = pulse_level, + .duration1 = rmt_ticks.t1l_ticks, + .level1 = idle_level, + }, + .flags = { + .msb_first = 1 // DShot is MSB first + }}; + + if (rmt_new_bytes_encoder(&encoder_config, out_encoder) != DSHOT_OK) + { + return {false, dshot_msg_code_t::DSHOT_ENCODER_INIT_FAILED}; + } + + return {true, dshot_msg_code_t::DSHOT_ENCODER_INIT_SUCCESS}; +} diff --git a/src/dshot_config.h b/src/dshot_config.h new file mode 100644 index 0000000..a50e2d5 --- /dev/null +++ b/src/dshot_config.h @@ -0,0 +1,16 @@ +#pragma once + +#include +#include +#include + +#include "dshot_definitions.h" + +// Function to initialize the RMT TX channel +dshot_result_t init_rmt_tx_channel(gpio_num_t gpio, rmt_channel_handle_t *out_channel, bool is_bidirectional); + +// Function to initialize the RMT RX channel +dshot_result_t init_rmt_rx_channel(gpio_num_t gpio, rmt_channel_handle_t *out_channel, rmt_rx_event_callbacks_t *rx_event_callbacks, void *user_data); + +// Function to initialize the DShot RMT encoder +dshot_result_t init_dshot_encoder(rmt_encoder_handle_t *out_encoder, const rmt_ticks_t &rmt_ticks, uint16_t pulse_level, uint16_t idle_level);