From fbf4559da3e94de4235265a796054f918a3be84b Mon Sep 17 00:00:00 2001 From: Wastl Kraus Date: Sat, 13 Sep 2025 11:54:30 +0200 Subject: [PATCH] Internal clean up Some sorting and grouping --- .github/workflows/ci.yml | 128 +++++---- examples/web_client/web_client.ino | 352 +++++++++++++++++++++++++ examples/web_control/web_control.ino | 352 +++++++++++++++++++++++++ library.properties | 2 +- src/DShotRMT.cpp | 380 ++++++++++++++------------- src/DShotRMT.h | 196 +++++++------- src/web_content.h | 361 ------------------------- 7 files changed, 1071 insertions(+), 700 deletions(-) delete mode 100644 src/web_content.h 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! +
+
+ + +
+
0
+ +
+ +
+ 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! +
+
+ + +
+
0
+ +
+ +
+ 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! -
-
- - -
-
0
- -
- -
- RPM: -- -
-
- - - - - - -)rawliteral"; \ No newline at end of file