re-enabled bidirectional support

This commit is contained in:
Wastl Kraus 2025-11-27 23:58:19 +01:00
parent 5246db57ef
commit 75d465fcf8
6 changed files with 55 additions and 30 deletions

View File

@ -6,6 +6,8 @@
An Arduino IDE library for generating DShot signals on ESP32 microcontrollers using the **modern ESP-IDF 5 RMT Encoder API** (`rmt_tx.h` / `rmt_rx.h`). This library specifically leverages the official `rmt_bytes_encoder` API for an efficient, hardware-timed and maintainable implementation. It provides a simple way to control BLHeli ESCs in both Arduino and ESP-IDF projects. An Arduino IDE library for generating DShot signals on ESP32 microcontrollers using the **modern ESP-IDF 5 RMT Encoder API** (`rmt_tx.h` / `rmt_rx.h`). This library specifically leverages the official `rmt_bytes_encoder` API for an efficient, hardware-timed and maintainable implementation. It provides a simple way to control BLHeli ESCs in both Arduino and ESP-IDF projects.
### Bidirectional DShot re-enabled for testing.
The legacy version using the old `rmt.h` API is available in the `oldAPI` branch. The legacy version using the old `rmt.h` API is available in the `oldAPI` branch.
--- ---

View File

@ -6,7 +6,6 @@
* @license MIT * @license MIT
*/ */
#include <Arduino.h>
#include <DShotRMT.h> #include <DShotRMT.h>
// USB serial port settings // USB serial port settings
@ -15,18 +14,17 @@ static constexpr auto USB_SERIAL_BAUD = 115200;
// Motor configuration - Pin number or GPIO_PIN // Motor configuration - Pin number or GPIO_PIN
static constexpr gpio_num_t MOTOR01_PIN = GPIO_NUM_27; static constexpr gpio_num_t MOTOR01_PIN = GPIO_NUM_27;
// static constexpr auto MOTOR01_PIN = 17; // static constexpr auto MOTOR01_PIN = 27;
// Supported: DSHOT150, DSHOT300, DSHOT600, (DSHOT1200) // Supported: DSHOT150, DSHOT300, DSHOT600, (DSHOT1200)
static constexpr dshot_mode_t DSHOT_MODE = 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 // re-enabled for testing
// due to instability and external hardware requirements. static constexpr auto IS_BIDIRECTIONAL = false;
static constexpr auto IS_BIDIRECTIONAL = true;
// 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);

View File

@ -1,10 +1,10 @@
name=DShotRMT name=DShotRMT
version=0.9.0 version=0.9.1
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
sentence=DShotRMT Library supporting all DShot Types and speeds. Tested with BlHeli_S. sentence=DShotRMT Library supporting all DShot Types and speeds. Bidirectional support re-enabled. Tested with BlHeli_S.
paragraph=This library can control a BlHeli_S by using encoded DShot commands. paragraph=This library can control a BlHeli_S by using encoded DShot commands. Bidirectional support re-enabled.
category=Signal Input/Output category=Signal Input/Output
url=https://github.com/derdoktor667/DShotRMT url=https://github.com/derdoktor667/DShotRMT
architectures=esp32 architectures=esp32

View File

