DShotRMT/src/DShotRMT.cpp

616 lines
18 KiB
C++
Raw Normal View History

/**
* @file DShotRMT.cpp
2025-07-30 23:36:58 +01:00
* @brief DShot signal generation using ESP32 RMT with bidirectional support
* @author Wastl Kraus
* @date 2025-06-11
* @license MIT
*/
2021-07-04 02:01:26 +01:00
2025-09-17 20:41:20 +01:00
#include <DShotRMT.h>
2025-07-30 23:36:58 +01:00
// Timing parameters for each DShot mode
// Format: {bit_length_us, t1h_length_us}
static constexpr dshot_timing_us_t DSHOT_TIMING_US[] = {
2025-09-18 10:23:39 +01:00
{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
2025-09-12 22:14:34 +01:00
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)
: _gpio(gpio),
_mode(mode),
_is_bidirectional(is_bidirectional),
_motor_magnet_count(magnet_count),
2025-09-12 22:14:34 +01:00
_dshot_timing(DSHOT_TIMING_US[mode]),
_frame_timer_us(0),
2025-09-12 22:14:34 +01:00
_rmt_ticks{0},
_last_throttle(DSHOT_CMD_MOTOR_STOP),
2025-09-12 22:14:34 +01:00
_last_transmission_time_us(0),
2025-09-18 15:10:59 +01:00
_last_command_timestamp(0),
_parsed_packet(0),
_packet{0},
2025-09-07 14:19:52 +01:00
_bitPositions{0},
2025-09-18 10:23:39 +01:00
_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),
_rmt_rx_channel(nullptr),
_dshot_encoder(nullptr),
_tx_channel_config{},
_rx_channel_config{},
2025-09-15 14:56:04 +01:00
_rmt_tx_config{},
_rmt_rx_config{},
_rx_event_callbacks{},
_last_erpm_atomic(0),
_telemetry_ready_flag_atomic(false)
{
2025-09-18 10:23:39 +01:00
// Pre-calculate timing and bit positions for performance
2025-09-15 14:56:04 +01:00
_preCalculateRMTTicks();
2025-09-17 20:41:20 +01:00
_preCalculateBitPositions();
2025-09-20 14:41:08 +01:00
// Activate internal pullup resistor
2025-09-20 22:41:04 +01:00
// gpio_set_pull_mode(_gpio, GPIO_PULLUP_ONLY);
}
2025-08-30 23:21:03 +01:00
// Constructor using pin number
DShotRMT::DShotRMT(uint16_t pin_nr, dshot_mode_t mode, bool is_bidirectional, uint16_t magnet_count)
: DShotRMT(static_cast<gpio_num_t>(pin_nr), mode, is_bidirectional, magnet_count)
2025-08-06 22:57:26 +01:00
{
// Delegates to primary constructor with type cast
2025-08-06 22:57:26 +01:00
}
// Destructor
DShotRMT::~DShotRMT()
{
// Cleanup TX channel
if (_rmt_tx_channel)
{
2025-09-05 22:28:08 +01:00
if (rmt_disable(_rmt_tx_channel) == DSHOT_OK)
{
2025-09-18 10:23:39 +01:00
rmt_del_channel(_rmt_tx_channel);
_rmt_tx_channel = nullptr;
2025-09-05 22:28:08 +01:00
}
}
// Cleanup RX channel
if (_rmt_rx_channel)
{
2025-09-05 22:28:08 +01:00
if (rmt_disable(_rmt_rx_channel) == DSHOT_OK)
{
2025-09-18 10:23:39 +01:00
rmt_del_channel(_rmt_rx_channel);
_rmt_rx_channel = nullptr;
2025-09-05 22:28:08 +01:00
}
}
// Cleanup encoder
if (_dshot_encoder)
{
rmt_del_encoder(_dshot_encoder);
_dshot_encoder = nullptr;
}
}
// Public Core Functions
// Initialize DShotRMT
2025-09-05 11:16:19 +01:00
dshot_result_t DShotRMT::begin()
2022-11-25 15:08:58 +00:00
{
2025-09-17 20:41:20 +01:00
if (!_initTXChannel().success)
{
return {false, TX_INIT_FAILED};
}
2025-08-31 10:43:48 +01:00
if (_is_bidirectional)
{
2025-09-07 12:48:48 +01:00
if (!_initRXChannel().success)
2025-08-31 10:43:48 +01:00
{
// Cleanup previously allocated TX channel on failure
rmt_disable(_rmt_tx_channel);
rmt_del_channel(_rmt_tx_channel);
_rmt_tx_channel = nullptr;
return {false, RX_INIT_FAILED};
2025-08-31 10:43:48 +01:00
}
}
2025-07-29 23:40:09 +01:00
2025-09-07 12:48:48 +01:00
if (!_initDShotEncoder().success)
{
// Cleanup previously allocated channels on failure
rmt_disable(_rmt_tx_channel);
rmt_del_channel(_rmt_tx_channel);
_rmt_tx_channel = nullptr;
if (_rmt_rx_channel) {
rmt_disable(_rmt_rx_channel);
rmt_del_channel(_rmt_rx_channel);
_rmt_rx_channel = nullptr;
}
return {false, ENCODER_INIT_FAILED};
}
return {true, INIT_SUCCESS};
2025-08-30 23:21:03 +01:00
}
// Send throttle value
dshot_result_t DShotRMT::sendThrottle(uint16_t throttle)
{
2025-09-18 10:23:39 +01:00
// A throttle value of 0 is a disarm command
if (throttle == 0)
{
return sendCommand(DSHOT_CMD_MOTOR_STOP);
}
2025-09-18 10:23:39 +01:00
// Constrain throttle to the valid DShot range
_last_throttle = constrain(throttle, DSHOT_THROTTLE_MIN, DSHOT_THROTTLE_MAX);
2025-09-18 10:23:39 +01:00
_packet = _buildDShotPacket(_last_throttle);
return _sendDShotFrame(_packet);
}
// Send throttle value as a percentage
dshot_result_t DShotRMT::sendThrottlePercent(float percent)
{
if (percent < 0.0f || percent > 100.0f)
{
return {false, PERCENT_NOT_IN_RANGE};
}
// Map percent to DShot throttle range
uint16_t throttle = static_cast<uint16_t>(DSHOT_THROTTLE_MIN + ((DSHOT_THROTTLE_MAX - DSHOT_THROTTLE_MIN) / 100.0f) * percent);
return sendThrottle(throttle);
}
// Send DShot command to ESC
dshot_result_t DShotRMT::sendCommand(uint16_t command)
{
2025-09-18 10:23:39 +01:00
if (command > DSHOT_CMD_MAX)
{
return {false, COMMAND_NOT_VALID};
}
_packet = _buildDShotPacket(command);
return _sendDShotFrame(_packet);
}
2025-09-18 15:10:59 +01:00
// 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)
{
dshot_result_t result = {false, UNKNOWN_ERROR};
if (!_isValidCommand(dshot_command))
{
result.msg = INVALID_COMMAND;
return result;
}
bool all_successful = true;
// Send command multiple times with delay
for (uint16_t i = 0; i < repeat_count; i++)
{
dshot_result_t single_result = _executeCommand(dshot_command);
if (!single_result.success)
{
all_successful = false;
result.msg = single_result.msg;
break;
}
// Add delay between repetitions (except for last repetition)
if (i < repeat_count - 1)
{
delayMicroseconds(delay_us);
}
}
//
result.success = all_successful;
if (result.success)
{
result.msg = COMMAND_SUCCESS;
}
return result;
}
// 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};
if (!_is_bidirectional)
{
result.msg = BIDIR_NOT_ENABLED;
return result;
}
// Use stored magnet count if parameter is 0 (default)
uint16_t final_magnet_count = (magnet_count == 0) ? _motor_magnet_count : magnet_count;
2025-09-18 10:23:39 +01:00
// Check if the callback has set the flag for new data
if (_telemetry_ready_flag_atomic)
{
2025-09-18 10:23:39 +01:00
_telemetry_ready_flag_atomic = false; // Reset the flag
uint16_t erpm = _last_erpm_atomic; // Read the atomic variable
if (erpm != DSHOT_NULL_PACKET && final_magnet_count >= MAGNETS_PER_POLE_PAIR)
{
2025-09-18 10:23:39 +01:00
// Calculate motor RPM from eRPM and magnet count
uint8_t pole_pairs = final_magnet_count / MAGNETS_PER_POLE_PAIR;
uint32_t motor_rpm = (erpm / pole_pairs);
result.success = true;
result.erpm = erpm;
result.motor_rpm = motor_rpm;
result.msg = TELEMETRY_SUCCESS;
}
}
return result;
}
2025-09-18 15:10:59 +01:00
// Reverse motor direction directly
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;
return sendCommand(command, SETTINGS_COMMAND_REPEATS, SETTINGS_COMMAND_DELAY_US);
}
dshot_result_t DShotRMT::getESCInfo()
{
return sendCommand(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
{
2025-09-18 10:23:39 +01:00
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)
{
2025-09-18 10:23:39 +01:00
output.print((_parsed_packet >> i) & 1);
}
2025-09-18 10:23:39 +01:00
output.printf("\nCurrent Value: %u\n", _packet.throttle_value);
}
2025-09-18 10:23:39 +01:00
//
void DShotRMT::printCpuInfo(Stream &output) const
{
2025-09-18 10:23:39 +01:00
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());
}
2025-09-18 15:10:59 +01:00
// Simple check
bool DShotRMT::_isValidCommand(dshot_commands_t command)
{
return (command >= DSHOT_CMD_MOTOR_STOP && command <= DSHOT_CMD_MAX);
}
//
dshot_result_t DShotRMT::_executeCommand(dshot_commands_t command)
{
uint64_t start_time = esp_timer_get_time();
// Execute the command using the DShotRMT instance
dshot_result_t result = sendCommand(static_cast<uint16_t>(command));
uint64_t end_time = esp_timer_get_time();
_last_command_timestamp = end_time;
return result;
}
// Private Initialization Functions
2025-09-07 12:48:48 +01:00
dshot_result_t DShotRMT::_initTXChannel()
2025-08-30 23:21:03 +01:00
{
_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;
2025-09-02 14:18:37 +01:00
_tx_channel_config.trans_queue_depth = RMT_QUEUE_DEPTH;
2025-08-30 23:21:03 +01:00
2025-09-18 10:23:39 +01:00
_rmt_tx_config.loop_count = 0; // No automatic loops - real-time calculation
_rmt_tx_config.flags.eot_level = _is_bidirectional ? 1 : 0;
2025-08-30 23:21:03 +01:00
if (rmt_new_tx_channel(&_tx_channel_config, &_rmt_tx_channel) != DSHOT_OK)
{
return {false, TX_INIT_FAILED};
2025-09-07 12:48:48 +01:00
}
if (rmt_enable(_rmt_tx_channel) != DSHOT_OK)
2025-09-07 12:48:48 +01:00
{
return {false, TX_INIT_FAILED};
2025-08-30 23:21:03 +01:00
}
return {true, TX_INIT_SUCCESS};
2025-08-30 23:21:03 +01:00
}
2025-09-07 12:48:48 +01:00
dshot_result_t DShotRMT::_initRXChannel()
2025-08-30 23:21:03 +01:00
{
2025-09-18 10:23:39 +01:00
// Double check if bidirectional mode is enabled
2025-09-17 20:41:20 +01:00
if (!_is_bidirectional)
{
return {true, NONE};
}
2025-08-31 19:23:04 +01:00
2025-08-30 23:21:03 +01:00
_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;
2025-08-30 23:21:03 +01:00
2025-09-18 10:23:39 +01:00
// Filter for pulses that are within a reasonable range for DShot telemetry
2025-09-15 14:56:04 +01:00
_rmt_rx_config.signal_range_min_ns = DSHOT_PULSE_MIN;
_rmt_rx_config.signal_range_max_ns = DSHOT_PULSE_MAX;
2025-08-30 23:21:03 +01:00
if (rmt_new_rx_channel(&_rx_channel_config, &_rmt_rx_channel) != DSHOT_OK)
{
return {false, RX_INIT_FAILED};
2025-08-30 23:21:03 +01:00
}
2025-09-18 10:23:39 +01:00
// Register the callback function that will be triggered when a frame is received
2025-09-17 20:41:20 +01:00
_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};
}
2025-09-18 10:23:39 +01:00
if (rmt_enable(_rmt_rx_channel) != DSHOT_OK)
2025-09-07 12:48:48 +01:00
{
return {false, RX_INIT_FAILED};
2025-09-07 12:48:48 +01:00
}
2025-09-18 10:23:39 +01:00
// Start the receiver to wait for incoming telemetry data
2025-09-17 20:41:20 +01:00
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, RECEIVER_FAILED};
}
return {true, RX_INIT_SUCCESS};
2025-08-30 23:21:03 +01:00
}
2025-09-07 12:48:48 +01:00
dshot_result_t DShotRMT::_initDShotEncoder()
2025-08-30 23:21:03 +01:00
{
rmt_copy_encoder_config_t encoder_config = {};
2025-08-30 23:21:03 +01:00
if (rmt_new_copy_encoder(&encoder_config, &_dshot_encoder) != DSHOT_OK)
{
return {false, ENCODER_INIT_FAILED};
2025-08-30 23:21:03 +01:00
}
2025-09-15 10:58:56 +01:00
return {true, ENCODER_INIT_SUCCESS};
2021-06-29 19:05:20 +01:00
}
// Private Packet Management Functions
2025-09-13 10:05:33 +01:00
dshot_packet_t DShotRMT::_buildDShotPacket(const uint16_t &value)
2025-07-30 23:36:58 +01:00
{
2025-08-30 23:21:03 +01:00
dshot_packet_t packet = {};
2025-06-14 17:16:45 +01:00
2025-09-15 14:56:04 +01:00
packet.throttle_value = value & DSHOT_THROTTLE_MAX;
2025-08-30 23:21:03 +01:00
packet.telemetric_request = _is_bidirectional ? 1 : 0;
2025-09-05 11:16:19 +01:00
2025-09-18 10:23:39 +01:00
// The data for CRC calculation includes the 11-bit value and the 1-bit telemetry flag
uint16_t data_for_crc = (packet.throttle_value << 1) | packet.telemetric_request;
packet.checksum = _calculateCRC(data_for_crc);
2025-07-30 23:36:58 +01:00
2025-08-30 23:21:03 +01:00
return packet;
2025-06-14 17:16:45 +01:00
}
2025-08-30 23:21:03 +01:00
uint16_t DShotRMT::_parseDShotPacket(const dshot_packet_t &packet)
{
2025-09-18 10:23:39 +01:00
// Combine throttle, telemetry bit, and CRC into a single 16-bit frame
uint16_t data_and_telemetry = (packet.throttle_value << 1) | packet.telemetric_request;
2025-09-15 14:56:04 +01:00
return (data_and_telemetry << 4) | packet.checksum;
}
uint16_t DShotRMT::_calculateCRC(const uint16_t &data)
{
2025-09-18 10:23:39 +01:00
// Standard DShot CRC calculation using XOR
2025-09-08 13:40:32 +01:00
uint16_t crc = (data ^ (data >> 4) ^ (data >> 8)) & DSHOT_CRC_MASK;
2025-08-30 23:21:03 +01:00
2025-09-18 10:23:39 +01:00
// For bidirectional DShot, the CRC is inverted
2025-08-30 23:21:03 +01:00
if (_is_bidirectional)
{
2025-09-08 13:40:32 +01:00
crc = (~crc) & DSHOT_CRC_MASK;
}
2025-08-30 23:21:03 +01:00
return crc;
}
2025-09-15 14:56:04 +01:00
void DShotRMT::_preCalculateRMTTicks()
{
2025-09-18 10:23:39 +01:00
// Pre-calculate all timing values in RMT ticks to save CPU cycles later
_rmt_ticks.bit_length_ticks = static_cast<uint16_t>(_dshot_timing.bit_length_us * RMT_TICKS_PER_US);
_rmt_ticks.t1h_ticks = static_cast<uint16_t>(_dshot_timing.t1h_lenght_us * RMT_TICKS_PER_US);
2025-09-18 10:23:39 +01:00
_rmt_ticks.t0h_ticks = _rmt_ticks.t1h_ticks >> 1; // High time for a 1 is always double of 0
_rmt_ticks.t1l_ticks = _rmt_ticks.bit_length_ticks - _rmt_ticks.t1h_ticks;
_rmt_ticks.t0l_ticks = _rmt_ticks.bit_length_ticks - _rmt_ticks.t0h_ticks;
2025-09-18 10:23:39 +01:00
// Calculate the minimum time required between frames
// Pause between frames is frame time in us, some padding and about 30 us is added by hardware
2025-09-15 14:56:04 +01:00
_frame_timer_us = (static_cast<uint64_t>(_dshot_timing.bit_length_us * DSHOT_BITS_PER_FRAME) << 1) + DSHOT_PADDING_US;
2025-09-18 10:23:39 +01:00
// For bidirectional, double up
if (_is_bidirectional)
{
_frame_timer_us = (_frame_timer_us << 1);
}
}
2025-09-07 13:45:11 +01:00
void DShotRMT::_preCalculateBitPositions()
{
2025-09-18 10:23:39 +01:00
// Pre-calculate bit positions to avoid redundant calculations in the encoding loop
for (int i = 0; i < DSHOT_BITS_PER_FRAME; ++i)
{
2025-09-07 13:45:11 +01:00
_bitPositions[i] = DSHOT_BITS_PER_FRAME - 1 - i;
}
2025-09-07 13:45:11 +01:00
}
// Private Frame Processing Functions
2025-09-05 11:16:19 +01:00
dshot_result_t DShotRMT::_sendDShotFrame(const dshot_packet_t &packet)
2025-06-13 20:50:00 +01:00
{
2025-09-18 10:23:39 +01:00
// Ensure enough time has passed since the last transmission
if (!_timer_signal())
{
2025-09-18 15:10:59 +01:00
return {true, NONE};
}
rmt_symbol_word_t tx_symbols[DSHOT_BITS_PER_FRAME];
2025-09-17 20:41:20 +01:00
dshot_result_t result = _encodeDShotFrame(packet, tx_symbols);
2025-09-18 10:23:39 +01:00
2025-09-17 20:41:20 +01:00
if (!result.success)
{
return result;
}
2025-08-05 23:32:03 +01:00
size_t tx_size_bytes = DSHOT_BITS_PER_FRAME * sizeof(rmt_symbol_word_t);
2025-09-15 14:56:04 +01:00
if (rmt_transmit(_rmt_tx_channel, _dshot_encoder, tx_symbols, tx_size_bytes, &_rmt_tx_config) != DSHOT_OK)
{
return {false, TRANSMISSION_FAILED};
2025-07-30 23:36:58 +01:00
}
2025-09-18 10:23:39 +01:00
_timer_reset(); // Reset the timer for the next frame
return {true, TRANSMISSION_SUCCESS};
2025-07-30 23:36:58 +01:00
}
2025-09-18 10:23:39 +01:00
// This function needs to be fast, as it generates the RMT symbols just before sending
2025-09-15 14:56:04 +01:00
dshot_result_t IRAM_ATTR DShotRMT::_encodeDShotFrame(const dshot_packet_t &packet, rmt_symbol_word_t *symbols)
2025-07-30 23:36:58 +01:00
{
_parsed_packet = _parseDShotPacket(packet);
for (int i = 0; i < DSHOT_BITS_PER_FRAME; ++i)
{
2025-09-07 13:45:11 +01:00
int bit_position = _bitPositions[i];
2025-09-18 10:23:39 +01:00
bool bit = (_parsed_packet >> bit_position) & 1;
2025-09-07 13:45:11 +01:00
2025-09-18 10:23:39 +01:00
// A '1' bit has a longer high-time, a '0' bit has a shorter high-time
symbols[i].level0 = _level0; // Go HIGH
2025-09-12 22:14:34 +01:00
symbols[i].duration0 = bit ? _rmt_ticks.t1h_ticks : _rmt_ticks.t0h_ticks;
2025-09-18 10:23:39 +01:00
symbols[i].level1 = _level1; // Go LOW
2025-09-12 22:14:34 +01:00
symbols[i].duration1 = bit ? _rmt_ticks.t1l_ticks : _rmt_ticks.t0l_ticks;
2023-04-15 06:45:14 +01:00
}
2025-09-15 14:56:04 +01:00
return {true, ENCODING_SUCCESS};
2021-06-29 19:05:20 +01:00
}
2025-09-18 10:23:39 +01:00
// Placed in IRAM for high performance, as it's called from an ISR context
uint16_t IRAM_ATTR DShotRMT::_decodeDShotFrame(const rmt_symbol_word_t *symbols)
{
uint32_t gcr_value = 0;
2025-09-18 10:23:39 +01:00
// Step 1: Decode RMT symbols into a 21-bit GCR (Group Code Recording) value.
// The ESC sends back a signal where the duration determines the bit value.
for (size_t i = 0; i < GCR_BITS_PER_FRAME; ++i)
{
bool bit_is_one = symbols[i].duration0 > symbols[i].duration1;
gcr_value = (gcr_value << 1) | bit_is_one;
}
2025-09-18 10:23:39 +01:00
// Step 2: Perform GCR decoding (GCR = Value ^ (Value >> 1))
uint32_t decoded_frame = gcr_value ^ (gcr_value >> 1);
2025-09-18 10:23:39 +01:00
// Step 3: Extract the 16-bit DShot frame from the decoded data
2025-09-08 13:40:32 +01:00
uint16_t data_and_crc = (decoded_frame & DSHOT_FULL_PACKET);
2025-09-18 10:23:39 +01:00
// Step 4: Extract data and CRC from the 16-bit frame
uint16_t received_data = data_and_crc >> 4;
2025-09-08 13:40:32 +01:00
uint16_t received_crc = data_and_crc & DSHOT_CRC_MASK;
2025-09-18 10:23:39 +01:00
// Step 5: A valid response must have the telemetry request bit set to 1. This is a sanity check.
if (!((received_data >> 11) & 1))
{
return DSHOT_NULL_PACKET;
}
2025-09-18 10:23:39 +01:00
// Step 6: Calculate and validate CRC
uint16_t calculated_crc = _calculateCRC(received_data);
if (received_crc != calculated_crc)
{
return DSHOT_NULL_PACKET;
}
2025-09-18 10:23:39 +01:00
// Return the eRPM value (first 11 bits).
2025-09-08 13:40:32 +01:00
return received_data & DSHOT_THROTTLE_MAX;
2025-07-30 23:36:58 +01:00
}
2025-09-17 20:41:20 +01:00
// Timing Control Functions
2025-09-15 10:58:56 +01:00
bool IRAM_ATTR DShotRMT::_timer_signal()
2025-08-30 23:21:03 +01:00
{
2025-09-18 10:23:39 +01:00
// Check if the minimum interval between frames has passed
uint64_t current_time = esp_timer_get_time();
2025-09-12 22:14:34 +01:00
uint64_t elapsed = current_time - _last_transmission_time_us;
2025-08-30 23:21:03 +01:00
return elapsed >= _frame_timer_us;
}
bool DShotRMT::_timer_reset()
{
2025-09-18 10:23:39 +01:00
// Record the time of the current transmission
2025-09-12 22:14:34 +01:00
_last_transmission_time_us = esp_timer_get_time();
2025-09-18 10:23:39 +01:00
return true;
2025-08-30 23:21:03 +01:00
}
2025-09-18 10:23:39 +01:00
// Static Callback Functions
// This function is called by the RMT driver's ISR when a frame is received
2025-09-15 10:58:56 +01:00
bool IRAM_ATTR DShotRMT::_on_rx_done(rmt_channel_handle_t rmt_rx_channel, const rmt_rx_done_event_data_t *edata, void *user_data)
{
DShotRMT *instance = static_cast<DShotRMT *>(user_data);
2025-09-17 20:41:20 +01:00
if (edata && edata->num_symbols == GCR_BITS_PER_FRAME)
{
uint16_t erpm = instance->_decodeDShotFrame(edata->received_symbols);
2025-09-18 10:23:39 +01:00
if (erpm != DSHOT_NULL_PACKET)
{
2025-09-18 10:23:39 +01:00
// Atomically store the new eRPM value and set the flag
2025-09-17 20:41:20 +01:00
instance->_last_erpm_atomic.store(erpm);
instance->_telemetry_ready_flag_atomic.store(true);
}
}
return false;
}