fix non dma on esp32-s3. hacky way shouldnt be used. TODO - fix underlying issue with lack of termination of signal

This commit is contained in:
franchioping 2026-04-13 15:03:58 +01:00
parent 262feec75c
commit 4f731edaea
3 changed files with 675 additions and 615 deletions

File diff suppressed because it is too large Load Diff

View File

@ -8,8 +8,8 @@
#pragma once
#include <atomic>
#include <Arduino.h>
#include <atomic>
#include <driver/gpio.h>
#include <driver/rmt_rx.h>
@ -27,115 +27,147 @@ static constexpr uint8_t DSHOTRMT_PATCH_VERSION = 5;
static constexpr auto DSHOT_THROTTLE_FAILSAFE = 0;
// DShotRMT class for generating DShot signals and receiving telemetry.
class DShotRMT
{
class DShotRMT {
public:
// Constructs a new DShotRMT object using a GPIO pin.
DShotRMT(gpio_num_t gpio, dshot_mode_t mode = DSHOT300, bool is_bidirectional = false, uint16_t magnet_count = DEFAULT_MOTOR_MAGNET_COUNT);
// Constructs a new DShotRMT object using a GPIO pin.
DShotRMT(gpio_num_t gpio, dshot_mode_t mode = DSHOT300,
bool is_bidirectional = false,
uint16_t magnet_count = DEFAULT_MOTOR_MAGNET_COUNT);
// Constructs a new DShotRMT object using an Arduino-style integer pin number.
DShotRMT(uint16_t pin_nr, dshot_mode_t mode, bool is_bidirectional = false, uint16_t magnet_count = DEFAULT_MOTOR_MAGNET_COUNT);
// Constructs a new DShotRMT object using an Arduino-style integer pin number.
DShotRMT(uint16_t pin_nr, dshot_mode_t mode, bool is_bidirectional = false,
uint16_t magnet_count = DEFAULT_MOTOR_MAGNET_COUNT);
// Destructor
~DShotRMT();
// Destructor
~DShotRMT();
// Initialize DShotRMT
dshot_result_t begin();
// Initialize DShotRMT
dshot_result_t begin();
// Sends a raw throttle value to the ESC.
dshot_result_t sendThrottle(uint16_t throttle);
// Sends a raw throttle value to the ESC.
dshot_result_t sendThrottle(uint16_t throttle);
// Sends a throttle value as a percentage to the ESC.
dshot_result_t sendThrottlePercent(float percent);
// Sends a throttle value as a percentage to the ESC.
dshot_result_t sendThrottlePercent(float percent);
// Sends a DShot command to the ESC by accepting an integer value.
dshot_result_t sendCommand(uint16_t command_value);
// Sends a DShot command to the ESC by accepting an integer value.
dshot_result_t sendCommand(uint16_t command_value);
// Sends a DShot command to the ESC.
dshot_result_t sendCommand(dshotCommands_e command);
// Sends a DShot command to the ESC.
dshot_result_t sendCommand(dshotCommands_e command);
// Sends a DShot command to the ESC with a specified repeat count and delay.
dshot_result_t sendCommand(dshotCommands_e command, uint16_t repeat_count, uint16_t delay_us);
// Sends a DShot command to the ESC with a specified repeat count and delay.
dshot_result_t sendCommand(dshotCommands_e command, uint16_t repeat_count,
uint16_t delay_us);
/**
* @brief Sends a custom DShot command to the ESC. Advanced feature, use with caution.
* @param command_value The raw command value (0-47).
* @param repeat_count The number of times to send the command.
* @param delay_us The delay in microseconds between repetitions.
* @return dshot_result_t The result of the operation.
*/
dshot_result_t sendCustomCommand(uint16_t command_value, uint16_t repeat_count, uint16_t delay_us);
/**
* @brief Sends a custom DShot command to the ESC. Advanced feature, use with
* caution.
* @param command_value The raw command value (0-47).
* @param repeat_count The number of times to send the command.
* @param delay_us The delay in microseconds between repetitions.
* @return dshot_result_t The result of the operation.
*/
dshot_result_t sendCustomCommand(uint16_t command_value,
uint16_t repeat_count, uint16_t delay_us);
// Retrieves telemetry data from the ESC.
dshot_result_t getTelemetry();
// Retrieves telemetry data from the ESC.
dshot_result_t getTelemetry();
// Sets the motor spin direction.
dshot_result_t setMotorSpinDirection(bool reversed);
// Sets the motor spin direction.
dshot_result_t setMotorSpinDirection(bool reversed);
// Sends a command to the ESC to save its current settings.
dshot_result_t saveESCSettings();
// Sends a command to the ESC to save its current settings.
dshot_result_t saveESCSettings();
// Getters for DShot info
dshot_mode_t getMode() const { return _mode; }
bool isBidirectional() const { return _is_bidirectional; }
uint16_t getThrottleValue() const { return _last_throttle; }
uint16_t getEncodedFrameValue() const { return _encoded_frame_value; }
// Getters for DShot info
dshot_mode_t getMode() const { return _mode; }
bool isBidirectional() const { return _is_bidirectional; }
uint16_t getThrottleValue() const { return _last_throttle; }
uint16_t getEncodedFrameValue() const { return _encoded_frame_value; }
rmt_channel_handle_t _rmt_tx_channel = nullptr; // RMT transmit channel handle
// Static Callback Function for RMT RX Events
void _cleanupRmtResources();
private:
dshot_result_t _sendRawDshotFrame(uint16_t value);
static bool IRAM_ATTR _on_rx_done(rmt_channel_handle_t rmt_rx_channel, const rmt_rx_done_event_data_t *edata, void *user_data);
uint16_t *_tx_payload;
dshot_result_t _sendRawDshotFrame(uint16_t value);
static bool IRAM_ATTR _on_rx_done(rmt_channel_handle_t rmt_rx_channel,
const rmt_rx_done_event_data_t *edata,
void *user_data);
// DShot Configuration Parameters
gpio_num_t _gpio; // GPIO pin used for DShot communication
dshot_mode_t _mode; // DShot mode (e.g., DSHOT300, DSHOT600)
bool _is_bidirectional; // True if bidirectional DShot is enabled
uint16_t _motor_magnet_count; // Number of magnets in the motor for RPM calculation
dshot_timing_us_t _dshot_timing; // DShot timing parameters in microseconds
// DShot Configuration Parameters
gpio_num_t _gpio; // GPIO pin used for DShot communication
dshot_mode_t _mode; // DShot mode (e.g., DSHOT300, DSHOT600)
bool _is_bidirectional; // True if bidirectional DShot is enabled
uint16_t
_motor_magnet_count; // Number of magnets in the motor for RPM calculation
dshot_timing_us_t _dshot_timing; // DShot timing parameters in microseconds
// RMT Hardware Handles and Configuration
rmt_channel_handle_t _rmt_tx_channel = nullptr; // RMT transmit channel handle
rmt_channel_handle_t _rmt_rx_channel = nullptr; // RMT receive channel handle
rmt_encoder_handle_t _dshot_encoder = nullptr; // DShot RMT encoder handle
rmt_ticks_t _rmt_ticks; // Pre-calculated RMT timing ticks
uint16_t _pulse_level = 1; // Output level for a pulse (typically high)
uint16_t _idle_level = 0; // Output level for idle (typically low)
// RMT Hardware Handles and Configuration
rmt_channel_handle_t _rmt_rx_channel = nullptr; // RMT receive channel handle
rmt_encoder_handle_t _dshot_encoder = nullptr; // DShot RMT encoder handle
rmt_ticks_t _rmt_ticks; // Pre-calculated RMT timing ticks
uint16_t _pulse_level = 1; // Output level for a pulse (typically high)
uint16_t _idle_level = 0; // Output level for idle (typically low)
// DShot Frame Timing and State Variables
uint64_t _last_transmission_time_us = 0; // Timestamp of the last DShot frame transmission
uint64_t _frame_timer_us = 0; // Minimum time required between DShot frames
float _percent_to_throttle_ratio = 0.0f; // Pre-calculated ratio for throttle percentage conversion
uint16_t _last_throttle = 0; // Last transmitted throttle value
dshot_packet_t _packet; // Current DShot packet being processed
uint16_t _encoded_frame_value = 0; // Last encoded 16-bit DShot frame value
// DShot Frame Timing and State Variables
uint64_t _last_transmission_time_us =
0; // Timestamp of the last DShot frame transmission
uint64_t _frame_timer_us = 0; // Minimum time required between DShot frames
float _percent_to_throttle_ratio =
0.0f; // Pre-calculated ratio for throttle percentage conversion
uint16_t _last_throttle = 0; // Last transmitted throttle value
dshot_packet_t _packet; // Current DShot packet being processed
uint16_t _encoded_frame_value = 0; // Last encoded 16-bit DShot frame value
// Telemetry Related Variables
std::atomic<uint16_t> _last_erpm_atomic = 0; // Atomically stored last received eRPM value
std::atomic<bool> _telemetry_ready_flag_atomic = false; // Atomically stored flag indicating new telemetry data
std::atomic<dshot_telemetry_data_t> _last_telemetry_data_atomic = {}; // Atomically stored last received full telemetry data
std::atomic<bool> _full_telemetry_ready_flag_atomic = false; // Atomically stored flag indicating new full telemetry data
rmt_rx_event_callbacks_t _rx_event_callbacks = {
// RMT receive event callbacks
.on_recv_done = _on_rx_done,
};
// Telemetry Related Variables
std::atomic<uint16_t> _last_erpm_atomic =
0; // Atomically stored last received eRPM value
std::atomic<bool> _telemetry_ready_flag_atomic =
false; // Atomically stored flag indicating new telemetry data
std::atomic<dshot_telemetry_data_t> _last_telemetry_data_atomic =
{}; // Atomically stored last received full telemetry data
std::atomic<bool> _full_telemetry_ready_flag_atomic =
false; // Atomically stored flag indicating new full telemetry data
rmt_rx_event_callbacks_t _rx_event_callbacks = {
// RMT receive event callbacks
.on_recv_done = _on_rx_done,
};
// Private Helper Functions for DShot Protocol Logic
bool _isValidCommand(dshotCommands_e command) const; // Checks if a given DShot command is valid
dshot_packet_t _buildDShotPacket(const uint16_t &value) const; // Builds a DShot packet from a value (throttle or command)
uint16_t _buildDShotFrameValue(const dshot_packet_t &packet) const; // Combines packet data into a 16-bit DShot frame value
uint16_t _calculateCRC(const uint16_t &data) const; // Calculates the 4-bit CRC for a DShot frame
uint8_t _calculateTelemetryCRC(const uint8_t *data, size_t len) const; // Calculates the 8-bit CRC for telemetry data
void _extractTelemetryData(const uint8_t *raw_telemetry_bytes, dshot_telemetry_data_t &telemetry_data) const; // Extracts telemetry data from raw bytes
void _preCalculateRMTTicks(); // Pre-calculates RMT timing ticks for the selected DShot mode
dshot_result_t _sendPacket(const dshot_packet_t &packet); // Sends a DShot frame via RMT TX channel
uint16_t IRAM_ATTR _decodeDShotFrame(const rmt_symbol_word_t *symbols) const; // Decodes a received RMT symbol array into an eRPM value
void IRAM_ATTR _processFullTelemetryFrame(const rmt_symbol_word_t *symbols, size_t num_symbols); // Processes a full telemetry frame
bool IRAM_ATTR _isFrameIntervalElapsed() const; // Checks if enough time has passed since the last frame transmission
void _recordFrameTransmissionTime(); // Records the current time as the last frame transmission time
// Private Helper Functions for DShot Protocol Logic
bool _isValidCommand(dshotCommands_e command)
const; // Checks if a given DShot command is valid
dshot_packet_t _buildDShotPacket(const uint16_t &value)
const; // Builds a DShot packet from a value (throttle or command)
uint16_t _buildDShotFrameValue(const dshot_packet_t &packet)
const; // Combines packet data into a 16-bit DShot frame value
uint16_t _calculateCRC(
const uint16_t &data) const; // Calculates the 4-bit CRC for a DShot frame
uint8_t _calculateTelemetryCRC(const uint8_t *data, size_t len)
const; // Calculates the 8-bit CRC for telemetry data
void _extractTelemetryData(const uint8_t *raw_telemetry_bytes,
dshot_telemetry_data_t &telemetry_data)
const; // Extracts telemetry data from raw bytes
void _preCalculateRMTTicks(); // Pre-calculates RMT timing ticks for the
// selected DShot mode
dshot_result_t _sendPacket(
const dshot_packet_t &packet); // Sends a DShot frame via RMT TX channel
uint16_t IRAM_ATTR _decodeDShotFrame(const rmt_symbol_word_t *symbols)
const; // Decodes a received RMT symbol array into an eRPM value
void IRAM_ATTR _processFullTelemetryFrame(
const rmt_symbol_word_t *symbols,
size_t num_symbols); // Processes a full telemetry frame
bool IRAM_ATTR
_isFrameIntervalElapsed() const; // Checks if enough time has passed since the
// last frame transmission
void _recordFrameTransmissionTime(); // Records the current time as the last
// frame transmission time
dshot_result_t _sendRepeatedCommand(uint16_t value, uint16_t repeat_count, uint16_t delay_us);
// Static Callback Function for RMT RX Events
void _cleanupRmtResources();
dshot_result_t _sendRepeatedCommand(uint16_t value, uint16_t repeat_count,
uint16_t delay_us);
};
#include "dshot_utils.h" // Include for helper functions

View File

@ -9,87 +9,85 @@
#include "dshot_init.h"
// Function to initialize the RMT TX channel
dshot_result_t init_rmt_tx_channel(gpio_num_t gpio, rmt_channel_handle_t *out_channel, bool is_bidirectional)
{
rmt_tx_channel_config_t tx_channel_config = {
.gpio_num = gpio,
.clk_src = DSHOT_CLOCK_SRC_DEFAULT,
.resolution_hz = DSHOT_RMT_RESOLUTION,
.mem_block_symbols = RMT_TX_BUFFER_SYMBOLS,
.trans_queue_depth = RMT_QUEUE_DEPTH,
.flags = {
.invert_out = static_cast<uint32_t>(is_bidirectional ? 1 : 0),
.init_level = 0}};
dshot_result_t init_rmt_tx_channel(gpio_num_t gpio,
rmt_channel_handle_t *out_channel,
bool is_bidirectional) {
rmt_tx_channel_config_t tx_channel_config = {
.gpio_num = gpio,
.clk_src = DSHOT_CLOCK_SRC_DEFAULT,
.resolution_hz = DSHOT_RMT_RESOLUTION,
.mem_block_symbols = 48,
.trans_queue_depth = RMT_QUEUE_DEPTH,
.flags = {.invert_out = static_cast<uint32_t>(is_bidirectional ? 1 : 0),
.init_level = 0,
.with_dma = 0}};
rmt_transmit_config_t rmt_tx_config = {}; // Initialize all members to zero
rmt_tx_config.loop_count = 0; // No automatic loops - real-time calculation
if (rmt_new_tx_channel(&tx_channel_config, out_channel) != DSHOT_OK) {
return {false, DSHOT_TX_INIT_FAILED};
}
if (rmt_new_tx_channel(&tx_channel_config, out_channel) != DSHOT_OK)
{
return {false, DSHOT_TX_INIT_FAILED};
}
if (rmt_enable(*out_channel) != DSHOT_OK) {
return {false, DSHOT_TX_INIT_FAILED};
}
if (rmt_enable(*out_channel) != DSHOT_OK)
{
return {false, DSHOT_TX_INIT_FAILED};
}
return {true, DSHOT_TX_INIT_SUCCESS};
return {true, DSHOT_TX_INIT_SUCCESS};
}
// Function to initialize the RMT RX channel
dshot_result_t init_rmt_rx_channel(gpio_num_t gpio, rmt_channel_handle_t *out_channel, rmt_rx_event_callbacks_t *rx_event_callbacks, void *user_data)
{
rmt_rx_channel_config_t rx_channel_config = {
.gpio_num = gpio,
.clk_src = DSHOT_CLOCK_SRC_DEFAULT,
.resolution_hz = DSHOT_RMT_RESOLUTION,
.mem_block_symbols = RMT_RX_BUFFER_SYMBOLS,
};
dshot_result_t init_rmt_rx_channel(gpio_num_t gpio,
rmt_channel_handle_t *out_channel,
rmt_rx_event_callbacks_t *rx_event_callbacks,
void *user_data) {
rmt_rx_channel_config_t rx_channel_config = {
.gpio_num = gpio,
.clk_src = DSHOT_CLOCK_SRC_DEFAULT,
.resolution_hz = DSHOT_RMT_RESOLUTION,
.mem_block_symbols = RMT_RX_BUFFER_SYMBOLS,
};
if (rmt_new_rx_channel(&rx_channel_config, out_channel) != DSHOT_OK)
{
return {false, DSHOT_RX_INIT_FAILED};
}
if (rmt_new_rx_channel(&rx_channel_config, out_channel) != DSHOT_OK) {
return {false, DSHOT_RX_INIT_FAILED};
}
if (rmt_rx_register_event_callbacks(*out_channel, rx_event_callbacks, user_data) != DSHOT_OK)
{
return {false, DSHOT_CALLBACK_REGISTERING_FAILED};
}
if (rmt_rx_register_event_callbacks(*out_channel, rx_event_callbacks,
user_data) != DSHOT_OK) {
return {false, DSHOT_CALLBACK_REGISTERING_FAILED};
}
if (rmt_enable(*out_channel) != DSHOT_OK)
{
return {false, DSHOT_RX_INIT_FAILED};
}
if (rmt_enable(*out_channel) != DSHOT_OK) {
return {false, DSHOT_RX_INIT_FAILED};
}
return {true, DSHOT_RX_INIT_SUCCESS};
return {true, DSHOT_RX_INIT_SUCCESS};
}
// Function to initialize the DShot RMT encoder
dshot_result_t init_dshot_encoder(rmt_encoder_handle_t *out_encoder, const rmt_ticks_t &rmt_ticks, uint16_t pulse_level, uint16_t idle_level)
{
rmt_bytes_encoder_config_t encoder_config = {
.bit0 = {
.duration0 = rmt_ticks.t0h_ticks,
.level0 = pulse_level,
.duration1 = rmt_ticks.t0l_ticks,
.level1 = idle_level,
},
.bit1 = {
.duration0 = rmt_ticks.t1h_ticks,
.level0 = pulse_level,
.duration1 = rmt_ticks.t1l_ticks,
.level1 = idle_level,
},
dshot_result_t init_dshot_encoder(rmt_encoder_handle_t *out_encoder,
const rmt_ticks_t &rmt_ticks,
uint16_t pulse_level, uint16_t idle_level) {
rmt_bytes_encoder_config_t encoder_config = {
.bit0 =
{
.duration0 = rmt_ticks.t0h_ticks,
.level0 = pulse_level,
.duration1 = rmt_ticks.t0l_ticks,
.level1 = idle_level,
},
.bit1 =
{
.duration0 = rmt_ticks.t1h_ticks,
.level0 = pulse_level,
.duration1 = rmt_ticks.t1l_ticks,
.level1 = idle_level,
},
.flags = {
.msb_first = 1 // DShot is MSB first
}};
.flags = {
.msb_first = 1 // DShot is MSB first
}};
if (rmt_new_bytes_encoder(&encoder_config, out_encoder) != DSHOT_OK)
{
return {false, DSHOT_ENCODER_INIT_FAILED};
}
if (rmt_new_bytes_encoder(&encoder_config, out_encoder) != DSHOT_OK) {
return {false, DSHOT_ENCODER_INIT_FAILED};
}
return {true, DSHOT_ENCODER_INIT_SUCCESS};
return {true, DSHOT_ENCODER_INIT_SUCCESS};
}