@ -119,7 +119,7 @@ dshot_result_t DShotRMT::sendThrottlePercent(float percent)
dshot_result_t DShotRMT::sendCommand(uint16_t command_value) dshot_result_t DShotRMT::sendCommand(uint16_t command_value)
{ {
// Validate the integer command value before casting // Validate the integer command value before casting
if (command_value < DSHOT_CMD_MOTOR_STOP || command_value > DSHOT_CMD_MAX) if (command_value < DSHOT_CMD_MOTOR_STOP || command_value > DSHOT_CMD_MAX_VALUE)
{ {
return {false, DSHOT_COMMAND_NOT_VALID}; return {false, DSHOT_COMMAND_NOT_VALID};
} }
@ -134,6 +134,7 @@ dshot_result_t DShotRMT::sendCommand(dshotCommands_e command)
switch (command) switch (command)
{ {
case DSHOT_CMD_MOTOR_STOP:
case DSHOT_CMD_SAVE_SETTINGS: case DSHOT_CMD_SAVE_SETTINGS:
case DSHOT_CMD_SPIN_DIRECTION_NORMAL: case DSHOT_CMD_SPIN_DIRECTION_NORMAL:
case DSHOT_CMD_SPIN_DIRECTION_REVERSED: case DSHOT_CMD_SPIN_DIRECTION_REVERSED:
@ -332,6 +333,24 @@ dshot_result_t DShotRMT::_sendDShotFrame(const dshot_packet_t &packet)
return {true, DSHOT_NONE}; return {true, DSHOT_NONE};
} }
if (_is_bidirectional)
{
// Start the receiver to wait for incoming telemetry data
rmt_symbol_word_t rx_symbols[GCR_BITS_PER_FRAME];
size_t rx_size_bytes = GCR_BITS_PER_FRAME * sizeof(rmt_symbol_word_t);
rmt_receive_config_t rmt_rx_config = {
.signal_range_min_ns = DSHOT_PULSE_MIN_NS,
.signal_range_max_ns = DSHOT_PULSE_MAX_NS,
};
if (rmt_receive(_rmt_rx_channel, rx_symbols, rx_size_bytes, &rmt_rx_config) != DSHOT_OK)
{
return {false, DSHOT_RECEIVER_FAILED};
}
}
// Now let's prepare the actual frame
_encoded_frame_value = _buildDShotFrameValue(packet); _encoded_frame_value = _buildDShotFrameValue(packet);
// Byte-swap the 16-bit value for correct transmission order (ESP32 is little-endian, DShot is MSB first) // Byte-swap the 16-bit value for correct transmission order (ESP32 is little-endian, DShot is MSB first)
@ -342,20 +361,37 @@ dshot_result_t DShotRMT::_sendDShotFrame(const dshot_packet_t &packet)
rmt_transmit_config_t tx_config = {}; // Initialize all members to zero rmt_transmit_config_t tx_config = {}; // Initialize all members to zero
tx_config.loop_count = 0; // No automatic loops - real-time calculation tx_config.loop_count = 0; // No automatic loops - real-time calculation
tx_config.flags.eot_level = _is_bidirectional ? 1 : 0;
// TODO: Find out, why this is needed
if (_is_bidirectional)
{
// Disable RMT RX for sending
if (rmt_disable(_rmt_rx_channel) != DSHOT_OK)
{
return {false, DSHOT_RECEIVER_FAILED};
}
}
if (rmt_transmit(_rmt_tx_channel, _dshot_encoder, &swapped_value, tx_size_bytes, &tx_config) != DSHOT_OK) if (rmt_transmit(_rmt_tx_channel, _dshot_encoder, &swapped_value, tx_size_bytes, &tx_config) != DSHOT_OK)
{ {
return {false, DSHOT_TRANSMISSION_FAILED}; return {false, DSHOT_TRANSMISSION_FAILED};
} }
// Re-enable RMT RX
if (_is_bidirectional)
{
if (rmt_enable(_rmt_rx_channel) != DSHOT_OK)
{
return {false, DSHOT_RECEIVER_FAILED};
}
}
_recordFrameTransmissionTime(); // Reset the timer for the next frame _recordFrameTransmissionTime(); // Reset the timer for the next frame
return {true, DSHOT_TRANSMISSION_SUCCESS}; return {true, DSHOT_TRANSMISSION_SUCCESS};
} }
// 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) const uint16_t IRAM_ATTR DShotRMT::_decodeDShotFrame(const rmt_symbol_word_t *symbols) const
{ {

View File

@ -127,7 +127,8 @@ enum dshotCommands_e
DSHOT_CMD_LED2_OFF, DSHOT_CMD_LED2_OFF,
DSHOT_CMD_LED3_OFF, DSHOT_CMD_LED3_OFF,
DSHOT_CMD_AUDIO_STREAM_MODE_ON_OFF = 30, DSHOT_CMD_AUDIO_STREAM_MODE_ON_OFF = 30,
DSHOT_CMD_SILENT_MODE_ON_OFF = 31 DSHOT_CMD_SILENT_MODE_ON_OFF = 31,
DSHOT_CMD_MAX_VALUE = 47
}; };
// Custom status codes // Custom status codes

View File

@ -17,11 +17,12 @@ dshot_result_t init_rmt_tx_channel(gpio_num_t gpio, rmt_channel_handle_t *out_ch
.resolution_hz = DSHOT_RMT_RESOLUTION, .resolution_hz = DSHOT_RMT_RESOLUTION,
.mem_block_symbols = RMT_BUFFER_SYMBOLS, .mem_block_symbols = RMT_BUFFER_SYMBOLS,
.trans_queue_depth = RMT_QUEUE_DEPTH, .trans_queue_depth = RMT_QUEUE_DEPTH,
}; .flags = {
.invert_out = is_bidirectional ? 1 : 0,
.init_level = is_bidirectional ? 0 : 1}};
rmt_transmit_config_t rmt_tx_config = {}; // Initialize all members to zero rmt_transmit_config_t rmt_tx_config = {}; // Initialize all members to zero
rmt_tx_config.loop_count = 0; // No automatic loops - real-time calculation rmt_tx_config.loop_count = 0; // No automatic loops - real-time calculation
rmt_tx_config.flags.eot_level = is_bidirectional ? 1 : 0;
if (rmt_new_tx_channel(&tx_channel_config, out_channel) != DSHOT_OK) if (rmt_new_tx_channel(&tx_channel_config, out_channel) != DSHOT_OK)
{ {
@ -61,20 +62,6 @@ dshot_result_t init_rmt_rx_channel(gpio_num_t gpio, rmt_channel_handle_t *out_ch
return {false, DSHOT_RX_INIT_FAILED}; return {false, DSHOT_RX_INIT_FAILED};
} }
// Start the receiver to wait for incoming telemetry data
rmt_symbol_word_t rx_symbols[GCR_BITS_PER_FRAME];
size_t rx_size_bytes = GCR_BITS_PER_FRAME * sizeof(rmt_symbol_word_t);
rmt_receive_config_t rmt_rx_config = {
.signal_range_min_ns = DSHOT_PULSE_MIN_NS,
.signal_range_max_ns = DSHOT_PULSE_MAX_NS,
};
if (rmt_receive(*out_channel, rx_symbols, rx_size_bytes, &rmt_rx_config) != DSHOT_OK)
{
return {false, DSHOT_RECEIVER_FAILED};
}
return {true, DSHOT_RX_INIT_SUCCESS}; return {true, DSHOT_RX_INIT_SUCCESS};
} }
@ -94,6 +81,7 @@ dshot_result_t init_dshot_encoder(rmt_encoder_handle_t *out_encoder, const rmt_t
.duration1 = rmt_ticks.t1l_ticks, .duration1 = rmt_ticks.t1l_ticks,
.level1 = idle_level, .level1 = idle_level,
}, },
.flags = { .flags = {
.msb_first = 1 // DShot is MSB first .msb_first = 1 // DShot is MSB first
}}; }};