commit
17e49f65ea
|
|
@ -10,9 +10,6 @@ concurrency:
|
||||||
group: ${{ github.workflow }}-${{ github.ref }}
|
group: ${{ github.workflow }}-${{ github.ref }}
|
||||||
cancel-in-progress: true
|
cancel-in-progress: true
|
||||||
|
|
||||||
env:
|
|
||||||
ESP32_CORE_VERSION: '3.3.0'
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
# Code Quality & Linting
|
# Code Quality & Linting
|
||||||
|
|
@ -33,15 +30,15 @@ jobs:
|
||||||
uses: actions/cache@v4
|
uses: actions/cache@v4
|
||||||
with:
|
with:
|
||||||
path: |
|
path: |
|
||||||
~/.arduino15
|
~/
|
||||||
key: ${{ runner.os }}-arduino-${{ env.ESP32_CORE_VERSION }}-${{ hashFiles('**/libraries/**') }}
|
key: ${{ runner.os }}-arduino-${{ hashFiles('**/libraries/**') }}
|
||||||
restore-keys: |
|
restore-keys: |
|
||||||
${{ runner.os }}-arduino-${{ env.ESP32_CORE_VERSION }}-
|
${{ runner.os }}-arduino-
|
||||||
|
|
||||||
- name: Install ESP32 core
|
- name: Install ESP32 core
|
||||||
run: |
|
run: |
|
||||||
arduino-cli core update-index
|
arduino-cli core update-index
|
||||||
arduino-cli core install esp32:esp32@${{ env.ESP32_CORE_VERSION }}
|
arduino-cli core install esp32:esp32
|
||||||
|
|
||||||
- name: Arduino Lint
|
- name: Arduino Lint
|
||||||
uses: arduino/arduino-lint-action@v1
|
uses: arduino/arduino-lint-action@v1
|
||||||
|
|
@ -79,15 +76,15 @@ jobs:
|
||||||
uses: actions/cache@v4
|
uses: actions/cache@v4
|
||||||
with:
|
with:
|
||||||
path: |
|
path: |
|
||||||
~/.arduino15
|
~/
|
||||||
key: ${{ runner.os }}-arduino-${{ env.ESP32_CORE_VERSION }}-${{ hashFiles('**/libraries/**') }}
|
key: ${{ runner.os }}-arduino-${{ hashFiles('**/libraries/**') }}
|
||||||
restore-keys: |
|
restore-keys: |
|
||||||
${{ runner.os }}-arduino-${{ env.ESP32_CORE_VERSION }}-
|
${{ runner.os }}-arduino-
|
||||||
|
|
||||||
- name: Install ESP32 core
|
- name: Install ESP32 core
|
||||||
run: |
|
run: |
|
||||||
arduino-cli core update-index
|
arduino-cli core update-index
|
||||||
arduino-cli core install esp32:esp32@${{ env.ESP32_CORE_VERSION }}
|
arduino-cli core install esp32:esp32
|
||||||
|
|
||||||
- name: Install Repo as Library
|
- name: Install Repo as Library
|
||||||
run: |
|
run: |
|
||||||
|
|
@ -117,15 +114,15 @@ jobs:
|
||||||
uses: actions/cache@v4
|
uses: actions/cache@v4
|
||||||
with:
|
with:
|
||||||
path: |
|
path: |
|
||||||
~/.arduino15
|
~/
|
||||||
key: ${{ runner.os }}-arduino-${{ env.ESP32_CORE_VERSION }}-${{ hashFiles('**/libraries/**') }}
|
key: ${{ runner.os }}-arduino-${{ hashFiles('**/libraries/**') }}
|
||||||
restore-keys: |
|
restore-keys: |
|
||||||
${{ runner.os }}-arduino-${{ env.ESP32_CORE_VERSION }}-
|
${{ runner.os }}-arduino-
|
||||||
|
|
||||||
- name: Install ESP32 core
|
- name: Install ESP32 core
|
||||||
run: |
|
run: |
|
||||||
arduino-cli core update-index
|
arduino-cli core update-index
|
||||||
arduino-cli core install esp32:esp32@${{ env.ESP32_CORE_VERSION }}
|
arduino-cli core install esp32:esp32
|
||||||
|
|
||||||
- name: Install Cppcheck
|
- name: Install Cppcheck
|
||||||
run: sudo apt-get update && sudo apt-get install -y cppcheck
|
run: sudo apt-get update && sudo apt-get install -y cppcheck
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,7 @@ dshot_result_t DShotCommandManager::begin()
|
||||||
{
|
{
|
||||||
dshot_result_t result;
|
dshot_result_t result;
|
||||||
result.success = true;
|
result.success = true;
|
||||||
result.error_message = "Success";
|
result.msg = "Success";
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -36,10 +36,10 @@ dshot_result_t DShotCommandManager::sendCommand(dshot_commands_t command, uint16
|
||||||
dshot_result_t DShotCommandManager::sendCommandWithDelay(dshot_commands_t command, uint16_t repeat_count, uint32_t delay_ms)
|
dshot_result_t DShotCommandManager::sendCommandWithDelay(dshot_commands_t command, uint16_t repeat_count, uint32_t delay_ms)
|
||||||
{
|
{
|
||||||
dshot_result_t result = {false, "Unknown error"};
|
dshot_result_t result = {false, "Unknown error"};
|
||||||
|
|
||||||
if (!isValidCommand(command))
|
if (!isValidCommand(command))
|
||||||
{
|
{
|
||||||
result.error_message = "Invalid command";
|
result.msg = "Invalid command";
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -53,7 +53,7 @@ dshot_result_t DShotCommandManager::sendCommandWithDelay(dshot_commands_t comman
|
||||||
if (!single_result.success)
|
if (!single_result.success)
|
||||||
{
|
{
|
||||||
all_successful = false;
|
all_successful = false;
|
||||||
result.error_message = single_result.error_message;
|
result.msg = single_result.msg;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -64,12 +64,12 @@ dshot_result_t DShotCommandManager::sendCommandWithDelay(dshot_commands_t comman
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
result.success = all_successful;
|
result.success = all_successful;
|
||||||
|
|
||||||
if (result.success)
|
if (result.success)
|
||||||
{
|
{
|
||||||
result.error_message = "Success";
|
result.msg = "Success";
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
|
|
@ -194,7 +194,7 @@ dshot_result_t DShotCommandManager::setSilentMode(bool enable)
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- SEQUENCE COMMANDS ---
|
// --- SEQUENCE COMMANDS ---
|
||||||
dshot_result_t DShotCommandManager::executeSequence(const dshot_command_sequence_item_t *sequence, size_t sequence_length)
|
dshot_result_t DShotCommandManager::executeSequence(const dshot_commandmanager_item_t *sequence, size_t sequence_length)
|
||||||
{
|
{
|
||||||
dshot_result_t result = {true, "Success"};
|
dshot_result_t result = {true, "Success"};
|
||||||
uint64_t total_start_time = esp_timer_get_time();
|
uint64_t total_start_time = esp_timer_get_time();
|
||||||
|
|
@ -209,7 +209,7 @@ dshot_result_t DShotCommandManager::executeSequence(const dshot_command_sequence
|
||||||
if (!item_result.success)
|
if (!item_result.success)
|
||||||
{
|
{
|
||||||
result.success = false;
|
result.success = false;
|
||||||
result.error_message = item_result.error_message;
|
result.msg = item_result.msg;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -229,7 +229,7 @@ dshot_result_t DShotCommandManager::executeSequence(const dshot_command_sequence
|
||||||
dshot_result_t DShotCommandManager::executeInitSequence()
|
dshot_result_t DShotCommandManager::executeInitSequence()
|
||||||
{
|
{
|
||||||
// Basic ESC initialization sequence
|
// Basic ESC initialization sequence
|
||||||
dshot_command_sequence_item_t init_sequence[] = {
|
dshot_commandmanager_item_t init_sequence[] = {
|
||||||
{DSHOT_CMD_MOTOR_STOP, 5, 100}, // Stop motor, repeat 5 times, wait 100ms
|
{DSHOT_CMD_MOTOR_STOP, 5, 100}, // Stop motor, repeat 5 times, wait 100ms
|
||||||
{DSHOT_CMD_EXTENDED_TELEMETRY_ENABLE, 1, 50}, // Enable telemetry, wait 50ms
|
{DSHOT_CMD_EXTENDED_TELEMETRY_ENABLE, 1, 50}, // Enable telemetry, wait 50ms
|
||||||
{DSHOT_CMD_ESC_INFO, 1, 100} // Request ESC info, wait 100ms
|
{DSHOT_CMD_ESC_INFO, 1, 100} // Request ESC info, wait 100ms
|
||||||
|
|
@ -242,7 +242,7 @@ dshot_result_t DShotCommandManager::executeInitSequence()
|
||||||
dshot_result_t DShotCommandManager::executeCalibrationSequence()
|
dshot_result_t DShotCommandManager::executeCalibrationSequence()
|
||||||
{
|
{
|
||||||
// Basic ESC calibration sequence
|
// Basic ESC calibration sequence
|
||||||
dshot_command_sequence_item_t calibration_sequence[] = {
|
dshot_commandmanager_item_t calibration_sequence[] = {
|
||||||
{DSHOT_CMD_MOTOR_STOP, 10, 500}, // Ensure motor is stopped
|
{DSHOT_CMD_MOTOR_STOP, 10, 500}, // Ensure motor is stopped
|
||||||
{DSHOT_CMD_SPIN_DIRECTION_NORMAL, 10, 100}, // Set normal spin direction
|
{DSHOT_CMD_SPIN_DIRECTION_NORMAL, 10, 100}, // Set normal spin direction
|
||||||
{DSHOT_CMD_3D_MODE_OFF, 10, 100}, // Disable 3D mode
|
{DSHOT_CMD_3D_MODE_OFF, 10, 100}, // Disable 3D mode
|
||||||
|
|
|
||||||
|
|
@ -12,13 +12,13 @@
|
||||||
#include <DShotRMT.h>
|
#include <DShotRMT.h>
|
||||||
#include <dshot_commands.h>
|
#include <dshot_commands.h>
|
||||||
|
|
||||||
// Command sequence item
|
// Command item
|
||||||
typedef struct
|
typedef struct
|
||||||
{
|
{
|
||||||
dshot_commands_t command;
|
dshot_commands_t command;
|
||||||
uint16_t repeat_count;
|
uint16_t repeat_count;
|
||||||
uint32_t delay_ms;
|
uint32_t delay_ms;
|
||||||
} dshot_command_sequence_item_t;
|
} dshot_commandmanager_item_t;
|
||||||
|
|
||||||
// Advanced DShot command manager class
|
// Advanced DShot command manager class
|
||||||
class DShotCommandManager
|
class DShotCommandManager
|
||||||
|
|
@ -76,7 +76,7 @@ public:
|
||||||
|
|
||||||
// --- SEQUENCE COMMANDS ---
|
// --- SEQUENCE COMMANDS ---
|
||||||
// Execute a sequence of DShot commands
|
// Execute a sequence of DShot commands
|
||||||
dshot_result_t executeSequence(const dshot_command_sequence_item_t *sequence, size_t sequence_length);
|
dshot_result_t executeSequence(const dshot_commandmanager_item_t *sequence, size_t sequence_length);
|
||||||
|
|
||||||
// Execute ESC initialization sequence
|
// Execute ESC initialization sequence
|
||||||
dshot_result_t executeInitSequence();
|
dshot_result_t executeInitSequence();
|
||||||
|
|
|
||||||
328
DShotRMT.cpp
328
DShotRMT.cpp
|
|
@ -23,25 +23,27 @@ DShotRMT::DShotRMT(gpio_num_t gpio, dshot_mode_t mode, bool is_bidirectional)
|
||||||
: _gpio(gpio),
|
: _gpio(gpio),
|
||||||
_mode(mode),
|
_mode(mode),
|
||||||
_is_bidirectional(is_bidirectional),
|
_is_bidirectional(is_bidirectional),
|
||||||
|
_last_erpm_atomic(0),
|
||||||
|
_telemetry_ready_flag(false),
|
||||||
_frame_timer_us(0),
|
_frame_timer_us(0),
|
||||||
_timing_config(DSHOT_TIMINGS[mode]),
|
_timing_config(DSHOT_TIMINGS[mode]),
|
||||||
|
_last_throttle(DSHOT_CMD_MOTOR_STOP),
|
||||||
_last_transmission_time(0),
|
_last_transmission_time(0),
|
||||||
_last_erpm(0),
|
|
||||||
_parsed_packet(0),
|
_parsed_packet(0),
|
||||||
_packet{0},
|
_packet{0},
|
||||||
_total_transmissions(0),
|
_bitPositions{0},
|
||||||
_failed_transmissions(0),
|
_level0(_is_bidirectional ? 0 : 1),
|
||||||
|
_level1(_is_bidirectional ? 1 : 0),
|
||||||
_rmt_tx_channel(nullptr),
|
_rmt_tx_channel(nullptr),
|
||||||
_rmt_rx_channel(nullptr),
|
_rmt_rx_channel(nullptr),
|
||||||
_dshot_encoder(nullptr),
|
_dshot_encoder(nullptr),
|
||||||
_tx_channel_config{},
|
_tx_channel_config{},
|
||||||
_rx_channel_config{},
|
_rx_channel_config{},
|
||||||
_transmit_config{},
|
_transmit_config{},
|
||||||
_receive_config{},
|
_receive_config{}
|
||||||
_rx_queue(nullptr)
|
|
||||||
{
|
{
|
||||||
// Calculate frame timing including switch/pause time
|
// Calculate frame timing including switch/pause time
|
||||||
_frame_timer_us = _timing_config.frame_length_us + DSHOT_SWITCH_TIME;
|
_frame_timer_us = _timing_config.frame_length_us + DSHOT_PAUSE_US;
|
||||||
|
|
||||||
// Double frame time for bidirectional mode (includes response time)
|
// Double frame time for bidirectional mode (includes response time)
|
||||||
if (_is_bidirectional)
|
if (_is_bidirectional)
|
||||||
|
|
@ -60,79 +62,66 @@ DShotRMT::DShotRMT(uint16_t pin_nr, dshot_mode_t mode, bool is_bidirectional)
|
||||||
// Destructor for "better" code
|
// Destructor for "better" code
|
||||||
DShotRMT::~DShotRMT()
|
DShotRMT::~DShotRMT()
|
||||||
{
|
{
|
||||||
// ...kill them all
|
// ...TX
|
||||||
if (_rmt_tx_channel)
|
if (_rmt_tx_channel)
|
||||||
{
|
{
|
||||||
rmt_disable(_rmt_tx_channel);
|
if (rmt_disable(_rmt_tx_channel) == DSHOT_OK)
|
||||||
rmt_del_channel(_rmt_tx_channel);
|
{
|
||||||
_rmt_tx_channel = nullptr;
|
rmt_del_channel(_rmt_tx_channel);
|
||||||
|
_rmt_tx_channel = nullptr;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
// ...RX
|
||||||
if (_rmt_rx_channel)
|
if (_rmt_rx_channel)
|
||||||
{
|
{
|
||||||
rmt_disable(_rmt_rx_channel);
|
if (rmt_disable(_rmt_rx_channel) == DSHOT_OK)
|
||||||
rmt_del_channel(_rmt_rx_channel);
|
{
|
||||||
_rmt_rx_channel = nullptr;
|
rmt_del_channel(_rmt_rx_channel);
|
||||||
|
_rmt_rx_channel = nullptr;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
// ...Encoder
|
||||||
if (_dshot_encoder)
|
if (_dshot_encoder)
|
||||||
{
|
{
|
||||||
rmt_del_encoder(_dshot_encoder);
|
rmt_del_encoder(_dshot_encoder);
|
||||||
_dshot_encoder = nullptr;
|
_dshot_encoder = nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
|
||||||
if (_rx_queue)
|
|
||||||
{
|
|
||||||
vQueueDelete(_rx_queue);
|
|
||||||
_rx_queue = nullptr;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initialize DShotRMT
|
// Init DShotRMT
|
||||||
dshot_result_t DShotRMT::begin()
|
dshot_result_t DShotRMT::begin()
|
||||||
{
|
{
|
||||||
dshot_result_t result = {false, UNKNOWN_ERROR};
|
|
||||||
uint64_t start_time = esp_timer_get_time();
|
|
||||||
|
|
||||||
// Init RX channel first
|
// Init RX channel first
|
||||||
if (_is_bidirectional)
|
if (_is_bidirectional)
|
||||||
{
|
{
|
||||||
if (!_initRXChannel())
|
if (!_initRXChannel().success)
|
||||||
{
|
{
|
||||||
result.error_message = RX_INIT_FAILED;
|
return {false, RX_INIT_FAILED};
|
||||||
_dshot_log(RX_INIT_FAILED);
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Init TX channel
|
// Init TX channel
|
||||||
if (!_initTXChannel())
|
if (!_initTXChannel().success)
|
||||||
{
|
{
|
||||||
result.error_message = TX_INIT_FAILED;
|
return {false, TX_INIT_FAILED};
|
||||||
_dshot_log(TX_INIT_FAILED);
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Init DShot encoder
|
// Init DShot encoder
|
||||||
if (_initDShotEncoder() != DSHOT_OK)
|
if (!_initDShotEncoder().success)
|
||||||
{
|
{
|
||||||
result.error_message = ENCODER_INIT_FAILED;
|
return {false, ENCODER_INIT_FAILED};
|
||||||
_dshot_log(ENCODER_INIT_FAILED);
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
uint64_t end_time = esp_timer_get_time();
|
// Bit positions precalculation
|
||||||
result.success = true;
|
_preCalculateBitPositions();
|
||||||
result.error_message = INIT_SUCCESS;
|
|
||||||
|
|
||||||
return result;
|
return {true, INIT_SUCCESS};
|
||||||
}
|
}
|
||||||
|
|
||||||
// Init RMT TX channel
|
// Init RMT TX channel
|
||||||
bool DShotRMT::_initTXChannel()
|
dshot_result_t DShotRMT::_initTXChannel()
|
||||||
{
|
{
|
||||||
// Configure TX channel
|
// Configure TX channel
|
||||||
_tx_channel_config.gpio_num = _gpio;
|
_tx_channel_config.gpio_num = _gpio;
|
||||||
|
|
@ -148,22 +137,24 @@ bool DShotRMT::_initTXChannel()
|
||||||
// Create RMT TX channel
|
// Create RMT TX channel
|
||||||
if (rmt_new_tx_channel(&_tx_channel_config, &_rmt_tx_channel) != DSHOT_OK)
|
if (rmt_new_tx_channel(&_tx_channel_config, &_rmt_tx_channel) != DSHOT_OK)
|
||||||
{
|
{
|
||||||
_dshot_log(TX_INIT_FAILED);
|
return {false, TX_INIT_FAILED};
|
||||||
return DSHOT_ERROR;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return (rmt_enable(_rmt_tx_channel) == DSHOT_OK);
|
//
|
||||||
|
if (rmt_enable(_rmt_tx_channel) != DSHOT_OK)
|
||||||
|
{
|
||||||
|
return {false, TX_INIT_FAILED};
|
||||||
|
}
|
||||||
|
|
||||||
|
return {true, TX_INIT_SUCCESS};
|
||||||
}
|
}
|
||||||
|
|
||||||
// Init RMT RX channel
|
// Init RMT RX channel
|
||||||
bool DShotRMT::_initRXChannel()
|
dshot_result_t DShotRMT::_initRXChannel()
|
||||||
{
|
{
|
||||||
// Create a queue for RX callback data
|
|
||||||
_rx_queue = xQueueCreate(RMT_QUEUE_DEPTH, sizeof(rmt_rx_done_event_data_t));
|
// Direct RMT symbol processing - Performance optimized
|
||||||
if (_rx_queue == nullptr)
|
_rx_event_callbacks.on_recv_done = _rmt_rx_done_callback;
|
||||||
{
|
|
||||||
return DSHOT_ERROR;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Config RMT RX
|
// Config RMT RX
|
||||||
_rx_channel_config.gpio_num = _gpio;
|
_rx_channel_config.gpio_num = _gpio;
|
||||||
|
|
@ -178,37 +169,43 @@ bool DShotRMT::_initRXChannel()
|
||||||
// Create RMT RX channel
|
// Create RMT RX channel
|
||||||
if (rmt_new_rx_channel(&_rx_channel_config, &_rmt_rx_channel) != DSHOT_OK)
|
if (rmt_new_rx_channel(&_rx_channel_config, &_rmt_rx_channel) != DSHOT_OK)
|
||||||
{
|
{
|
||||||
_dshot_log(RX_INIT_FAILED);
|
return {false, RX_INIT_FAILED};
|
||||||
return DSHOT_ERROR;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Register RX callback
|
//
|
||||||
_rx_event_callbacks.on_recv_done = _rmt_rx_done_callback;
|
if (rmt_enable(_rmt_rx_channel) != DSHOT_OK)
|
||||||
|
|
||||||
if (rmt_rx_register_event_callbacks(_rmt_rx_channel, &_rx_event_callbacks, _rx_queue) != DSHOT_OK)
|
|
||||||
{
|
{
|
||||||
_dshot_log(RX_INIT_FAILED);
|
return {false, RX_INIT_FAILED};
|
||||||
return DSHOT_ERROR;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return (rmt_enable(_rmt_rx_channel) == DSHOT_OK);
|
return {true, RX_INIT_SUCCESS};
|
||||||
}
|
}
|
||||||
|
|
||||||
// Callback for RMT RX
|
// Callback for RMT RX
|
||||||
bool IRAM_ATTR DShotRMT::_rmt_rx_done_callback(rmt_channel_handle_t rmt_rx_channel, const rmt_rx_done_event_data_t *edata, void *user_data)
|
bool IRAM_ATTR DShotRMT::_rmt_rx_done_callback(rmt_channel_handle_t rmt_rx_channel, const rmt_rx_done_event_data_t *edata, void *user_data)
|
||||||
{
|
{
|
||||||
// Init RX buffer
|
DShotRMT *instance = static_cast<DShotRMT *>(user_data);
|
||||||
QueueHandle_t rx_queue = (QueueHandle_t)user_data;
|
|
||||||
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
|
|
||||||
|
|
||||||
// Copy callback data into RX buffer
|
// ISR check for valid data
|
||||||
xQueueGenericSendFromISR(rx_queue, edata, &xHigherPriorityTaskWoken, queueSEND_TO_BACK);
|
if (edata && edata->num_symbols >= GCR_BITS_PER_FRAME && edata->num_symbols <= GCR_BITS_PER_FRAME)
|
||||||
|
{
|
||||||
|
|
||||||
return (xHigherPriorityTaskWoken == pdTRUE);
|
// Direct decoding
|
||||||
|
uint16_t erpm = instance->_decodeDShotFrame(edata->received_symbols);
|
||||||
|
|
||||||
|
if (erpm != DSHOT_NULL_PACKET)
|
||||||
|
{
|
||||||
|
// Atomic writes - thread-safe
|
||||||
|
instance->_last_erpm_atomic = erpm;
|
||||||
|
instance->_telemetry_ready_flag = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initialize DShot encoder
|
// Initialize DShot encoder
|
||||||
bool DShotRMT::_initDShotEncoder()
|
dshot_result_t DShotRMT::_initDShotEncoder()
|
||||||
{
|
{
|
||||||
// Create copy encoder configuration
|
// Create copy encoder configuration
|
||||||
rmt_copy_encoder_config_t encoder_config = {};
|
rmt_copy_encoder_config_t encoder_config = {};
|
||||||
|
|
@ -216,37 +213,27 @@ bool DShotRMT::_initDShotEncoder()
|
||||||
// Create encoder instance
|
// Create encoder instance
|
||||||
if (rmt_new_copy_encoder(&encoder_config, &_dshot_encoder) != DSHOT_OK)
|
if (rmt_new_copy_encoder(&encoder_config, &_dshot_encoder) != DSHOT_OK)
|
||||||
{
|
{
|
||||||
_dshot_log(ENCODER_INIT_FAILED);
|
return {false, ENCODER_INIT_FAILED};
|
||||||
return DSHOT_ERROR;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return DSHOT_OK;
|
return {true, TX_INIT_SUCCESS};
|
||||||
}
|
}
|
||||||
|
|
||||||
// Send throttle value
|
// Send throttle value
|
||||||
dshot_result_t DShotRMT::sendThrottle(uint16_t throttle)
|
dshot_result_t DShotRMT::sendThrottle(uint16_t throttle)
|
||||||
{
|
{
|
||||||
static uint16_t last_throttle = DSHOT_CMD_MOTOR_STOP;
|
|
||||||
dshot_result_t result = {false, UNKNOWN_ERROR};
|
|
||||||
|
|
||||||
// Special case: if throttle is 0, use sendCommand() instead
|
// Special case: if throttle is 0, use sendCommand() instead
|
||||||
if (throttle == 0)
|
if (throttle == 0)
|
||||||
{
|
{
|
||||||
return sendCommand(DSHOT_CMD_MOTOR_STOP);
|
return sendCommand(DSHOT_CMD_MOTOR_STOP);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Log only if throttle is out of range and different from last time
|
|
||||||
if ((throttle < DSHOT_THROTTLE_MIN || throttle > DSHOT_THROTTLE_MAX) && throttle != last_throttle)
|
|
||||||
{
|
|
||||||
_dshot_log(THROTTLE_NOT_IN_RANGE);
|
|
||||||
result.error_message = THROTTLE_NOT_IN_RANGE;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Always store the original throttle value
|
// Always store the original throttle value
|
||||||
last_throttle = throttle;
|
_last_throttle = throttle;
|
||||||
|
|
||||||
// Constrain throttle for transmission and send
|
// Constrain throttle for transmission and send
|
||||||
uint16_t new_throttle = constrain(throttle, DSHOT_THROTTLE_MIN, DSHOT_THROTTLE_MAX);
|
uint16_t new_throttle = constrain(throttle, DSHOT_THROTTLE_MIN, DSHOT_THROTTLE_MAX);
|
||||||
|
|
||||||
_packet = _buildDShotPacket(new_throttle);
|
_packet = _buildDShotPacket(new_throttle);
|
||||||
|
|
||||||
return _sendDShotFrame(_packet);
|
return _sendDShotFrame(_packet);
|
||||||
|
|
@ -255,79 +242,52 @@ dshot_result_t DShotRMT::sendThrottle(uint16_t throttle)
|
||||||
// Send DShot command to ESC
|
// Send DShot command to ESC
|
||||||
dshot_result_t DShotRMT::sendCommand(uint16_t command)
|
dshot_result_t DShotRMT::sendCommand(uint16_t command)
|
||||||
{
|
{
|
||||||
dshot_result_t result = {false, UNKNOWN_ERROR};
|
|
||||||
|
|
||||||
// Validate command is within DShot specification range
|
// Validate command is within DShot specification range
|
||||||
if (command < DSHOT_CMD_MOTOR_STOP || command > DSHOT_CMD_MAX)
|
if (command < DSHOT_CMD_MOTOR_STOP || command > DSHOT_CMD_MAX)
|
||||||
{
|
{
|
||||||
_dshot_log(COMMAND_NOT_VALID);
|
return {false, COMMAND_NOT_VALID};
|
||||||
result.error_message = COMMAND_NOT_VALID;
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build packet and transmit
|
// Build packet and transmit
|
||||||
_packet = _buildDShotPacket(command);
|
_packet = _buildDShotPacket(command);
|
||||||
|
|
||||||
return _sendDShotFrame(_packet);
|
return _sendDShotFrame(_packet);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get telemetry data with timing and error handling
|
// Get telemetry data with timing and error handling
|
||||||
dshot_telemetry_result_t DShotRMT::getTelemetry(uint8_t magnet_count)
|
dshot_telemetry_result_t DShotRMT::getTelemetry(uint16_t magnet_count)
|
||||||
{
|
{
|
||||||
dshot_telemetry_result_t result = {false, 0, 0, UNKNOWN_ERROR};
|
// Result container
|
||||||
|
dshot_telemetry_result_t result = {false, NO_DSHOT_ERPM, NO_DSHOT_RPM, TELEMETRY_FAILED};
|
||||||
|
|
||||||
// Check if bidirectional mode is enabled
|
// Check if bidirectional mode is enabled
|
||||||
if (!_is_bidirectional)
|
if (!_is_bidirectional)
|
||||||
{
|
{
|
||||||
result.error_message = BIDIR_NOT_ENABLED;
|
result.msg = BIDIR_NOT_ENABLED;
|
||||||
_dshot_log(BIDIR_NOT_ENABLED);
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get eRPM
|
//
|
||||||
uint16_t erpm = getERPM();
|
if (_telemetry_ready_flag)
|
||||||
|
|
||||||
if (erpm == DSHOT_NULL_PACKET)
|
|
||||||
{
|
{
|
||||||
result.error_message = TELEMETRY_TIMEOUT;
|
_telemetry_ready_flag = false;
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Calculate motor RPM
|
uint16_t erpm = _last_erpm_atomic;
|
||||||
uint8_t pole_pairs = max(1, magnet_count / 2);
|
|
||||||
uint32_t motor_rpm = erpm / pole_pairs;
|
|
||||||
|
|
||||||
result.success = true;
|
//
|
||||||
result.erpm = erpm;
|
if (erpm != DSHOT_NULL_PACKET && magnet_count >= 1)
|
||||||
result.motor_rpm = motor_rpm;
|
|
||||||
result.error_message = TELEMETRY_SUCCESS;
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get RPM from ESC (bidirectional mode only) - backward compatibility
|
|
||||||
uint16_t DShotRMT::getERPM()
|
|
||||||
{
|
|
||||||
// Check if bidirectional mode is enabled
|
|
||||||
if (!_is_bidirectional)
|
|
||||||
{
|
|
||||||
_dshot_log(BIDIR_NOT_ENABLED);
|
|
||||||
return _last_erpm;
|
|
||||||
}
|
|
||||||
|
|
||||||
// RMT RX event data
|
|
||||||
rmt_rx_done_event_data_t rx_data;
|
|
||||||
|
|
||||||
// Wait for data from the RX callback for a certain timeout
|
|
||||||
if (xQueueReceive(_rx_queue, &rx_data, pdMS_TO_TICKS(DSHOT_RX_TIMEOUT_MS)) == pdTRUE)
|
|
||||||
{
|
|
||||||
// Decode the received symbols if a valid frame was received
|
|
||||||
if (rx_data.num_symbols > DSHOT_NULL_PACKET)
|
|
||||||
{
|
{
|
||||||
_last_erpm = _decodeDShotFrame(rx_data.received_symbols);
|
uint8_t pole_pairs = max(MIN_POLE_PAIRS, (magnet_count / MAGNETS_PER_POLE_PAIR));
|
||||||
|
uint32_t motor_rpm = (erpm / pole_pairs);
|
||||||
|
|
||||||
|
result.success = true;
|
||||||
|
result.erpm = erpm;
|
||||||
|
result.motor_rpm = motor_rpm;
|
||||||
|
result.msg = TELEMETRY_SUCCESS;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return _last_erpm;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build a complete DShot packet
|
// Build a complete DShot packet
|
||||||
|
|
@ -339,8 +299,6 @@ dshot_packet_t DShotRMT::_buildDShotPacket(const uint16_t value)
|
||||||
// Re-check for valid value
|
// Re-check for valid value
|
||||||
if (value > DSHOT_THROTTLE_MAX)
|
if (value > DSHOT_THROTTLE_MAX)
|
||||||
{
|
{
|
||||||
_dshot_log(PACKET_BUILD_ERROR);
|
|
||||||
|
|
||||||
// Something is really wrong
|
// Something is really wrong
|
||||||
return packet;
|
return packet;
|
||||||
}
|
}
|
||||||
|
|
@ -371,39 +329,47 @@ uint16_t DShotRMT::_parseDShotPacket(const dshot_packet_t &packet)
|
||||||
uint16_t DShotRMT::_calculateCRC(const uint16_t data)
|
uint16_t DShotRMT::_calculateCRC(const uint16_t data)
|
||||||
{
|
{
|
||||||
// DShot CRC
|
// DShot CRC
|
||||||
uint16_t crc = (data ^ (data >> 4) ^ (data >> 8)) & 0b0000000000001111;
|
uint16_t crc = (data ^ (data >> 4) ^ (data >> 8)) & DSHOT_CRC_MASK;
|
||||||
|
|
||||||
// Invert CRC for bidirectional DShot mode
|
// Invert CRC for bidirectional DShot mode
|
||||||
if (_is_bidirectional)
|
if (_is_bidirectional)
|
||||||
{
|
{
|
||||||
crc = (~crc) & 0b0000000000001111;
|
crc = (~crc) & DSHOT_CRC_MASK;
|
||||||
}
|
}
|
||||||
|
|
||||||
return crc;
|
return crc;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Per calculate bits - Performance optimized
|
||||||
|
void DShotRMT::_preCalculateBitPositions()
|
||||||
|
{
|
||||||
|
for (int i = 0; i < DSHOT_BITS_PER_FRAME; ++i)
|
||||||
|
{
|
||||||
|
_bitPositions[i] = DSHOT_BITS_PER_FRAME - 1 - i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Transmit DShot packet via RMT
|
// Transmit DShot packet via RMT
|
||||||
dshot_result_t DShotRMT::_sendDShotFrame(const dshot_packet_t &packet)
|
dshot_result_t DShotRMT::_sendDShotFrame(const dshot_packet_t &packet)
|
||||||
{
|
{
|
||||||
dshot_result_t result = {false, UNKNOWN_ERROR};
|
|
||||||
uint64_t start_time = esp_timer_get_time();
|
|
||||||
|
|
||||||
// Check timing requirements
|
// Check timing requirements
|
||||||
if (!_timer_signal())
|
if (!_timer_signal())
|
||||||
{
|
{
|
||||||
return result;
|
return {false, TIMING_CORRECTION};
|
||||||
}
|
}
|
||||||
|
|
||||||
// Enable RMT RX before RMT TX
|
// Enable RMT RX before RMT TX
|
||||||
if (_is_bidirectional)
|
if (_is_bidirectional)
|
||||||
{
|
{
|
||||||
|
// Calculate transmission data size
|
||||||
|
size_t rx_size_bytes = GCR_BITS_PER_FRAME * sizeof(rmt_symbol_word_t);
|
||||||
|
|
||||||
// Performance reasons
|
// Performance reasons
|
||||||
rmt_symbol_word_t rx_symbols[DSHOT_BITS_PER_FRAME];
|
rmt_symbol_word_t rx_symbols[DSHOT_BITS_PER_FRAME];
|
||||||
|
|
||||||
if (rmt_receive(_rmt_rx_channel, rx_symbols, sizeof(rx_symbols), &_receive_config) != DSHOT_OK)
|
if (rmt_receive(_rmt_rx_channel, rx_symbols, rx_size_bytes, &_receive_config) != DSHOT_OK)
|
||||||
{
|
{
|
||||||
result.error_message = RX_RMT_RECEIVER_ERROR;
|
return {false, RECEIVER_FAILED};
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -422,18 +388,14 @@ dshot_result_t DShotRMT::_sendDShotFrame(const dshot_packet_t &packet)
|
||||||
// Disable RMT RX for sending
|
// Disable RMT RX for sending
|
||||||
if (rmt_disable(_rmt_rx_channel) != DSHOT_OK)
|
if (rmt_disable(_rmt_rx_channel) != DSHOT_OK)
|
||||||
{
|
{
|
||||||
result.error_message = RX_RMT_RECEIVER_ERROR;
|
return {false, RECEIVER_FAILED};
|
||||||
_dshot_log(RX_RMT_RECEIVER_ERROR);
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Perform RMT transmission
|
// Perform RMT transmission
|
||||||
if (rmt_transmit(_rmt_tx_channel, _dshot_encoder, tx_symbols, tx_size_bytes, &_transmit_config) != DSHOT_OK)
|
if (rmt_transmit(_rmt_tx_channel, _dshot_encoder, tx_symbols, tx_size_bytes, &_transmit_config) != DSHOT_OK)
|
||||||
{
|
{
|
||||||
result.error_message = TRANSMITTER_ERROR;
|
return {false, TRANSMISSION_FAILED};
|
||||||
_dshot_log(TRANSMITTER_ERROR);
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Re-enable RMT RX
|
// Re-enable RMT RX
|
||||||
|
|
@ -441,20 +403,14 @@ dshot_result_t DShotRMT::_sendDShotFrame(const dshot_packet_t &packet)
|
||||||
{
|
{
|
||||||
if (rmt_enable(_rmt_rx_channel) != DSHOT_OK)
|
if (rmt_enable(_rmt_rx_channel) != DSHOT_OK)
|
||||||
{
|
{
|
||||||
result.error_message = RX_RMT_RECEIVER_ERROR;
|
return {false, RECEIVER_FAILED};
|
||||||
_dshot_log(RX_RMT_RECEIVER_ERROR);
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update timestamp and calculate execution time
|
// Update timestamp and calculate execution time
|
||||||
_timer_reset();
|
_timer_reset();
|
||||||
uint64_t end_time = esp_timer_get_time();
|
|
||||||
|
|
||||||
result.success = true;
|
return {true, TRANSMISSION_SUCCESS};
|
||||||
result.error_message = TRANSMISSION_SUCCESS;
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Encode DShot packet into RMT symbol format (placed in IRAM for performance)
|
// Encode DShot packet into RMT symbol format (placed in IRAM for performance)
|
||||||
|
|
@ -462,16 +418,16 @@ bool IRAM_ATTR DShotRMT::_encodeDShotFrame(const dshot_packet_t &packet, rmt_sym
|
||||||
{
|
{
|
||||||
_parsed_packet = _parseDShotPacket(packet);
|
_parsed_packet = _parseDShotPacket(packet);
|
||||||
|
|
||||||
const uint16_t level0 = _is_bidirectional ? 0 : 1;
|
// Decode MSB
|
||||||
const uint16_t level1 = _is_bidirectional ? 1 : 0;
|
for (int i = 0; i < DSHOT_BITS_PER_FRAME; ++i)
|
||||||
|
|
||||||
for (int i = 0; i < DSHOT_BITS_PER_FRAME; i++)
|
|
||||||
{
|
{
|
||||||
// Decode MSB
|
// Use precalculated bit positions - Performace optimized
|
||||||
bool bit = (_parsed_packet >> (DSHOT_BITS_PER_FRAME - 1 - i)) & 0b0000000000000001;
|
int bit_position = _bitPositions[i];
|
||||||
symbols[i].level0 = level0;
|
|
||||||
|
bool bit = (_parsed_packet >> bit_position) & 0b0000000000000001;
|
||||||
|
symbols[i].level0 = _level0;
|
||||||
symbols[i].duration0 = bit ? _timing_config.ticks_one_high : _timing_config.ticks_zero_high;
|
symbols[i].duration0 = bit ? _timing_config.ticks_one_high : _timing_config.ticks_zero_high;
|
||||||
symbols[i].level1 = level1;
|
symbols[i].level1 = _level1;
|
||||||
symbols[i].duration1 = bit ? _timing_config.ticks_one_low : _timing_config.ticks_zero_low;
|
symbols[i].duration1 = bit ? _timing_config.ticks_one_low : _timing_config.ticks_zero_low;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -497,15 +453,15 @@ uint16_t DShotRMT::_decodeDShotFrame(const rmt_symbol_word_t *symbols)
|
||||||
|
|
||||||
// Extract 16 data bits and 4 CRC bits from 20-bit frame.
|
// Extract 16 data bits and 4 CRC bits from 20-bit frame.
|
||||||
// The first bit of the GCR frame is a start bit and is discarded.
|
// The first bit of the GCR frame is a start bit and is discarded.
|
||||||
uint16_t data_and_crc = (decoded_frame & 0xFFFF);
|
uint16_t data_and_crc = (decoded_frame & DSHOT_FULL_PACKET);
|
||||||
|
|
||||||
// Cutting 4 bits?
|
// Cutting 4 bits?
|
||||||
uint16_t received_data = data_and_crc >> 4;
|
uint16_t received_data = data_and_crc >> 4;
|
||||||
|
|
||||||
// Masking CRC
|
// Masking CRC
|
||||||
uint16_t received_crc = data_and_crc & 0b0000000000001111;
|
uint16_t received_crc = data_and_crc & DSHOT_CRC_MASK;
|
||||||
|
|
||||||
// Telemetry request bit is always 1.
|
// Telemetry request bit has to be 1
|
||||||
if (!(received_data & (1 << 11)))
|
if (!(received_data & (1 << 11)))
|
||||||
{
|
{
|
||||||
return DSHOT_NULL_PACKET;
|
return DSHOT_NULL_PACKET;
|
||||||
|
|
@ -522,7 +478,7 @@ uint16_t DShotRMT::_decodeDShotFrame(const rmt_symbol_word_t *symbols)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return the eRPM value (first 11 bits of received data).
|
// Return the eRPM value (first 11 bits of received data).
|
||||||
return received_data & 0b0000011111111111;
|
return received_data & DSHOT_THROTTLE_MAX;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if enough time has passed for next transmission
|
// Check if enough time has passed for next transmission
|
||||||
|
|
@ -540,6 +496,7 @@ bool IRAM_ATTR DShotRMT::_timer_signal()
|
||||||
bool DShotRMT::_timer_reset()
|
bool DShotRMT::_timer_reset()
|
||||||
{
|
{
|
||||||
_last_transmission_time = esp_timer_get_time();
|
_last_transmission_time = esp_timer_get_time();
|
||||||
|
|
||||||
return DSHOT_OK;
|
return DSHOT_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -550,22 +507,19 @@ void DShotRMT::printDShotInfo(Stream &output) const
|
||||||
output.println(" === DShot Signal Info === ");
|
output.println(" === DShot Signal Info === ");
|
||||||
|
|
||||||
// Current DShot mode
|
// Current DShot mode
|
||||||
output.printf("Current Mode: DSHOT%d\n",
|
output.printf("Current Mode: DSHOT%d\n",
|
||||||
_mode == DSHOT150 ? 150 :
|
_mode == DSHOT150 ? 150 :
|
||||||
_mode == DSHOT300 ? 300 :
|
_mode == DSHOT300 ? 300 :
|
||||||
_mode == DSHOT600 ? 600 :
|
_mode == DSHOT600 ? 600 :
|
||||||
_mode == DSHOT1200 ? 1200 : 0);
|
_mode == DSHOT1200 ? 1200 : 0);
|
||||||
|
|
||||||
output.printf("Bidirectional: %s\n", _is_bidirectional ? "YES" : "NO");
|
output.printf("Bidirectional: %s\n", _is_bidirectional ? "YES" : "NO");
|
||||||
|
|
||||||
// Timing Info
|
|
||||||
output.printf("Frame Length: %u us\n", _timing_config.frame_length_us);
|
|
||||||
|
|
||||||
// Packet Info
|
// Packet Info
|
||||||
output.printf("Current Packet: ");
|
output.printf("Current Packet: ");
|
||||||
|
|
||||||
// Print bit by bit
|
// Print bit by bit
|
||||||
for (int i = 15; i >= 0; --i)
|
for (int i = DSHOT_BITS_PER_FRAME - 1; i >= 0; --i)
|
||||||
{
|
{
|
||||||
if ((_parsed_packet >> i) & 1)
|
if ((_parsed_packet >> i) & 1)
|
||||||
{
|
{
|
||||||
|
|
@ -592,3 +546,25 @@ void DShotRMT::printCpuInfo(Stream &output) const
|
||||||
output.printf("XTAL Freq = %lu MHz\n", getXtalFrequencyMhz());
|
output.printf("XTAL Freq = %lu MHz\n", getXtalFrequencyMhz());
|
||||||
output.printf("APB Freq = %lu Hz\n", getApbFrequency());
|
output.printf("APB Freq = %lu Hz\n", getApbFrequency());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --- HELPERS ---
|
||||||
|
void printDShotResult(dshot_result_t &result, Stream &output)
|
||||||
|
{
|
||||||
|
output.printf("Status: %s - %s\n", result.success ? "SUCCESS" : "FAILED", result.msg);
|
||||||
|
output.println(" ");
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
void printDShotTelemetry(dshot_telemetry_result_t &result, Stream &output)
|
||||||
|
{
|
||||||
|
if (result.success)
|
||||||
|
{
|
||||||
|
output.printf("Telemetry: eRPM=%u, Motor RPM=%u\n", result.erpm, result.motor_rpm);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
output.printf("Telemetry: FAILED - %s\n", result.msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
output.println(" ");
|
||||||
|
}
|
||||||
|
|
|
||||||
120
DShotRMT.h
120
DShotRMT.h
|
|
@ -15,19 +15,26 @@
|
||||||
#include <driver/rmt_rx.h>
|
#include <driver/rmt_rx.h>
|
||||||
|
|
||||||
// DShot Protocol Constants
|
// DShot Protocol Constants
|
||||||
constexpr auto DSHOT_THROTTLE_FAILSAFE = 0;
|
static constexpr auto DSHOT_THROTTLE_FAILSAFE = 0;
|
||||||
constexpr auto DSHOT_THROTTLE_MIN = 48;
|
static constexpr auto DSHOT_THROTTLE_MIN = 48;
|
||||||
constexpr auto DSHOT_THROTTLE_MAX = 2047;
|
static constexpr auto DSHOT_THROTTLE_MAX = 2047;
|
||||||
constexpr auto DSHOT_BITS_PER_FRAME = 16;
|
static constexpr auto DSHOT_BITS_PER_FRAME = 16;
|
||||||
constexpr auto DSHOT_SWITCH_TIME = 30; // Time in us between TX and RX
|
static constexpr auto DSHOT_PAUSE_US = 30; // Additional frame pause time
|
||||||
constexpr auto DSHOT_NULL_PACKET = 0b0000000000000000;
|
static constexpr auto DSHOT_NULL_PACKET = 0b0000000000000000;
|
||||||
constexpr auto DSHOT_RX_TIMEOUT_MS = 2;
|
static constexpr auto DSHOT_FULL_PACKET = 0b1111111111111111;
|
||||||
|
static constexpr auto DSHOT_CRC_MASK = 0b0000000000001111;
|
||||||
|
static constexpr auto DSHOT_RX_TIMEOUT_MS = 2; // Never reached, just a timeeout
|
||||||
|
static constexpr auto GCR_BITS_PER_FRAME = 21; // Number of GCR bits in a DShot answer frame (1 start + 16 data + 4 CRC)
|
||||||
|
static constexpr auto DEFAULT_MOTOR_MAGNET_COUNT = 14;
|
||||||
|
static constexpr auto MAGNETS_PER_POLE_PAIR = 2;
|
||||||
|
static constexpr auto MIN_POLE_PAIRS = 1;
|
||||||
|
static constexpr auto NO_DSHOT_ERPM = 0;
|
||||||
|
static constexpr auto NO_DSHOT_RPM = 0;
|
||||||
|
|
||||||
// RMT Configuration Constants
|
// RMT Configuration Constants
|
||||||
constexpr auto DSHOT_CLOCK_SRC_DEFAULT = RMT_CLK_SRC_DEFAULT;
|
constexpr auto DSHOT_CLOCK_SRC_DEFAULT = RMT_CLK_SRC_DEFAULT;
|
||||||
constexpr auto DSHOT_RMT_RESOLUTION = 10 * 1000 * 1000; // 10 MHz resolution
|
constexpr auto DSHOT_RMT_RESOLUTION = 10 * 1000 * 1000; // 10 MHz resolution
|
||||||
constexpr auto RMT_BUFFER_SIZE = DSHOT_BITS_PER_FRAME;
|
constexpr auto RMT_BUFFER_SIZE = DSHOT_BITS_PER_FRAME;
|
||||||
constexpr auto GCR_BITS_PER_FRAME = 21; // Number of GCR bits in a DShot answer frame (1 start + 16 data + 4 CRC)
|
|
||||||
constexpr auto RMT_BUFFER_SYMBOLS = 64;
|
constexpr auto RMT_BUFFER_SYMBOLS = 64;
|
||||||
constexpr auto RMT_QUEUE_DEPTH = 1;
|
constexpr auto RMT_QUEUE_DEPTH = 1;
|
||||||
|
|
||||||
|
|
@ -36,7 +43,7 @@ constexpr auto RMT_QUEUE_DEPTH = 1;
|
||||||
constexpr uint32_t DSHOT_PULSE_MIN = 3000;
|
constexpr uint32_t DSHOT_PULSE_MIN = 3000;
|
||||||
constexpr uint32_t DSHOT_PULSE_MAX = 60000;
|
constexpr uint32_t DSHOT_PULSE_MAX = 60000;
|
||||||
|
|
||||||
// DShot Mode Enumeration
|
// DShot Modes
|
||||||
typedef enum
|
typedef enum
|
||||||
{
|
{
|
||||||
DSHOT_OFF,
|
DSHOT_OFF,
|
||||||
|
|
@ -46,7 +53,7 @@ typedef enum
|
||||||
DSHOT1200
|
DSHOT1200
|
||||||
} dshot_mode_t;
|
} dshot_mode_t;
|
||||||
|
|
||||||
// DShot Packet Structure
|
// DShot Packet
|
||||||
typedef struct
|
typedef struct
|
||||||
{
|
{
|
||||||
uint16_t throttle_value : 11;
|
uint16_t throttle_value : 11;
|
||||||
|
|
@ -54,7 +61,7 @@ typedef struct
|
||||||
uint16_t checksum : 4;
|
uint16_t checksum : 4;
|
||||||
} dshot_packet_t;
|
} dshot_packet_t;
|
||||||
|
|
||||||
// DShot Timing Configuration Structure
|
// DShot Timing Configuration
|
||||||
typedef struct
|
typedef struct
|
||||||
{
|
{
|
||||||
uint32_t frame_length_us;
|
uint32_t frame_length_us;
|
||||||
|
|
@ -65,25 +72,29 @@ typedef struct
|
||||||
uint16_t ticks_zero_low;
|
uint16_t ticks_zero_low;
|
||||||
} dshot_timing_t;
|
} dshot_timing_t;
|
||||||
|
|
||||||
// Command execution result structure
|
// Error handling
|
||||||
typedef struct
|
typedef struct
|
||||||
{
|
{
|
||||||
bool success;
|
bool success;
|
||||||
const char *error_message;
|
const char *msg;
|
||||||
} dshot_result_t;
|
} dshot_result_t;
|
||||||
|
|
||||||
// DShot telemetry result structure
|
// DShot telemetry result
|
||||||
typedef struct
|
typedef struct
|
||||||
{
|
{
|
||||||
bool success;
|
bool success;
|
||||||
uint16_t erpm;
|
uint16_t erpm;
|
||||||
uint32_t motor_rpm;
|
uint16_t motor_rpm;
|
||||||
const char *error_message;
|
const char *msg;
|
||||||
} dshot_telemetry_result_t;
|
} dshot_telemetry_result_t;
|
||||||
|
|
||||||
// Naming convention
|
// Naming convention
|
||||||
typedef dshotCommands_e dshot_commands_t;
|
typedef dshotCommands_e dshot_commands_t;
|
||||||
|
|
||||||
|
// --- HELPERS ---
|
||||||
|
void printDShotResult(dshot_result_t &result, Stream &output = Serial);
|
||||||
|
void printDShotTelemetry(dshot_telemetry_result_t &result, Stream &output = Serial);
|
||||||
|
|
||||||
//
|
//
|
||||||
class DShotRMT
|
class DShotRMT
|
||||||
{
|
{
|
||||||
|
|
@ -106,21 +117,12 @@ public:
|
||||||
// Send DShot command (0-47)
|
// Send DShot command (0-47)
|
||||||
dshot_result_t sendCommand(uint16_t command);
|
dshot_result_t sendCommand(uint16_t command);
|
||||||
|
|
||||||
// Get telemetry data (bidirectional mode only)
|
|
||||||
dshot_telemetry_result_t getTelemetry(uint8_t magnet_count = 14);
|
|
||||||
|
|
||||||
// Get eRPM only (bidirectional mode only)
|
|
||||||
uint16_t getERPM();
|
|
||||||
|
|
||||||
// --- GETTERS ---
|
// --- GETTERS ---
|
||||||
gpio_num_t getGPIO() const { return _gpio; }
|
gpio_num_t getGPIO() const { return _gpio; }
|
||||||
uint16_t getDShotPacket() const { return _parsed_packet; }
|
uint16_t getDShotPacket() const { return _parsed_packet; }
|
||||||
bool is_bidirectional() const { return _is_bidirectional; }
|
bool is_bidirectional() const { return _is_bidirectional; }
|
||||||
dshot_mode_t getMode() const { return _mode; }
|
dshot_mode_t getMode() const { return _mode; }
|
||||||
|
dshot_telemetry_result_t getTelemetry(uint16_t magnet_count = DEFAULT_MOTOR_MAGNET_COUNT);
|
||||||
// Get execution statistics
|
|
||||||
uint32_t getTotalTransmissions() const { return _total_transmissions; }
|
|
||||||
uint32_t getFailedTransmissions() const { return _failed_transmissions; }
|
|
||||||
|
|
||||||
// --- INFO ---
|
// --- INFO ---
|
||||||
void printDShotInfo(Stream &output = Serial) const;
|
void printDShotInfo(Stream &output = Serial) const;
|
||||||
|
|
@ -141,7 +143,7 @@ public:
|
||||||
return result.success;
|
return result.success;
|
||||||
}
|
}
|
||||||
|
|
||||||
[[deprecated("Use getTelemetry() instead")]]
|
[[deprecated("Use getTelemetry() instead")]]
|
||||||
uint32_t getMotorRPM(uint8_t magnet_count)
|
uint32_t getMotorRPM(uint8_t magnet_count)
|
||||||
{
|
{
|
||||||
auto result = getTelemetry(magnet_count);
|
auto result = getTelemetry(magnet_count);
|
||||||
|
|
@ -155,16 +157,15 @@ private:
|
||||||
bool _is_bidirectional;
|
bool _is_bidirectional;
|
||||||
uint32_t _frame_timer_us;
|
uint32_t _frame_timer_us;
|
||||||
const dshot_timing_t &_timing_config;
|
const dshot_timing_t &_timing_config;
|
||||||
|
uint16_t _last_throttle;
|
||||||
|
|
||||||
// --- TIMING & STATE VARIABLES ---
|
// --- TIMING & PACKET VARIABLES ---
|
||||||
uint64_t _last_transmission_time;
|
uint64_t _last_transmission_time;
|
||||||
uint16_t _last_erpm;
|
|
||||||
uint16_t _parsed_packet;
|
uint16_t _parsed_packet;
|
||||||
dshot_packet_t _packet;
|
dshot_packet_t _packet;
|
||||||
|
uint8_t _bitPositions[DSHOT_BITS_PER_FRAME];
|
||||||
// --- STATISTICS ---
|
uint16_t _level0;
|
||||||
uint32_t _total_transmissions;
|
uint16_t _level1;
|
||||||
uint32_t _failed_transmissions;
|
|
||||||
|
|
||||||
// --- RMT HARDWARE HANDLES ---
|
// --- RMT HARDWARE HANDLES ---
|
||||||
rmt_channel_handle_t _rmt_tx_channel;
|
rmt_channel_handle_t _rmt_tx_channel;
|
||||||
|
|
@ -178,14 +179,15 @@ private:
|
||||||
rmt_receive_config_t _receive_config;
|
rmt_receive_config_t _receive_config;
|
||||||
|
|
||||||
// --- INITS ---
|
// --- INITS ---
|
||||||
bool _initTXChannel();
|
dshot_result_t _initTXChannel();
|
||||||
bool _initRXChannel();
|
dshot_result_t _initRXChannel();
|
||||||
bool _initDShotEncoder();
|
dshot_result_t _initDShotEncoder();
|
||||||
|
|
||||||
// --- PACKET MANAGEMENT ---
|
// --- PACKET MANAGEMENT ---
|
||||||
dshot_packet_t _buildDShotPacket(const uint16_t value);
|
dshot_packet_t _buildDShotPacket(const uint16_t value);
|
||||||
uint16_t _parseDShotPacket(const dshot_packet_t &packet);
|
uint16_t _parseDShotPacket(const dshot_packet_t &packet);
|
||||||
uint16_t _calculateCRC(const uint16_t data);
|
uint16_t _calculateCRC(const uint16_t data);
|
||||||
|
void _preCalculateBitPositions();
|
||||||
|
|
||||||
// --- FRAME PROCESSING ---
|
// --- FRAME PROCESSING ---
|
||||||
dshot_result_t _sendDShotFrame(const dshot_packet_t &packet);
|
dshot_result_t _sendDShotFrame(const dshot_packet_t &packet);
|
||||||
|
|
@ -197,34 +199,38 @@ private:
|
||||||
bool _timer_reset();
|
bool _timer_reset();
|
||||||
|
|
||||||
// -- CALLBACKS ---
|
// -- CALLBACKS ---
|
||||||
QueueHandle_t _rx_queue;
|
|
||||||
rmt_rx_event_callbacks_t _rx_event_callbacks;
|
rmt_rx_event_callbacks_t _rx_event_callbacks;
|
||||||
|
volatile rmt_symbol_word_t _rx_symbols_direct[GCR_BITS_PER_FRAME];
|
||||||
|
volatile uint16_t _last_erpm_atomic;
|
||||||
|
volatile bool _telemetry_ready_flag;
|
||||||
static bool IRAM_ATTR _rmt_rx_done_callback(rmt_channel_handle_t rmt_rx_channel, const rmt_rx_done_event_data_t *edata, void *user_data);
|
static bool IRAM_ATTR _rmt_rx_done_callback(rmt_channel_handle_t rmt_rx_channel, const rmt_rx_done_event_data_t *edata, void *user_data);
|
||||||
|
|
||||||
// --- DSHOT DEFAULTS ---
|
// --- DSHOT DEFAULTS ---
|
||||||
static constexpr auto DSHOT_TELEMETRY_INVALID = (0xffff);
|
static constexpr auto const DSHOT_TELEMETRY_INVALID = (0xffff);
|
||||||
|
|
||||||
// --- ERROR HANDLING & LOGGING ---
|
|
||||||
void _dshot_log(const char *msg, Stream &output = Serial) const { output.println(msg); }
|
|
||||||
|
|
||||||
// --- CONSTANTS & ERROR MESSAGES ---
|
// --- CONSTANTS & ERROR MESSAGES ---
|
||||||
static constexpr bool DSHOT_OK = 0;
|
static constexpr bool DSHOT_OK = 0;
|
||||||
static constexpr bool DSHOT_ERROR = 1;
|
static constexpr bool DSHOT_ERROR = 1;
|
||||||
|
|
||||||
static constexpr char *NEW_LINE = " ";
|
static constexpr char const *NONE = "";
|
||||||
static constexpr char *UNKNOWN_ERROR = "Unknown Error!";
|
static constexpr char const *UNKNOWN_ERROR = "Unknown Error!";
|
||||||
static constexpr char *TX_INIT_FAILED = "Failed to initialize TX channel!";
|
static constexpr char const *INIT_SUCCESS = "SignalGeneratorRMT initialized successfully";
|
||||||
static constexpr char *RX_INIT_FAILED = "Failed to initialize RX channel!";
|
static constexpr char const *INIT_FAILED = "SignalGeneratorRMT init failed!";
|
||||||
static constexpr char *ENCODER_INIT_FAILED = "Failed to initialize DShot encoder!";
|
static constexpr char const *TX_INIT_SUCCESS = "TX RMT channel initialized successfully";
|
||||||
static constexpr char *CRC_CHECK_FAILED = "RX CRC Check failed!";
|
static constexpr char const *TX_INIT_FAILED = "TX RMT channel init failed!";
|
||||||
static constexpr char *THROTTLE_NOT_IN_RANGE = "Throttle value not in range (48 - 2047)!";
|
static constexpr char const *RX_INIT_SUCCESS = "RX RMT channel initialized successfully";
|
||||||
static constexpr char *COMMAND_NOT_VALID = "Not a valid DShot Command (0 - 47)!";
|
static constexpr char const *RX_INIT_FAILED = "RX RMT channel init failed!";
|
||||||
static constexpr char *BIDIR_NOT_ENABLED = "Bidirectional DShot support not enabled!";
|
static constexpr char const *RX_BUFFER_FAILED = "RX RMT buffer init failed!";
|
||||||
static constexpr char *RX_RMT_RECEIVER_ERROR = "RX RMT receiver error!";
|
static constexpr char const *ENCODER_INIT_SUCCESS = "RMT encoder initialized successfully";
|
||||||
static constexpr char *PACKET_BUILD_ERROR = "Value too big for DShot Packet!";
|
static constexpr char const *ENCODER_INIT_FAILED = "RMT encoder init failed!";
|
||||||
static constexpr char *TRANSMITTER_ERROR = "RMT TX Transmitter Error!";
|
static constexpr char const *TRANSMISSION_SUCCESS = "Transmission successfully";
|
||||||
static constexpr char *INIT_SUCCESS = "DShotRMT initialized successfully";
|
static constexpr char const *TRANSMISSION_FAILED = "Transmission failed!";
|
||||||
static constexpr char *TELEMETRY_SUCCESS = "Telemetry read successfully";
|
static constexpr char const *RECEIVER_FAILED = "RMT receiver failed!";
|
||||||
static constexpr char *TELEMETRY_TIMEOUT = "Telemetry read timeout";
|
static constexpr char const *THROTTLE_NOT_IN_RANGE = "Throttle not in range! (48 - 2047)";
|
||||||
static constexpr char *TRANSMISSION_SUCCESS = "Transmission successful";
|
static constexpr char const *COMMAND_NOT_VALID = "Command not valid! (0 - 47)";
|
||||||
|
static constexpr char const *BIDIR_NOT_ENABLED = "Bidirectional DShot not enabled!";
|
||||||
|
static constexpr char const *TELEMETRY_SUCCESS = "Valid Telemetric Frame received!";
|
||||||
|
static constexpr char const *TELEMETRY_FAILED = "No valid Telemetric Frame received!";
|
||||||
|
static constexpr char const *INVALID_MAGNET_COUNT = "Invalid motor magnet count!";
|
||||||
|
static constexpr char const *TIMING_CORRECTION = "Timing correction!";
|
||||||
};
|
};
|
||||||
|
|
|
||||||
334
README.md
334
README.md
|
|
@ -3,7 +3,7 @@
|
||||||
# DShotRMT - ESP32 Library (Rewrite for ESP-IDF 5)
|
# DShotRMT - ESP32 Library (Rewrite for ESP-IDF 5)
|
||||||
|
|
||||||
A modern, robust C++ library for generating DShot signals on the ESP32 using the new ESP-IDF 5 RMT encoder API (`rmt_tx.h` / `rmt_rx.h`).
|
A modern, robust C++ library for generating DShot signals on the ESP32 using the new ESP-IDF 5 RMT encoder API (`rmt_tx.h` / `rmt_rx.h`).
|
||||||
Supports all standard DShot modes (150, 300, 600) and features continuous frame transmission with configurable pause.
|
Supports all standard DShot modes (150, 300, 600, 1200) and features continuous frame transmission with configurable timing.
|
||||||
**Now with BiDirectional DShot support and advanced command management!**
|
**Now with BiDirectional DShot support and advanced command management!**
|
||||||
|
|
||||||
> The legacy version (using the old `rmt.h` API) is still available in the `oldAPI` branch.
|
> The legacy version (using the old `rmt.h` API) is still available in the `oldAPI` branch.
|
||||||
|
|
@ -12,20 +12,36 @@ Supports all standard DShot modes (150, 300, 600) and features continuous frame
|
||||||
|
|
||||||
## 🚀 Features
|
## 🚀 Features
|
||||||
|
|
||||||
- **All DShot Modes:** DSHOT150, DSHOT300 (default), DSHOT600, (DSHOT1200)
|
- **All DShot Modes:** DSHOT150, DSHOT300 (default), DSHOT600, DSHOT1200
|
||||||
- **BiDirectional DShot:** Experimental support for RPM feedback
|
- **BiDirectional DShot:** Full support for RPM telemetry feedback
|
||||||
- **Advanced Command Manager:** High-level API for ESC configuration and control
|
- **Advanced Command Manager:** High-level API for ESC configuration and control
|
||||||
- **Command Sequences:** Predefined initialization and calibration sequences
|
- **Command Sequences:** Predefined initialization and calibration sequences
|
||||||
- **Continuous Frames:** Independent timed, Hardware signal generation
|
- **Hardware-Timed Signals:** Independent, precise signal generation using ESP32 RMT peripheral
|
||||||
- **Configurable Pause:** Ensures ESCs can reliably detect frame boundaries
|
- **Configurable Timing:** Ensures ESCs can reliably detect frame boundaries
|
||||||
|
- **Error Handling:** Comprehensive result reporting with success/failure status
|
||||||
- **Simple API:** Easy integration into your Arduino or ESP-IDF project
|
- **Simple API:** Easy integration into your Arduino or ESP-IDF project
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 📦 Installation
|
## 📦 Installation
|
||||||
|
|
||||||
Clone this repository and add it to your Arduino libraries or ESP-IDF components.
|
### Arduino IDE
|
||||||
|
1. Search "Arduino Library Manager" for "DShotRMT"
|
||||||
|
|
||||||
|
or
|
||||||
|
|
||||||
|
1. Clone this repository or download as ZIP
|
||||||
|
2. Place in your Arduino libraries folder (`~/Arduino/libraries/DShotRMT/`)
|
||||||
|
3. Restart Arduino IDE
|
||||||
|
|
||||||
|
### PlatformIO
|
||||||
|
Add to your `platformio.ini`:
|
||||||
|
```ini
|
||||||
|
lib_deps =
|
||||||
|
https://github.com/derdoktor667/DShotRMT.git
|
||||||
|
```
|
||||||
|
|
||||||
|
### Manual Installation
|
||||||
```sh
|
```sh
|
||||||
git clone https://github.com/derdoktor667/DShotRMT.git
|
git clone https://github.com/derdoktor667/DShotRMT.git
|
||||||
```
|
```
|
||||||
|
|
@ -39,216 +55,208 @@ git clone https://github.com/derdoktor667/DShotRMT.git
|
||||||
```cpp
|
```cpp
|
||||||
#include <DShotRMT.h>
|
#include <DShotRMT.h>
|
||||||
|
|
||||||
// Create motor instance
|
// Create motor instance (GPIO 17, DSHOT300, non-bidirectional)
|
||||||
DShotRMT motor(17, DSHOT300);
|
DShotRMT motor(17, DSHOT300, false);
|
||||||
|
|
||||||
void setup() {
|
void setup() {
|
||||||
Serial.begin(115200);
|
Serial.begin(115200);
|
||||||
motor.begin();
|
|
||||||
|
// Initialize the motor
|
||||||
|
dshot_result_t result = motor.begin();
|
||||||
|
if (result.success) {
|
||||||
|
Serial.println("Motor initialized successfully");
|
||||||
|
} else {
|
||||||
|
Serial.printf("Motor init failed: %s\n", result.msg);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void loop() {
|
void loop() {
|
||||||
motor.sendThrottle(1000); // Send throttle value
|
// Send throttle value (48-2047)
|
||||||
delay(20);
|
dshot_result_t result = motor.sendThrottle(1000);
|
||||||
|
if (!result.success) {
|
||||||
|
Serial.printf("Throttle command failed: %s\n", result.msg);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### Advanced Usage (DShotCommandManager)
|
### Bidirectional DShot (RPM Telemetry)
|
||||||
|
|
||||||
```cpp
|
```cpp
|
||||||
#include <DShotRMT.h>
|
#include <DShotRMT.h>
|
||||||
#include <DShotCommandManager.h>
|
|
||||||
|
|
||||||
// Create motor and command manager instances
|
// Enable bidirectional mode for telemetry
|
||||||
DShotRMT motor(17, DSHOT300);
|
DShotRMT motor(17, DSHOT300, true);
|
||||||
DShotCommandManager cmdManager(motor);
|
|
||||||
|
|
||||||
void setup() {
|
void setup() {
|
||||||
Serial.begin(115200);
|
Serial.begin(115200);
|
||||||
motor.begin();
|
motor.begin();
|
||||||
cmdManager.begin();
|
|
||||||
|
|
||||||
// Execute initialization sequence
|
|
||||||
cmdManager.executeInitSequence();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void loop() {
|
void loop() {
|
||||||
// Your main code here
|
// Send throttle
|
||||||
|
motor.sendThrottle(1000);
|
||||||
|
|
||||||
|
// Get telemetry data
|
||||||
|
dshot_telemetry_result_t telemetry = motor.getTelemetry(14); // 14 magnets
|
||||||
|
if (telemetry.success) {
|
||||||
|
Serial.printf("eRPM: %u, Motor RPM: %u\n",
|
||||||
|
telemetry.erpm,
|
||||||
|
telemetry.motor_rpm);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 🎛️ DShotCommandManager API
|
|
||||||
|
|
||||||
The `DShotCommandManager` provides a high-level interface for ESC control and configuration:
|
|
||||||
|
|
||||||
### Motor Control
|
|
||||||
- `stopMotor()` - Stop motor immediately
|
|
||||||
- `set3DMode(bool enable)` - Enable/disable 3D mode
|
|
||||||
- `setSpinDirection(bool reversed)` - Set motor spin direction
|
|
||||||
- `saveSettings()` - Save current settings to ESC
|
|
||||||
|
|
||||||
### LED Control (BLHeli32 only)
|
|
||||||
- `setLED(uint8_t led_number, bool state)` - Control ESC LEDs (0-3)
|
|
||||||
|
|
||||||
### Beacon Functions
|
|
||||||
- `activateBeacon(uint8_t beacon_number)` - Activate motor beeping (1-5)
|
|
||||||
|
|
||||||
### Telemetry
|
|
||||||
- `setExtendedTelemetry(bool enable)` - Enable/disable extended telemetry
|
|
||||||
- `requestESCInfo()` - Request ESC information
|
|
||||||
|
|
||||||
### Command Sequences
|
|
||||||
- `executeInitSequence()` - Basic ESC initialization
|
|
||||||
- `executeCalibrationSequence()` - ESC calibration sequence
|
|
||||||
- `executeSequence(sequence, length)` - Custom command sequences
|
|
||||||
|
|
||||||
### Utility Functions
|
|
||||||
- `getCommandName(command)` - Get command name as string
|
|
||||||
- `isValidCommand(command)` - Validate command
|
|
||||||
- `printStatistics()` - Print execution statistics
|
|
||||||
- `resetStatistics()` - Reset execution counters
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 📚 Examples
|
## 📚 Examples
|
||||||
|
|
||||||
### 1. Basic DShot Control
|
The library includes comprehensive examples:
|
||||||
Use the `dshot300.ino` example for simple throttle control.
|
|
||||||
|
|
||||||
### 2. Advanced Command Management
|
### 1. Basic DShot Control (`dshot300.ino`)
|
||||||
Use the `command_manager.ino` example for interactive ESC control:
|
- Simple throttle control
|
||||||
|
- Command execution
|
||||||
|
- Serial interface for testing
|
||||||
|
- Telemetry reading (if bidirectional enabled)
|
||||||
|
|
||||||
|
### 2. Advanced Command Management (`command_manager.ino`)
|
||||||
|
Interactive ESC control with full menu system:
|
||||||
```
|
```
|
||||||
=== DShot Command Manager Menu ===
|
=== DShot Command Manager Menu ===
|
||||||
Basic Commands:
|
1 - Stop Motor
|
||||||
1 - Stop Motor
|
2 - Activate Beacon 1
|
||||||
2 - Activate Beacon 1
|
3 - Set Normal Spin Direction
|
||||||
3 - Set Normal Spin Direction
|
4 - Set Reversed Spin Direction
|
||||||
4 - Set Reversed Spin Direction
|
5 - Get ESC Info
|
||||||
5 - Enable 3D Mode
|
6 - Turn LED 0 ON
|
||||||
6 - Disable 3D Mode
|
7 - Turn LED 0 OFF
|
||||||
7 - Save Settings
|
0 - Emergency Stop
|
||||||
8 - Turn LED 0 ON
|
|
||||||
9 - Turn LED 0 OFF
|
|
||||||
|
|
||||||
Sequences:
|
Advanced Commands:
|
||||||
i - Execute Initialization Sequence
|
cmd <number> - Send DShot command (0-47)
|
||||||
c - Execute Calibration Sequence
|
throttle <value> - Set throttle (48-2047)
|
||||||
|
repeat cmd <num> count <count> - Repeat command
|
||||||
Advanced:
|
|
||||||
cmd <number> - Send DShot command (0 - 47)
|
|
||||||
throttle <value> - Set throttle (48 - 2047)
|
|
||||||
throttle 0 - Stop sending throttle
|
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 📚 DShot Protocol Overview
|
## 🔧 Hardware Configuration
|
||||||
|
|
||||||
DShot transmits 16-bit packets to brushless ESCs:
|
### Supported DShot Modes
|
||||||
|
|
||||||
- **11 bits:** Throttle value
|
| Mode | Bitrate | Bit Time | Frame Time | Use Case |
|
||||||
- **1 bit:** Telemetry request
|
|----------|-------------|----------|------------|----------|
|
||||||
- **4 bits:** Checksum (CRC)
|
| DSHOT150 | 150 kbit/s | 6.67 µs | ~107 µs | Long wires, EMI-prone |
|
||||||
|
| DSHOT300 | 300 kbit/s | 3.33 µs | ~53 µs | Standard (recommended) |
|
||||||
|
| DSHOT600 | 600 kbit/s | 1.67 µs | ~27 µs | High performance |
|
||||||
|
| DSHOT1200| 1200 kbit/s | 0.83 µs | ~13 µs | Racing applications |
|
||||||
|
|
||||||
Data is sent MSB-first. Pulse timing depends on the selected DShot mode.
|
### GPIO Configuration
|
||||||
|
```cpp
|
||||||
|
// Using GPIO number
|
||||||
|
DShotRMT motor(17, DSHOT300);
|
||||||
|
|
||||||
| DSHOT | Bitrate | TH1 | TH0 | Bit Time (µs) | Frame Time (µs) |
|
// Using GPIO enum
|
||||||
|-------|-------------|-------|--------|---------------|-----------------|
|
DShotRMT motor(GPIO_NUM_17, DSHOT300);
|
||||||
| 150 | 150 kbit/s | 5.00 | 2.50 | 6.67 | ~106.72 |
|
|
||||||
| 300 | 300 kbit/s | 2.50 | 1.25 | 3.33 | ~53.28 |
|
|
||||||
| 600 | 600 kbit/s | 1.25 | 0.625 | 1.67 | ~26.72 |
|
|
||||||
|
|
||||||
Each frame is followed by a pause to help ESCs detect separate frames.
|
// With bidirectional support
|
||||||
|
DShotRMT motor(17, DSHOT300, true);
|
||||||

|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🔒 Checksum Calculation
|
|
||||||
|
|
||||||
The checksum is calculated over the first 12 bits (throttle + telemetry):
|
|
||||||
|
|
||||||
```c
|
|
||||||
crc = (value ^ (value >> 4) ^ (value >> 8)) & 0x0F;
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### Bidirectional DSHOT
|
|
||||||
|
|
||||||
Bidirectional DSHOT (sometimes called "inverted DSHOT") inverts the signal level:
|
|
||||||
A logical '1' is low, and a '0' is high. This signals the ESC to send telemetry packets back.
|
|
||||||
|
|
||||||
**Bidirectional CRC:**
|
|
||||||
|
|
||||||
```c
|
|
||||||
crc = (~(value ^ (value >> 4) ^ (value >> 8))) & 0x0F;
|
|
||||||
```
|
|
||||||
|
|
||||||
> **Note:** Bidirectional DShot is experimental. Further hardware testing is needed.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🛠️ ESP32 RMT Peripheral
|
|
||||||
|
|
||||||
The RMT (Remote Control) peripheral generates accurate, hardware-timed signals for controlling external devices.
|
|
||||||
Perfect for DShot:
|
|
||||||
- Utilizes latest ESP-IDF APIs
|
|
||||||
- Hardware-timed pulses
|
|
||||||
- CPU-independent
|
|
||||||
- Loop mode with inter-frame pause
|
|
||||||
- Reliable under system load
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 📝 Core API Reference
|
|
||||||
|
|
||||||
### DShotRMT Class
|
|
||||||
- `DShotRMT(gpio_num_t gpio, dshot_mode_t mode, bool isBidirectional)`
|
|
||||||
- `uint16_t begin()`
|
|
||||||
- `bool sendThrottle(uint16_t throttle)`
|
|
||||||
- `bool sendCommand(uint16_t command)`
|
|
||||||
- `uint16_t getERPM()` - Get eRPM (bidirectional mode only)
|
|
||||||
- `uint32_t getMotorRPM(uint8_t magnet_count)` - Convert to motor RPM
|
|
||||||
|
|
||||||
### DShotCommandManager Class
|
|
||||||
- `DShotCommandManager(DShotRMT &dshot_instance)`
|
|
||||||
- `bool begin()`
|
|
||||||
- `dshot_command_result_t sendCommand(dshot_commands_t command, uint16_t repeat_count = 1)`
|
|
||||||
- `dshot_command_result_t sendCommandWithDelay(dshot_commands_t command, uint16_t repeat_count, uint32_t delay_ms)`
|
|
||||||
|
|
||||||
All command methods return a `dshot_command_result_t` structure containing:
|
|
||||||
- `bool success` - Command execution status
|
|
||||||
- `uint32_t execution_time_us` - Execution time in microseconds
|
|
||||||
- `const char* error_message` - Error description
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 🎯 DShot Commands
|
## 🎯 DShot Commands
|
||||||
|
|
||||||
The library supports all standard DShot commands:
|
| Command | Value | Description | Usage |
|
||||||
|
|---------|-------|-------------|-------|
|
||||||
| Command | Value | Description |
|
| MOTOR_STOP | 0 | Stop motor | Always available |
|
||||||
|---------|-------|-------------|
|
| BEACON1 - 5 | 1 - 5 | Motor beeping | Motor identification |
|
||||||
| MOTOR_STOP | 0 | Stop motor |
|
| ESC_INFO | 6 | Request ESC info | Get ESC version/settings |
|
||||||
| BEACON1-5 | 1-5 | Motor beeping |
|
| SPIN_DIRECTION_1/2 | 7 - 8 | Set spin direction | Motor configuration |
|
||||||
| ESC_INFO | 6 | Request ESC information |
|
| 3D_MODE_OFF/ON | 9 - 10 | 3D mode control | Bidirectional flight |
|
||||||
| SPIN_DIRECTION_1/2 | 7-8 | Set spin direction |
|
| SAVE_SETTINGS | 12 | Save to EEPROM | Permanent configuration |
|
||||||
| 3D_MODE_OFF/ON | 9-10 | 3D mode control |
|
| EXTENDED_TELEMETRY_ENABLE/DISABLE | 13 - 14 | Telemetry control | Data transmission |
|
||||||
| SAVE_SETTINGS | 12 | Save settings to ESC |
|
| SPIN_DIRECTION_NORMAL/REVERSED | 20 - 21 | Spin direction | Alias commands |
|
||||||
| EXTENDED_TELEMETRY_ENABLE/DISABLE | 13-14 | Telemetry control |
|
| LED0-3_ON/OFF | 22 - 29 | LED control | BLHeli32 only |
|
||||||
| LED0-3_ON/OFF | 22-29 | LED control (BLHeli32) |
|
| AUDIO_STREAM_MODE | 30 | Audio mode toggle | KISS ESCs |
|
||||||
| AUDIO_STREAM_MODE | 30 | KISS audio mode |
|
| SILENT_MODE | 31 | Silent mode toggle | KISS ESCs |
|
||||||
| SILENT_MODE | 31 | KISS silent mode |
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 📖 References
|
## 📚 DShot Protocol Details
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
### Packet Structure
|
||||||
|
Each DShot frame consists of 16 bits:
|
||||||
|
- **11 bits:** Throttle/command value (0-2047)
|
||||||
|
- **1 bit:** Telemetry request flag
|
||||||
|
- **4 bits:** CRC checksum
|
||||||
|
|
||||||
|
### Checksum Calculation
|
||||||
|
```cpp
|
||||||
|
// Standard DShot CRC
|
||||||
|
uint16_t crc = (data ^ (data >> 4) ^ (data >> 8)) & 0x0F;
|
||||||
|
|
||||||
|
// Bidirectional DShot (inverted CRC)
|
||||||
|
uint16_t crc = (~(data ^ (data >> 4) ^ (data >> 8))) & 0x0F;
|
||||||
|
```
|
||||||
|
|
||||||
|
### Bidirectional DShot
|
||||||
|
- **Inverted Logic:** High/low levels are inverted
|
||||||
|
- **GCR Encoding:** Telemetry uses Group Code Recording
|
||||||
|
- **21-bit Response:** 1 start + 16 data + 4 CRC bits
|
||||||
|
- **eRPM Data:** Electrical RPM transmitted back to controller
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🛠️ ESP32 RMT Peripheral
|
||||||
|
|
||||||
|
The library utilizes the ESP32's RMT (Remote Control) peripheral for precise signal generation:
|
||||||
|
|
||||||
|
### Advantages
|
||||||
|
- **Hardware Timing:** No CPU intervention during transmission
|
||||||
|
- **Concurrent Operation:** Multiple channels can run simultaneously
|
||||||
|
- **DMA Support:** Efficient memory-to-peripheral transfers
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📖 References & Documentation
|
||||||
|
|
||||||
|
### DShot Protocol
|
||||||
- [DSHOT – the missing Handbook](https://brushlesswhoop.com/dshot-and-bidirectional-dshot/)
|
- [DSHOT – the missing Handbook](https://brushlesswhoop.com/dshot-and-bidirectional-dshot/)
|
||||||
- [DSHOT in the Dark](https://dmrlawson.co.uk/index.php/2017/12/04/dshot-in-the-dark/)
|
- [DSHOT in the Dark](https://dmrlawson.co.uk/index.php/2017/12/04/dshot-in-the-dark/)
|
||||||
|
- [Betaflight DShot Implementation](https://github.com/betaflight/betaflight)
|
||||||
|
|
||||||
|
### ESP32 Documentation
|
||||||
- [ESP32 Technical Reference Manual](https://www.espressif.com/sites/default/files/documentation/esp32_technical_reference_manual_en.pdf)
|
- [ESP32 Technical Reference Manual](https://www.espressif.com/sites/default/files/documentation/esp32_technical_reference_manual_en.pdf)
|
||||||
|
- [ESP-IDF RMT Driver](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/peripherals/rmt.html)
|
||||||
|
- [Arduino ESP32 Core](https://github.com/espressif/arduino-esp32)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🤝 Contributing
|
||||||
|
|
||||||
|
We welcome contributions! Please:
|
||||||
|
|
||||||
|
1. Fork the repository
|
||||||
|
2. Create a feature branch
|
||||||
|
3. Make your changes with tests
|
||||||
|
4. Submit a pull request
|
||||||
|
|
||||||
|
### Development Guidelines
|
||||||
|
- Follow existing code style
|
||||||
|
- Add documentation for new features
|
||||||
|
- Include examples where appropriate
|
||||||
|
- Test with real hardware when possible
|
||||||
|
|
||||||
|
### Reporting Issues
|
||||||
|
When reporting issues, please include:
|
||||||
|
- ESP32 board type and version
|
||||||
|
- Arduino/ESP-IDF version
|
||||||
|
- ESC type and firmware
|
||||||
|
- Complete error messages
|
||||||
|
- Minimal reproduction code
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
@ -261,5 +269,5 @@ MIT License – see [LICENSE](LICENSE)
|
||||||
## 👤 Author
|
## 👤 Author
|
||||||
|
|
||||||
**Wastl Kraus**
|
**Wastl Kraus**
|
||||||
GitHub: [@derdoktor667](https://github.com/derdoktor667)
|
- GitHub: [@derdoktor667](https://github.com/derdoktor667)
|
||||||
Website: [wir-sind-die-matrix.de](https://wir-sind-die-matrix.de)
|
- Website: [wir-sind-die-matrix.de](https://wir-sind-die-matrix.de)
|
||||||
|
|
|
||||||
|
|
@ -37,7 +37,7 @@ void printTelemetryResult(const dshot_telemetry_result_t &result)
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
USB_SERIAL.printf("Telemetry: FAILED - %s\n", result.error_message);
|
USB_SERIAL.printf("Telemetry: FAILED - %s\n", result.msg);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -270,7 +270,7 @@ void printResult(const dshot_result_t &result)
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
USB_SERIAL.printf("FAILED - %s \n", result.error_message);
|
USB_SERIAL.printf("FAILED - %s \n", result.msg);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -53,7 +53,7 @@ void loop()
|
||||||
static bool continuous_throttle = true;
|
static bool continuous_throttle = true;
|
||||||
|
|
||||||
// Time Measurement
|
// Time Measurement
|
||||||
static uint32_t last_stats_print = 0;
|
static uint64_t last_stats_print = 0;
|
||||||
|
|
||||||
// Handle serial input
|
// Handle serial input
|
||||||
if (USB_SERIAL.available() > 0)
|
if (USB_SERIAL.available() > 0)
|
||||||
|
|
@ -73,20 +73,24 @@ void loop()
|
||||||
motor01.sendThrottle(throttle);
|
motor01.sendThrottle(throttle);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Print motor stats every 5 seconds in continuous mode
|
// Print motor stats every 3 seconds in continuous mode
|
||||||
if (continuous_throttle && (millis() - last_stats_print >= 5000))
|
if (continuous_throttle && (esp_timer_get_time() - last_stats_print >= 3000000))
|
||||||
{
|
{
|
||||||
motor01.printDShotInfo();
|
motor01.printDShotInfo();
|
||||||
|
|
||||||
|
USB_SERIAL.println(" ");
|
||||||
|
|
||||||
// Get Motor RPM if bidirectional
|
// Get Motor RPM if bidirectional
|
||||||
if (IS_BIDIRECTIONAL)
|
if (IS_BIDIRECTIONAL)
|
||||||
{
|
{
|
||||||
dshot_telemetry_result_t telem_result = motor01.getTelemetry(MOTOR01_MAGNET_COUNT);
|
dshot_telemetry_result_t telem_result = motor01.getTelemetry(MOTOR01_MAGNET_COUNT);
|
||||||
printTelemetryResult(telem_result);
|
printDShotTelemetry(telem_result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
USB_SERIAL.println("Type 'help' to show Menu");
|
||||||
|
|
||||||
// Time Stamp
|
// Time Stamp
|
||||||
last_stats_print = millis();
|
last_stats_print = esp_timer_get_time();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -94,47 +98,21 @@ void loop()
|
||||||
void printMenu()
|
void printMenu()
|
||||||
{
|
{
|
||||||
USB_SERIAL.println(" ");
|
USB_SERIAL.println(" ");
|
||||||
USB_SERIAL.println("******************************************");
|
USB_SERIAL.println("*******************************************");
|
||||||
USB_SERIAL.println(" DShotRMT Demo ");
|
USB_SERIAL.println(" DShotRMT Demo ");
|
||||||
USB_SERIAL.println("******************************************");
|
USB_SERIAL.println("*******************************************");
|
||||||
USB_SERIAL.println(" <value> - Set throttle (48 – 2047)");
|
USB_SERIAL.println(" <value> - Set throttle (48 – 2047)");
|
||||||
USB_SERIAL.println(" 0 - Stop motor");
|
USB_SERIAL.println(" 0 - Stop motor");
|
||||||
USB_SERIAL.println("******************************************");
|
USB_SERIAL.println("*******************************************");
|
||||||
USB_SERIAL.println(" cmd <number> - Send DShot command (0-47)");
|
USB_SERIAL.println(" cmd <number> - Send DShot command (0 - 47)");
|
||||||
USB_SERIAL.println(" info - Show motor info");
|
USB_SERIAL.println(" info - Show motor info");
|
||||||
if (IS_BIDIRECTIONAL)
|
if (IS_BIDIRECTIONAL)
|
||||||
{
|
{
|
||||||
USB_SERIAL.println(" rpm - Get telemetry data");
|
USB_SERIAL.println(" rpm - Get telemetry data");
|
||||||
}
|
}
|
||||||
USB_SERIAL.println("******************************************");
|
USB_SERIAL.println("*******************************************");
|
||||||
USB_SERIAL.println(" h / help - Show this Menu");
|
USB_SERIAL.println(" h / help - Show this Menu");
|
||||||
USB_SERIAL.println("******************************************");
|
USB_SERIAL.println("*******************************************");
|
||||||
}
|
|
||||||
|
|
||||||
// Helper to print command results
|
|
||||||
void printCommandResult(const dshot_result_t &result, const String &operation)
|
|
||||||
{
|
|
||||||
if (result.success)
|
|
||||||
{
|
|
||||||
USB_SERIAL.printf("%s: SUCCESS\n", operation.c_str());
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
USB_SERIAL.printf("%s: FAILED - %s\n", operation.c_str(), result.error_message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Helper to print telemetry results
|
|
||||||
void printTelemetryResult(const dshot_telemetry_result_t &result)
|
|
||||||
{
|
|
||||||
if (result.success)
|
|
||||||
{
|
|
||||||
USB_SERIAL.printf("Telemetry: eRPM=%u, Motor RPM=%u (%s)\n", result.erpm, result.motor_rpm, result.error_message);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
USB_SERIAL.printf("Telemetry: FAILED - %s\n", result.error_message);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
|
|
@ -144,19 +122,18 @@ void handleSerialInput(const String &input, uint16_t &throttle, bool &continuous
|
||||||
{
|
{
|
||||||
// Stop motor
|
// Stop motor
|
||||||
throttle = 0;
|
throttle = 0;
|
||||||
continuous_throttle = true; // kill motor for sure
|
continuous_throttle = true;
|
||||||
dshot_result_t result = motor01.sendCommand(DSHOT_CMD_MOTOR_STOP);
|
dshot_result_t result = motor01.sendCommand(DSHOT_CMD_MOTOR_STOP);
|
||||||
printCommandResult(result, "Stop Motor");
|
printDShotResult(result);
|
||||||
}
|
}
|
||||||
else if (input == "info")
|
else if (input == "info")
|
||||||
{
|
{
|
||||||
continuous_throttle = false;
|
|
||||||
motor01.printDShotInfo();
|
motor01.printDShotInfo();
|
||||||
}
|
}
|
||||||
else if (input == "rpm" && IS_BIDIRECTIONAL)
|
else if (input == "rpm" && IS_BIDIRECTIONAL)
|
||||||
{
|
{
|
||||||
dshot_telemetry_result_t result = motor01.getTelemetry(MOTOR01_MAGNET_COUNT);
|
dshot_telemetry_result_t result = motor01.getTelemetry(MOTOR01_MAGNET_COUNT);
|
||||||
printTelemetryResult(result);
|
printDShotTelemetry(result);
|
||||||
}
|
}
|
||||||
else if (input.startsWith("cmd "))
|
else if (input.startsWith("cmd "))
|
||||||
{
|
{
|
||||||
|
|
@ -168,7 +145,7 @@ void handleSerialInput(const String &input, uint16_t &throttle, bool &continuous
|
||||||
if (cmd_num >= DSHOT_CMD_MOTOR_STOP && cmd_num <= DSHOT_CMD_MAX)
|
if (cmd_num >= DSHOT_CMD_MOTOR_STOP && cmd_num <= DSHOT_CMD_MAX)
|
||||||
{
|
{
|
||||||
dshot_result_t result = motor01.sendCommand(cmd_num);
|
dshot_result_t result = motor01.sendCommand(cmd_num);
|
||||||
printCommandResult(result, "DShot Command " + String(cmd_num));
|
printDShotResult(result);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|
@ -190,13 +167,12 @@ void handleSerialInput(const String &input, uint16_t &throttle, bool &continuous
|
||||||
continuous_throttle = true;
|
continuous_throttle = true;
|
||||||
|
|
||||||
dshot_result_t result = motor01.sendThrottle(throttle);
|
dshot_result_t result = motor01.sendThrottle(throttle);
|
||||||
printCommandResult(result, "Set Throttle " + String(throttle));
|
printDShotResult(result);
|
||||||
|
|
||||||
USB_SERIAL.println("Continuous throttle mode enabled. Send '0' to stop.");
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
USB_SERIAL.printf("Invalid input: '%s'\n", input.c_str());
|
USB_SERIAL.println(" ");
|
||||||
|
USB_SERIAL.printf("Invalid input: '%s'\n", input);
|
||||||
USB_SERIAL.printf("Valid throttle range: %d - %d\n", DSHOT_THROTTLE_MIN, DSHOT_THROTTLE_MAX);
|
USB_SERIAL.printf("Valid throttle range: %d - %d\n", DSHOT_THROTTLE_MIN, DSHOT_THROTTLE_MAX);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
name=DShotRMT
|
name=DShotRMT
|
||||||
version=0.7.1
|
version=0.7.2
|
||||||
author=derdoktor667
|
author=derdoktor667
|
||||||
maintainer=derdoktor667
|
maintainer=derdoktor667
|
||||||
sentence=DShotRMT Library supporting all DShot Types and speeds. Tested with BlHeli_S.
|
sentence=DShotRMT Library supporting all DShot Types and speeds. Tested with BlHeli_S.
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue