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();
// Print CPU Info
DShotRMT::printCpuInfo(Serial);
printCpuInfo(Serial);
Serial.println("Motor initialized. Ramping up to 25% throttle...");
@ -79,7 +79,7 @@ void loop() {
motor.sendThrottlePercent(0);
// 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:
- `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.
- `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.
@ -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.
- `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.
- `DShotRMT::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.
- `printDShotInfo(const DShotRMT &dshot_rmt, Stream &output = Serial)`: Prints detailed DShot signal information for a given DShotRMT instance.
- `printCpuInfo(Stream &output = Serial)`: Prints detailed CPU information.
- `setMotorMagnetCount(uint16_t magnet_count)`: Sets the motor magnet count for RPM calculation.
- `getMode()`: Gets the current DShot mode.
- `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;
// 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)
// 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;
// Motor magnet count for RPM calculation
static constexpr auto MOTOR01_MAGNET_COUNT = 14;
// static constexpr auto MOTOR01_MAGNET_COUNT = 14;
// Creates the motor instance
DShotRMT motor01(MOTOR01_PIN, DSHOT_MODE, IS_BIDIRECTIONAL);
@ -41,7 +41,7 @@ void setup()
motor01.begin();
// Print CPU Info
DShotRMT::printCpuInfo(USB_SERIAL);
printCpuInfo(USB_SERIAL);
//
printMenu();
@ -80,7 +80,7 @@ void loop()
// Print motor stats every 3 seconds in continuous mode
if (continuous_throttle && (esp_timer_get_time() - last_stats_print >= 3000000))
{
DShotRMT::printDShotInfo(motor01, USB_SERIAL);
printDShotInfo(motor01, USB_SERIAL);
USB_SERIAL.println(" ");
@ -132,7 +132,7 @@ void handleSerialInput(const String &input, uint16_t &throttle, bool &continuous
}
else if (input == "info")
{
DShotRMT::printDShotInfo(motor01, USB_SERIAL);
printDShotInfo(motor01, USB_SERIAL);
}
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;
// 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)
// Note: Bidirectional DShot is currently not officially supported
@ -44,7 +44,7 @@ void setup()
motor01.begin();
// Print CPU Info
motor01.printCpuInfo();
printCpuInfo(USB_SERIAL);
//
printMenu();
@ -98,7 +98,7 @@ void handleSerialInput(const String &input)
}
else if (input == "info")
{
DShotRMT::printDShotInfo(motor01, USB_SERIAL);
printDShotInfo(motor01, USB_SERIAL);
}
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;
// 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)
static constexpr dshot_mode_t DSHOT_MODE = dshot_mode_t::DSHOT300;
static constexpr dshot_mode_t DSHOT_MODE = DSHOT300;
// 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.
@ -84,7 +84,7 @@ void setup()
USB_SERIAL.println("DShotRMT Web Client Demo Starting...");
motor01.begin();
motor01.printCpuInfo();
printCpuInfo(USB_SERIAL);
// Connect 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
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
if (IS_BIDIRECTIONAL && isArmed)
@ -447,7 +447,7 @@ void handleSerialInput(const String &input)
if (input == "info")
{
DShotRMT::printDShotInfo(motor01, USB_SERIAL);
printDShotInfo(motor01, USB_SERIAL);
USB_SERIAL.println(" ");
USB_SERIAL.printf("Arming Status: %s\n", isArmed ? "ARMED" : "DISARMED");
return;

View File

@ -38,10 +38,10 @@ static constexpr auto &USB_SERIAL = Serial;
static constexpr auto USB_SERIAL_BAUD = 115200;
// 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)
static constexpr dshot_mode_t DSHOT_MODE = dshot_mode_t::DSHOT300;
static constexpr dshot_mode_t DSHOT_MODE = DSHOT300;
// 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.
@ -74,7 +74,7 @@ void setup()
USB_SERIAL.begin(USB_SERIAL_BAUD);
motor01.begin();
motor01.printCpuInfo();
printCpuInfo(USB_SERIAL);
// Set IP Address
WiFi.softAPConfig(local_IP, gateway, subnet);
@ -138,7 +138,7 @@ void loop()
// Print motor stats every 3 seconds in continuous mode
if ((esp_timer_get_time() - last_serial_update >= 3000000))
{
DShotRMT::printDShotInfo(motor01, USB_SERIAL);
printDShotInfo(motor01, USB_SERIAL);
USB_SERIAL.println(" ");
@ -254,7 +254,7 @@ void handleSerialInput(const String &input)
}
if (input == "info")
{
DShotRMT::printDShotInfo(motor01, USB_SERIAL);
printDShotInfo(motor01, USB_SERIAL);
USB_SERIAL.println(" ");
USB_SERIAL.printf("Arming Status: %s\n", isArmed ? "ARMED" : "DISARMED");
return;

View File

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

View File

@ -8,33 +8,13 @@
#include <DShotRMT.h>
// 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),
_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)
_dshot_timing(DSHOT_TIMING_US[static_cast<int>(mode)])
{
// Pre-calculate timing and bit positions for performance
_preCalculateRMTTicks();
@ -78,7 +58,6 @@ DShotRMT::~DShotRMT()
}
}
// Public Core Functions
// Initialize DShotRMT
dshot_result_t DShotRMT::begin()
{
@ -149,20 +128,16 @@ dshot_result_t DShotRMT::sendThrottlePercent(float percent)
}
// 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)
{
return {false, dshot_msg_code_t::DSHOT_COMMAND_NOT_VALID};
}
_packet = _buildDShotPacket(command);
_packet = _buildDShotPacket(static_cast<uint16_t>(command));
return _sendDShotFrame(_packet);
}
// Send full DShot commands for setup etc
// 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};
@ -205,7 +180,7 @@ dshot_result_t DShotRMT::sendCommand(dshotCommands_e dshot_command, uint16_t rep
}
// 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};
@ -216,7 +191,7 @@ dshot_result_t DShotRMT::getTelemetry(uint16_t magnet_count)
}
// 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
if (_telemetry_ready_flag_atomic)
@ -246,22 +221,19 @@ 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 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
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
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);
}
@ -271,8 +243,8 @@ dshot_result_t DShotRMT::_executeCommand(dshotCommands_e command)
{
uint64_t start_time = esp_timer_get_time();
// Execute the command using the DShotRMT instance
dshot_result_t result = sendCommand(command);
_packet = _buildDShotPacket(static_cast<uint16_t>(command));
dshot_result_t result = _sendDShotFrame(_packet);
uint64_t end_time = esp_timer_get_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};
}
//
dshot_result_t DShotRMT::_initDShotEncoder()
{
rmt_bytes_encoder_config_t encoder_config = {
@ -367,8 +340,7 @@ dshot_result_t DShotRMT::_initDShotEncoder()
},
.flags = {
.msb_first = 1 // DShot is MSB first
}
};
}};
if (rmt_new_bytes_encoder(&encoder_config, &_dshot_encoder) != DSHOT_OK)
{
@ -379,7 +351,7 @@ dshot_result_t DShotRMT::_initDShotEncoder()
}
// 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 = {};
@ -393,14 +365,14 @@ dshot_packet_t DShotRMT::_buildDShotPacket(const uint16_t &value)
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
uint16_t data_and_telemetry = (packet.throttle_value << 1) | packet.telemetric_request;
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
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
// 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;
@ -503,7 +475,7 @@ uint16_t IRAM_ATTR DShotRMT::_decodeDShotFrame(const rmt_symbol_word_t *symbols)
}
// Timing Control Functions
bool IRAM_ATTR DShotRMT::_isFrameIntervalElapsed()
bool IRAM_ATTR DShotRMT::_isFrameIntervalElapsed() const
{
// Check if the minimum interval between frames has passed
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;
}
// 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
#include <Arduino.h>
#include <dshot_definitions.h>
#include <driver/gpio.h>
#include <driver/rmt_tx.h>
#include <driver/rmt_rx.h>
#include <atomic>
// 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.
#include "dshot_definitions.h"
#include <driver/rmt_encoder.h>
// DShotRMT Class Definition
class DShotRMT
{
public:
// 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.
DShotRMT(uint16_t pin_nr, dshot_mode_t mode, bool is_bidirectional, uint16_t magnet_count = DEFAULT_MOTOR_MAGNET_COUNT);
// Destructor for DShotRMT.
// Cleans up RMT channels and encoder resources.
// Destructor
~DShotRMT();
// Public Core Functions
// Initializes the DShot RMT channels and encoder.
dshot_result_t begin();
// Sends a DShot throttle value to the ESC.
dshot_result_t sendThrottle(uint16_t throttle);
// Sends a DShot throttle value as a percentage to the ESC.
// Sends a throttle value as a percentage (0.0-100.0) to the ESC.
dshot_result_t sendThrottlePercent(float percent);
// Sends a single DShot command to the ESC.
dshot_result_t sendCommand(uint16_t command);
// Sends a raw throttle value (48-2047) to the ESC. A value of 0 sends a motor stop command.
dshot_result_t sendThrottle(uint16_t throttle);
// Sends a DShot command multiple times with a delay between repetitions. This is a blocking function.
dshot_result_t 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 (0-47) to the ESC.
dshot_result_t sendCommand(dshotCommands_e command);
// 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.
dshot_result_t getESCInfo();
// Sets the motor spin direction.
// Sets the motor spin direction. 'true' for reversed, 'false' for normal.
dshot_result_t setMotorSpinDirection(bool reversed);
// Sends a command to the ESC to save its current settings.
// Use with caution as this writes to ESC's non-volatile memory.
// Sends a command to the ESC to save its current settings. Use with caution as this writes to ESC's non-volatile memory.
dshot_result_t saveESCSettings();
// 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.
void setMotorMagnetCount(uint16_t magnet_count);
@ -76,41 +61,29 @@ public:
// Checks if bidirectional DShot is enabled.
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; }
// Gets the last transmitted 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. Use sendThrottle() instead.
[[deprecated("Use sendThrottle() instead")]]
bool setThrottle(uint16_t throttle)
{
auto result = sendThrottle(throttle);
return result.success;
}
dshot_result_t sendValue(uint16_t value) { return sendThrottle(value); }
// Deprecated. Use sendCommand() instead.
[[deprecated("Use sendCommand() instead")]]
bool sendDShotCommand(uint16_t 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;
}
dshot_result_t sendCommand(uint16_t command) { return sendCommand(static_cast<dshotCommands_e>(command)); }
private:
// --- UTILITY METHODS ---
bool _isValidCommand(dshotCommands_e command);
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;
@ -118,33 +91,33 @@ private:
bool _is_bidirectional;
uint16_t _motor_magnet_count;
const dshot_timing_us_t &_dshot_timing;
uint64_t _frame_timer_us;
uint64_t _frame_timer_us = 0;
// Timing & Packet Variables
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;
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.
rmt_ticks_t _rmt_ticks{};
uint16_t _last_throttle = dshotCommands_e::DSHOT_CMD_MOTOR_STOP;
uint64_t _last_transmission_time_us = 0;
uint64_t _last_command_timestamp = 0;
uint16_t _encoded_frame_value = 0;
dshot_packet_t _packet{};
uint16_t _pulse_level = 1; // DShot protocol: Signal is idle-low, so pulses start by going HIGH.
uint16_t _idle_level = 0; // DShot protocol: Signal returns to LOW after the high pulse.
// RMT Hardware Handles
rmt_channel_handle_t _rmt_tx_channel;
rmt_channel_handle_t _rmt_rx_channel;
rmt_encoder_handle_t _dshot_encoder;
rmt_channel_handle_t _rmt_tx_channel = nullptr;
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;
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<uint16_t> _last_erpm_atomic;
std::atomic<bool> _telemetry_ready_flag_atomic;
rmt_rx_event_callbacks_t _rx_event_callbacks{};
std::atomic<uint16_t> _last_erpm_atomic{0};
std::atomic<bool> _telemetry_ready_flag_atomic{false};
// Private Initialization Functions
dshot_result_t _initTXChannel();
@ -152,19 +125,78 @@ private:
dshot_result_t _initDShotEncoder();
// Private Packet Management Functions
dshot_packet_t _buildDShotPacket(const uint16_t &value);
uint16_t _buildDShotFrameValue(const dshot_packet_t &packet);
uint16_t _calculateCRC(const uint16_t &data);
dshot_packet_t _buildDShotPacket(const uint16_t &value) const;
uint16_t _buildDShotFrameValue(const dshot_packet_t &packet) const;
uint16_t _calculateCRC(const uint16_t &data) const;
void _preCalculateRMTTicks();
// Private Frame Processing Functions
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
bool _isFrameIntervalElapsed();
bool _isFrameIntervalElapsed() const;
void _recordFrameTransmissionTime();
// 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);
};
// 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 <atomic>
// Defines the available DShot communication speeds with type safety.
enum class dshot_mode_t
// Defines the available DShot communication speeds.
enum dshot_mode_t
{
DSHOT_OFF,
DSHOT150,
DSHOT300,
DSHOT600,
DSHOT1200
DSHOT1200,
DSHOT_MODE_MAX
};
// 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
{3.33, 2.50}, // DSHOT300
{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
@ -246,17 +248,3 @@ inline const char *_get_result_code_str(dshot_msg_code_t code)
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
* @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
* @date 2025-09-13
* @license MIT

View File

@ -1,6 +1,6 @@
/**
* @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
* @date 2025-09-13
* @license MIT