fixing reboot loop

This commit is contained in:
Wastl Kraus 2025-10-01 09:48:26 +02:00
parent 51cac2f1ad
commit 145ce88312
11 changed files with 158 additions and 210 deletions

View File

@ -62,7 +62,7 @@ void setup() {
motor.begin(); motor.begin();
// Print CPU Info // Print CPU Info
DShotRMT::printCpuInfo(Serial); printCpuInfo(Serial);
Serial.println("Motor initialized. Ramping up to 25% throttle..."); Serial.println("Motor initialized. Ramping up to 25% throttle...");
@ -79,7 +79,7 @@ void loop() {
motor.sendThrottlePercent(0); motor.sendThrottlePercent(0);
// Print DShot Info // Print DShot Info
DShotRMT::printDShotInfo(motor, Serial); printDShotInfo(motor, Serial);
} }
``` ```
@ -104,7 +104,7 @@ The `web_control` and `web_client` examples require the following additional lib
The main class is `DShotRMT`. Here are the most important methods: The main class is `DShotRMT`. Here are the most important methods:
- `DShotRMT(gpio_num_t gpio, dshot_mode_t mode, bool is_bidirectional = false, uint16_t magnet_count = DEFAULT_MOTOR_MAGNET_COUNT)`: Constructor to create a new DShotRMT instance. - `DShotRMT(gpio_num_t gpio, dshot_mode_t mode = DSHOT300, bool is_bidirectional = false, uint16_t magnet_count = DEFAULT_MOTOR_MAGNET_COUNT)`: Constructor to create a new DShotRMT instance.
- `begin()`: Initializes the DShot RMT channels and encoder. - `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. - `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. - `sendThrottle(uint16_t throttle)`: Sends a raw throttle value (48-2047) to the ESC. A value of 0 sends a motor stop command.
@ -115,8 +115,8 @@ The main class is `DShotRMT`. Here are the most important methods:
- `setMotorSpinDirection(bool reversed)`: Sets the motor spin direction. `true` for reversed, `false` for normal. - `setMotorSpinDirection(bool reversed)`: Sets the motor spin direction. `true` for reversed, `false` for normal.
- `saveESCSettings()`: Sends a command to the ESC to save its current settings. Use with caution as this writes to ESC's non-volatile memory. - `saveESCSettings()`: Sends a command to the ESC to save its current settings. Use with caution as this writes to ESC's non-volatile memory.
- `printDShotResult(dshot_result_t &result, Stream &output = Serial)`: Prints the result of a DShot operation to the specified output stream. - `printDShotResult(dshot_result_t &result, Stream &output = Serial)`: Prints the result of a DShot operation to the specified output stream.
- `DShotRMT::printDShotInfo(const DShotRMT &dshot_rmt, Stream &output = Serial)`: Prints detailed DShot signal information for a given DShotRMT instance. - `printDShotInfo(const DShotRMT &dshot_rmt, Stream &output = Serial)`: Prints detailed DShot signal information for a given DShotRMT instance.
- `DShotRMT::printCpuInfo(Stream &output = Serial)`: Prints detailed CPU information. - `printCpuInfo(Stream &output = Serial)`: Prints detailed CPU information.
- `setMotorMagnetCount(uint16_t magnet_count)`: Sets the motor magnet count for RPM calculation. - `setMotorMagnetCount(uint16_t magnet_count)`: Sets the motor magnet count for RPM calculation.
- `getMode()`: Gets the current DShot mode. - `getMode()`: Gets the current DShot mode.
- `isBidirectional()`: Checks if bidirectional DShot is enabled. - `isBidirectional()`: Checks if bidirectional DShot is enabled.

View File

