...release 0.7.0
...update TX logic ...rewrite GCR decoding ...type fix ...add CommandManager ...preparing release
This commit is contained in:
parent
5eaf066dab
commit
252209dd1b
|
|
@ -11,7 +11,6 @@ concurrency:
|
|||
cancel-in-progress: true
|
||||
|
||||
env:
|
||||
ARDUINO_CLI_VERSION: '1.3.0'
|
||||
ESP32_CORE_VERSION: '3.3.0'
|
||||
|
||||
jobs:
|
||||
|
|
@ -29,8 +28,6 @@ jobs:
|
|||
|
||||
- name: Setup Arduino CLI
|
||||
uses: arduino/setup-arduino-cli@v2
|
||||
with:
|
||||
version: ${{ env.ARDUINO_CLI_VERSION }}
|
||||
|
||||
- name: Cache Arduino data
|
||||
uses: actions/cache@v4
|
||||
|
|
@ -66,6 +63,7 @@ jobs:
|
|||
matrix:
|
||||
example:
|
||||
- "examples/dshot300/dshot300.ino"
|
||||
- "examples/command_manager/command_manager.ino"
|
||||
build-flags:
|
||||
- name: "Release"
|
||||
flags: "Automated Build"
|
||||
|
|
@ -76,8 +74,6 @@ jobs:
|
|||
|
||||
- name: Setup Arduino CLI
|
||||
uses: arduino/setup-arduino-cli@v2
|
||||
with:
|
||||
version: ${{ env.ARDUINO_CLI_VERSION }}
|
||||
|
||||
- name: Cache Arduino data
|
||||
uses: actions/cache@v4
|
||||
|
|
@ -116,8 +112,6 @@ jobs:
|
|||
|
||||
- name: Setup Arduino CLI
|
||||
uses: arduino/setup-arduino-cli@v2
|
||||
with:
|
||||
version: ${{ env.ARDUINO_CLI_VERSION }}
|
||||
|
||||
- name: Cache Arduino data
|
||||
uses: actions/cache@v4
|
||||
|
|
@ -143,7 +137,8 @@ jobs:
|
|||
--language=c++ \
|
||||
--platform=unix32 \
|
||||
--inline-suppr \
|
||||
./DShotRMT.cpp ./DShotRMT.h
|
||||
./DShotRMT.cpp ./DShotRMT.h \
|
||||
./DShotCommandManager.cpp ./DShotCommandManager.h
|
||||
|
||||
# ============================================================================
|
||||
# Build Status Report
|
||||
|
|
|
|||
|
|
@ -0,0 +1,393 @@
|
|||
/*
|
||||
* DShotCommandManager.cpp
|
||||
* Advanced DShot command management for DShotRMT library
|
||||
* Author: Wastl Kraus
|
||||
* Date: 2025-09-04
|
||||
* License: MIT
|
||||
*/
|
||||
|
||||
#include <DShotCommandManager.h>
|
||||
|
||||
// Constructor
|
||||
DShotCommandManager::DShotCommandManager(DShotRMT &dshot_instance)
|
||||
: _dshot(dshot_instance),
|
||||
_total_commands_sent(0),
|
||||
_failed_commands(0),
|
||||
_last_execution_time_us(0),
|
||||
_last_command_timestamp(0)
|
||||
{
|
||||
}
|
||||
|
||||
// Init command manager
|
||||
bool DShotCommandManager::begin()
|
||||
{
|
||||
resetStatistics();
|
||||
return true;
|
||||
}
|
||||
|
||||
// --- BASIC COMMAND METHODS ---
|
||||
dshot_command_result_t DShotCommandManager::sendCommand(dshot_commands_t command, uint16_t repeat_count)
|
||||
{
|
||||
return sendCommandWithDelay(command, repeat_count, DEFAULT_COMMAND_DELAY_MS);
|
||||
}
|
||||
|
||||
//
|
||||
dshot_command_result_t DShotCommandManager::sendCommandWithDelay(dshot_commands_t command, uint16_t repeat_count, uint32_t delay_ms)
|
||||
{
|
||||
dshot_command_result_t result = {false, 0, "Unknown error"};
|
||||
|
||||
if (!isValidCommand(command))
|
||||
{
|
||||
result.error_message = "Invalid command";
|
||||
_updateStatistics(false, 0);
|
||||
return result;
|
||||
}
|
||||
|
||||
uint64_t start_time = esp_timer_get_time();
|
||||
bool all_successful = true;
|
||||
|
||||
// Send command multiple times with delay
|
||||
for (uint16_t i = 0; i < repeat_count; i++)
|
||||
{
|
||||
dshot_command_result_t single_result = _executeCommand(command);
|
||||
|
||||
if (!single_result.success)
|
||||
{
|
||||
all_successful = false;
|
||||
result.error_message = single_result.error_message;
|
||||
break;
|
||||
}
|
||||
|
||||
// Add delay between repetitions (except for last repetition)
|
||||
if (i < repeat_count - 1)
|
||||
{
|
||||
_delay(delay_ms);
|
||||
}
|
||||
}
|
||||
|
||||
uint64_t end_time = esp_timer_get_time();
|
||||
result.execution_time_us = (uint32_t)(end_time - start_time);
|
||||
result.success = all_successful;
|
||||
|
||||
if (result.success)
|
||||
{
|
||||
result.error_message = "Success";
|
||||
}
|
||||
|
||||
_updateStatistics(result.success, result.execution_time_us);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// --- MOTOR CONTROL COMMANDS ---
|
||||
dshot_command_result_t DShotCommandManager::stopMotor()
|
||||
{
|
||||
return sendCommand(DSHOT_CMD_MOTOR_STOP);
|
||||
}
|
||||
|
||||
//
|
||||
dshot_command_result_t DShotCommandManager::set3DMode(bool enable)
|
||||
{
|
||||
dshot_commands_t command = enable ? DSHOT_CMD_3D_MODE_ON : DSHOT_CMD_3D_MODE_OFF;
|
||||
return sendCommandWithDelay(command, SETTINGS_COMMAND_REPEATS, SETTINGS_COMMAND_DELAY_MS);
|
||||
}
|
||||
|
||||
//
|
||||
dshot_command_result_t DShotCommandManager::setSpinDirection(bool reversed)
|
||||
{
|
||||
dshot_commands_t command = reversed ? DSHOT_CMD_SPIN_DIRECTION_REVERSED : DSHOT_CMD_SPIN_DIRECTION_NORMAL;
|
||||
return sendCommandWithDelay(command, SETTINGS_COMMAND_REPEATS, SETTINGS_COMMAND_DELAY_MS);
|
||||
}
|
||||
|
||||
//
|
||||
dshot_command_result_t DShotCommandManager::saveSettings()
|
||||
{
|
||||
return sendCommandWithDelay(DSHOT_CMD_SAVE_SETTINGS, SETTINGS_COMMAND_REPEATS, SETTINGS_COMMAND_DELAY_MS);
|
||||
}
|
||||
|
||||
// --- TELEMETRY COMMANDS ---
|
||||
dshot_command_result_t DShotCommandManager::setExtendedTelemetry(bool enable)
|
||||
{
|
||||
dshot_commands_t command = enable ? DSHOT_CMD_EXTENDED_TELEMETRY_ENABLE : DSHOT_CMD_EXTENDED_TELEMETRY_DISABLE;
|
||||
return sendCommand(command);
|
||||
}
|
||||
|
||||
//
|
||||
dshot_command_result_t DShotCommandManager::requestESCInfo()
|
||||
{
|
||||
return sendCommand(DSHOT_CMD_ESC_INFO);
|
||||
}
|
||||
|
||||
// --- LED CONTROL COMMANDS ---
|
||||
dshot_command_result_t DShotCommandManager::setLED(uint8_t led_number, bool state)
|
||||
{
|
||||
if (led_number > 3)
|
||||
{
|
||||
dshot_command_result_t result = {false, 0, "Invalid LED number (0-3)"};
|
||||
_updateStatistics(false, 0);
|
||||
return result;
|
||||
}
|
||||
|
||||
dshot_commands_t command;
|
||||
if (state)
|
||||
{
|
||||
// LED ON commands
|
||||
switch (led_number)
|
||||
{
|
||||
case 0:
|
||||
command = DSHOT_CMD_LED0_ON;
|
||||
break;
|
||||
case 1:
|
||||
command = DSHOT_CMD_LED1_ON;
|
||||
break;
|
||||
case 2:
|
||||
command = DSHOT_CMD_LED2_ON;
|
||||
break;
|
||||
case 3:
|
||||
command = DSHOT_CMD_LED3_ON;
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// LED OFF commands
|
||||
switch (led_number)
|
||||
{
|
||||
case 0:
|
||||
command = DSHOT_CMD_LED0_OFF;
|
||||
break;
|
||||
case 1:
|
||||
command = DSHOT_CMD_LED1_OFF;
|
||||
break;
|
||||
case 2:
|
||||
command = DSHOT_CMD_LED2_OFF;
|
||||
break;
|
||||
case 3:
|
||||
command = DSHOT_CMD_LED3_OFF;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return sendCommand(command);
|
||||
}
|
||||
|
||||
// --- BEACON COMMANDS ---
|
||||
dshot_command_result_t DShotCommandManager::activateBeacon(uint8_t beacon_number)
|
||||
{
|
||||
if (beacon_number < 1 || beacon_number > 5)
|
||||
{
|
||||
dshot_command_result_t result = {false, 0, "Invalid beacon number (1-5)"};
|
||||
_updateStatistics(false, 0);
|
||||
return result;
|
||||
}
|
||||
|
||||
dshot_commands_t command = static_cast<dshot_commands_t>(DSHOT_CMD_BEACON1 + beacon_number - 1);
|
||||
return sendCommand(command);
|
||||
}
|
||||
|
||||
// --- KISS ESC SPECIFIC COMMANDS ---
|
||||
dshot_command_result_t DShotCommandManager::setAudioStreamMode(bool enable)
|
||||
{
|
||||
// KISS audio stream mode is a toggle command
|
||||
return sendCommand(DSHOT_CMD_AUDIO_STREAM_MODE_ON_OFF);
|
||||
}
|
||||
|
||||
//
|
||||
dshot_command_result_t DShotCommandManager::setSilentMode(bool enable)
|
||||
{
|
||||
// KISS silent mode is a toggle command
|
||||
return sendCommand(DSHOT_CMD_SILENT_MODE_ON_OFF);
|
||||
}
|
||||
|
||||
// --- SEQUENCE COMMANDS ---
|
||||
dshot_command_result_t DShotCommandManager::executeSequence(const dshot_command_sequence_item_t *sequence, size_t sequence_length)
|
||||
{
|
||||
dshot_command_result_t result = {true, 0, "Success"};
|
||||
uint64_t total_start_time = esp_timer_get_time();
|
||||
|
||||
for (size_t i = 0; i < sequence_length; i++)
|
||||
{
|
||||
dshot_command_result_t item_result = sendCommandWithDelay(
|
||||
sequence[i].command,
|
||||
sequence[i].repeat_count,
|
||||
DEFAULT_COMMAND_DELAY_MS);
|
||||
|
||||
if (!item_result.success)
|
||||
{
|
||||
result.success = false;
|
||||
result.error_message = item_result.error_message;
|
||||
break;
|
||||
}
|
||||
|
||||
// Add delay after command if specified
|
||||
if (sequence[i].delay_ms > 0)
|
||||
{
|
||||
_delay(sequence[i].delay_ms);
|
||||
}
|
||||
}
|
||||
|
||||
uint64_t total_end_time = esp_timer_get_time();
|
||||
result.execution_time_us = (uint32_t)(total_end_time - total_start_time);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
//
|
||||
dshot_command_result_t DShotCommandManager::executeInitSequence()
|
||||
{
|
||||
// Basic ESC initialization sequence
|
||||
dshot_command_sequence_item_t init_sequence[] = {
|
||||
{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_ESC_INFO, 1, 100} // Request ESC info, wait 100ms
|
||||
};
|
||||
|
||||
return executeSequence(init_sequence, sizeof(init_sequence) / sizeof(init_sequence[0]));
|
||||
}
|
||||
|
||||
//
|
||||
dshot_command_result_t DShotCommandManager::executeCalibrationSequence()
|
||||
{
|
||||
// Basic ESC calibration sequence
|
||||
dshot_command_sequence_item_t calibration_sequence[] = {
|
||||
{DSHOT_CMD_MOTOR_STOP, 10, 500}, // Ensure motor is stopped
|
||||
{DSHOT_CMD_SPIN_DIRECTION_NORMAL, 10, 100}, // Set normal spin direction
|
||||
{DSHOT_CMD_3D_MODE_OFF, 10, 100}, // Disable 3D mode
|
||||
{DSHOT_CMD_SAVE_SETTINGS, 10, 1000}, // Save settings
|
||||
{DSHOT_CMD_MOTOR_STOP, 5, 100} // Final stop
|
||||
};
|
||||
|
||||
return executeSequence(calibration_sequence, sizeof(calibration_sequence) / sizeof(calibration_sequence[0]));
|
||||
}
|
||||
|
||||
// --- UTILITY METHODS ---
|
||||
const char *DShotCommandManager::getCommandName(dshot_commands_t command)
|
||||
{
|
||||
switch (command)
|
||||
{
|
||||
case DSHOT_CMD_MOTOR_STOP:
|
||||
return "MOTOR_STOP";
|
||||
case DSHOT_CMD_BEACON1:
|
||||
return "BEACON1";
|
||||
case DSHOT_CMD_BEACON2:
|
||||
return "BEACON2";
|
||||
case DSHOT_CMD_BEACON3:
|
||||
return "BEACON3";
|
||||
case DSHOT_CMD_BEACON4:
|
||||
return "BEACON4";
|
||||
case DSHOT_CMD_BEACON5:
|
||||
return "BEACON5";
|
||||
case DSHOT_CMD_ESC_INFO:
|
||||
return "ESC_INFO";
|
||||
case DSHOT_CMD_SPIN_DIRECTION_1:
|
||||
return "SPIN_DIRECTION_1";
|
||||
case DSHOT_CMD_SPIN_DIRECTION_2:
|
||||
return "SPIN_DIRECTION_2";
|
||||
case DSHOT_CMD_3D_MODE_OFF:
|
||||
return "3D_MODE_OFF";
|
||||
case DSHOT_CMD_3D_MODE_ON:
|
||||
return "3D_MODE_ON";
|
||||
case DSHOT_CMD_SETTINGS_REQUEST:
|
||||
return "SETTINGS_REQUEST";
|
||||
case DSHOT_CMD_SAVE_SETTINGS:
|
||||
return "SAVE_SETTINGS";
|
||||
case DSHOT_CMD_EXTENDED_TELEMETRY_ENABLE:
|
||||
return "EXTENDED_TELEMETRY_ENABLE";
|
||||
case DSHOT_CMD_EXTENDED_TELEMETRY_DISABLE:
|
||||
return "EXTENDED_TELEMETRY_DISABLE";
|
||||
case DSHOT_CMD_SPIN_DIRECTION_NORMAL:
|
||||
return "SPIN_DIRECTION_NORMAL";
|
||||
case DSHOT_CMD_SPIN_DIRECTION_REVERSED:
|
||||
return "SPIN_DIRECTION_REVERSED";
|
||||
case DSHOT_CMD_LED0_ON:
|
||||
return "LED0_ON";
|
||||
case DSHOT_CMD_LED1_ON:
|
||||
return "LED1_ON";
|
||||
case DSHOT_CMD_LED2_ON:
|
||||
return "LED2_ON";
|
||||
case DSHOT_CMD_LED3_ON:
|
||||
return "LED3_ON";
|
||||
case DSHOT_CMD_LED0_OFF:
|
||||
return "LED0_OFF";
|
||||
case DSHOT_CMD_LED1_OFF:
|
||||
return "LED1_OFF";
|
||||
case DSHOT_CMD_LED2_OFF:
|
||||
return "LED2_OFF";
|
||||
case DSHOT_CMD_LED3_OFF:
|
||||
return "LED3_OFF";
|
||||
case DSHOT_CMD_AUDIO_STREAM_MODE_ON_OFF:
|
||||
return "AUDIO_STREAM_MODE_ON_OFF";
|
||||
case DSHOT_CMD_SILENT_MODE_ON_OFF:
|
||||
return "SILENT_MODE_ON_OFF";
|
||||
default:
|
||||
return "UNKNOWN";
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
bool DShotCommandManager::isValidCommand(dshot_commands_t command)
|
||||
{
|
||||
return (command >= DSHOT_CMD_MOTOR_STOP && command <= DSHOT_CMD_MAX);
|
||||
}
|
||||
|
||||
//
|
||||
void DShotCommandManager::printStatistics(Stream &output) const
|
||||
{
|
||||
output.println("\n--- DShot Command Manager Statistics ---");
|
||||
output.printf("Total commands sent: %u\n", _total_commands_sent);
|
||||
output.printf("Failed commands: %u\n", _failed_commands);
|
||||
output.printf("Success rate: %.2f%%\n",
|
||||
_total_commands_sent > 0 ? (float)(_total_commands_sent - _failed_commands) / _total_commands_sent * 100.0f : 0.0f);
|
||||
output.printf("Last execution time: %u us\n", _last_execution_time_us);
|
||||
output.printf("Last command timestamp: %llu us\n", _last_command_timestamp);
|
||||
}
|
||||
|
||||
//
|
||||
void DShotCommandManager::resetStatistics()
|
||||
{
|
||||
_total_commands_sent = 0;
|
||||
_failed_commands = 0;
|
||||
_last_execution_time_us = 0;
|
||||
_last_command_timestamp = 0;
|
||||
}
|
||||
|
||||
// --- PRIVATE METHODS ---
|
||||
dshot_command_result_t DShotCommandManager::_executeCommand(dshot_commands_t command)
|
||||
{
|
||||
dshot_command_result_t result = {false, 0, "Execution failed"};
|
||||
|
||||
uint64_t start_time = esp_timer_get_time();
|
||||
|
||||
// Execute the command using the DShotRMT instance
|
||||
bool success = _dshot.sendCommand(static_cast<uint16_t>(command));
|
||||
|
||||
uint64_t end_time = esp_timer_get_time();
|
||||
|
||||
result.success = success;
|
||||
result.execution_time_us = (uint32_t)(end_time - start_time);
|
||||
result.error_message = success ? "Success" : "Command transmission failed";
|
||||
|
||||
_last_command_timestamp = end_time;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
//
|
||||
void DShotCommandManager::_delay(uint32_t delay_ms)
|
||||
{
|
||||
if (delay_ms > 0)
|
||||
{
|
||||
delay(delay_ms);
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
void DShotCommandManager::_updateStatistics(bool success, uint32_t execution_time_us)
|
||||
{
|
||||
_total_commands_sent++;
|
||||
if (!success)
|
||||
{
|
||||
_failed_commands++;
|
||||
}
|
||||
_last_execution_time_us = execution_time_us;
|
||||
}
|
||||
|
|
@ -0,0 +1,146 @@
|
|||
/*
|
||||
* DShotCommandManager.h
|
||||
* Advanced DShot command management for DShotRMT library
|
||||
* Author: Wastl Kraus
|
||||
* Date: 2025-09-04
|
||||
* License: MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <DShotRMT.h>
|
||||
#include <dshot_commands.h>
|
||||
|
||||
// Naming convention
|
||||
typedef dshotCommands_e dshot_commands_t;
|
||||
|
||||
// Command execution result structure
|
||||
typedef struct
|
||||
{
|
||||
bool success;
|
||||
uint32_t execution_time_us;
|
||||
const char *error_message;
|
||||
} dshot_command_result_t;
|
||||
|
||||
// Command sequence item
|
||||
typedef struct
|
||||
{
|
||||
dshot_commands_t command;
|
||||
uint16_t repeat_count;
|
||||
uint32_t delay_ms;
|
||||
} dshot_command_sequence_item_t;
|
||||
|
||||
// Advanced DShot command manager class
|
||||
class DShotCommandManager
|
||||
{
|
||||
public:
|
||||
// Constructor
|
||||
explicit DShotCommandManager(DShotRMT &dshot_instance);
|
||||
|
||||
// Initialize command manager
|
||||
bool begin();
|
||||
|
||||
bool printMenu(Stream &output);
|
||||
|
||||
void handleMenuInput(const String &input, Stream &output = Serial);
|
||||
|
||||
// Send a single DShot command
|
||||
dshot_command_result_t sendCommand(dshot_commands_t command, uint16_t repeat_count = 1);
|
||||
|
||||
// Send command with specified delay between repetitions
|
||||
dshot_command_result_t sendCommandWithDelay(dshot_commands_t command, uint16_t repeat_count, uint32_t delay_ms);
|
||||
|
||||
// --- MOTOR CONTROL COMMANDS ---
|
||||
// Stop motor (send MOTOR_STOP command)
|
||||
dshot_command_result_t stopMotor();
|
||||
|
||||
// Enable/disable 3D mode
|
||||
dshot_command_result_t set3DMode(bool enable);
|
||||
|
||||
// Set motor spin direction
|
||||
dshot_command_result_t setSpinDirection(bool reversed);
|
||||
|
||||
// Save current settings to ESC
|
||||
dshot_command_result_t saveSettings();
|
||||
|
||||
// --- TELEMETRY COMMANDS ---
|
||||
// Enable/disable extended telemetry
|
||||
dshot_command_result_t setExtendedTelemetry(bool enable);
|
||||
|
||||
// Request ESC information
|
||||
dshot_command_result_t requestESCInfo();
|
||||
|
||||
// --- LED CONTROL COMMANDS (BLHeli32 only) ---
|
||||
|
||||
// Control ESC LEDs (BLHeli32 only)
|
||||
dshot_command_result_t setLED(uint8_t led_number, bool state);
|
||||
|
||||
// --- BEACON COMMANDS ---
|
||||
// Activate beacon (motor beeping)
|
||||
dshot_command_result_t activateBeacon(uint8_t beacon_number);
|
||||
|
||||
// --- KISS ESC SPECIFIC COMMANDS ---
|
||||
// Enable/disable audio stream mode (KISS ESCs)
|
||||
dshot_command_result_t setAudioStreamMode(bool enable);
|
||||
|
||||
// Enable/disable silent mode (KISS ESCs)
|
||||
dshot_command_result_t setSilentMode(bool enable);
|
||||
|
||||
// --- SEQUENCE COMMANDS ---
|
||||
// Execute a sequence of DShot commands
|
||||
dshot_command_result_t executeSequence(const dshot_command_sequence_item_t *sequence, size_t sequence_length);
|
||||
|
||||
// Execute ESC initialization sequence
|
||||
dshot_command_result_t executeInitSequence();
|
||||
|
||||
// Execute ESC calibration sequence
|
||||
dshot_command_result_t executeCalibrationSequence();
|
||||
|
||||
// --- UTILITY METHODS ---
|
||||
// Get command name as string
|
||||
static const char *getCommandName(dshot_commands_t command);
|
||||
|
||||
// Check if command is valid
|
||||
static bool isValidCommand(dshot_commands_t command);
|
||||
|
||||
// Print command execution statistics
|
||||
void printStatistics(Stream &output = Serial) const;
|
||||
|
||||
// Reset execution statistics
|
||||
void resetStatistics();
|
||||
|
||||
// --- GETTERS ---
|
||||
// Get total number of commands sent
|
||||
uint32_t getTotalCommandCount() const { return _total_commands_sent; }
|
||||
|
||||
// Get number of failed commands
|
||||
uint32_t getFailedCommandCount() const { return _failed_commands; }
|
||||
|
||||
// Get last command execution time
|
||||
uint32_t getLastExecutionTime() const { return _last_execution_time_us; }
|
||||
|
||||
private:
|
||||
// --- PRIVATE MEMBERS ---
|
||||
DShotRMT &_dshot; // Reference to DShotRMT instance
|
||||
uint32_t _total_commands_sent; // Total commands sent counter
|
||||
uint32_t _failed_commands; // Failed commands counter
|
||||
uint32_t _last_execution_time_us; // Last command execution time
|
||||
uint64_t _last_command_timestamp; // Timestamp of last command
|
||||
|
||||
// --- PRIVATE METHODS ---
|
||||
// Execute single command with timing
|
||||
dshot_command_result_t _executeCommand(dshot_commands_t command);
|
||||
|
||||
// Wait for specified delay
|
||||
void _delay(uint32_t delay_ms);
|
||||
|
||||
// Update execution statistics
|
||||
void _updateStatistics(bool success, uint32_t execution_time_us);
|
||||
|
||||
// --- CONSTANTS ---
|
||||
static constexpr uint32_t DEFAULT_COMMAND_DELAY_MS = 10;
|
||||
static constexpr uint16_t DEFAULT_REPEAT_COUNT = 1;
|
||||
static constexpr uint16_t SETTINGS_COMMAND_REPEATS = 10; // Settings commands need 10 repeats
|
||||
static constexpr uint32_t SETTINGS_COMMAND_DELAY_MS = 5;
|
||||
};
|
||||
105
DShotRMT.cpp
105
DShotRMT.cpp
|
|
@ -63,6 +63,7 @@ DShotRMT::~DShotRMT()
|
|||
{
|
||||
rmt_disable(_rmt_tx_channel);
|
||||
rmt_del_channel(_rmt_tx_channel);
|
||||
_rmt_tx_channel = nullptr;
|
||||
}
|
||||
|
||||
//
|
||||
|
|
@ -70,32 +71,28 @@ DShotRMT::~DShotRMT()
|
|||
{
|
||||
rmt_disable(_rmt_rx_channel);
|
||||
rmt_del_channel(_rmt_rx_channel);
|
||||
_rmt_rx_channel = nullptr;
|
||||
}
|
||||
|
||||
//
|
||||
if (_dshot_encoder)
|
||||
{
|
||||
rmt_del_encoder(_dshot_encoder);
|
||||
_dshot_encoder = nullptr;
|
||||
}
|
||||
|
||||
//
|
||||
if (_rx_queue)
|
||||
{
|
||||
vQueueDelete(_rx_queue);
|
||||
_rx_queue = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize DShotRMT
|
||||
uint16_t DShotRMT::begin()
|
||||
{
|
||||
// Init TX channel
|
||||
if (!_initTXChannel())
|
||||
{
|
||||
_dshot_log(TX_INIT_FAILED);
|
||||
return DSHOT_ERROR;
|
||||
}
|
||||
|
||||
// Init RX channel
|
||||
// Init RX channel first
|
||||
if (_is_bidirectional)
|
||||
{
|
||||
if (!_initRXChannel())
|
||||
|
|
@ -105,6 +102,13 @@ uint16_t DShotRMT::begin()
|
|||
}
|
||||
}
|
||||
|
||||
// Init TX channel
|
||||
if (!_initTXChannel())
|
||||
{
|
||||
_dshot_log(TX_INIT_FAILED);
|
||||
return DSHOT_ERROR;
|
||||
}
|
||||
|
||||
// Init DShot encoder
|
||||
if (_initDShotEncoder() != DSHOT_OK)
|
||||
{
|
||||
|
|
@ -297,7 +301,7 @@ dshot_packet_t DShotRMT::_buildDShotPacket(const uint16_t value)
|
|||
}
|
||||
|
||||
// Build packet
|
||||
packet.throttle_value = value;
|
||||
packet.throttle_value = value & 0b0000011111111111;
|
||||
packet.telemetric_request = _is_bidirectional ? 1 : 0;
|
||||
|
||||
// CRC is calculated over 11bit
|
||||
|
|
@ -334,7 +338,7 @@ uint16_t DShotRMT::_calculateCRC(const uint16_t data)
|
|||
}
|
||||
|
||||
// Transmit DShot packet via RMT
|
||||
uint16_t DShotRMT::_sendDShotFrame(const dshot_packet_t &packet)
|
||||
bool DShotRMT::_sendDShotFrame(const dshot_packet_t &packet)
|
||||
{
|
||||
// Check timing requirements
|
||||
if (!_timer_signal())
|
||||
|
|
@ -349,9 +353,6 @@ uint16_t DShotRMT::_sendDShotFrame(const dshot_packet_t &packet)
|
|||
rmt_symbol_word_t rx_symbols[DSHOT_BITS_PER_FRAME];
|
||||
|
||||
rmt_receive(_rmt_rx_channel, rx_symbols, sizeof(rx_symbols), &_receive_config);
|
||||
|
||||
// Disable RMT RX for sending
|
||||
rmt_disable(_rmt_rx_channel);
|
||||
}
|
||||
|
||||
// Local for performance
|
||||
|
|
@ -363,11 +364,21 @@ uint16_t DShotRMT::_sendDShotFrame(const dshot_packet_t &packet)
|
|||
// Calculate transmission data size
|
||||
size_t tx_size_bytes = DSHOT_BITS_PER_FRAME * sizeof(rmt_symbol_word_t);
|
||||
|
||||
// Perform RMT transmission
|
||||
uint16_t result = rmt_transmit(_rmt_tx_channel, _dshot_encoder, tx_symbols, tx_size_bytes, &_transmit_config);
|
||||
|
||||
if (result != DSHOT_OK)
|
||||
// TODO: Find out, why this is needed
|
||||
if (_is_bidirectional)
|
||||
{
|
||||
// Disable RMT RX for sending
|
||||
if (rmt_disable(_rmt_rx_channel) != DSHOT_OK)
|
||||
{
|
||||
_dshot_log(RX_RMT_RECEIVER_ERROR);
|
||||
return DSHOT_ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
// Perform RMT transmission
|
||||
if (rmt_transmit(_rmt_tx_channel, _dshot_encoder, tx_symbols, tx_size_bytes, &_transmit_config) != DSHOT_OK)
|
||||
{
|
||||
_dshot_log(TRANSMITTER_ERROR);
|
||||
return DSHOT_ERROR;
|
||||
}
|
||||
|
||||
|
|
@ -377,11 +388,13 @@ uint16_t DShotRMT::_sendDShotFrame(const dshot_packet_t &packet)
|
|||
if (rmt_enable(_rmt_rx_channel) != DSHOT_OK)
|
||||
{
|
||||
_dshot_log(RX_RMT_RECEIVER_ERROR);
|
||||
return DSHOT_ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
// Update timestamp and return success
|
||||
_timer_reset();
|
||||
|
||||
return DSHOT_OK;
|
||||
}
|
||||
|
||||
|
|
@ -406,47 +419,51 @@ bool IRAM_ATTR DShotRMT::_encodeDShotFrame(const dshot_packet_t &packet, rmt_sym
|
|||
return DSHOT_OK;
|
||||
}
|
||||
|
||||
// Decode received RMT symbols
|
||||
// Decodes a DShot telemetry frame from received RMT symbols.
|
||||
uint16_t DShotRMT::_decodeDShotFrame(const rmt_symbol_word_t *symbols)
|
||||
{
|
||||
// DShot answer is GCR encoded.
|
||||
// GCR decoding: bit_N = gcr_bit_N ^ gcr_bit_(N-1)
|
||||
uint32_t raw_gcr_data = 0;
|
||||
uint32_t gcr_value = 0;
|
||||
|
||||
// Based on DShot bidirectional protocol, idle state is high,
|
||||
// so the first duration is a low pulse.
|
||||
// Bit 1: long low pulse, short high pulse
|
||||
// Bit 0: short low pulse, long high pulse
|
||||
// Decode GCR symbols into a 21-bit value.
|
||||
// '1' has a longer low pulse (duration0 > duration1).
|
||||
// '0' has a longer high pulse (duration1 > duration0).
|
||||
for (size_t i = 0; i < GCR_BITS_PER_FRAME; ++i)
|
||||
{
|
||||
// Check which duration is longer to determine if it's a '1' bit
|
||||
bool bit_is_one = symbols[i].duration0 > symbols[i].duration1;
|
||||
raw_gcr_data = (raw_gcr_data << 1) | bit_is_one;
|
||||
gcr_value = (gcr_value << 1) | bit_is_one;
|
||||
}
|
||||
|
||||
// Extract the 10-bit data from the GCR frame
|
||||
uint16_t gcr_data = (raw_gcr_data >> 5) & 0b0000001111111111; // Mask for 10 bits
|
||||
// Perform GCR decoding: data = gcr ^ (gcr >> 1).
|
||||
uint32_t decoded_frame = gcr_value ^ (gcr_value >> 1);
|
||||
|
||||
// GCR decoding over the "throttle" bits
|
||||
uint16_t received_data = gcr_data ^ (gcr_data >> 1);
|
||||
// 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.
|
||||
uint16_t data_and_crc = (decoded_frame & 0xFFFF);
|
||||
|
||||
// Extract CRC from gcr answer (4 bits)
|
||||
uint16_t received_crc = raw_gcr_data & 0b0000000000001111; // Mask for 4 bits
|
||||
// Cutting 4 bits?
|
||||
uint16_t received_data = data_and_crc >> 4;
|
||||
|
||||
// Calculate expected CRC using the new, centralized function
|
||||
// Telemetry request bit is always 1 for bidirectional
|
||||
uint16_t data_for_crc = (received_data << 1) | 1;
|
||||
uint16_t calculated_crc = _calculateCRC(data_for_crc);
|
||||
|
||||
// Validate CRC
|
||||
if (received_crc != calculated_crc)
|
||||
// Masking CRC
|
||||
uint16_t received_crc = data_and_crc & 0b0000000000001111;
|
||||
|
||||
// Telemetry request bit is always 1.
|
||||
if (!(received_data & (1 << 11)))
|
||||
{
|
||||
_dshot_log(CRC_CHECK_FAILED);
|
||||
return DSHOT_NULL_PACKET;
|
||||
}
|
||||
|
||||
// The data is eRPM * 100
|
||||
return received_data;
|
||||
// Calculate expected CRC
|
||||
uint16_t data_for_crc = received_data;
|
||||
uint16_t calculated_crc = _calculateCRC(data_for_crc);
|
||||
|
||||
// Validate CRC
|
||||
if (received_crc != calculated_crc)
|
||||
{
|
||||
return DSHOT_NULL_PACKET;
|
||||
}
|
||||
|
||||
// Return the eRPM value (first 11 bits of received data).
|
||||
return received_data & 0b0000011111111111;
|
||||
}
|
||||
|
||||
// Check if enough time has passed for next transmission
|
||||
|
|
@ -468,7 +485,7 @@ bool DShotRMT::_timer_reset()
|
|||
}
|
||||
|
||||
// Print timing diagnostic information to specified stream
|
||||
void DShotRMT::printDshotInfo(Stream &output) const
|
||||
void DShotRMT::printDShotInfo(Stream &output) const
|
||||
{
|
||||
output.println(" ");
|
||||
output.println(" === DShot Signal Info === ");
|
||||
|
|
|
|||
26
DShotRMT.h
26
DShotRMT.h
|
|
@ -27,7 +27,7 @@ constexpr auto DSHOT_RX_TIMEOUT_MS = 2;
|
|||
constexpr auto DSHOT_CLOCK_SRC_DEFAULT = RMT_CLK_SRC_DEFAULT;
|
||||
constexpr auto DSHOT_RMT_RESOLUTION = 10 * 1000 * 1000; // 10 MHz resolution
|
||||
constexpr auto RMT_BUFFER_SIZE = DSHOT_BITS_PER_FRAME;
|
||||
constexpr auto GCR_BITS_PER_FRAME = 20;
|
||||
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_QUEUE_DEPTH = 1;
|
||||
|
||||
|
|
@ -47,15 +47,15 @@ typedef enum
|
|||
} dshot_mode_t;
|
||||
|
||||
// DShot Packet Structure
|
||||
typedef struct
|
||||
typedef struct
|
||||
{
|
||||
uint16_t throttle_value : 11;
|
||||
uint16_t telemetric_request : 1;
|
||||
bool telemetric_request : 1;
|
||||
uint16_t checksum : 4;
|
||||
} dshot_packet_t;
|
||||
|
||||
// DShot Timing Configuration Structure
|
||||
typedef struct
|
||||
typedef struct
|
||||
{
|
||||
uint32_t frame_length_us;
|
||||
uint16_t ticks_per_bit;
|
||||
|
|
@ -101,16 +101,16 @@ public:
|
|||
bool is_bidirectional() const { return _is_bidirectional; }
|
||||
|
||||
// --- INFO ---
|
||||
void printDshotInfo(Stream &output = Serial0) const;
|
||||
void printCpuInfo(Stream &output = Serial0) const;
|
||||
void printDShotInfo(Stream &output = Serial) const;
|
||||
void printCpuInfo(Stream &output = Serial) const;
|
||||
|
||||
// --- DEPRECATED METHODS ---
|
||||
[[deprecated("Use sendThrottle() instead")]]
|
||||
bool setThrottle(uint16_t throttle) { return sendThrottle(throttle); }
|
||||
|
||||
|
||||
[[deprecated("Use sendCommand() instead")]]
|
||||
bool sendDShotCommand(uint16_t command) { return sendCommand(command); }
|
||||
|
||||
|
||||
private:
|
||||
// --- CONFIG ---
|
||||
gpio_num_t _gpio;
|
||||
|
|
@ -147,7 +147,7 @@ private:
|
|||
uint16_t _calculateCRC(const uint16_t data);
|
||||
|
||||
// --- FRAME PROCESSING ---
|
||||
uint16_t _sendDShotFrame(const dshot_packet_t &packet);
|
||||
bool _sendDShotFrame(const dshot_packet_t &packet);
|
||||
bool IRAM_ATTR _encodeDShotFrame(const dshot_packet_t &packet, rmt_symbol_word_t *symbols);
|
||||
uint16_t _decodeDShotFrame(const rmt_symbol_word_t *symbols);
|
||||
|
||||
|
|
@ -160,12 +160,15 @@ private:
|
|||
rmt_rx_event_callbacks_t _rx_event_callbacks;
|
||||
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 ---
|
||||
static constexpr auto DSHOT_TELEMETRY_INVALID = (0xffff);
|
||||
|
||||
// --- ERROR HANDLING & LOGGING ---
|
||||
void _dshot_log(const char *msg, Stream &output = Serial) { output.println(msg); }
|
||||
|
||||
// --- CONSTANTS & ERROR MESSAGES ---
|
||||
static constexpr uint16_t DSHOT_OK = 0;
|
||||
static constexpr uint16_t DSHOT_ERROR = 1;
|
||||
static constexpr bool DSHOT_OK = 0;
|
||||
static constexpr bool DSHOT_ERROR = 1;
|
||||
|
||||
static constexpr char *NEW_LINE = " ";
|
||||
static constexpr char *TX_INIT_FAILED = "Failed to initialize TX channel!";
|
||||
|
|
@ -177,4 +180,5 @@ private:
|
|||
static constexpr char *BIDIR_NOT_ENABLED = "Bidirectional DShot support not enabled!";
|
||||
static constexpr char *RX_RMT_RECEIVER_ERROR = "RX RMT receiver error!";
|
||||
static constexpr char *PACKET_BUILD_ERROR = "Value too big for DShot Packet!";
|
||||
static constexpr char *TRANSMITTER_ERROR = "RMT TX Transmitter Error!";
|
||||
};
|
||||
|
|
|
|||
151
README.md
151
README.md
|
|
@ -4,7 +4,7 @@
|
|||
|
||||
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.
|
||||
**Now with BiDirectional DShot support!**
|
||||
**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.
|
||||
|
||||
|
|
@ -14,6 +14,8 @@ Supports all standard DShot modes (150, 300, 600) and features continuous frame
|
|||
|
||||
- **All DShot Modes:** DSHOT150, DSHOT300 (default), DSHOT600, (DSHOT1200)
|
||||
- **BiDirectional DShot:** Experimental support for RPM feedback
|
||||
- **Advanced Command Manager:** High-level API for ESC configuration and control
|
||||
- **Command Sequences:** Predefined initialization and calibration sequences
|
||||
- **Continuous Frames:** Independent timed, Hardware signal generation
|
||||
- **Configurable Pause:** Ensures ESCs can reliably detect frame boundaries
|
||||
- **Simple API:** Easy integration into your Arduino or ESP-IDF project
|
||||
|
|
@ -32,8 +34,113 @@ git clone https://github.com/derdoktor667/DShotRMT.git
|
|||
|
||||
## ⚡ Quick Start
|
||||
|
||||
### Basic Usage (DShotRMT)
|
||||
|
||||
```cpp
|
||||
Use "dshot300.ino" example sketch
|
||||
#include <DShotRMT.h>
|
||||
|
||||
// Create motor instance
|
||||
DShotRMT motor(17, DSHOT300);
|
||||
|
||||
void setup() {
|
||||
Serial.begin(115200);
|
||||
motor.begin();
|
||||
}
|
||||
|
||||
void loop() {
|
||||
motor.sendThrottle(1000); // Send throttle value
|
||||
delay(20);
|
||||
}
|
||||
```
|
||||
|
||||
### Advanced Usage (DShotCommandManager)
|
||||
|
||||
```cpp
|
||||
#include <DShotRMT.h>
|
||||
#include <DShotCommandManager.h>
|
||||
|
||||
// Create motor and command manager instances
|
||||
DShotRMT motor(17, DSHOT300);
|
||||
DShotCommandManager cmdManager(motor);
|
||||
|
||||
void setup() {
|
||||
Serial.begin(115200);
|
||||
motor.begin();
|
||||
cmdManager.begin();
|
||||
|
||||
// Execute initialization sequence
|
||||
cmdManager.executeInitSequence();
|
||||
}
|
||||
|
||||
void loop() {
|
||||
// Your main code here
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🎛️ 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
|
||||
|
||||
### 1. Basic DShot Control
|
||||
Use the `dshot300.ino` example for simple throttle control.
|
||||
|
||||
### 2. Advanced Command Management
|
||||
Use the `command_manager.ino` example for interactive ESC control:
|
||||
|
||||
```
|
||||
=== DShot Command Manager Menu ===
|
||||
Basic Commands:
|
||||
1 - Stop Motor
|
||||
2 - Activate Beacon 1
|
||||
3 - Set Normal Spin Direction
|
||||
4 - Set Reversed Spin Direction
|
||||
5 - Enable 3D Mode
|
||||
6 - Disable 3D Mode
|
||||
7 - Save Settings
|
||||
8 - Turn LED 0 ON
|
||||
9 - Turn LED 0 OFF
|
||||
|
||||
Sequences:
|
||||
i - Execute Initialization Sequence
|
||||
c - Execute Calibration Sequence
|
||||
|
||||
Advanced:
|
||||
cmd <number> - Send DShot command (0 - 47)
|
||||
throttle <value> - Set throttle (48 - 2047)
|
||||
throttle 0 - Stop sending throttle
|
||||
```
|
||||
|
||||
---
|
||||
|
|
@ -95,13 +202,45 @@ Perfect for DShot:
|
|||
|
||||
---
|
||||
|
||||
## 📝 API Reference
|
||||
## 📝 Core API Reference
|
||||
|
||||
### DShotRMT Class
|
||||
- `DShotRMT(gpio_num_t gpio, dshot_mode_t mode, bool isBidirectional)`
|
||||
- `void begin()`
|
||||
- `void sendThrottle(uint16_t throttle)`
|
||||
- `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
|
||||
|
||||
See [examples/dshot300/dshot300.ino](examples/dshot300/dshot300.ino) for a more informations.
|
||||
### 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
|
||||
|
||||
The library supports all standard DShot commands:
|
||||
|
||||
| Command | Value | Description |
|
||||
|---------|-------|-------------|
|
||||
| MOTOR_STOP | 0 | Stop motor |
|
||||
| BEACON1-5 | 1-5 | Motor beeping |
|
||||
| ESC_INFO | 6 | Request ESC information |
|
||||
| SPIN_DIRECTION_1/2 | 7-8 | Set spin direction |
|
||||
| 3D_MODE_OFF/ON | 9-10 | 3D mode control |
|
||||
| SAVE_SETTINGS | 12 | Save settings to ESC |
|
||||
| EXTENDED_TELEMETRY_ENABLE/DISABLE | 13-14 | Telemetry control |
|
||||
| LED0-3_ON/OFF | 22-29 | LED control (BLHeli32) |
|
||||
| AUDIO_STREAM_MODE | 30 | KISS audio mode |
|
||||
| SILENT_MODE | 31 | KISS silent mode |
|
||||
|
||||
---
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,275 @@
|
|||
/*
|
||||
* command_manager_example.ino
|
||||
* Example sketch demonstrating DShotCommandManager usage
|
||||
* Author: Wastl Kraus
|
||||
* Date: 2025-09-04
|
||||
* License: MIT
|
||||
*
|
||||
* Modified by Especiallist to support continuous throttle sending.
|
||||
*/
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <DShotRMT.h>
|
||||
#include <DShotCommandManager.h>
|
||||
|
||||
// USB serial port settings
|
||||
static constexpr auto &USB_SERIAL = Serial0;
|
||||
static constexpr auto USB_SERIAL_BAUD = 115200;
|
||||
|
||||
// Motor configuration
|
||||
static constexpr auto MOTOR01_PIN = 17;
|
||||
static constexpr auto IS_BIDIRECTIONAL = false;
|
||||
|
||||
// Motor magnet count for RPM calculation
|
||||
static constexpr auto MOTOR01_MAGNET_COUNT = 14;
|
||||
|
||||
// Create motor and command manager instances
|
||||
DShotRMT motor01(MOTOR01_PIN, DSHOT300, IS_BIDIRECTIONAL);
|
||||
DShotCommandManager commandManager(motor01);
|
||||
|
||||
// Global variable to store the desired continuous throttle value
|
||||
static volatile uint16_t throttle_now = NULL;
|
||||
|
||||
//
|
||||
void setup()
|
||||
{
|
||||
// Start USB Serial Port
|
||||
USB_SERIAL.begin(USB_SERIAL_BAUD);
|
||||
|
||||
USB_SERIAL.println("=== DShotRMT Command Manager Example ===");
|
||||
|
||||
// Initialize DShot
|
||||
if (motor01.begin() != 0)
|
||||
{
|
||||
USB_SERIAL.println("ERROR: Failed to initialize DShotRMT!");
|
||||
while (1)
|
||||
delay(1000);
|
||||
}
|
||||
|
||||
// Init Command Manager
|
||||
if (!commandManager.begin())
|
||||
{
|
||||
USB_SERIAL.println("ERROR: Failed to initialize DShotCommandManager!");
|
||||
while (1)
|
||||
delay(1000);
|
||||
}
|
||||
|
||||
USB_SERIAL.println("Initialization successful!");
|
||||
|
||||
// Print Menu
|
||||
printMenu();
|
||||
}
|
||||
|
||||
//
|
||||
void loop()
|
||||
{
|
||||
// Time Measurement
|
||||
static uint64_t last_stats_print = 0;
|
||||
|
||||
// Check for serial input
|
||||
if (USB_SERIAL.available() > 0)
|
||||
{
|
||||
String input = USB_SERIAL.readStringUntil('\n');
|
||||
input.trim();
|
||||
handleUserInput(input);
|
||||
}
|
||||
|
||||
// Continuously send the stored throttle value
|
||||
if (throttle_now != NULL)
|
||||
{
|
||||
motor01.sendThrottle(throttle_now);
|
||||
|
||||
// Print motor stats every 2 seconds
|
||||
if (esp_timer_get_time() - last_stats_print >= 2000000)
|
||||
{
|
||||
motor01.printDShotInfo();
|
||||
|
||||
// Get Motor RPM
|
||||
if (IS_BIDIRECTIONAL)
|
||||
{
|
||||
uint32_t rpm = motor01.getMotorRPM(MOTOR01_MAGNET_COUNT);
|
||||
USB_SERIAL.printf("Motor RPM: %u\n", rpm);
|
||||
}
|
||||
|
||||
// Time Stamp
|
||||
last_stats_print = esp_timer_get_time();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
void handleUserInput(const String &input)
|
||||
{
|
||||
dshot_command_result_t result;
|
||||
|
||||
if (input == "1")
|
||||
{
|
||||
// Stop motor command should also reset the continuous throttle value
|
||||
throttle_now = 0;
|
||||
USB_SERIAL.print("Stopping motor... ");
|
||||
result = commandManager.stopMotor();
|
||||
printResult(result);
|
||||
}
|
||||
else if (input == "2")
|
||||
{
|
||||
USB_SERIAL.print("Activating beacon 1... ");
|
||||
result = commandManager.activateBeacon(1);
|
||||
printResult(result);
|
||||
}
|
||||
else if (input == "3")
|
||||
{
|
||||
USB_SERIAL.print("Setting normal spin direction... ");
|
||||
result = commandManager.setSpinDirection(false);
|
||||
printResult(result);
|
||||
}
|
||||
else if (input == "4")
|
||||
{
|
||||
USB_SERIAL.print("Setting reversed spin direction... ");
|
||||
result = commandManager.setSpinDirection(true);
|
||||
printResult(result);
|
||||
}
|
||||
else if (input == "5")
|
||||
{
|
||||
USB_SERIAL.print("Enabling 3D mode... ");
|
||||
result = commandManager.set3DMode(true);
|
||||
printResult(result);
|
||||
}
|
||||
else if (input == "6")
|
||||
{
|
||||
USB_SERIAL.print("Disabling 3D mode... ");
|
||||
result = commandManager.set3DMode(false);
|
||||
printResult(result);
|
||||
}
|
||||
else if (input == "7")
|
||||
{
|
||||
USB_SERIAL.print("Saving settings... ");
|
||||
result = commandManager.saveSettings();
|
||||
printResult(result);
|
||||
}
|
||||
else if (input == "8")
|
||||
{
|
||||
USB_SERIAL.print("Turning LED 0 ON... ");
|
||||
result = commandManager.setLED(0, true);
|
||||
printResult(result);
|
||||
}
|
||||
else if (input == "9")
|
||||
{
|
||||
USB_SERIAL.print("Turning LED 0 OFF... ");
|
||||
result = commandManager.setLED(0, false);
|
||||
printResult(result);
|
||||
}
|
||||
else if (input == "i")
|
||||
{
|
||||
USB_SERIAL.print("Executing initialization sequence... ");
|
||||
result = commandManager.executeInitSequence();
|
||||
printResult(result);
|
||||
}
|
||||
else if (input == "c")
|
||||
{
|
||||
USB_SERIAL.print("Executing calibration sequence... ");
|
||||
result = commandManager.executeCalibrationSequence();
|
||||
printResult(result);
|
||||
}
|
||||
else if (input == "s")
|
||||
{
|
||||
commandManager.printStatistics();
|
||||
}
|
||||
else if (input == "r")
|
||||
{
|
||||
commandManager.resetStatistics();
|
||||
USB_SERIAL.println("Statistics reset.");
|
||||
}
|
||||
else if (input == "h")
|
||||
{
|
||||
printMenu();
|
||||
}
|
||||
else if (input == "help")
|
||||
{
|
||||
printMenu();
|
||||
}
|
||||
else if (input.startsWith("cmd "))
|
||||
{
|
||||
// Direct command execution: "cmd 5" sends command 5
|
||||
int cmd_num = input.substring(4).toInt();
|
||||
if (DShotCommandManager::isValidCommand(static_cast<dshot_commands_t>(cmd_num)))
|
||||
{
|
||||
USB_SERIAL.printf("Sending command %d (%s)... ", cmd_num,
|
||||
DShotCommandManager::getCommandName(static_cast<dshot_commands_t>(cmd_num)));
|
||||
result = commandManager.sendCommand(static_cast<dshot_commands_t>(cmd_num));
|
||||
printResult(result);
|
||||
}
|
||||
else
|
||||
{
|
||||
USB_SERIAL.printf("Invalid command number: %d (valid range: 0-%d)\n", cmd_num, DSHOT_CMD_MAX);
|
||||
}
|
||||
}
|
||||
else if (input.startsWith("throttle "))
|
||||
{
|
||||
// Throttle control: "throttle 1000" sets throttle to 1000
|
||||
int throttle_value = input.substring(9).toInt();
|
||||
if (throttle_value >= DSHOT_THROTTLE_MIN && throttle_value <= DSHOT_THROTTLE_MAX)
|
||||
{
|
||||
throttle_now = throttle_value;
|
||||
USB_SERIAL.printf("Setting continuous throttle to %d\n", throttle_now);
|
||||
}
|
||||
else if (throttle_value == 0)
|
||||
{
|
||||
throttle_now = 0;
|
||||
USB_SERIAL.println("Continuous throttle stopped.");
|
||||
}
|
||||
else
|
||||
{
|
||||
USB_SERIAL.printf("Invalid throttle value: %d (valid range: 48-2047, use 0 to stop)\n", throttle_value);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
USB_SERIAL.println("Unknown command. Type 'h' for help.");
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
void printResult(const dshot_command_result_t &result)
|
||||
{
|
||||
if (!result.success)
|
||||
{
|
||||
USB_SERIAL.printf("SUCCESS (%u us)\n", result.execution_time_us);
|
||||
}
|
||||
else
|
||||
{
|
||||
USB_SERIAL.printf("FAILED - %s (%u us)\n", result.error_message, result.execution_time_us);
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
void printMenu()
|
||||
{
|
||||
USB_SERIAL.println("================================================");
|
||||
USB_SERIAL.println("\n=== DShot Command Manager Menu ===");
|
||||
USB_SERIAL.println("Basic Commands:");
|
||||
USB_SERIAL.println(" 1 - Stop Motor");
|
||||
USB_SERIAL.println(" 2 - Activate Beacon 1");
|
||||
USB_SERIAL.println(" 3 - Set Normal Spin Direction");
|
||||
USB_SERIAL.println(" 4 - Set Reversed Spin Direction");
|
||||
USB_SERIAL.println(" 5 - Enable 3D Mode");
|
||||
USB_SERIAL.println(" 6 - Disable 3D Mode");
|
||||
USB_SERIAL.println(" 7 - Save Settings");
|
||||
USB_SERIAL.println(" 8 - Turn LED 0 ON");
|
||||
USB_SERIAL.println(" 9 - Turn LED 0 OFF");
|
||||
USB_SERIAL.println("");
|
||||
USB_SERIAL.println("Sequences:");
|
||||
USB_SERIAL.println(" i - Execute Initialization Sequence");
|
||||
USB_SERIAL.println(" c - Execute Calibration Sequence");
|
||||
USB_SERIAL.println("");
|
||||
USB_SERIAL.println("Advanced:");
|
||||
USB_SERIAL.println(" cmd <number> - Send DShot command (0 - 47)");
|
||||
USB_SERIAL.println(" throttle <value> - Set throttle (48 - 2047)");
|
||||
USB_SERIAL.println(" throttle 0 - Stop sending throttle");
|
||||
USB_SERIAL.println("");
|
||||
USB_SERIAL.println(" h - Show this Menu");
|
||||
USB_SERIAL.println("");
|
||||
USB_SERIAL.println("Examples:");
|
||||
USB_SERIAL.println(" cmd 1 - Stop Motor");
|
||||
USB_SERIAL.println(" throttle 1000 - Set throttle to 1000");
|
||||
USB_SERIAL.println("================================================");
|
||||
}
|
||||
|
|
@ -71,7 +71,7 @@ void loop()
|
|||
// Print motor stats every 2 seconds
|
||||
if (millis() - last_stats_print >= 2000)
|
||||
{
|
||||
motor01.printDshotInfo();
|
||||
motor01.printDShotInfo();
|
||||
|
||||
// Get Motor RPM
|
||||
if (IS_BIDIRECTIONAL)
|
||||
|
|
|
|||
85
keywords.txt
85
keywords.txt
|
|
@ -7,9 +7,13 @@
|
|||
#######################################
|
||||
|
||||
DShotRMT KEYWORD1
|
||||
DShotCommandManager KEYWORD1
|
||||
dshot_mode_t KEYWORD1
|
||||
dshot_packet_t KEYWORD1
|
||||
dshot_timing_t KEYWORD1
|
||||
dshot_commands_t KEYWORD1
|
||||
dshot_command_result_t KEYWORD1
|
||||
dshot_command_sequence_item_t KEYWORD1
|
||||
dshotCommands_e KEYWORD1
|
||||
dshotCommandType_e KEYWORD1
|
||||
|
||||
|
|
@ -17,6 +21,7 @@ dshotCommandType_e KEYWORD1
|
|||
# Methods and Functions (KEYWORD2)
|
||||
#######################################
|
||||
|
||||
# DShotRMT Methods
|
||||
begin KEYWORD2
|
||||
setThrottle KEYWORD2
|
||||
sendThrottle KEYWORD2
|
||||
|
|
@ -27,23 +32,101 @@ getMotorRPM KEYWORD2
|
|||
getGPIO KEYWORD2
|
||||
getDShotPacket KEYWORD2
|
||||
is_bidirectional KEYWORD2
|
||||
printDShotInfo KEYWORD2
|
||||
printCpuInfo KEYWORD2
|
||||
|
||||
# DShotCommandManager Methods
|
||||
sendCommand KEYWORD2
|
||||
sendCommandWithDelay KEYWORD2
|
||||
stopMotor KEYWORD2
|
||||
set3DMode KEYWORD2
|
||||
setSpinDirection KEYWORD2
|
||||
saveSettings KEYWORD2
|
||||
setExtendedTelemetry KEYWORD2
|
||||
requestESCInfo KEYWORD2
|
||||
setLED KEYWORD2
|
||||
activateBeacon KEYWORD2
|
||||
setAudioStreamMode KEYWORD2
|
||||
setSilentMode KEYWORD2
|
||||
executeSequence KEYWORD2
|
||||
executeInitSequence KEYWORD2
|
||||
executeCalibrationSequence KEYWORD2
|
||||
getCommandName KEYWORD2
|
||||
isValidCommand KEYWORD2
|
||||
printStatistics KEYWORD2
|
||||
resetStatistics KEYWORD2
|
||||
getTotalCommandCount KEYWORD2
|
||||
getFailedCommandCount KEYWORD2
|
||||
getLastExecutionTime KEYWORD2
|
||||
printMenu KEYWORD2
|
||||
handleMenuInput KEYWORD2
|
||||
|
||||
#######################################
|
||||
# Constants (LITERAL1)
|
||||
#######################################
|
||||
|
||||
# DShot Modes
|
||||
DSHOT_OFF LITERAL1
|
||||
DSHOT150 LITERAL1
|
||||
DSHOT300 LITERAL1
|
||||
DSHOT600 LITERAL1
|
||||
DSHOT1200 LITERAL1
|
||||
|
||||
# DShot Throttle Constants
|
||||
DSHOT_THROTTLE_FAILSAFE LITERAL1
|
||||
DSHOT_THROTTLE_MIN LITERAL1
|
||||
DSHOT_THROTTLE_MAX LITERAL1
|
||||
|
||||
# DShot Commands
|
||||
DSHOT_CMD_MOTOR_STOP LITERAL1
|
||||
DSHOT_CMD_BEACON1 LITERAL1
|
||||
DSHOT_CMD_BEACON2 LITERAL1
|
||||
DSHOT_CMD_BEACON3 LITERAL1
|
||||
DSHOT_CMD_BEACON4 LITERAL1
|
||||
DSHOT_CMD_BEACON5 LITERAL1
|
||||
DSHOT_CMD_ESC_INFO LITERAL1
|
||||
DSHOT_CMD_SPIN_DIRECTION_1 LITERAL1
|
||||
DSHOT_CMD_SPIN_DIRECTION_2 LITERAL1
|
||||
DSHOT_CMD_3D_MODE_OFF LITERAL1
|
||||
DSHOT_CMD_3D_MODE_ON LITERAL1
|
||||
DSHOT_CMD_SETTINGS_REQUEST LITERAL1
|
||||
DSHOT_CMD_SAVE_SETTINGS LITERAL1
|
||||
DSHOT_CMD_EXTENDED_TELEMETRY_ENABLE LITERAL1
|
||||
DSHOT_CMD_EXTENDED_TELEMETRY_DISABLE LITERAL1
|
||||
DSHOT_CMD_SPIN_DIRECTION_NORMAL LITERAL1
|
||||
DSHOT_CMD_SPIN_DIRECTION_REVERSED LITERAL1
|
||||
DSHOT_CMD_LED0_ON LITERAL1
|
||||
DSHOT_CMD_LED1_ON LITERAL1
|
||||
DSHOT_CMD_LED2_ON LITERAL1
|
||||
DSHOT_CMD_LED3_ON LITERAL1
|
||||
DSHOT_CMD_LED0_OFF LITERAL1
|
||||
DSHOT_CMD_LED1_OFF LITERAL1
|
||||
DSHOT_CMD_LED2_OFF LITERAL1
|
||||
DSHOT_CMD_LED3_OFF LITERAL1
|
||||
DSHOT_CMD_AUDIO_STREAM_MODE_ON_OFF LITERAL1
|
||||
DSHOT_CMD_SILENT_MODE_ON_OFF LITERAL1
|
||||
DSHOT_CMD_MAX LITERAL1
|
||||
|
||||
# DShot Command Types
|
||||
DSHOT_CMD_TYPE_INLINE LITERAL1
|
||||
DSHOT_CMD_TYPE_BLOCKING LITERAL1
|
||||
DSHOT_CMD_TYPE_BLOCKING LITERAL1
|
||||
|
||||
# Protocol Constants
|
||||
DSHOT_BITS_PER_FRAME LITERAL1
|
||||
DSHOT_SWITCH_TIME LITERAL1
|
||||
DSHOT_NULL_PACKET LITERAL1
|
||||
DSHOT_RX_TIMEOUT_MS LITERAL1
|
||||
GCR_BITS_PER_FRAME LITERAL1
|
||||
|
||||
# RMT Constants
|
||||
DSHOT_CLOCK_SRC_DEFAULT LITERAL1
|
||||
DSHOT_RMT_RESOLUTION LITERAL1
|
||||
RMT_BUFFER_SIZE LITERAL1
|
||||
RMT_BUFFER_SYMBOLS LITERAL1
|
||||
RMT_QUEUE_DEPTH LITERAL1
|
||||
DSHOT_PULSE_MIN LITERAL1
|
||||
DSHOT_PULSE_MAX LITERAL1
|
||||
|
||||
# Status Constants
|
||||
DSHOT_OK LITERAL1
|
||||
DSHOT_ERROR LITERAL1
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
name=DShotRMT
|
||||
version=0.6.6
|
||||
version=0.7.0
|
||||
author=derdoktor667
|
||||
maintainer=derdoktor667
|
||||
sentence=DShotRMT Library supporting all DShot Types and speeds. Tested with BlHeli_S.
|
||||
|
|
|
|||
Loading…
Reference in New Issue