2025-09-09 16:19:57 +01:00
|
|
|
/**
|
|
|
|
|
* @file DShotRMT.h
|
2025-09-18 10:23:39 +01:00
|
|
|
* @brief Optimized DShot signal generation using ESP32 RMT with bidirectional support
|
2025-09-09 16:19:57 +01:00
|
|
|
* @author Wastl Kraus
|
2025-09-18 10:23:39 +01:00
|
|
|
* @date 2025-09-18
|
2025-09-09 16:19:57 +01:00
|
|
|
* @license MIT
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
#pragma once
|
|
|
|
|
|
|
|
|
|
#include <Arduino.h>
|
2025-09-25 15:15:36 +01:00
|
|
|
#include <dshot_definitions.h>
|
2025-09-09 16:19:57 +01:00
|
|
|
#include <driver/gpio.h>
|
|
|
|
|
#include <driver/rmt_tx.h>
|
|
|
|
|
#include <driver/rmt_rx.h>
|
2025-09-13 10:54:30 +01:00
|
|
|
#include <atomic>
|
2025-09-09 16:19:57 +01:00
|
|
|
|
2025-09-27 13:59:23 +01:00
|
|
|
// 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.
|
2025-09-09 16:19:57 +01:00
|
|
|
class DShotRMT
|
|
|
|
|
{
|
|
|
|
|
public:
|
2025-09-27 13:59:23 +01:00
|
|
|
// Constructor for DShotRMT with GPIO number.
|
2025-09-25 13:03:55 +01:00
|
|
|
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);
|
|
|
|
|
|
2025-09-27 13:59:23 +01:00
|
|
|
// Constructor for DShotRMT with Arduino pin number.
|
2025-09-20 15:47:39 +01:00
|
|
|
DShotRMT(uint16_t pin_nr, dshot_mode_t mode, bool is_bidirectional, uint16_t magnet_count = DEFAULT_MOTOR_MAGNET_COUNT);
|
2025-09-18 10:23:39 +01:00
|
|
|
|
2025-09-27 13:59:23 +01:00
|
|
|
// Destructor for DShotRMT.
|
|
|
|
|
// Cleans up RMT channels and encoder resources.
|
2025-09-09 16:19:57 +01:00
|
|
|
~DShotRMT();
|
|
|
|
|
|
2025-09-13 10:54:30 +01:00
|
|
|
// Public Core Functions
|
2025-09-27 13:59:23 +01:00
|
|
|
// Initializes the DShot RMT channels and encoder.
|
2025-09-09 16:19:57 +01:00
|
|
|
dshot_result_t begin();
|
2025-09-25 13:03:55 +01:00
|
|
|
|
2025-09-27 13:59:23 +01:00
|
|
|
// Sends a DShot throttle value to the ESC.
|
2025-09-09 16:19:57 +01:00
|
|
|
dshot_result_t sendThrottle(uint16_t throttle);
|
2025-09-25 13:03:55 +01:00
|
|
|
|
2025-09-27 13:59:23 +01:00
|
|
|
// Sends a DShot throttle value as a percentage to the ESC.
|
2025-09-20 15:47:39 +01:00
|
|
|
dshot_result_t sendThrottlePercent(float percent);
|
2025-09-25 13:03:55 +01:00
|
|
|
|
2025-09-27 13:59:23 +01:00
|
|
|
// Sends a single DShot command to the ESC.
|
2025-09-09 16:19:57 +01:00
|
|
|
dshot_result_t sendCommand(uint16_t command);
|
2025-09-25 13:03:55 +01:00
|
|
|
|
2025-09-27 13:59:23 +01:00
|
|
|
// Sends a DShot command multiple times with a delay between repetitions. This is a blocking function.
|
2025-09-25 13:03:55 +01:00
|
|
|
dshot_result_t sendCommand(dshotCommands_e dshot_command, uint16_t repeat_count = DEFAULT_CMD_REPEAT_COUNT, uint16_t delay_us = DEFAULT_CMD_DELAY_US);
|
|
|
|
|
|
2025-09-27 13:59:23 +01:00
|
|
|
// Retrieves telemetry data from the ESC.
|
2025-09-20 15:47:39 +01:00
|
|
|
dshot_result_t getTelemetry(uint16_t magnet_count = 0);
|
2025-09-25 13:03:55 +01:00
|
|
|
|
2025-09-27 13:59:23 +01:00
|
|
|
// Sends a command to the ESC to request ESC information.
|
2025-09-18 15:10:59 +01:00
|
|
|
dshot_result_t getESCInfo();
|
2025-09-25 13:03:55 +01:00
|
|
|
|
2025-09-27 13:59:23 +01:00
|
|
|
// Sets the motor spin direction.
|
2025-09-18 15:10:59 +01:00
|
|
|
dshot_result_t setMotorSpinDirection(bool reversed);
|
2025-09-25 13:03:55 +01:00
|
|
|
|
2025-09-27 13:59:23 +01:00
|
|
|
// Sends a command to the ESC to save its current settings.
|
|
|
|
|
// Use with caution as this writes to ESC's non-volatile memory.
|
2025-09-18 15:10:59 +01:00
|
|
|
dshot_result_t saveESCSettings();
|
2025-09-18 10:23:39 +01:00
|
|
|
|
2025-09-18 15:10:59 +01:00
|
|
|
// Public Utility & Info Functions
|
2025-09-27 13:59:23 +01:00
|
|
|
// Prints detailed DShot signal information for a given DShotRMT instance.
|
2025-09-25 15:15:36 +01:00
|
|
|
static void printDShotInfo(const DShotRMT &dshot_rmt, Stream &output = Serial);
|
|
|
|
|
|
2025-09-27 13:59:23 +01:00
|
|
|
// Prints detailed CPU information.
|
2025-09-25 15:15:36 +01:00
|
|
|
static void printCpuInfo(Stream &output = Serial);
|
|
|
|
|
|
2025-09-27 13:59:23 +01:00
|
|
|
// Sets the motor magnet count for RPM calculation.
|
2025-09-20 15:47:39 +01:00
|
|
|
void setMotorMagnetCount(uint16_t magnet_count);
|
2025-09-25 13:03:55 +01:00
|
|
|
|
2025-09-27 13:59:23 +01:00
|
|
|
// Gets the current DShot mode.
|
2025-09-25 13:03:55 +01:00
|
|
|
dshot_mode_t getMode() const { return _mode; }
|
|
|
|
|
|
2025-09-27 13:59:23 +01:00
|
|
|
// Checks if bidirectional DShot is enabled.
|
2025-09-25 13:03:55 +01:00
|
|
|
bool isBidirectional() const { return _is_bidirectional; }
|
|
|
|
|
|
2025-09-27 13:59:23 +01:00
|
|
|
// Gets the last encoded DShot frame value.
|
2025-09-25 13:03:55 +01:00
|
|
|
uint16_t getEncodedFrameValue() const { return _encoded_frame_value; }
|
|
|
|
|
|
2025-09-27 13:59:23 +01:00
|
|
|
// Gets the last transmitted throttle value.
|
2025-09-25 13:03:55 +01:00
|
|
|
uint16_t getThrottleValue() const { return _packet.throttle_value; }
|
2025-09-18 10:23:39 +01:00
|
|
|
|
2025-09-13 10:54:30 +01:00
|
|
|
// Deprecated Methods
|
2025-09-27 13:59:23 +01:00
|
|
|
// Deprecated. Use sendThrottle() instead.
|
2025-09-09 16:19:57 +01:00
|
|
|
[[deprecated("Use sendThrottle() instead")]]
|
|
|
|
|
bool setThrottle(uint16_t throttle)
|
|
|
|
|
{
|
|
|
|
|
auto result = sendThrottle(throttle);
|
|
|
|
|
return result.success;
|
|
|
|
|
}
|
|
|
|
|
|
2025-09-27 13:59:23 +01:00
|
|
|
// Deprecated. Use sendCommand() instead.
|
2025-09-09 16:19:57 +01:00
|
|
|
[[deprecated("Use sendCommand() instead")]]
|
|
|
|
|
bool sendDShotCommand(uint16_t command)
|
|
|
|
|
{
|
|
|
|
|
auto result = sendCommand(command);
|
|
|
|
|
return result.success;
|
|
|
|
|
}
|
|
|
|
|
|
2025-09-27 13:59:23 +01:00
|
|
|
// Deprecated. Use getTelemetry() instead.
|
2025-09-09 16:19:57 +01:00
|
|
|
[[deprecated("Use getTelemetry() instead")]]
|
|
|
|
|
uint32_t getMotorRPM(uint8_t magnet_count)
|
|
|
|
|
{
|
|
|
|
|
auto result = getTelemetry(magnet_count);
|
2025-09-11 12:58:22 +01:00
|
|
|
return result.motor_rpm;
|
2025-09-09 16:19:57 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private:
|
2025-09-18 15:10:59 +01:00
|
|
|
// --- UTILITY METHODS ---
|
2025-09-25 13:03:55 +01:00
|
|
|
bool _isValidCommand(dshotCommands_e command);
|
|
|
|
|
dshot_result_t _executeCommand(dshotCommands_e command);
|
2025-09-17 20:41:20 +01:00
|
|
|
|
2025-09-13 10:54:30 +01:00
|
|
|
// Core Configuration Variables
|
2025-09-09 16:19:57 +01:00
|
|
|
gpio_num_t _gpio;
|
|
|
|
|
dshot_mode_t _mode;
|
|
|
|
|
bool _is_bidirectional;
|
2025-09-20 15:47:39 +01:00
|
|
|
uint16_t _motor_magnet_count;
|
2025-09-13 10:54:30 +01:00
|
|
|
const dshot_timing_us_t &_dshot_timing;
|
2025-09-15 14:56:04 +01:00
|
|
|
uint64_t _frame_timer_us;
|
2025-09-13 10:54:30 +01:00
|
|
|
|
|
|
|
|
// Timing & Packet Variables
|
2025-09-12 22:14:34 +01:00
|
|
|
rmt_ticks_t _rmt_ticks;
|
2025-09-09 16:19:57 +01:00
|
|
|
uint16_t _last_throttle;
|
2025-09-12 22:14:34 +01:00
|
|
|
uint64_t _last_transmission_time_us;
|
2025-09-25 15:15:36 +01:00
|
|
|
uint64_t _last_command_timestamp;
|
|
|
|
|
uint16_t _encoded_frame_value;
|
|
|
|
|
dshot_packet_t _packet;
|
2025-09-26 18:24:50 +01:00
|
|
|
uint16_t _pulse_level; // DShot protocol: Signal is idle-low, so pulses start by going HIGH.
|
|
|
|
|
uint16_t _idle_level; // DShot protocol: Signal returns to LOW after the high pulse.
|
2025-09-18 10:23:39 +01:00
|
|
|
|
2025-09-13 10:54:30 +01:00
|
|
|
// RMT Hardware Handles
|
2025-09-09 16:19:57 +01:00
|
|
|
rmt_channel_handle_t _rmt_tx_channel;
|
|
|
|
|
rmt_channel_handle_t _rmt_rx_channel;
|
|
|
|
|
rmt_encoder_handle_t _dshot_encoder;
|
|
|
|
|
|
2025-09-13 10:54:30 +01:00
|
|
|
// RMT Configuration Structures
|
2025-09-09 16:19:57 +01:00
|
|
|
rmt_tx_channel_config_t _tx_channel_config;
|
|
|
|
|
rmt_rx_channel_config_t _rx_channel_config;
|
2025-09-15 14:56:04 +01:00
|
|
|
rmt_transmit_config_t _rmt_tx_config;
|
|
|
|
|
rmt_receive_config_t _rmt_rx_config;
|
2025-09-09 16:19:57 +01:00
|
|
|
|
2025-09-13 10:54:30 +01:00
|
|
|
// Bidirectional / Telemetry Variables
|
|
|
|
|
rmt_rx_event_callbacks_t _rx_event_callbacks;
|
|
|
|
|
std::atomic<uint16_t> _last_erpm_atomic;
|
|
|
|
|
std::atomic<bool> _telemetry_ready_flag_atomic;
|
|
|
|
|
|
|
|
|
|
// Private Initialization Functions
|
2025-09-09 16:19:57 +01:00
|
|
|
dshot_result_t _initTXChannel();
|
|
|
|
|
dshot_result_t _initRXChannel();
|
|
|
|
|
dshot_result_t _initDShotEncoder();
|
|
|
|
|
|
2025-09-13 10:54:30 +01:00
|
|
|
// Private Packet Management Functions
|
2025-09-13 10:05:33 +01:00
|
|
|
dshot_packet_t _buildDShotPacket(const uint16_t &value);
|
2025-09-25 13:03:55 +01:00
|
|
|
uint16_t _buildDShotFrameValue(const dshot_packet_t &packet);
|
2025-09-15 13:40:47 +01:00
|
|
|
uint16_t _calculateCRC(const uint16_t &data);
|
2025-09-15 14:56:04 +01:00
|
|
|
void _preCalculateRMTTicks();
|
2025-09-09 16:19:57 +01:00
|
|
|
|
2025-09-13 10:54:30 +01:00
|
|
|
// Private Frame Processing Functions
|
2025-09-09 16:19:57 +01:00
|
|
|
dshot_result_t _sendDShotFrame(const dshot_packet_t &packet);
|
|
|
|
|
uint16_t _decodeDShotFrame(const rmt_symbol_word_t *symbols);
|
2025-09-18 10:23:39 +01:00
|
|
|
|
2025-09-13 10:54:30 +01:00
|
|
|
// Private Timing Control Functions
|
2025-09-26 18:24:50 +01:00
|
|
|
bool _isFrameIntervalElapsed();
|
|
|
|
|
void _recordFrameTransmissionTime();
|
2025-09-18 10:23:39 +01:00
|
|
|
|
2025-09-13 10:54:30 +01:00
|
|
|
// Static Callback Functions
|
2025-09-15 10:58:56 +01:00
|
|
|
static bool _on_rx_done(rmt_channel_handle_t rmt_rx_channel, const rmt_rx_done_event_data_t *edata, void *user_data);
|
2025-09-25 15:15:36 +01:00
|
|
|
};
|