@ -18,7 +18,7 @@ static constexpr gpio_num_t MOTOR01_PIN = GPIO_NUM_27;
// static constexpr auto MOTOR01_PIN = 17; // static constexpr auto MOTOR01_PIN = 17;
// Supported: DSHOT150, DSHOT300, DSHOT600, (DSHOT1200) // Supported: DSHOT150, DSHOT300, DSHOT600, (DSHOT1200)
static constexpr dshot_mode_t DSHOT_MODE = dshot_mode_t::DSHOT300; static constexpr dshot_mode_t DSHOT_MODE = DSHOT300;
// BiDirectional DShot Support (default: false) // BiDirectional DShot Support (default: false)
// Note: Bidirectional DShot is currently not officially supported // Note: Bidirectional DShot is currently not officially supported
@ -26,7 +26,7 @@ static constexpr dshot_mode_t DSHOT_MODE = dshot_mode_t::DSHOT300;
static constexpr auto IS_BIDIRECTIONAL = false; static constexpr auto IS_BIDIRECTIONAL = false;
// Motor magnet count for RPM calculation // Motor magnet count for RPM calculation
static constexpr auto MOTOR01_MAGNET_COUNT = 14; // static constexpr auto MOTOR01_MAGNET_COUNT = 14;
// Creates the motor instance // Creates the motor instance
DShotRMT motor01(MOTOR01_PIN, DSHOT_MODE, IS_BIDIRECTIONAL); DShotRMT motor01(MOTOR01_PIN, DSHOT_MODE, IS_BIDIRECTIONAL);
@ -41,7 +41,7 @@ void setup()
motor01.begin(); motor01.begin();
// Print CPU Info // Print CPU Info
DShotRMT::printCpuInfo(USB_SERIAL); printCpuInfo(USB_SERIAL);
// //
printMenu(); printMenu();
@ -80,7 +80,7 @@ void loop()
// Print motor stats every 3 seconds in continuous mode // Print motor stats every 3 seconds in continuous mode
if (continuous_throttle && (esp_timer_get_time() - last_stats_print >= 3000000)) if (continuous_throttle && (esp_timer_get_time() - last_stats_print >= 3000000))
{ {
DShotRMT::printDShotInfo(motor01, USB_SERIAL); printDShotInfo(motor01, USB_SERIAL);
USB_SERIAL.println(" "); USB_SERIAL.println(" ");
@ -132,7 +132,7 @@ void handleSerialInput(const String &input, uint16_t &throttle, bool &continuous
} }
else if (input == "info") else if (input == "info")
{ {
DShotRMT::printDShotInfo(motor01, USB_SERIAL); printDShotInfo(motor01, USB_SERIAL);
} }
else if (input == "rpm" && IS_BIDIRECTIONAL) else if (input == "rpm" && IS_BIDIRECTIONAL)
{ {

View File

@ -17,7 +17,7 @@ static constexpr auto USB_SERIAL_BAUD = 115200;
static constexpr gpio_num_t MOTOR01_PIN = GPIO_NUM_27; static constexpr gpio_num_t MOTOR01_PIN = GPIO_NUM_27;
// Supported: DSHOT150, DSHOT300, DSHOT600, (DSHOT1200) // Supported: DSHOT150, DSHOT300, DSHOT600, (DSHOT1200)
static constexpr dshot_mode_t DSHOT_MODE = dshot_mode_t::DSHOT300; static constexpr dshot_mode_t DSHOT_MODE = DSHOT300;
// BiDirectional DShot Support (default: false) // BiDirectional DShot Support (default: false)
// Note: Bidirectional DShot is currently not officially supported // Note: Bidirectional DShot is currently not officially supported
@ -44,7 +44,7 @@ void setup()
motor01.begin(); motor01.begin();
// Print CPU Info // Print CPU Info
motor01.printCpuInfo(); printCpuInfo(USB_SERIAL);
// //
printMenu(); printMenu();
@ -98,7 +98,7 @@ void handleSerialInput(const String &input)
} }
else if (input == "info") else if (input == "info")
{ {
DShotRMT::printDShotInfo(motor01, USB_SERIAL); printDShotInfo(motor01, USB_SERIAL);
} }
else if (input == "rpm" && IS_BIDIRECTIONAL) else if (input == "rpm" && IS_BIDIRECTIONAL)
{ {

View File

@ -39,10 +39,10 @@ static constexpr auto &USB_SERIAL = Serial;
static constexpr auto USB_SERIAL_BAUD = 115200; static constexpr auto USB_SERIAL_BAUD = 115200;
// Motor configuration - Pin number or GPIO_PIN // Motor configuration - Pin number or GPIO_PIN
static constexpr auto MOTOR01_PIN = 17; static constexpr gpio_num_t MOTOR01_PIN = GPIO_NUM_27;
// Supported: DSHOT150, DSHOT300, DSHOT600, (DSHOT1200) // Supported: DSHOT150, DSHOT300, DSHOT600, (DSHOT1200)
static constexpr dshot_mode_t DSHOT_MODE = dshot_mode_t::DSHOT300; static constexpr dshot_mode_t DSHOT_MODE = DSHOT300;
// BiDirectional DShot Support (default: false) // BiDirectional DShot Support (default: false)
static constexpr auto IS_BIDIRECTIONAL = false; // Note: Bidirectional DShot is currently not officially supported due to instability and external hardware requirements. static constexpr auto IS_BIDIRECTIONAL = false; // Note: Bidirectional DShot is currently not officially supported due to instability and external hardware requirements.
@ -84,7 +84,7 @@ void setup()
USB_SERIAL.println("DShotRMT Web Client Demo Starting..."); USB_SERIAL.println("DShotRMT Web Client Demo Starting...");
motor01.begin(); motor01.begin();
motor01.printCpuInfo(); printCpuInfo(USB_SERIAL);
// Connect to WiFi network // Connect to WiFi network
USB_SERIAL.println("\nConnecting to WiFi network..."); USB_SERIAL.println("\nConnecting to WiFi network...");
@ -172,7 +172,7 @@ void loop()
// Print motor stats every 3 seconds in continuous mode // Print motor stats every 3 seconds in continuous mode
if ((esp_timer_get_time() - last_serial_update >= 3000000)) if ((esp_timer_get_time() - last_serial_update >= 3000000))
{ {
DShotRMT::printDShotInfo(motor01, USB_SERIAL); printDShotInfo(motor01, USB_SERIAL);
// Get Motor RPM if bidirectional and armed // Get Motor RPM if bidirectional and armed
if (IS_BIDIRECTIONAL && isArmed) if (IS_BIDIRECTIONAL && isArmed)
@ -447,7 +447,7 @@ void handleSerialInput(const String &input)
if (input == "info") if (input == "info")
{ {
DShotRMT::printDShotInfo(motor01, USB_SERIAL); printDShotInfo(motor01, USB_SERIAL);
USB_SERIAL.println(" "); USB_SERIAL.println(" ");
USB_SERIAL.printf("Arming Status: %s\n", isArmed ? "ARMED" : "DISARMED"); USB_SERIAL.printf("Arming Status: %s\n", isArmed ? "ARMED" : "DISARMED");
return; return;

View File

@ -38,10 +38,10 @@ static constexpr auto &USB_SERIAL = Serial;
static constexpr auto USB_SERIAL_BAUD = 115200; static constexpr auto USB_SERIAL_BAUD = 115200;
// Motor configuration - Pin number or GPIO_PIN // Motor configuration - Pin number or GPIO_PIN
static constexpr auto MOTOR01_PIN = 17; static constexpr gpio_num_t MOTOR01_PIN = GPIO_NUM_27;
// Supported: DSHOT150, DSHOT300, DSHOT600, (DSHOT1200) // Supported: DSHOT150, DSHOT300, DSHOT600, (DSHOT1200)
static constexpr dshot_mode_t DSHOT_MODE = dshot_mode_t::DSHOT300; static constexpr dshot_mode_t DSHOT_MODE = DSHOT300;
// BiDirectional DShot Support (default: false) // BiDirectional DShot Support (default: false)
static constexpr auto IS_BIDIRECTIONAL = false; // Note: Bidirectional DShot is currently not officially supported due to instability and external hardware requirements. static constexpr auto IS_BIDIRECTIONAL = false; // Note: Bidirectional DShot is currently not officially supported due to instability and external hardware requirements.
@ -74,7 +74,7 @@ void setup()
USB_SERIAL.begin(USB_SERIAL_BAUD); USB_SERIAL.begin(USB_SERIAL_BAUD);
motor01.begin(); motor01.begin();
motor01.printCpuInfo(); printCpuInfo(USB_SERIAL);
// Set IP Address // Set IP Address
WiFi.softAPConfig(local_IP, gateway, subnet); WiFi.softAPConfig(local_IP, gateway, subnet);
@ -138,7 +138,7 @@ void loop()
// Print motor stats every 3 seconds in continuous mode // Print motor stats every 3 seconds in continuous mode
if ((esp_timer_get_time() - last_serial_update >= 3000000)) if ((esp_timer_get_time() - last_serial_update >= 3000000))
{ {
DShotRMT::printDShotInfo(motor01, USB_SERIAL); printDShotInfo(motor01, USB_SERIAL);
USB_SERIAL.println(" "); USB_SERIAL.println(" ");
@ -254,7 +254,7 @@ void handleSerialInput(const String &input)
} }
if (input == "info") if (input == "info")
{ {
DShotRMT::printDShotInfo(motor01, USB_SERIAL); printDShotInfo(motor01, USB_SERIAL);
USB_SERIAL.println(" "); USB_SERIAL.println(" ");
USB_SERIAL.printf("Arming Status: %s\n", isArmed ? "ARMED" : "DISARMED"); USB_SERIAL.printf("Arming Status: %s\n", isArmed ? "ARMED" : "DISARMED");
return; return;

View File

@ -1,5 +1,5 @@
name=DShotRMT name=DShotRMT
version=0.8.7 version=0.8.8
author=Wastl Kraus <wir-sind-die-matrix.de> author=Wastl Kraus <wir-sind-die-matrix.de>
maintainer=Wastl Kraus <wir-sind-die-matrix.de> maintainer=Wastl Kraus <wir-sind-die-matrix.de>
license=MIT license=MIT

View File

@ -8,33 +8,13 @@
#include <DShotRMT.h> #include <DShotRMT.h>
// Constructors & Destructor
// Constructor with GPIO number // Constructor with GPIO number
DShotRMT::DShotRMT(gpio_num_t gpio, dshot_mode_t mode, bool is_bidirectional, uint16_t magnet_count) DShotRMT::DShotRMT(gpio_num_t gpio, dshot_mode_t mode, bool is_bidirectional, uint16_t magnet_count)
: _gpio(gpio), : _gpio(gpio),
_mode(mode), _mode(mode),
_is_bidirectional(is_bidirectional), _is_bidirectional(is_bidirectional),
_motor_magnet_count(magnet_count), _motor_magnet_count(magnet_count),
_dshot_timing(DSHOT_TIMING_US[static_cast<int>(mode)]), _dshot_timing(DSHOT_TIMING_US[static_cast<int>(mode)])
_frame_timer_us(0),
_rmt_ticks{0},
_last_throttle(dshotCommands_e::DSHOT_CMD_MOTOR_STOP),
_last_transmission_time_us(0),
_last_command_timestamp(0),
_encoded_frame_value(0),
_packet{0},
_pulse_level(1), // DShot standard: signal is idle-low, so pulses start by going HIGH
_idle_level(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{},
_rmt_tx_config{},
_rmt_rx_config{},
_rx_event_callbacks{},
_last_erpm_atomic(0),
_telemetry_ready_flag_atomic(false)
{ {
// Pre-calculate timing and bit positions for performance // Pre-calculate timing and bit positions for performance
_preCalculateRMTTicks(); _preCalculateRMTTicks();
@ -78,7 +58,6 @@ DShotRMT::~DShotRMT()
} }
} }
// Public Core Functions
// Initialize DShotRMT // Initialize DShotRMT
dshot_result_t DShotRMT::begin() dshot_result_t DShotRMT::begin()
{ {
@ -149,20 +128,16 @@ dshot_result_t DShotRMT::sendThrottlePercent(float percent)
} }
// Send DShot command to ESC // Send DShot command to ESC
dshot_result_t DShotRMT::sendCommand(uint16_t command) dshot_result_t DShotRMT::sendCommand(dshotCommands_e command)
{ {
if (command > dshotCommands_e::DSHOT_CMD_MAX) _packet = _buildDShotPacket(static_cast<uint16_t>(command));
{
return {false, dshot_msg_code_t::DSHOT_COMMAND_NOT_VALID};
}
_packet = _buildDShotPacket(command);
return _sendDShotFrame(_packet); return _sendDShotFrame(_packet);
} }
// Send full DShot commands for setup etc // Send full DShot commands for setup etc
// This is a blocking function that uses delayMicroseconds for repetitions. // 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 DShotRMT::_sendCommandInternal(dshotCommands_e dshot_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}; dshot_result_t result = {false, dshot_msg_code_t::DSHOT_UNKNOWN, NO_DSHOT_TELEMETRY, NO_DSHOT_TELEMETRY};
@ -205,7 +180,7 @@ dshot_result_t DShotRMT::sendCommand(dshotCommands_e dshot_command, uint16_t rep
} }
// Get telemetry data // Get telemetry data
dshot_result_t DShotRMT::getTelemetry(uint16_t magnet_count) dshot_result_t DShotRMT::getTelemetry()
{ {
dshot_result_t result = {false, dshot_msg_code_t::DSHOT_TELEMETRY_FAILED, NO_DSHOT_TELEMETRY, NO_DSHOT_TELEMETRY}; dshot_result_t result = {false, dshot_msg_code_t::DSHOT_TELEMETRY_FAILED, NO_DSHOT_TELEMETRY, NO_DSHOT_TELEMETRY};
@ -216,7 +191,7 @@ dshot_result_t DShotRMT::getTelemetry(uint16_t magnet_count)
} }
// Use stored magnet count if parameter is 0 (default) // Use stored magnet count if parameter is 0 (default)
uint16_t final_magnet_count = (magnet_count == 0) ? _motor_magnet_count : magnet_count; uint16_t final_magnet_count = _motor_magnet_count;
// Check if the callback has set the flag for new data // Check if the callback has set the flag for new data
if (_telemetry_ready_flag_atomic) if (_telemetry_ready_flag_atomic)
@ -246,22 +221,19 @@ dshot_result_t DShotRMT::setMotorSpinDirection(bool reversed)
// Use command as a yes / no switch // 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; 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); return _sendCommandInternal(command, SETTINGS_COMMAND_REPEATS, SETTINGS_COMMAND_DELAY_US);
} }
dshot_result_t DShotRMT::getESCInfo()
{
return sendCommand(dshotCommands_e::DSHOT_CMD_ESC_INFO);
}
// Use with caution // Use with caution
dshot_result_t DShotRMT::saveESCSettings() dshot_result_t DShotRMT::saveESCSettings()
{ {
return sendCommand(dshotCommands_e::DSHOT_CMD_SAVE_SETTINGS, SETTINGS_COMMAND_REPEATS, SETTINGS_COMMAND_DELAY_US); return _sendCommandInternal(dshotCommands_e::DSHOT_CMD_SAVE_SETTINGS, SETTINGS_COMMAND_REPEATS, SETTINGS_COMMAND_DELAY_US);
} }
// Simple check // Simple check
bool DShotRMT::_isValidCommand(dshotCommands_e command) bool DShotRMT::_isValidCommand(dshotCommands_e command) const
{ {
return (command >= dshotCommands_e::DSHOT_CMD_MOTOR_STOP && command <= dshotCommands_e::DSHOT_CMD_MAX); return (command >= dshotCommands_e::DSHOT_CMD_MOTOR_STOP && command <= dshotCommands_e::DSHOT_CMD_MAX);
} }
@ -271,8 +243,8 @@ dshot_result_t DShotRMT::_executeCommand(dshotCommands_e command)
{ {
uint64_t start_time = esp_timer_get_time(); uint64_t start_time = esp_timer_get_time();
// Execute the command using the DShotRMT instance _packet = _buildDShotPacket(static_cast<uint16_t>(command));
dshot_result_t result = sendCommand(command); dshot_result_t result = _sendDShotFrame(_packet);
uint64_t end_time = esp_timer_get_time(); uint64_t end_time = esp_timer_get_time();
_last_command_timestamp = end_time; _last_command_timestamp = end_time;
@ -350,6 +322,7 @@ dshot_result_t DShotRMT::_initRXChannel()
return {true, dshot_msg_code_t::DSHOT_RX_INIT_SUCCESS}; return {true, dshot_msg_code_t::DSHOT_RX_INIT_SUCCESS};
} }
//
dshot_result_t DShotRMT::_initDShotEncoder() dshot_result_t DShotRMT::_initDShotEncoder()
{ {
rmt_bytes_encoder_config_t encoder_config = { rmt_bytes_encoder_config_t encoder_config = {
@ -367,8 +340,7 @@ dshot_result_t DShotRMT::_initDShotEncoder()
}, },
.flags = { .flags = {
.msb_first = 1 // DShot is MSB first .msb_first = 1 // DShot is MSB first
} }};
};
if (rmt_new_bytes_encoder(&encoder_config, &_dshot_encoder) != DSHOT_OK) if (rmt_new_bytes_encoder(&encoder_config, &_dshot_encoder) != DSHOT_OK)
{ {
@ -379,7 +351,7 @@ dshot_result_t DShotRMT::_initDShotEncoder()
} }
// Private Packet Management Functions // Private Packet Management Functions
dshot_packet_t DShotRMT::_buildDShotPacket(const uint16_t &value) dshot_packet_t DShotRMT::_buildDShotPacket(const uint16_t &value) const
{ {
dshot_packet_t packet = {}; dshot_packet_t packet = {};
@ -393,14 +365,14 @@ dshot_packet_t DShotRMT::_buildDShotPacket(const uint16_t &value)
return packet; return packet;
} }
uint16_t DShotRMT::_buildDShotFrameValue(const dshot_packet_t &packet) uint16_t DShotRMT::_buildDShotFrameValue(const dshot_packet_t &packet) const
{ {
// Combine throttle, telemetry bit, and CRC into a single 16-bit frame // Combine throttle, telemetry bit, and CRC into a single 16-bit frame
uint16_t data_and_telemetry = (packet.throttle_value << 1) | packet.telemetric_request; uint16_t data_and_telemetry = (packet.throttle_value << 1) | packet.telemetric_request;
return (data_and_telemetry << 4) | packet.checksum; return (data_and_telemetry << 4) | packet.checksum;
} }
uint16_t DShotRMT::_calculateCRC(const uint16_t &data) uint16_t DShotRMT::_calculateCRC(const uint16_t &data) const
{ {
// Standard DShot CRC calculation using XOR // Standard DShot CRC calculation using XOR
uint16_t crc = (data ^ (data >> 4) ^ (data >> 8)) & DSHOT_CRC_MASK; uint16_t crc = (data ^ (data >> 4) ^ (data >> 8)) & DSHOT_CRC_MASK;
@ -463,7 +435,7 @@ dshot_result_t DShotRMT::_sendDShotFrame(const dshot_packet_t &packet)
// This function needs to be fast, as it generates the RMT symbols just before sending // This function needs to be fast, as it generates the RMT symbols just before sending
// Placed in IRAM for high performance, as it's called from an ISR context // 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) uint16_t IRAM_ATTR DShotRMT::_decodeDShotFrame(const rmt_symbol_word_t *symbols) const
{ {
uint32_t gcr_value = 0; uint32_t gcr_value = 0;
@ -503,7 +475,7 @@ uint16_t IRAM_ATTR DShotRMT::_decodeDShotFrame(const rmt_symbol_word_t *symbols)
} }
// Timing Control Functions // Timing Control Functions
bool IRAM_ATTR DShotRMT::_isFrameIntervalElapsed() bool IRAM_ATTR DShotRMT::_isFrameIntervalElapsed() const
{ {
// Check if the minimum interval between frames has passed // Check if the minimum interval between frames has passed
uint64_t current_time = esp_timer_get_time(); uint64_t current_time = esp_timer_get_time();
@ -537,47 +509,3 @@ bool IRAM_ATTR DShotRMT::_on_rx_done(rmt_channel_handle_t rmt_rx_channel, const
return false; return false;
} }
// Public Static Utility Functions
void DShotRMT::printDShotInfo(const DShotRMT &dshot_rmt, Stream &output)
{
output.println("\n === DShot Signal Info === ");
uint16_t dshot_mode_val = 0;
switch (dshot_rmt.getMode())
{
case dshot_mode_t::DSHOT150:
dshot_mode_val = 150;
break;
case dshot_mode_t::DSHOT300:
dshot_mode_val = 300;
break;
case dshot_mode_t::DSHOT600:
dshot_mode_val = 600;
break;
case dshot_mode_t::DSHOT1200:
dshot_mode_val = 1200;
break;
}
output.printf("Current Mode: DSHOT%d\n", dshot_mode_val);
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 DShotRMT::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());
}

View File

@ -9,64 +9,49 @@
#pragma once #pragma once
#include <Arduino.h> #include <Arduino.h>
#include <dshot_definitions.h>
#include <driver/gpio.h>
#include <driver/rmt_tx.h> #include <driver/rmt_tx.h>
#include <driver/rmt_rx.h> #include <driver/rmt_rx.h>
#include <atomic> #include <atomic>
// Main class for DShot signal generation and reception. #include "dshot_definitions.h"
// This class provides an interface to generate DShot signals for Electronic Speed Controllers (ESCs) #include <driver/rmt_encoder.h>
// and to receive telemetry data using the ESP32's RMT peripheral.
// DShotRMT Class Definition
class DShotRMT class DShotRMT
{ {
public: public:
// Constructor for DShotRMT with GPIO number. // Constructor for DShotRMT with GPIO number.
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); 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);
// Constructor for DShotRMT with Arduino pin number. // Constructor for DShotRMT with Arduino pin number.
DShotRMT(uint16_t pin_nr, dshot_mode_t mode, bool is_bidirectional, uint16_t magnet_count = DEFAULT_MOTOR_MAGNET_COUNT); DShotRMT(uint16_t pin_nr, dshot_mode_t mode, bool is_bidirectional, uint16_t magnet_count = DEFAULT_MOTOR_MAGNET_COUNT);
// Destructor for DShotRMT. // Destructor
// Cleans up RMT channels and encoder resources.
~DShotRMT(); ~DShotRMT();
// Public Core Functions // Public Core Functions
// Initializes the DShot RMT channels and encoder. // Initializes the DShot RMT channels and encoder.
dshot_result_t begin(); dshot_result_t begin();
// Sends a DShot throttle value to the ESC. // Sends a throttle value as a percentage (0.0-100.0) to the ESC.
dshot_result_t sendThrottle(uint16_t throttle);
// Sends a DShot throttle value as a percentage to the ESC.
dshot_result_t sendThrottlePercent(float percent); dshot_result_t sendThrottlePercent(float percent);
// Sends a single DShot command to the ESC. // Sends a raw throttle value (48-2047) to the ESC. A value of 0 sends a motor stop command.
dshot_result_t sendCommand(uint16_t command); dshot_result_t sendThrottle(uint16_t throttle);
// Sends a DShot command multiple times with a delay between repetitions. This is a blocking function. // Sends a DShot command (0-47) to the ESC.
dshot_result_t sendCommand(dshotCommands_e dshot_command, uint16_t repeat_count = DEFAULT_CMD_REPEAT_COUNT, uint16_t delay_us = DEFAULT_CMD_DELAY_US); dshot_result_t sendCommand(dshotCommands_e command);
// Retrieves telemetry data from the ESC. // Retrieves telemetry data from the ESC.
dshot_result_t getTelemetry(uint16_t magnet_count = 0); dshot_result_t getTelemetry();
// Sends a command to the ESC to request ESC information. // Sets the motor spin direction. 'true' for reversed, 'false' for normal.
dshot_result_t getESCInfo();
// Sets the motor spin direction.
dshot_result_t setMotorSpinDirection(bool reversed); dshot_result_t setMotorSpinDirection(bool reversed);
// Sends a command to the ESC to save its current settings. // Sends a command to the ESC to save its current settings. Use with caution as this writes to ESC's non-volatile memory.
// Use with caution as this writes to ESC's non-volatile memory.
dshot_result_t saveESCSettings(); dshot_result_t saveESCSettings();
// Public Utility & Info Functions // Public Utility & Info Functions
// Prints detailed DShot signal information for a given DShotRMT instance.
static void printDShotInfo(const DShotRMT &dshot_rmt, Stream &output = Serial);
// Prints detailed CPU information.
static void printCpuInfo(Stream &output = Serial);
// Sets the motor magnet count for RPM calculation. // Sets the motor magnet count for RPM calculation.
void setMotorMagnetCount(uint16_t magnet_count); void setMotorMagnetCount(uint16_t magnet_count);
@ -76,41 +61,29 @@ public:
// Checks if bidirectional DShot is enabled. // Checks if bidirectional DShot is enabled.
bool isBidirectional() const { return _is_bidirectional; } bool isBidirectional() const { return _is_bidirectional; }
// Gets the last encoded DShot frame value. // Gets the encoded frame value.
uint16_t getEncodedFrameValue() const { return _encoded_frame_value; } uint16_t getEncodedFrameValue() const { return _encoded_frame_value; }
// Gets the last transmitted throttle value. // Gets the last transmitted throttle value.
uint16_t getThrottleValue() const { return _packet.throttle_value; } uint16_t getThrottleValue() const { return _packet.throttle_value; }
// Testing return "verbose" messages
const char *getDShotMsg(dshot_result_t &result) const { return (_get_result_code_str(result.result_code)); }
// Deprecated Methods // Deprecated Methods
// Deprecated. Use sendThrottle() instead. // Deprecated. Use sendThrottle() instead.
[[deprecated("Use sendThrottle() instead")]] [[deprecated("Use sendThrottle() instead")]]
bool setThrottle(uint16_t throttle) dshot_result_t sendValue(uint16_t value) { return sendThrottle(value); }
{
auto result = sendThrottle(throttle);
return result.success;
}
// Deprecated. Use sendCommand() instead. // Deprecated. Use sendCommand() instead.
[[deprecated("Use sendCommand() instead")]] [[deprecated("Use sendCommand() instead")]]
bool sendDShotCommand(uint16_t command) dshot_result_t sendCommand(uint16_t command) { return sendCommand(static_cast<dshotCommands_e>(command)); }
{
auto result = sendCommand(command);
return result.success;
}
// Deprecated. Use getTelemetry() instead.
[[deprecated("Use getTelemetry() instead")]]
uint32_t getMotorRPM(uint8_t magnet_count)
{
auto result = getTelemetry(magnet_count);
return result.motor_rpm;
}
private: private:
// --- UTILITY METHODS --- // --- UTILITY METHODS ---
bool _isValidCommand(dshotCommands_e command); bool _isValidCommand(dshotCommands_e command) const;
dshot_result_t _executeCommand(dshotCommands_e command); 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 // Core Configuration Variables
gpio_num_t _gpio; gpio_num_t _gpio;
@ -118,33 +91,33 @@ private:
bool _is_bidirectional; bool _is_bidirectional;
uint16_t _motor_magnet_count; uint16_t _motor_magnet_count;
const dshot_timing_us_t &_dshot_timing; const dshot_timing_us_t &_dshot_timing;
uint64_t _frame_timer_us; uint64_t _frame_timer_us = 0;
// Timing & Packet Variables // Timing & Packet Variables
rmt_ticks_t _rmt_ticks; rmt_ticks_t _rmt_ticks{};
uint16_t _last_throttle; uint16_t _last_throttle = dshotCommands_e::DSHOT_CMD_MOTOR_STOP;
uint64_t _last_transmission_time_us; uint64_t _last_transmission_time_us = 0;
uint64_t _last_command_timestamp; uint64_t _last_command_timestamp = 0;
uint16_t _encoded_frame_value; uint16_t _encoded_frame_value = 0;
dshot_packet_t _packet; dshot_packet_t _packet{};
uint16_t _pulse_level; // DShot protocol: Signal is idle-low, so pulses start by going HIGH. uint16_t _pulse_level = 1; // 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. uint16_t _idle_level = 0; // DShot protocol: Signal returns to LOW after the high pulse.
// RMT Hardware Handles // RMT Hardware Handles
rmt_channel_handle_t _rmt_tx_channel; rmt_channel_handle_t _rmt_tx_channel = nullptr;
rmt_channel_handle_t _rmt_rx_channel; rmt_channel_handle_t _rmt_rx_channel = nullptr;
rmt_encoder_handle_t _dshot_encoder; rmt_encoder_handle_t _dshot_encoder = nullptr;
// RMT Configuration Structures // RMT Configuration Structures
rmt_tx_channel_config_t _tx_channel_config; rmt_tx_channel_config_t _tx_channel_config{};
rmt_rx_channel_config_t _rx_channel_config; rmt_rx_channel_config_t _rx_channel_config{};
rmt_transmit_config_t _rmt_tx_config; rmt_transmit_config_t _rmt_tx_config{};
rmt_receive_config_t _rmt_rx_config; rmt_receive_config_t _rmt_rx_config{};
// Bidirectional / Telemetry Variables // Bidirectional / Telemetry Variables
rmt_rx_event_callbacks_t _rx_event_callbacks; rmt_rx_event_callbacks_t _rx_event_callbacks{};
std::atomic<uint16_t> _last_erpm_atomic; std::atomic<uint16_t> _last_erpm_atomic{0};
std::atomic<bool> _telemetry_ready_flag_atomic; std::atomic<bool> _telemetry_ready_flag_atomic{false};
// Private Initialization Functions // Private Initialization Functions
dshot_result_t _initTXChannel(); dshot_result_t _initTXChannel();
@ -152,19 +125,78 @@ private:
dshot_result_t _initDShotEncoder(); dshot_result_t _initDShotEncoder();
// Private Packet Management Functions // Private Packet Management Functions
dshot_packet_t _buildDShotPacket(const uint16_t &value); dshot_packet_t _buildDShotPacket(const uint16_t &value) const;
uint16_t _buildDShotFrameValue(const dshot_packet_t &packet); uint16_t _buildDShotFrameValue(const dshot_packet_t &packet) const;
uint16_t _calculateCRC(const uint16_t &data); uint16_t _calculateCRC(const uint16_t &data) const;
void _preCalculateRMTTicks(); void _preCalculateRMTTicks();
// Private Frame Processing Functions // Private Frame Processing Functions
dshot_result_t _sendDShotFrame(const dshot_packet_t &packet); dshot_result_t _sendDShotFrame(const dshot_packet_t &packet);
uint16_t _decodeDShotFrame(const rmt_symbol_word_t *symbols); uint16_t _decodeDShotFrame(const rmt_symbol_word_t *symbols) const;
// Private Timing Control Functions // Private Timing Control Functions
bool _isFrameIntervalElapsed(); bool _isFrameIntervalElapsed() const;
void _recordFrameTransmissionTime(); void _recordFrameTransmissionTime();
// Static Callback Functions // 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); static bool _on_rx_done(rmt_channel_handle_t rmt_rx_channel, const rmt_rx_done_event_data_t *edata, void *user_data);
}; };
// Helper to quick print DShot result codes
inline void printDShotResult(dshot_result_t &result, Stream &output = Serial)
{
output.printf("Status: %s - %s", result.success ? "SUCCESS" : "FAILED", _get_result_code_str(result.result_code));
// 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();
}
// Helper to print DShot signal info
inline void printDShotInfo(const DShotRMT &dshot_rmt, Stream &output = Serial)
{
output.println("\n === DShot Signal Info === ");
uint16_t dshot_mode_val = 0;
switch (dshot_rmt.getMode())
{
case DSHOT150:
dshot_mode_val = 150;
break;
case DSHOT300:
dshot_mode_val = 300;
break;
case DSHOT600:
dshot_mode_val = 600;
break;
case DSHOT1200:
dshot_mode_val = 1200;
break;
}
output.printf("Current Mode: DSHOT%d\n", dshot_mode_val);
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());
}
// Helper to print CPU info
inline void printCpuInfo(Stream &output = Serial)
{
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());
}

View File

@ -6,14 +6,15 @@
#include <driver/rmt_rx.h> #include <driver/rmt_rx.h>
#include <atomic> #include <atomic>
// Defines the available DShot communication speeds with type safety. // Defines the available DShot communication speeds.
enum class dshot_mode_t enum dshot_mode_t
{ {
DSHOT_OFF, DSHOT_OFF,
DSHOT150, DSHOT150,
DSHOT300, DSHOT300,
DSHOT600, DSHOT600,
DSHOT1200 DSHOT1200,
DSHOT_MODE_MAX
}; };
// Represents the 16-bit DShot data packet sent to the ESC. // Represents the 16-bit DShot data packet sent to the ESC.
@ -157,7 +158,8 @@ const dshot_timing_us_t DSHOT_TIMING_US[] = {
{6.67, 5.00}, // DSHOT150 {6.67, 5.00}, // DSHOT150
{3.33, 2.50}, // DSHOT300 {3.33, 2.50}, // DSHOT300
{1.67, 1.25}, // DSHOT600 {1.67, 1.25}, // DSHOT600
{0.83, 0.67} // DSHOT1200 {0.83, 0.67}, // DSHOT1200
{0.00, 0.00} // DSHOT_MODE_MAX (dummy entry)
}; };
// Error Messages // Error Messages
@ -245,18 +247,4 @@ inline const char *_get_result_code_str(dshot_msg_code_t code)
default: default:
return UNKNOWN_ERROR; return UNKNOWN_ERROR;
} }
} }
// Helper to quick print DShot result codes
inline void printDShotResult(dshot_result_t &result, Stream &output = Serial)
{
output.printf("Status: %s - %s", result.success ? "SUCCESS" : "FAILED", _get_result_code_str(result.result_code));
// 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();
}

View File

@ -1,6 +1,6 @@
/** /**
* @file ota_update.h * @file ota_update.h
* @brief DShot signal generation using ESP32 RMT with bidirectional support * @brief Contains the HTML, CSS, and JavaScript for the OTA (Over-The-Air) update web page.
* @author Wastl Kraus * @author Wastl Kraus
* @date 2025-09-13 * @date 2025-09-13
* @license MIT * @license MIT

View File

@ -1,6 +1,6 @@
/** /**
* @file web_content.h * @file web_content.h
* @brief DShot signal generation using ESP32 RMT with bidirectional support * @brief Contains the HTML, CSS, and JavaScript for the main DShot control web page.
* @author Wastl Kraus * @author Wastl Kraus
* @date 2025-09-13 * @date 2025-09-13
* @license MIT * @license MIT