diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 23500d5..8e95371 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -1,4 +1,5 @@
name: ESP32 Build & Quality Check
+
permissions:
contents: read
@@ -8,80 +9,106 @@ on:
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
+ cancel-in-progress: true
jobs:
# ============================================================================
# Code Quality & Linting
# ============================================================================
quality-check:
- name: 'Arduino Lint Check'
+ name: Arduino Lint Check
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- - name: Checkout Repository
- uses: actions/checkout@v5
+ - uses: actions/checkout@v5
- - name: Setup Arduino CLI
- uses: arduino/setup-arduino-cli@v2
+ - uses: arduino/setup-arduino-cli@v2
- - name: Install ESP32 core
+ - name: Cache Arduino Core
+ uses: actions/cache@v4
+ with:
+ path: |
+ ~/.arduino15/packages
+ ~/.arduino15/cache
+ key: arduino-core-${{ runner.os }}-esp32-v1
+ restore-keys: |
+ arduino-core-${{ runner.os }}-
+
+ - name: Install ESP32 Core
run: |
- arduino-cli core update-index > /dev/null
- arduino-cli core install esp32:esp32 > /dev/null
+ arduino-cli core update-index --additional-urls https://raw.githubusercontent.com/espressif/arduino-esp32/gh-pages/package_esp32_index.json
+ arduino-cli core install esp32:esp32
- - name: Arduino Lint
- uses: arduino/arduino-lint-action@v2
+ - uses: arduino/arduino-lint-action@v2
with:
path: ${{ github.workspace }}
compliance: strict
library-manager: update
verbose: true
-# ============================================================================
+ # ============================================================================
# Compilation Test
# ============================================================================
compile-test:
- name: 'Compile Example Sketches'
+ name: Compile Example Sketches
runs-on: ubuntu-latest
timeout-minutes: 15
strategy:
fail-fast: false
matrix:
- examples:
- - "examples/dshot300/dshot300.ino"
- - "examples/command_manager/command_manager.ino"
- - "examples/web_control/web_control.ino"
- - "examples/web_client/web_client.ino"
+ example:
+ - examples/dshot300/dshot300.ino
+ - examples/command_manager/command_manager.ino
+ - examples/web_control/web_control.ino
+ - examples/web_client/web_client.ino
steps:
- - name: Checkout Repository
- uses: actions/checkout@v5
+ - uses: actions/checkout@v5
- - name: Setup Arduino CLI
- uses: arduino/setup-arduino-cli@v2
+ - uses: arduino/setup-arduino-cli@v2
+
+ - name: Cache Arduino Core & Libraries
+ uses: actions/cache@v4
+ with:
+ path: |
+ ~/.arduino15/packages
+ ~/.arduino15/cache
+ ~/Arduino/libraries
+ key: arduino-full-${{ runner.os }}-esp32-v1
+ restore-keys: |
+ arduino-full-${{ runner.os }}-
- name: Install ESP32 Core and Dependencies
run: |
- arduino-cli core update-index
+ arduino-cli core update-index --additional-urls https://raw.githubusercontent.com/espressif/arduino-esp32/gh-pages/package_esp32_index.json
arduino-cli core install esp32:esp32
-
arduino-cli lib install "ArduinoJson"
- # Workround for ESPAsyncWebServer
- git clone https://github.com/ESP32Async/ESPAsyncWebServer ~/Arduino/libraries/ESPAsyncWebServer
- git clone https://github.com/ESP32Async/AsyncTCP ~/Arduino/libraries/AsyncTCP
-
+ mkdir -p ~/Arduino/libraries
+
+ # Cached repository check
+ if [ ! -d ~/Arduino/libraries/ESPAsyncWebServer ]; then
+ git clone --depth=1 https://github.com/ESP32Async/ESPAsyncWebServer ~/Arduino/libraries/ESPAsyncWebServer
+ fi
+
+ if [ ! -d ~/Arduino/libraries/AsyncTCP ]; then
+ git clone --depth=1 https://github.com/ESP32Async/AsyncTCP ~/Arduino/libraries/AsyncTCP
+ fi
+
- name: Compile Sketch
run: |
- arduino-cli compile --fqbn esp32:esp32:esp32 --library ${{ github.workspace }} ${{ matrix.examples}}
+ arduino-cli compile \
+ --fqbn esp32:esp32:esp32 \
+ --library ${{ github.workspace }} \
+ ${{ matrix.example }}
# ============================================================================
# Build Status Report
# ============================================================================
build-summary:
- name: 'Build Summary'
+ name: Build Summary
runs-on: ubuntu-latest
if: always()
needs: [quality-check, compile-test]
@@ -90,32 +117,21 @@ jobs:
- name: Create Build Summary
run: |
echo "# 🔧 DShotRMT Build Report" >> $GITHUB_STEP_SUMMARY
- echo "" >> $GITHUB_STEP_SUMMARY
- echo "| Check | Status | Details |" >> $GITHUB_STEP_SUMMARY
+ echo "| Check | Status | Details |" >> $GITHUB_STEP_SUMMARY
echo "|-------|--------|---------|" >> $GITHUB_STEP_SUMMARY
-
- # Quality Check Status
- if [[ "${{ needs.quality-check.result }}" == "success" ]]; then
- echo "| 📋 Quality Check | ✅ Passed | Arduino Lint completed successfully |" >> $GITHUB_STEP_SUMMARY
- else
- echo "| 📋 Quality Check | ❌ Failed | Check Arduino Lint report |" >> $GITHUB_STEP_SUMMARY
- fi
-
- # Compile Test Status
- if [[ "${{ needs.compile-test.result }}" == "success" ]]; then
- echo "| 🔨 Compilation | ✅ Passed | All examples compiled successfully |" >> $GITHUB_STEP_SUMMARY
- else
- echo "| 🔨 Compilation | ❌ Failed | Compilation errors detected |" >> $GITHUB_STEP_SUMMARY
- fi
-
+
+ [[ "${{ needs.quality-check.result }}" == "success" ]] \
+ && echo "| 📋 Quality Check | ✅ Passed | Arduino Lint completed successfully |" >> $GITHUB_STEP_SUMMARY \
+ || echo "| 📋 Quality Check | ❌ Failed | Check Arduino Lint report |" >> $GITHUB_STEP_SUMMARY
+
+ [[ "${{ needs.compile-test.result }}" == "success" ]] \
+ && echo "| 🔨 Compilation | ✅ Passed | All examples compiled successfully |" >> $GITHUB_STEP_SUMMARY \
+ || echo "| 🔨 Compilation | ❌ Failed | Compilation errors detected |" >> $GITHUB_STEP_SUMMARY
+
echo "" >> $GITHUB_STEP_SUMMARY
-
- # Overall Status
- if [[ "${{ needs.quality-check.result }}" == "success" &&
- "${{ needs.compile-test.result }}" == "success" ]]; then
- echo "## 🎉 All Checks Passed!" >> $GITHUB_STEP_SUMMARY
- echo "Your DShotRMT library is ready for deployment." >> $GITHUB_STEP_SUMMARY
- else
- echo "## ⚠️ Action Required" >> $GITHUB_STEP_SUMMARY
- echo "Please review the failed checks and address any issues."
- fi
+
+ [[ "${{ needs.quality-check.result }}" == "success" && "${{ needs.compile-test.result }}" == "success" ]] \
+ && echo "## 🎉 All Checks Passed!" >> $GITHUB_STEP_SUMMARY \
+ && echo "Your DShotRMT library is ready for deployment." >> $GITHUB_STEP_SUMMARY \
+ || echo "## ⚠️ Action Required" >> $GITHUB_STEP_SUMMARY \
+ && echo "Please review the failed checks and address any issues." >> $GITHUB_STEP_SUMMARY
diff --git a/examples/web_client/web_client.ino b/examples/web_client/web_client.ino
index 79c9004..e4a3e2f 100644
--- a/examples/web_client/web_client.ino
+++ b/examples/web_client/web_client.ino
@@ -62,6 +62,358 @@ void onWsEvent(AsyncWebSocket *server, AsyncWebSocketClient *client, AwsEventTyp
bool connectToWiFi();
void printWiFiStatus();
+// Web Site Content
+const char index_html[] PROGMEM = R"rawliteral(
+
+
+
+
+
+
+ DShotRMT Web Client
+
+
+
+
+ DShotRMT Web Client
+
+
+
+
+ ARMING SWITCH
+
+
+
+ DISARMED
+
+
+ ⚠️ Disabled when disarmed!
+
+
+
+
+
+
+
+ RPM: --
+
+
+
+
+
+
+
+
+)rawliteral";
+
//
void setup()
{
diff --git a/examples/web_control/web_control.ino b/examples/web_control/web_control.ino
index 3f1e400..2b66c8b 100644
--- a/examples/web_control/web_control.ino
+++ b/examples/web_control/web_control.ino
@@ -58,6 +58,358 @@ void handleWebSocketMessage(void *arg, uint8_t *data, size_t len);
void onWsEvent(AsyncWebSocket *server, AsyncWebSocketClient *client, AwsEventType type, void *arg, uint8_t *data, size_t len);
void setArmingStatus(bool armed);
+// Web Site Content
+const char index_html[] PROGMEM = R"rawliteral(
+
+
+
+
+
+
+ DShotRMT Web Client
+
+
+
+
+ DShotRMT Web Client
+
+
+
+
+ ARMING SWITCH
+
+
+
+ DISARMED
+
+
+ ⚠️ Disabled when disarmed!
+
+
+
+
+
+
+
+ RPM: --
+
+
+
+
+
+
+
+
+)rawliteral";
+
//
void setup()
{
diff --git a/library.properties b/library.properties
index 346113b..243274d 100644
--- a/library.properties
+++ b/library.properties
@@ -8,5 +8,5 @@ paragraph=This library can control a BlHeli_S by using encoded DShot commands. F
category=Signal Input/Output
url=https://github.com/derdoktor667/DShotRMT
architectures=esp32
-provides_includes=DShotRMT.h, DShotCommandManager.h, dshot_commands.h, web_content.h
+provides_includes=DShotRMT.h, DShotCommandManager.h, dshot_commands.h
depends=ArduinoJson
\ No newline at end of file
diff --git a/src/DShotRMT.cpp b/src/DShotRMT.cpp
index 395125e..aef9490 100644
--- a/src/DShotRMT.cpp
+++ b/src/DShotRMT.cpp
@@ -8,7 +8,17 @@
#include "DShotRMT.h"
-// --- HELPERS ---
+// Static Data & Helper Functions
+// Timing parameters for each DShot mode
+// Format: {frame_length_ticks, ticks_per_bit, t1h_ticks, t1l_ticks, t0h_ticks, t0l_ticks}
+static constexpr dshot_timing_us_t DSHOT_TIMING_US[] = {
+ {0.00, 0.00},
+ {6.67, 5.00},
+ {3.33, 2.50},
+ {1.67, 1.25},
+ {0.83, 0.67}};
+
+// Helper function to print DShot results
void printDShotResult(dshot_result_t &result, Stream &output)
{
output.printf("Status: %s - %s", result.success ? "SUCCESS" : "FAILED", result.msg);
@@ -22,25 +32,14 @@ void printDShotResult(dshot_result_t &result, Stream &output)
output.println();
}
-// Timing parameters for each DShot mode
-// Format: {frame_length_ticks, ticks_per_bit, t1h_ticks, t1l_ticks, t0h_ticks, t0l_ticks}
-static constexpr dshot_timing_us_t DSHOT_TIMING_US[] = {
- {0.00, 0.00},
- {6.67, 5.00},
- {3.33, 2.50},
- {1.67, 1.25},
- {0.83, 0.67}
-};
-
+// Constructors & Destructor
// Constructor with GPIO number
DShotRMT::DShotRMT(gpio_num_t gpio, dshot_mode_t mode, bool is_bidirectional)
: _gpio(gpio),
_mode(mode),
_is_bidirectional(is_bidirectional),
- _last_erpm_atomic(0),
- _telemetry_ready_flag(false),
- _frame_timer_us(0),
_dshot_timing(DSHOT_TIMING_US[mode]),
+ _frame_timer_us(0),
_rmt_ticks{0},
_last_throttle(DSHOT_CMD_MOTOR_STOP),
_last_transmission_time_us(0),
@@ -55,23 +54,13 @@ DShotRMT::DShotRMT(gpio_num_t gpio, dshot_mode_t mode, bool is_bidirectional)
_tx_channel_config{},
_rx_channel_config{},
_transmit_config{},
- _receive_config{}
+ _receive_config{},
+ _rx_event_callbacks{},
+ _last_erpm_atomic(0),
+ _telemetry_ready_flag_atomic(false)
{
- // Convert DShot timings (us) to RMT ticks
- _rmt_ticks.ticks_per_bit = static_cast(_dshot_timing.bit_length_us * RMT_TICKS_PER_US);
- _rmt_ticks.t1h_ticks = static_cast(_dshot_timing.t1h_lenght_us * RMT_TICKS_PER_US);
- _rmt_ticks.t0h_ticks = _rmt_ticks.t1h_ticks >> 1; // High time for a 1 is always double that of a 0
- _rmt_ticks.t1l_ticks = _rmt_ticks.ticks_per_bit - _rmt_ticks.t1h_ticks;
- _rmt_ticks.t0l_ticks = _rmt_ticks.ticks_per_bit - _rmt_ticks.t0h_ticks;
-
- // Pause between frames is frame time in us, some padding and about 30 us is added by hardware
- _frame_timer_us = (static_cast(_dshot_timing.bit_length_us * DSHOT_BITS_PER_FRAME) << 1) + DSHOT_PADDING_US;
-
- // Double frame time for bidirectional mode (includes response time)
- if (_is_bidirectional)
- {
- _frame_timer_us = (_frame_timer_us << 1);
- }
+ // Configure RMT ticks for DShot timings
+ _configureRMTTiming();
}
// Constructor using pin number
@@ -81,10 +70,10 @@ DShotRMT::DShotRMT(uint16_t pin_nr, dshot_mode_t mode, bool is_bidirectional)
// Delegates to primary constructor with type cast
}
-// Destructor for "better" code
+// Destructor
DShotRMT::~DShotRMT()
{
- // ...TX
+ // Cleanup TX channel
if (_rmt_tx_channel)
{
if (rmt_disable(_rmt_tx_channel) == DSHOT_OK)
@@ -94,7 +83,7 @@ DShotRMT::~DShotRMT()
}
}
- // ...RX
+ // Cleanup RX channel
if (_rmt_rx_channel)
{
if (rmt_disable(_rmt_rx_channel) == DSHOT_OK)
@@ -104,7 +93,7 @@ DShotRMT::~DShotRMT()
}
}
- // ...Encoder
+ // Cleanup encoder
if (_dshot_encoder)
{
rmt_del_encoder(_dshot_encoder);
@@ -112,10 +101,12 @@ DShotRMT::~DShotRMT()
}
}
-// Init DShotRMT
+
+// Public Core Functions
+// Initialize DShotRMT
dshot_result_t DShotRMT::begin()
{
- // Init RX channel first
+ // Init RX channel first (for bidirectional mode)
if (_is_bidirectional)
{
if (!_initRXChannel().success)
@@ -142,104 +133,6 @@ dshot_result_t DShotRMT::begin()
return {true, INIT_SUCCESS};
}
-// Init RMT TX channel
-dshot_result_t DShotRMT::_initTXChannel()
-{
- // Configure TX channel
- _tx_channel_config.gpio_num = _gpio;
- _tx_channel_config.clk_src = DSHOT_CLOCK_SRC_DEFAULT;
- _tx_channel_config.resolution_hz = DSHOT_RMT_RESOLUTION;
- _tx_channel_config.mem_block_symbols = RMT_BUFFER_SYMBOLS;
- _tx_channel_config.trans_queue_depth = RMT_QUEUE_DEPTH;
-
- // Config RMT TX
- _transmit_config.loop_count = 0; // No automatic loops - real-time calculation
- _transmit_config.flags.eot_level = _is_bidirectional ? 1 : 0; // Telemetric Bit used as bidir flag
-
- // Create RMT TX channel
- if (rmt_new_tx_channel(&_tx_channel_config, &_rmt_tx_channel) != DSHOT_OK)
- {
- return {false, TX_INIT_FAILED};
- }
-
- //
- if (rmt_enable(_rmt_tx_channel) != DSHOT_OK)
- {
- return {false, TX_INIT_FAILED};
- }
-
- return {true, TX_INIT_SUCCESS};
-}
-
-// Init RMT RX channel
-dshot_result_t DShotRMT::_initRXChannel()
-{
- // Direct RMT symbol processing - Performance optimized
- _rx_event_callbacks.on_recv_done = _rmt_rx_done_callback;
-
- // Config RMT RX
- _rx_channel_config.gpio_num = _gpio;
- _rx_channel_config.clk_src = DSHOT_CLOCK_SRC_DEFAULT;
- _rx_channel_config.resolution_hz = DSHOT_RMT_RESOLUTION;
- _rx_channel_config.mem_block_symbols = RMT_BUFFER_SYMBOLS;
-
- // Config RMT RX parameters
- _receive_config.signal_range_min_ns = DSHOT_PULSE_MIN;
- _receive_config.signal_range_max_ns = DSHOT_PULSE_MAX;
-
- // Create RMT RX channel
- if (rmt_new_rx_channel(&_rx_channel_config, &_rmt_rx_channel) != DSHOT_OK)
- {
- return {false, RX_INIT_FAILED};
- }
-
- //
- if (rmt_enable(_rmt_rx_channel) != DSHOT_OK)
- {
- return {false, RX_INIT_FAILED};
- }
-
- return {true, RX_INIT_SUCCESS};
-}
-
-// Callback for RMT RX
-bool DShotRMT::_rmt_rx_done_callback(rmt_channel_handle_t rmt_rx_channel, const rmt_rx_done_event_data_t *edata, void *user_data)
-{
- DShotRMT *instance = static_cast(user_data);
-
- // ISR check for valid data
- if (edata && edata->num_symbols >= GCR_BITS_PER_FRAME && edata->num_symbols <= GCR_BITS_PER_FRAME)
- {
-
- // 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
-dshot_result_t DShotRMT::_initDShotEncoder()
-{
- // Create copy encoder configuration
- rmt_copy_encoder_config_t encoder_config = {};
-
- // Create encoder instance
- if (rmt_new_copy_encoder(&encoder_config, &_dshot_encoder) != DSHOT_OK)
- {
- return {false, ENCODER_INIT_FAILED};
- }
-
- return {true, TX_INIT_SUCCESS};
-}
-
// Send throttle value
dshot_result_t DShotRMT::sendThrottle(uint16_t throttle)
{
@@ -288,14 +181,14 @@ dshot_result_t DShotRMT::getTelemetry(uint16_t magnet_count)
return result;
}
- //
- if (_telemetry_ready_flag)
+ // Check for new telemetry data
+ if (_telemetry_ready_flag_atomic)
{
- _telemetry_ready_flag = false;
+ _telemetry_ready_flag_atomic = false;
uint16_t erpm = _last_erpm_atomic;
- //
+ // Calculate motor RPM from eRPM
if (erpm != DSHOT_NULL_PACKET && magnet_count >= 1)
{
uint8_t pole_pairs = max(POLE_PAIRS_MIN, (magnet_count / MAGNETS_PER_POLE_PAIR));
@@ -311,6 +204,131 @@ dshot_result_t DShotRMT::getTelemetry(uint16_t magnet_count)
return result;
}
+// Public Info & Debug Functions
+// Print timing diagnostic information to specified stream
+void DShotRMT::printDShotInfo(Stream &output) const
+{
+ output.println(" ");
+ output.println(" === DShot Signal Info === ");
+
+ // Current DShot mode
+ output.printf("Current Mode: DSHOT%d\n",
+ _mode == DSHOT150 ? 150 :
+ _mode == DSHOT300 ? 300 :
+ _mode == DSHOT600 ? 600 :
+ _mode == DSHOT1200 ? 1200 : 0);
+
+ output.printf("Bidirectional: %s\n", _is_bidirectional ? "YES" : "NO");
+
+ // Packet Info
+ output.printf("Current Packet: ");
+
+ // Print bit by bit
+ for (int i = DSHOT_BITS_PER_FRAME - 1; i >= 0; --i)
+ {
+ if ((_parsed_packet >> i) & 1)
+ {
+ output.print("1");
+ }
+ else
+ {
+ output.print("0");
+ }
+ }
+ output.printf("\n");
+
+ output.printf("Current Value: %u\n", _packet.throttle_value);
+}
+
+// Print CPU information
+void DShotRMT::printCpuInfo(Stream &output) const
+{
+ output.println(" ");
+ output.println(" === CPU Info === ");
+ output.printf("Chip Model: %s\n", ESP.getChipModel());
+ output.printf("Chip Revision: %d\n", ESP.getChipRevision());
+ output.printf("CPU Freq = %lu MHz\n", ESP.getCpuFreqMHz());
+ output.printf("XTAL Freq = %lu MHz\n", getXtalFrequencyMhz());
+ output.printf("APB Freq = %lu Hz\n", getApbFrequency());
+}
+
+// Private Initialization Functions
+// Initialize RMT TX channel
+dshot_result_t DShotRMT::_initTXChannel()
+{
+ // Configure TX channel
+ _tx_channel_config.gpio_num = _gpio;
+ _tx_channel_config.clk_src = DSHOT_CLOCK_SRC_DEFAULT;
+ _tx_channel_config.resolution_hz = DSHOT_RMT_RESOLUTION;
+ _tx_channel_config.mem_block_symbols = RMT_BUFFER_SYMBOLS;
+ _tx_channel_config.trans_queue_depth = RMT_QUEUE_DEPTH;
+
+ // Config RMT TX
+ _transmit_config.loop_count = 0; // No automatic loops - real-time calculation
+ _transmit_config.flags.eot_level = _is_bidirectional ? 1 : 0; // Telemetric Bit used as bidir flag
+
+ // Create RMT TX channel
+ if (rmt_new_tx_channel(&_tx_channel_config, &_rmt_tx_channel) != DSHOT_OK)
+ {
+ return {false, TX_INIT_FAILED};
+ }
+
+ // Enable TX channel
+ if (rmt_enable(_rmt_tx_channel) != DSHOT_OK)
+ {
+ return {false, TX_INIT_FAILED};
+ }
+
+ return {true, TX_INIT_SUCCESS};
+}
+
+// Initialize RMT RX channel
+dshot_result_t DShotRMT::_initRXChannel()
+{
+ // Direct RMT symbol processing - Performance optimized
+ _rx_event_callbacks.on_recv_done = _rmt_rx_done_callback;
+
+ // Config RMT RX
+ _rx_channel_config.gpio_num = _gpio;
+ _rx_channel_config.clk_src = DSHOT_CLOCK_SRC_DEFAULT;
+ _rx_channel_config.resolution_hz = DSHOT_RMT_RESOLUTION;
+ _rx_channel_config.mem_block_symbols = RMT_BUFFER_SYMBOLS;
+
+ // Config RMT RX parameters
+ _receive_config.signal_range_min_ns = DSHOT_PULSE_MIN;
+ _receive_config.signal_range_max_ns = DSHOT_PULSE_MAX;
+
+ // Create RMT RX channel
+ if (rmt_new_rx_channel(&_rx_channel_config, &_rmt_rx_channel) != DSHOT_OK)
+ {
+ return {false, RX_INIT_FAILED};
+ }
+
+ // Enable RX channel
+ if (rmt_enable(_rmt_rx_channel) != DSHOT_OK)
+ {
+ return {false, RX_INIT_FAILED};
+ }
+
+ return {true, RX_INIT_SUCCESS};
+}
+
+// Initialize DShot encoder
+dshot_result_t DShotRMT::_initDShotEncoder()
+{
+ // Create copy encoder configuration
+ rmt_copy_encoder_config_t encoder_config = {};
+
+ // Create encoder instance
+ if (rmt_new_copy_encoder(&encoder_config, &_dshot_encoder) != DSHOT_OK)
+ {
+ return {false, ENCODER_INIT_FAILED};
+ }
+
+ return {true, TX_INIT_SUCCESS};
+}
+
+// Private Packet Management Functions
// Build a complete DShot packet
dshot_packet_t DShotRMT::_buildDShotPacket(const uint16_t &value)
{
@@ -361,7 +379,27 @@ uint16_t DShotRMT::_calculateCRC(const uint16_t data)
return crc;
}
-// Per calculate bits - Performance optimized
+// Configure RMT ticks for DShot timings
+void DShotRMT::_configureRMTTiming()
+{
+ // Convert DShot timings (us) to RMT ticks
+ _rmt_ticks.ticks_per_bit = static_cast(_dshot_timing.bit_length_us * RMT_TICKS_PER_US);
+ _rmt_ticks.t1h_ticks = static_cast(_dshot_timing.t1h_lenght_us * RMT_TICKS_PER_US);
+ _rmt_ticks.t0h_ticks = _rmt_ticks.t1h_ticks >> 1; // High time for a 1 is always double that of a 0
+ _rmt_ticks.t1l_ticks = _rmt_ticks.ticks_per_bit - _rmt_ticks.t1h_ticks;
+ _rmt_ticks.t0l_ticks = _rmt_ticks.ticks_per_bit - _rmt_ticks.t0h_ticks;
+
+ // Pause between frames is frame time in us, some padding and about 30 us is added by hardware
+ _frame_timer_us = (static_cast(_dshot_timing.bit_length_us * DSHOT_BITS_PER_FRAME) << 1) + DSHOT_PADDING_US;
+
+ // Double frame time for bidirectional mode (includes response time)
+ if (_is_bidirectional)
+ {
+ _frame_timer_us = (_frame_timer_us << 1);
+ }
+}
+
+// Precalculate bit positions for performance optimization
void DShotRMT::_preCalculateBitPositions()
{
for (int i = 0; i < DSHOT_BITS_PER_FRAME; ++i)
@@ -370,6 +408,7 @@ void DShotRMT::_preCalculateBitPositions()
}
}
+// Private Frame Processing Functions
// Transmit DShot packet via RMT
dshot_result_t DShotRMT::_sendDShotFrame(const dshot_packet_t &packet)
{
@@ -379,7 +418,7 @@ dshot_result_t DShotRMT::_sendDShotFrame(const dshot_packet_t &packet)
return {false, TIMING_CORRECTION};
}
- // Enable RMT RX before RMT TX
+ // Enable RMT RX before RMT TX (bidirectional mode)
if (_is_bidirectional)
{
// Calculate transmission data size
@@ -442,7 +481,7 @@ bool DShotRMT::_encodeDShotFrame(const dshot_packet_t &packet, rmt_symbol_word_t
// Decode MSB
for (int i = 0; i < DSHOT_BITS_PER_FRAME; ++i)
{
- // Use precalculated bit positions - Performace optimized
+ // Use precalculated bit positions - Performance optimized
int bit_position = _bitPositions[i];
bool bit = (_parsed_packet >> bit_position) & 0b0000000000000001;
@@ -455,7 +494,7 @@ bool DShotRMT::_encodeDShotFrame(const dshot_packet_t &packet, rmt_symbol_word_t
return DSHOT_OK;
}
-// Decodes a DShot telemetry frame from received RMT symbols.
+// Decode DShot telemetry frame from received RMT symbols
uint16_t DShotRMT::_decodeDShotFrame(const rmt_symbol_word_t *symbols)
{
uint32_t gcr_value = 0;
@@ -476,10 +515,8 @@ uint16_t DShotRMT::_decodeDShotFrame(const rmt_symbol_word_t *symbols)
// The first bit of the GCR frame is a start bit and is discarded.
uint16_t data_and_crc = (decoded_frame & DSHOT_FULL_PACKET);
- // Cutting 4 bits?
+ // Extract data (first 12 bits) and CRC (last 4 bits)
uint16_t received_data = data_and_crc >> 4;
-
- // Masking CRC
uint16_t received_crc = data_and_crc & DSHOT_CRC_MASK;
// Telemetry request bit has to be 1
@@ -502,6 +539,7 @@ uint16_t DShotRMT::_decodeDShotFrame(const rmt_symbol_word_t *symbols)
return received_data & DSHOT_THROTTLE_MAX;
}
+// Private Timing Control Functions
// Check if enough time has passed for next transmission
bool DShotRMT::_timer_signal()
{
@@ -521,49 +559,25 @@ bool DShotRMT::_timer_reset()
return DSHOT_OK;
}
-// Print timing diagnostic information to specified stream
-void DShotRMT::printDShotInfo(Stream &output) const
+// Static Callback Functions
+// Callback for RMT RX
+bool DShotRMT::_rmt_rx_done_callback(rmt_channel_handle_t rmt_rx_channel, const rmt_rx_done_event_data_t *edata, void *user_data)
{
- output.println(" ");
- output.println(" === DShot Signal Info === ");
+ DShotRMT *instance = static_cast(user_data);
- // Current DShot mode
- output.printf("Current Mode: DSHOT%d\n",
- _mode == DSHOT150 ? 150 :
- _mode == DSHOT300 ? 300 :
- _mode == DSHOT600 ? 600 :
- _mode == DSHOT1200 ? 1200 : 0);
-
- output.printf("Bidirectional: %s\n", _is_bidirectional ? "YES" : "NO");
-
- // Packet Info
- output.printf("Current Packet: ");
-
- // Print bit by bit
- for (int i = DSHOT_BITS_PER_FRAME - 1; i >= 0; --i)
+ // ISR check for valid data
+ if (edata && edata->num_symbols >= GCR_BITS_PER_FRAME && edata->num_symbols <= GCR_BITS_PER_FRAME)
{
- if ((_parsed_packet >> i) & 1)
+ // Direct decoding
+ uint16_t erpm = instance->_decodeDShotFrame(edata->received_symbols);
+
+ if (erpm != DSHOT_NULL_PACKET)
{
- output.print("1");
- }
- else
- {
- output.print("0");
+ // Atomic writes - thread-safe
+ instance->_last_erpm_atomic = erpm;
+ instance->_telemetry_ready_flag_atomic = true;
}
}
- output.printf("\n");
- output.printf("Current Value: %u\n", _packet.throttle_value);
-}
-
-// Print CPU information
-void DShotRMT::printCpuInfo(Stream &output) const
-{
- output.println(" ");
- output.println(" === CPU Info === ");
- output.printf("Chip Model: %s\n", ESP.getChipModel());
- output.printf("Chip Revision: %d\n", ESP.getChipRevision());
- output.printf("CPU Freq = %lu MHz\n", ESP.getCpuFreqMHz());
- output.printf("XTAL Freq = %lu MHz\n", getXtalFrequencyMhz());
- output.printf("APB Freq = %lu Hz\n", getApbFrequency());
+ return false;
}
diff --git a/src/DShotRMT.h b/src/DShotRMT.h
index 31cad2d..954d354 100644
--- a/src/DShotRMT.h
+++ b/src/DShotRMT.h
@@ -10,12 +10,12 @@
#include
#include
-#include
#include
#include
#include
+#include
-// DShot Protocol Constants
+// DShot Protocol Constants & Types
static constexpr auto DSHOT_THROTTLE_FAILSAFE = 0;
static constexpr auto DSHOT_THROTTLE_MIN = 48;
static constexpr auto DSHOT_THROTTLE_MAX = 2047;
@@ -32,7 +32,7 @@ typedef enum
DSHOT1200
} dshot_mode_t;
-// DShot Packet
+// DShot Packet Structure
typedef struct
{
uint16_t throttle_value : 11;
@@ -40,14 +40,14 @@ typedef struct
uint16_t checksum : 4;
} dshot_packet_t;
-// DShot Timings
+// DShot Timing Configuration
typedef struct
{
double bit_length_us;
double t1h_lenght_us;
} dshot_timing_us_t;
-// RMT Ticks Configuration
+// RMT Timing Configuration
typedef struct
{
uint16_t ticks_per_bit;
@@ -57,7 +57,7 @@ typedef struct
uint16_t t0l_ticks;
} rmt_ticks_t;
-// Unified DShot result structure
+// Unified DShot Result Structure
typedef struct
{
bool success;
@@ -66,46 +66,46 @@ typedef struct
uint16_t motor_rpm;
} dshot_result_t;
-// Naming convention
+// Command Type Alias
typedef dshotCommands_e dshot_commands_t;
-// --- HELPERS ---
+// Helper Functions
void printDShotResult(dshot_result_t &result, Stream &output = Serial);
//
+// DShotRMT Main Class
class DShotRMT
{
public:
- // Constructor with GPIO enum
+ // Constructors & Destructor
explicit DShotRMT(gpio_num_t gpio = GPIO_NUM_16, dshot_mode_t mode = DSHOT300, bool is_bidirectional = false);
-
- // Constructor with pin number
DShotRMT(uint16_t pin_nr, dshot_mode_t mode, bool is_bidirectional);
-
- // Destructor for "better" code
~DShotRMT();
+ // Public Core Functions
// Initialize the RMT module and DShot config
dshot_result_t begin();
-
+
// Send throttle value (48-2047)
dshot_result_t sendThrottle(uint16_t throttle);
-
+
// Send DShot command (0-47)
dshot_result_t sendCommand(uint16_t command);
-
- // --- GETTERS ---
+
+ // Get telemetry data (bidirectional mode only)
+ dshot_result_t getTelemetry(uint16_t magnet_count = DEFAULT_MOTOR_MAGNET_COUNT);
+
+ // Public Getter Functions
gpio_num_t getGPIO() const { return _gpio; }
uint16_t getDShotPacket() const { return _parsed_packet; }
bool is_bidirectional() const { return _is_bidirectional; }
dshot_mode_t getMode() const { return _mode; }
- dshot_result_t getTelemetry(uint16_t magnet_count = DEFAULT_MOTOR_MAGNET_COUNT);
- // --- INFO ---
+ // Public Info & Debug Functions
void printDShotInfo(Stream &output = Serial) const;
void printCpuInfo(Stream &output = Serial) const;
-
- // --- DEPRECATED METHODS ---
+
+ // Deprecated Methods
[[deprecated("Use sendThrottle() instead")]]
bool setThrottle(uint16_t throttle)
{
@@ -128,67 +128,30 @@ public:
}
private:
- // --- CONFIG ---
- gpio_num_t _gpio;
- dshot_mode_t _mode;
- bool _is_bidirectional;
- uint32_t _frame_timer_us;
- rmt_ticks_t _rmt_ticks;
- const dshot_timing_us_t &_dshot_timing;
- uint16_t _last_throttle;
-
- // --- TIMING & PACKET VARIABLES ---
- uint64_t _last_transmission_time_us;
- uint16_t _parsed_packet;
- dshot_packet_t _packet;
- uint8_t _bitPositions[DSHOT_BITS_PER_FRAME];
- uint16_t _level0;
- uint16_t _level1;
-
- // --- RMT HARDWARE HANDLES ---
- rmt_channel_handle_t _rmt_tx_channel;
- rmt_channel_handle_t _rmt_rx_channel;
- rmt_encoder_handle_t _dshot_encoder;
-
- // --- RMT CONFIG STRUCTURES ---
- rmt_tx_channel_config_t _tx_channel_config;
- rmt_rx_channel_config_t _rx_channel_config;
- rmt_transmit_config_t _transmit_config;
- rmt_receive_config_t _receive_config;
-
- // --- INITS ---
- dshot_result_t _initTXChannel();
- dshot_result_t _initRXChannel();
- dshot_result_t _initDShotEncoder();
-
- // --- PACKET MANAGEMENT ---
- dshot_packet_t _buildDShotPacket(const uint16_t &value);
- uint16_t _parseDShotPacket(const dshot_packet_t &packet);
- uint16_t _calculateCRC(const uint16_t data);
- void _preCalculateBitPositions();
-
- // --- FRAME PROCESSING ---
- dshot_result_t _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);
-
- // --- TIMING CONTROL ---
- bool IRAM_ATTR _timer_signal();
- bool _timer_reset();
-
- // -- CALLBACKS ---
- rmt_rx_event_callbacks_t _rx_event_callbacks;
- 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);
-
- // --- DSHOT DEFAULTS ---
- static constexpr auto const DSHOT_TELEMETRY_INVALID = 0b1111111111111111;
-
- // --- CONSTANTS & ERROR MESSAGES ---
+ // Configuration Constants
static constexpr bool DSHOT_OK = 0;
static constexpr bool DSHOT_ERROR = 1;
+
+ static constexpr auto const DSHOT_NULL_PACKET = 0b0000000000000000;
+ static constexpr auto const DSHOT_FULL_PACKET = 0b1111111111111111;
+ static constexpr auto const DSHOT_CRC_MASK = 0b0000000000001111;
+ static constexpr auto const DSHOT_CLOCK_SRC_DEFAULT = RMT_CLK_SRC_DEFAULT;
+ static constexpr auto const DSHOT_RMT_RESOLUTION = 8 * 1000 * 1000; // 8 MHz resolution
+ static constexpr auto const RMT_TICKS_PER_US = DSHOT_RMT_RESOLUTION / (1 * 1000 * 1000); // RMT Ticks per microsecond
+ static constexpr auto const RMT_BUFFER_SIZE = DSHOT_BITS_PER_FRAME;
+ static constexpr auto const DSHOT_RX_TIMEOUT_MS = 2;
+ static constexpr auto const DSHOT_PADDING_US = 3;
+ static constexpr auto const RMT_BUFFER_SYMBOLS = 64;
+ static constexpr auto const RMT_QUEUE_DEPTH = 1;
+ static constexpr auto const GCR_BITS_PER_FRAME = 21; // Number of GCR bits in a DShot answer frame
+ static constexpr auto const POLE_PAIRS_MIN = 1;
+ static constexpr auto const MAGNETS_PER_POLE_PAIR = 2;
+ static constexpr auto const NO_DSHOT_TELEMETRY = 0;
+ static constexpr auto const DSHOT_PULSE_MIN = 3000; // 3us minimum pulse
+ static constexpr auto const DSHOT_PULSE_MAX = 60000; // 60us maximum pulse
+ static constexpr auto const DSHOT_TELEMETRY_INVALID = 0b1111111111111111;
+ // Error Messages
static constexpr char const *NONE = "";
static constexpr char const *UNKNOWN_ERROR = "Unknown Error!";
static constexpr char const *INIT_SUCCESS = "SignalGeneratorRMT initialized successfully";
@@ -210,26 +173,61 @@ private:
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!";
+
+ // Core Configuration Variables
+ gpio_num_t _gpio;
+ dshot_mode_t _mode;
+ bool _is_bidirectional;
+ const dshot_timing_us_t &_dshot_timing;
+ uint32_t _frame_timer_us;
- // Configuration Constants
- static constexpr auto const DSHOT_NULL_PACKET = 0b0000000000000000;
- static constexpr auto const DSHOT_FULL_PACKET = 0b1111111111111111;
- static constexpr auto const DSHOT_CRC_MASK = 0b0000000000001111;
- static constexpr auto const DSHOT_CLOCK_SRC_DEFAULT = RMT_CLK_SRC_DEFAULT;
- static constexpr auto const DSHOT_RMT_RESOLUTION = 8 * 1000 * 1000; // 8 MHz resolution
- static constexpr auto const RMT_TICKS_PER_US = DSHOT_RMT_RESOLUTION / (1 * 1000 * 1000); // RMT Ticks per microsecond, based on the RMT resolution in MHz
- static constexpr auto const RMT_BUFFER_SIZE = DSHOT_BITS_PER_FRAME;
- static constexpr auto const DSHOT_RX_TIMEOUT_MS = 2; // Never reached
- static constexpr auto const DSHOT_PADDING_US = 3;
- static constexpr auto const RMT_BUFFER_SYMBOLS = 64;
- static constexpr auto const RMT_QUEUE_DEPTH = 1;
- static constexpr auto const GCR_BITS_PER_FRAME = 21; // Number of GCR bits in a DShot answer frame (1 start + 16 data + 4 CRC)
- static constexpr auto const POLE_PAIRS_MIN = 1;
- static constexpr auto const MAGNETS_PER_POLE_PAIR = 2;
- static constexpr auto const NO_DSHOT_TELEMETRY = 0;
+ // Timing & Packet Variables
+ rmt_ticks_t _rmt_ticks;
+ uint16_t _last_throttle;
+ uint64_t _last_transmission_time_us;
+ uint16_t _parsed_packet;
+ dshot_packet_t _packet;
+ uint8_t _bitPositions[DSHOT_BITS_PER_FRAME];
+ uint16_t _level0;
+ uint16_t _level1;
+
+ // RMT Hardware Handles
+ rmt_channel_handle_t _rmt_tx_channel;
+ rmt_channel_handle_t _rmt_rx_channel;
+ rmt_encoder_handle_t _dshot_encoder;
- // Smallest pulse for DShot1200 is 2us. Largest for DShot150 is 40us.
- // The range is set from 3us (3000ns) to 60us (60000ns) to be safe across all modes.
- static constexpr auto const DSHOT_PULSE_MIN = 3000;
- static constexpr auto const DSHOT_PULSE_MAX = 60000;
+ // RMT Configuration Structures
+ rmt_tx_channel_config_t _tx_channel_config;
+ rmt_rx_channel_config_t _rx_channel_config;
+ rmt_transmit_config_t _transmit_config;
+ rmt_receive_config_t _receive_config;
+
+ // Bidirectional / Telemetry Variables
+ rmt_rx_event_callbacks_t _rx_event_callbacks;
+ std::atomic _last_erpm_atomic;
+ std::atomic _telemetry_ready_flag_atomic;
+
+ // Private Initialization Functions
+ dshot_result_t _initTXChannel();
+ dshot_result_t _initRXChannel();
+ dshot_result_t _initDShotEncoder();
+
+ // Private Packet Management Functions
+ dshot_packet_t _buildDShotPacket(const uint16_t &value);
+ uint16_t _parseDShotPacket(const dshot_packet_t &packet);
+ uint16_t _calculateCRC(const uint16_t data);
+ void _configureRMTTiming();
+ void _preCalculateBitPositions();
+
+ // Private Frame Processing Functions
+ dshot_result_t _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);
+
+ // Private Timing Control Functions
+ bool IRAM_ATTR _timer_signal();
+ bool _timer_reset();
+
+ // Static Callback Functions
+ 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);
};
diff --git a/src/web_content.h b/src/web_content.h
deleted file mode 100644
index 65bbe01..0000000
--- a/src/web_content.h
+++ /dev/null
@@ -1,361 +0,0 @@
-/**
- * @file web_content.h
- * @brief DShotRMT_Control Website content with Arming Switch
- * @author Wastl Kraus
- * @date 2025-09-09
- * @license MIT
- */
-
-#pragma once
-
-// Web Site Content
-const char index_html[] PROGMEM = R"rawliteral(
-
-
-
-
-
-
- DShotRMT Web Client
-
-
-
-
- DShotRMT Web Client
-
-
-
-
- ARMING SWITCH
-
-
-
- DISARMED
-
-
- ⚠️ Disabled when disarmed!
-
-
-
-
-
-
-
- RPM: --
-
-
-
-
-
-
-
-
-)rawliteral";
\ No newline at end of file