Use official byte-Encoder

This commit is contained in:
Wastl Kraus 2025-09-26 19:24:50 +02:00
parent 0028db7dca
commit d899ba1471
3 changed files with 35 additions and 41 deletions

View File

@ -2,7 +2,7 @@
[![Arduino CI](https://github.com/derdoktor667/DShotRMT/actions/workflows/ci.yml/badge.svg)](https://github.com/derdoktor667/DShotRMT/actions/workflows/ci.yml)
A C++ library for generating DShot signals on ESP32 microcontrollers using the **modern ESP-IDF 5 RMT encoder API** (`rmt_tx.h` / `rmt_rx.h`). It provides a simple, efficient, and hardware-timed way to control brushless motors in both Arduino and ESP-IDF projects. The legacy version using the old `rmt.h` API is available in the `oldAPI` branch.
A C++ library for generating DShot signals on ESP32 microcontrollers using the **modern ESP-IDF 5 RMT encoder API** (`rmt_tx.h` / `rmt_rx.h`). It leverages the standard `rmt_bytes_encoder` to ensure an efficient, hardware-timed, and maintainable implementation. **Note:** A byte-swapping fix has been implemented to address endianness differences when using `rmt_bytes_encoder` on ESP32. The library provides a simple way to control brushless motors in both Arduino and ESP-IDF projects. The legacy version using the old `rmt.h` API is available in the `oldAPI` branch.
## 🚀 Core Features

View File

@ -23,8 +23,8 @@ DShotRMT::DShotRMT(gpio_num_t gpio, dshot_mode_t mode, bool is_bidirectional, ui
_last_command_timestamp(0),
_encoded_frame_value(0),
_packet{0},
_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
_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),
@ -355,9 +355,25 @@ dshot_result_t DShotRMT::_initRXChannel()
dshot_result_t DShotRMT::_initDShotEncoder()
{
rmt_copy_encoder_config_t encoder_config = {};
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
}
};
if (rmt_new_copy_encoder(&encoder_config, &_dshot_encoder) != DSHOT_OK)
if (rmt_new_bytes_encoder(&encoder_config, &_dshot_encoder) != DSHOT_OK)
{
return {false, dshot_msg_code_t::DSHOT_ERROR_ENCODER_INIT_FAILED};
}
@ -424,50 +440,30 @@ void DShotRMT::_preCalculateRMTTicks()
dshot_result_t DShotRMT::_sendDShotFrame(const dshot_packet_t &packet)
{
// Ensure enough time has passed since the last transmission
if (!_timer_signal())
if (!_isFrameIntervalElapsed())
{
return {true, dshot_msg_code_t::DSHOT_ERROR_NONE};
}
rmt_symbol_word_t tx_symbols[DSHOT_BITS_PER_FRAME];
dshot_result_t result = _encodeDShotFrame(packet, tx_symbols);
_encoded_frame_value = _buildDShotFrameValue(packet);
if (!result.success)
{
return result;
}
// Byte-swap the 16-bit value for correct transmission order (ESP32 is little-endian, DShot is MSB first)
uint16_t swapped_value = __builtin_bswap16(_encoded_frame_value);
size_t tx_size_bytes = DSHOT_BITS_PER_FRAME * sizeof(rmt_symbol_word_t);
// The DShot frame is 16 bits, which is 2 bytes
size_t tx_size_bytes = sizeof(swapped_value);
if (rmt_transmit(_rmt_tx_channel, _dshot_encoder, tx_symbols, tx_size_bytes, &_rmt_tx_config) != DSHOT_OK)
if (rmt_transmit(_rmt_tx_channel, _dshot_encoder, &swapped_value, tx_size_bytes, &_rmt_tx_config) != DSHOT_OK)
{
return {false, dshot_msg_code_t::DSHOT_ERROR_TRANSMISSION_FAILED};
}
_timer_reset(); // Reset the timer for the next frame
_recordFrameTransmissionTime(); // Reset the timer for the next frame
return {true, dshot_msg_code_t::DSHOT_ERROR_TRANSMISSION_SUCCESS};
}
// This function needs to be fast, as it generates the RMT symbols just before sending
dshot_result_t IRAM_ATTR DShotRMT::_encodeDShotFrame(const dshot_packet_t &packet, rmt_symbol_word_t *symbols)
{
_encoded_frame_value = _buildDShotFrameValue(packet);
for (int i = 0; i < DSHOT_BITS_PER_FRAME; ++i)
{
int bit_position = DSHOT_BITS_PER_FRAME - 1 - i;
bool bit = (_encoded_frame_value >> bit_position) & 1;
// A '1' bit has a longer high-time, a '0' bit has a shorter high-time
symbols[i].level0 = _level0; // Go HIGH
symbols[i].duration0 = bit ? _rmt_ticks.t1h_ticks : _rmt_ticks.t0h_ticks;
symbols[i].level1 = _level1; // Go LOW
symbols[i].duration1 = bit ? _rmt_ticks.t1l_ticks : _rmt_ticks.t0l_ticks;
}
return {true, dshot_msg_code_t::DSHOT_ERROR_ENCODING_SUCCESS};
}
// 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)
@ -510,7 +506,7 @@ uint16_t IRAM_ATTR DShotRMT::_decodeDShotFrame(const rmt_symbol_word_t *symbols)
}
// Timing Control Functions
bool IRAM_ATTR DShotRMT::_timer_signal()
bool IRAM_ATTR DShotRMT::_isFrameIntervalElapsed()
{
// Check if the minimum interval between frames has passed
uint64_t current_time = esp_timer_get_time();
@ -518,11 +514,10 @@ bool IRAM_ATTR DShotRMT::_timer_signal()
return elapsed >= _frame_timer_us;
}
bool DShotRMT::_timer_reset()
void DShotRMT::_recordFrameTransmissionTime()
{
// Record the time of the current transmission
_last_transmission_time_us = esp_timer_get_time();
return true;
}
// Static Callback Functions

View File

@ -213,8 +213,8 @@ private:
uint64_t _last_command_timestamp;
uint16_t _encoded_frame_value;
dshot_packet_t _packet;
uint16_t _level0; // DShot protocol: Signal is idle-low, so pulses start by going HIGH.
uint16_t _level1; // DShot protocol: Signal returns to LOW after the high pulse.
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 Hardware Handles
rmt_channel_handle_t _rmt_tx_channel;
@ -245,12 +245,11 @@ private:
// Private Frame Processing Functions
dshot_result_t _sendDShotFrame(const dshot_packet_t &packet);
dshot_result_t _encodeDShotFrame(const dshot_packet_t &packet, rmt_symbol_word_t *symbols);
uint16_t _decodeDShotFrame(const rmt_symbol_word_t *symbols);
// Private Timing Control Functions
bool _timer_signal();
bool _timer_reset();
bool _isFrameIntervalElapsed();
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);