Use official byte-Encoder
This commit is contained in:
parent
0028db7dca
commit
d899ba1471
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
[](https://github.com/derdoktor667/DShotRMT/actions/workflows/ci.yml)
|
[](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
|
## 🚀 Core Features
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -23,8 +23,8 @@ DShotRMT::DShotRMT(gpio_num_t gpio, dshot_mode_t mode, bool is_bidirectional, ui
|
||||||
_last_command_timestamp(0),
|
_last_command_timestamp(0),
|
||||||
_encoded_frame_value(0),
|
_encoded_frame_value(0),
|
||||||
_packet{0},
|
_packet{0},
|
||||||
_level0(1), // DShot standard: signal is idle-low, so pulses start by going HIGH
|
_pulse_level(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
|
_idle_level(0), // DShot standard: signal returns to LOW after the high pulse
|
||||||
_rmt_tx_channel(nullptr),
|
_rmt_tx_channel(nullptr),
|
||||||
_rmt_rx_channel(nullptr),
|
_rmt_rx_channel(nullptr),
|
||||||
_dshot_encoder(nullptr),
|
_dshot_encoder(nullptr),
|
||||||
|
|
@ -355,9 +355,25 @@ dshot_result_t DShotRMT::_initRXChannel()
|
||||||
|
|
||||||
dshot_result_t DShotRMT::_initDShotEncoder()
|
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};
|
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)
|
dshot_result_t DShotRMT::_sendDShotFrame(const dshot_packet_t &packet)
|
||||||
{
|
{
|
||||||
// Ensure enough time has passed since the last transmission
|
// Ensure enough time has passed since the last transmission
|
||||||
if (!_timer_signal())
|
if (!_isFrameIntervalElapsed())
|
||||||
{
|
{
|
||||||
return {true, dshot_msg_code_t::DSHOT_ERROR_NONE};
|
return {true, dshot_msg_code_t::DSHOT_ERROR_NONE};
|
||||||
}
|
}
|
||||||
|
|
||||||
rmt_symbol_word_t tx_symbols[DSHOT_BITS_PER_FRAME];
|
_encoded_frame_value = _buildDShotFrameValue(packet);
|
||||||
dshot_result_t result = _encodeDShotFrame(packet, tx_symbols);
|
|
||||||
|
|
||||||
if (!result.success)
|
// 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);
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
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};
|
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};
|
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
|
// 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
|
// 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)
|
||||||
|
|
@ -510,7 +506,7 @@ uint16_t IRAM_ATTR DShotRMT::_decodeDShotFrame(const rmt_symbol_word_t *symbols)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Timing Control Functions
|
// Timing Control Functions
|
||||||
bool IRAM_ATTR DShotRMT::_timer_signal()
|
bool IRAM_ATTR DShotRMT::_isFrameIntervalElapsed()
|
||||||
{
|
{
|
||||||
// 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();
|
||||||
|
|
@ -518,11 +514,10 @@ bool IRAM_ATTR DShotRMT::_timer_signal()
|
||||||
return elapsed >= _frame_timer_us;
|
return elapsed >= _frame_timer_us;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool DShotRMT::_timer_reset()
|
void DShotRMT::_recordFrameTransmissionTime()
|
||||||
{
|
{
|
||||||
// Record the time of the current transmission
|
// Record the time of the current transmission
|
||||||
_last_transmission_time_us = esp_timer_get_time();
|
_last_transmission_time_us = esp_timer_get_time();
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Static Callback Functions
|
// Static Callback Functions
|
||||||
|
|
|
||||||
|
|
@ -213,8 +213,8 @@ private:
|
||||||
uint64_t _last_command_timestamp;
|
uint64_t _last_command_timestamp;
|
||||||
uint16_t _encoded_frame_value;
|
uint16_t _encoded_frame_value;
|
||||||
dshot_packet_t _packet;
|
dshot_packet_t _packet;
|
||||||
uint16_t _level0; // DShot protocol: Signal is idle-low, so pulses start by going HIGH.
|
uint16_t _pulse_level; // 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 _idle_level; // 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;
|
||||||
|
|
@ -245,12 +245,11 @@ private:
|
||||||
|
|
||||||
// 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);
|
||||||
dshot_result_t _encodeDShotFrame(const dshot_packet_t &packet, rmt_symbol_word_t *symbols);
|
|
||||||
uint16_t _decodeDShotFrame(const rmt_symbol_word_t *symbols);
|
uint16_t _decodeDShotFrame(const rmt_symbol_word_t *symbols);
|
||||||
|
|
||||||
// Private Timing Control Functions
|
// Private Timing Control Functions
|
||||||
bool _timer_signal();
|
bool _isFrameIntervalElapsed();
|
||||||
bool _timer_reset();
|
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);
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue