diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index aa77d15..c950f20 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,98 +1,92 @@ +# This GitHub Actions workflow is designed to ensure code quality and +# successful compilation for an ESP32 Arduino library. +# It runs on every push to any branch. + name: ESP32 Build & Quality Check +# Set permissions for the GitHub token. +# 'contents: read' is required to checkout the repository. permissions: contents: read +# Define the trigger for the workflow. +# It runs on any push to any branch. on: push: branches: [ '*' ] +# Concurrency control ensures that only the latest commit for a branch +# runs, canceling any workflows already in progress for the same branch. concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true jobs: # ============================================================================ - # Code Quality & Linting + # Prepare Environment & Cache + # This job installs the Arduino core and libraries and saves them to a cache + # for later jobs to use. # ============================================================================ - - quality-check-linux: - name: Arduino Lint Check (Linux) + prepare-cache: + name: Prepare Cache (Linux) runs-on: ubuntu-latest - timeout-minutes: 10 + steps: - - uses: actions/checkout@v5 - - uses: arduino/setup-arduino-cli@v2 - - name: Restore Arduino Core Cache - id: cache-core-linux - uses: actions/cache/restore@v4 + - name: Checkout Repository + uses: actions/checkout@v4 + + - name: Setup Arduino CLI + uses: arduino/setup-arduino-cli@v2 + + - name: Restore Arduino Core & Libraries Cache + uses: actions/cache@v4 + id: cache-arduino with: path: | ~/.arduino15/packages ~/.arduino15/cache - key: arduino-core-linux-esp32-v1 - restore-keys: | - arduino-core-linux- - - name: Ensure ESP32 Core is installed (Linux) + ~/Arduino/libraries + key: linux-arduino-esp32-v2-${{ hashFiles('**/library.properties') }} + + - name: Install Dependencies + if: steps.cache-arduino.outputs.cache-hit != 'true' shell: bash run: | - if [[ "${{ steps.cache-core-linux.outputs.cache-hit }}" != "true" ]]; then - arduino-cli core update-index --additional-urls https://raw.githubusercontent.com/espressif/arduino-esp32/gh-pages/package_esp32_index.json - fi - if ! arduino-cli core list | grep -q 'esp32'; then - arduino-cli core install esp32:esp32 - fi - - name: Save Arduino Core Cache - if: always() && steps.cache-core-linux.outputs.cache-hit != 'true' - uses: actions/cache/save@v4 - with: - path: | - ~/.arduino15/packages - ~/.arduino15/cache - key: arduino-core-linux-esp32-v1 - - uses: arduino/arduino-lint-action@v2 - with: - path: ${{ github.workspace }} - compliance: strict - library-manager: update - verbose: true + 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" + mkdir -p "$HOME/Arduino/libraries" + git clone --depth=1 https://github.com/ESP32Async/ESPAsyncWebServer "$HOME/Arduino/libraries/ESPAsyncWebServer" + git clone --depth=1 https://github.com/ESP32Async/AsyncTCP "$HOME/Arduino/libraries/AsyncTCP" - quality-check-windows: - name: Arduino Lint Check (Windows) - runs-on: windows-latest + # ============================================================================ + # Code Quality & Linting + # This job runs after the cache is prepared and checks the code quality. + # ============================================================================ + quality-check: + name: Arduino Lint Check (Linux) + needs: prepare-cache + runs-on: ubuntu-latest timeout-minutes: 10 + steps: - - uses: actions/checkout@v5 - - uses: arduino/setup-arduino-cli@v2 - - name: Restore Arduino Core Cache - id: cache-core-win - uses: actions/cache/restore@v4 + - name: Checkout Repository + uses: actions/checkout@v4 + + - name: Setup Arduino CLI + uses: arduino/setup-arduino-cli@v2 + + - name: Restore Arduino Core & Libraries Cache + uses: actions/cache@v4 with: path: | ~/.arduino15/packages ~/.arduino15/cache - key: arduino-core-windows-esp32-v1 - restore-keys: | - arduino-core-windows- - - name: Ensure ESP32 Core is installed (Windows) - if: steps.cache-core-win.outputs.cache-hit != 'true' - shell: pwsh - run: | - if ($env:cache-core-win.outputs.cache-hit -ne 'true') { - arduino-cli core update-index --additional-urls https://raw.githubusercontent.com/espressif/arduino-esp32/gh-pages/package_esp32_index.json - } - if (-not (arduino-cli core list | Select-String 'esp32')) { - arduino-cli core install esp32:esp32 - } - - name: Save Arduino Core Cache - if: always() && steps.cache-core-win.outputs.cache-hit != 'true' - uses: actions/cache/save@v4 - with: - path: | - ~/.arduino15/packages - ~/.arduino15/cache - key: arduino-core-windows-esp32-v1 - - uses: arduino/arduino-lint-action@v2 + ~/Arduino/libraries + key: linux-arduino-esp32-v2-${{ hashFiles('**/library.properties') }} + + - name: Run Arduino Lint + uses: arduino/arduino-lint-action@v2 with: path: ${{ github.workspace }} compliance: strict @@ -101,114 +95,66 @@ jobs: # ============================================================================ # Compilation Test + # This job compiles all example sketches. # ============================================================================ - - compile-all-sketches: - name: Compile ${{ matrix.sketch }} (${{ matrix.os }}) - runs-on: ${{ matrix.os }} + compile-sketches: + name: Compile ${{ matrix.sketch }} (Linux) + needs: prepare-cache + runs-on: ubuntu-latest timeout-minutes: 15 strategy: fail-fast: false matrix: - os: [ubuntu-latest, windows-latest] sketch: - examples/throttle_percent/throttle_percent.ino - examples/dshot300/dshot300.ino - examples/web_control/web_control.ino - examples/web_client/web_client.ino + steps: - - uses: actions/checkout@v5 - - uses: arduino/setup-arduino-cli@v2 + - name: Checkout Repository + uses: actions/checkout@v4 + + - name: Setup Arduino CLI + uses: arduino/setup-arduino-cli@v2 - name: Restore Arduino Core & Libraries Cache - id: cache-all - uses: actions/cache/restore@v4 + uses: actions/cache@v4 with: path: | ~/.arduino15/packages ~/.arduino15/cache ~/Arduino/libraries - key: ${{ runner.os }}-arduino-full-esp32-v1-${{ hashFiles('**/library.properties', '**/platform.txt') }} - restore-keys: | - ${{ runner.os }}-arduino-full-esp32-v1- - - - name: Ensure ESP32 Core and Dependencies (Linux) - if: runner.os == 'Linux' && steps.cache-all.outputs.cache-hit != 'true' - shell: bash - run: | - 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 - mkdir -p ~/Arduino/libraries - git clone --depth=1 https://github.com/bblanchon/ArduinoJson.git ~/Arduino/libraries/ArduinoJson - git clone --depth=1 https://github.com/ESP32Async/ESPAsyncWebServer ~/Arduino/libraries/ESPAsyncWebServer - git clone --depth=1 https://github.com/ESP32Async/AsyncTCP ~/Arduino/libraries/AsyncTCP - - - name: Ensure ESP32 Core and Dependencies (Windows) - if: runner.os == 'Windows' && steps.cache-all.outputs.cache-hit != 'true' - shell: pwsh - run: | - 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 - if (-not (Test-Path -Path "$env:USERPROFILE/Arduino/libraries/ArduinoJson")) { - git clone --depth=1 https://github.com/bblanchon/ArduinoJson.git "$env:USERPROFILE/Arduino/libraries/ArduinoJson" - } - if (-not (Test-Path -Path "$env:USERPROFILE/Arduino/libraries/ESPAsyncWebServer")) { - git clone --depth=1 https://github.com/ESP32Async/ESPAsyncWebServer "$env:USERPROFILE/Arduino/libraries/ESPAsyncWebServer" - } - if (-not (Test-Path -Path "$env:USERPROFILE/Arduino/libraries/AsyncTCP")) { - git clone --depth=1 https://github.com/ESP32Async/AsyncTCP "$env:USERPROFILE/Arduino/libraries/AsyncTCP" - } - - - name: Save Arduino Core & Libraries Cache - if: always() && steps.cache-all.outputs.cache-hit != 'true' - uses: actions/cache/save@v4 - with: - path: | - ~/.arduino15/packages - ~/.arduino15/cache - ~/Arduino/libraries - key: ${{ runner.os }}-arduino-full-esp32-v1-${{ hashFiles('**/library.properties', '**/platform.txt') }} - - - name: Compile Sketch (Linux) - shell: bash - if: runner.os == 'Linux' + key: linux-arduino-esp32-v2-${{ hashFiles('**/library.properties') }} + + - name: Compile Sketch run: arduino-cli compile --fqbn esp32:esp32:esp32 --library ${{ github.workspace }} "${{ matrix.sketch }}" - - name: Compile Sketch (Windows) - shell: pwsh - if: runner.os == 'Windows' - run: arduino-cli compile --fqbn esp32:esp32:esp32 --library ${{ github.workspace }} "${{ matrix.sketch }}" # ============================================================================ # Build Status Report + # This job runs last and provides a summary of the build status. # ============================================================================ build-summary: name: Build Summary runs-on: ubuntu-latest - if: always() + if: always() # This ensures the job runs even if previous jobs fail needs: - - quality-check-linux - - quality-check-windows - - compile-all-sketches + - quality-check + - compile-sketches + steps: - name: Create Build Summary run: | echo "# 🔧 DShotRMT Build Report" >> $GITHUB_STEP_SUMMARY echo "| Check | Status | Details |" >> $GITHUB_STEP_SUMMARY echo "|-------|--------|---------|" >> $GITHUB_STEP_SUMMARY - - [[ "${{ needs.quality-check-linux.result }}" == "success" && "${{ needs.quality-check-windows.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-all-sketches.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 - - [[ "${{ needs.quality-check-linux.result }}" == "success" && "${{ needs.quality-check-windows.result }}" == "success" && "${{ needs.compile-all-sketches.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 - \ No newline at end of file + 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 for errors |" >> $GITHUB_STEP_SUMMARY + fi + if [[ "${{ needs.compile-sketches.result }}" == "success" ]]; then + echo "| 🔨 Compilation | ✅ Passed | All examples compiled successfully |" >> $GITHUB_STEP_SUMMARY + else + echo "| 🔨 Compilation | ❌ Failed | One or more examples failed to compile |" >> $GITHUB_STEP_SUMMARY + fi diff --git a/README.md b/README.md index 81ea373..890cb95 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ [![Arduino CI](https://github.com/derdoktor667/DShotRMT/actions/workflows/ci.yml/badge.svg)](https://github.com/derdoktor667/DShotRMT/actions/workflows/ci.yml) -A C++ library for generating DShot signals on ESP32 microcontrollers using the **modern ESP-IDF 5 RMT encoder API** (`rmt_tx.h` / `rmt_rx.h`). It provides a simple, efficient, and hardware-timed way to control brushless motors in both Arduino and ESP-IDF projects. The legacy version using the old `rmt.h` API is available in the `oldAPI` branch. +A C++ library for generating DShot signals on ESP32 microcontrollers using the **modern ESP-IDF 5 RMT encoder API** (`rmt_tx.h` / `rmt_rx.h`). It leverages the standard `rmt_bytes_encoder` to ensure an efficient, hardware-timed, and maintainable implementation. The library provides a simple way to control brushless motors in both Arduino and ESP-IDF projects. The legacy version using the old `rmt.h` API is available in the `oldAPI` branch. ## 🚀 Core Features diff --git a/examples/dshot300/dshot300.ino b/examples/dshot300/dshot300.ino index abdb6bd..619833d 100644 --- a/examples/dshot300/dshot300.ino +++ b/examples/dshot300/dshot300.ino @@ -88,7 +88,7 @@ void loop() if (IS_BIDIRECTIONAL) { dshot_result_t telem_result = motor01.getTelemetry(); - printDShotResult(telem_result); + DShotRMT::printDShotResult(telem_result); } USB_SERIAL.println("Type 'help' to show Menu"); @@ -128,7 +128,7 @@ void handleSerialInput(const String &input, uint16_t &throttle, bool &continuous throttle = 0; continuous_throttle = true; dshot_result_t result = motor01.sendCommand(DSHOT_CMD_MOTOR_STOP); - printDShotResult(result); + DShotRMT::printDShotResult(result); } else if (input == "info") { @@ -137,7 +137,7 @@ void handleSerialInput(const String &input, uint16_t &throttle, bool &continuous else if (input == "rpm" && IS_BIDIRECTIONAL) { dshot_result_t result = motor01.getTelemetry(); - printDShotResult(result); + DShotRMT::printDShotResult(result); } else if (input.startsWith("cmd ")) { @@ -149,7 +149,7 @@ void handleSerialInput(const String &input, uint16_t &throttle, bool &continuous if (cmd_num >= DSHOT_CMD_MOTOR_STOP && cmd_num <= DSHOT_CMD_MAX) { dshot_result_t result = motor01.sendCommand(cmd_num); - printDShotResult(result); + DShotRMT::printDShotResult(result); } else { @@ -171,7 +171,7 @@ void handleSerialInput(const String &input, uint16_t &throttle, bool &continuous continuous_throttle = true; dshot_result_t result = motor01.sendThrottle(throttle); - printDShotResult(result); + DShotRMT::printDShotResult(result); } else { diff --git a/examples/throttle_percent/throttle_percent.ino b/examples/throttle_percent/throttle_percent.ino index 5fb9000..13e7f8f 100644 --- a/examples/throttle_percent/throttle_percent.ino +++ b/examples/throttle_percent/throttle_percent.ino @@ -94,7 +94,7 @@ void handleSerialInput(const String &input) { // Stop motor dshot_result_t result = motor01.sendThrottlePercent(0.0f); - printDShotResult(result); + DShotRMT::printDShotResult(result); } else if (input == "info") { @@ -103,7 +103,7 @@ void handleSerialInput(const String &input) else if (input == "rpm" && IS_BIDIRECTIONAL) { dshot_result_t result = motor01.getTelemetry(); - printDShotResult(result); + DShotRMT::printDShotResult(result); } else if (input.startsWith("cmd ")) { @@ -113,7 +113,7 @@ void handleSerialInput(const String &input) if (cmd_num >= DSHOT_CMD_MOTOR_STOP && cmd_num <= DSHOT_CMD_MAX) { dshot_result_t result = motor01.sendCommand(cmd_num); - printDShotResult(result); + DShotRMT::printDShotResult(result); } else { @@ -132,7 +132,7 @@ void handleSerialInput(const String &input) if (throttle_percent >= 0.0f && throttle_percent <= 100.0f) { dshot_result_t result = motor01.sendThrottlePercent(throttle_percent); - printDShotResult(result); + DShotRMT::printDShotResult(result); } else { diff --git a/examples/web_client/web_client.ino b/examples/web_client/web_client.ino index 9d1ca57..92e0ca3 100644 --- a/examples/web_client/web_client.ino +++ b/examples/web_client/web_client.ino @@ -178,7 +178,7 @@ void loop() if (IS_BIDIRECTIONAL && isArmed) { dshot_result_t telem_result = motor01.getTelemetry(MOTOR01_MAGNET_COUNT); - printDShotResult(telem_result); + DShotRMT::printDShotResult(telem_result); } USB_SERIAL.println("Type 'help' to show Menu"); @@ -495,7 +495,7 @@ void handleSerialInput(const String &input) if (isArmed) { dshot_result_t result = motor01.getTelemetry(MOTOR01_MAGNET_COUNT); - printDShotResult(result); + DShotRMT::printDShotResult(result); } else { @@ -520,7 +520,7 @@ void handleSerialInput(const String &input) if (cmd_num >= DSHOT_CMD_MOTOR_STOP && cmd_num <= DSHOT_CMD_MAX) { dshot_result_t result = motor01.sendCommand(cmd_num); - printDShotResult(result); + DShotRMT::printDShotResult(result); } else { @@ -581,7 +581,7 @@ void handleSerialInput(const String &input) throttle = 0; continuous_throttle = false; dshot_result_t result = motor01.sendCommand(DSHOT_CMD_MOTOR_STOP); - printDShotResult(result); + DShotRMT::printDShotResult(result); return; } diff --git a/examples/web_control/web_control.ino b/examples/web_control/web_control.ino index 6cc773f..76cc775 100644 --- a/examples/web_control/web_control.ino +++ b/examples/web_control/web_control.ino @@ -146,7 +146,7 @@ void loop() if (IS_BIDIRECTIONAL && isArmed) { dshot_result_t telem_result = motor01.getTelemetry(MOTOR01_MAGNET_COUNT); - printDShotResult(telem_result); + DShotRMT::printDShotResult(telem_result); } USB_SERIAL.println("Type 'help' to show Menu"); @@ -264,7 +264,7 @@ void handleSerialInput(const String &input) if (isArmed) { dshot_result_t result = motor01.getTelemetry(MOTOR01_MAGNET_COUNT); - printDShotResult(result); + DShotRMT::printDShotResult(result); } else { @@ -288,7 +288,7 @@ void handleSerialInput(const String &input) if (cmd_num >= DSHOT_CMD_MOTOR_STOP && cmd_num <= DSHOT_CMD_MAX) { dshot_result_t result = motor01.sendCommand(cmd_num); - printDShotResult(result); + DShotRMT::printDShotResult(result); } else { @@ -341,7 +341,7 @@ void handleSerialInput(const String &input) throttle = 0; continuous_throttle = false; dshot_result_t result = motor01.sendCommand(DSHOT_CMD_MOTOR_STOP); - printDShotResult(result); + DShotRMT::printDShotResult(result); return; } diff --git a/src/DShotRMT.cpp b/src/DShotRMT.cpp index 25c86bf..04f36f0 100644 --- a/src/DShotRMT.cpp +++ b/src/DShotRMT.cpp @@ -23,8 +23,8 @@ DShotRMT::DShotRMT(gpio_num_t gpio, dshot_mode_t mode, bool is_bidirectional, ui _last_command_timestamp(0), _encoded_frame_value(0), _packet{0}, - _level0(1), // DShot standard: signal is idle-low, so pulses start by going HIGH - _level1(0), // DShot standard: signal returns to LOW after the high pulse + _pulse_level(1), // DShot standard: signal is idle-low, so pulses start by going HIGH + _idle_level(0), // DShot standard: signal returns to LOW after the high pulse _rmt_tx_channel(nullptr), _rmt_rx_channel(nullptr), _dshot_encoder(nullptr), @@ -355,9 +355,25 @@ dshot_result_t DShotRMT::_initRXChannel() dshot_result_t DShotRMT::_initDShotEncoder() { - rmt_copy_encoder_config_t encoder_config = {}; + rmt_bytes_encoder_config_t encoder_config = { + .bit0 = { + .duration0 = _rmt_ticks.t0h_ticks, + .level0 = _pulse_level, + .duration1 = _rmt_ticks.t0l_ticks, + .level1 = _idle_level, + }, + .bit1 = { + .duration0 = _rmt_ticks.t1h_ticks, + .level0 = _pulse_level, + .duration1 = _rmt_ticks.t1l_ticks, + .level1 = _idle_level, + }, + .flags = { + .msb_first = 1 // DShot is MSB first + } + }; - if (rmt_new_copy_encoder(&encoder_config, &_dshot_encoder) != DSHOT_OK) + if (rmt_new_bytes_encoder(&encoder_config, &_dshot_encoder) != DSHOT_OK) { return {false, dshot_msg_code_t::DSHOT_ERROR_ENCODER_INIT_FAILED}; } @@ -424,50 +440,27 @@ void DShotRMT::_preCalculateRMTTicks() dshot_result_t DShotRMT::_sendDShotFrame(const dshot_packet_t &packet) { // Ensure enough time has passed since the last transmission - if (!_timer_signal()) + if (!_isFrameIntervalElapsed()) { return {true, dshot_msg_code_t::DSHOT_ERROR_NONE}; } - rmt_symbol_word_t tx_symbols[DSHOT_BITS_PER_FRAME]; - dshot_result_t result = _encodeDShotFrame(packet, tx_symbols); + _encoded_frame_value = _buildDShotFrameValue(packet); - if (!result.success) - { - return result; - } + // The DShot frame is 16 bits, which is 2 bytes + size_t tx_size_bytes = sizeof(_encoded_frame_value); - size_t tx_size_bytes = DSHOT_BITS_PER_FRAME * sizeof(rmt_symbol_word_t); - - if (rmt_transmit(_rmt_tx_channel, _dshot_encoder, tx_symbols, tx_size_bytes, &_rmt_tx_config) != DSHOT_OK) + if (rmt_transmit(_rmt_tx_channel, _dshot_encoder, &_encoded_frame_value, tx_size_bytes, &_rmt_tx_config) != DSHOT_OK) { return {false, dshot_msg_code_t::DSHOT_ERROR_TRANSMISSION_FAILED}; } - _timer_reset(); // Reset the timer for the next frame + _recordFrameTransmissionTime(); // Reset the timer for the next frame return {true, dshot_msg_code_t::DSHOT_ERROR_TRANSMISSION_SUCCESS}; } -// This function needs to be fast, as it generates the RMT symbols just before sending -dshot_result_t IRAM_ATTR DShotRMT::_encodeDShotFrame(const dshot_packet_t &packet, rmt_symbol_word_t *symbols) -{ - _encoded_frame_value = _buildDShotFrameValue(packet); - for (int i = 0; i < DSHOT_BITS_PER_FRAME; ++i) - { - int bit_position = DSHOT_BITS_PER_FRAME - 1 - i; - bool bit = (_encoded_frame_value >> bit_position) & 1; - - // A '1' bit has a longer high-time, a '0' bit has a shorter high-time - symbols[i].level0 = _level0; // Go HIGH - symbols[i].duration0 = bit ? _rmt_ticks.t1h_ticks : _rmt_ticks.t0h_ticks; - symbols[i].level1 = _level1; // Go LOW - symbols[i].duration1 = bit ? _rmt_ticks.t1l_ticks : _rmt_ticks.t0l_ticks; - } - - return {true, dshot_msg_code_t::DSHOT_ERROR_ENCODING_SUCCESS}; -} // Placed in IRAM for high performance, as it's called from an ISR context uint16_t IRAM_ATTR DShotRMT::_decodeDShotFrame(const rmt_symbol_word_t *symbols) @@ -510,7 +503,7 @@ uint16_t IRAM_ATTR DShotRMT::_decodeDShotFrame(const rmt_symbol_word_t *symbols) } // Timing Control Functions -bool IRAM_ATTR DShotRMT::_timer_signal() +bool IRAM_ATTR DShotRMT::_isFrameIntervalElapsed() { // Check if the minimum interval between frames has passed uint64_t current_time = esp_timer_get_time(); @@ -518,11 +511,10 @@ bool IRAM_ATTR DShotRMT::_timer_signal() return elapsed >= _frame_timer_us; } -bool DShotRMT::_timer_reset() +void DShotRMT::_recordFrameTransmissionTime() { // Record the time of the current transmission _last_transmission_time_us = esp_timer_get_time(); - return true; } // Static Callback Functions @@ -546,14 +538,123 @@ bool IRAM_ATTR DShotRMT::_on_rx_done(rmt_channel_handle_t rmt_rx_channel, const return false; } +void DShotRMT::printDShotResult(const dshot_result_t &result, Stream &output) +{ + const char *msg_str; + switch (result.error_code) + { + case dshot_msg_code_t::DSHOT_ERROR_NONE: + msg_str = "None"; + break; + case dshot_msg_code_t::DSHOT_ERROR_UNKNOWN: + msg_str = "Unknown Error!"; + break; + case dshot_msg_code_t::DSHOT_ERROR_TX_INIT_FAILED: + msg_str = "TX RMT channel init failed!"; + break; + case dshot_msg_code_t::DSHOT_ERROR_RX_INIT_FAILED: + msg_str = "RX RMT channel init failed!"; + break; + case dshot_msg_code_t::DSHOT_ERROR_ENCODER_INIT_FAILED: + msg_str = "RMT encoder init failed!"; + break; + case dshot_msg_code_t::DSHOT_ERROR_CALLBACK_REGISTERING_FAILED: + msg_str = "RMT RX Callback registering failed!"; + break; + case dshot_msg_code_t::DSHOT_ERROR_RECEIVER_FAILED: + msg_str = "RMT receiver failed!"; + break; + case dshot_msg_code_t::DSHOT_ERROR_TRANSMISSION_FAILED: + msg_str = "Transmission failed!"; + break; + case dshot_msg_code_t::DSHOT_ERROR_THROTTLE_NOT_IN_RANGE: + msg_str = "Throttle not in range! (48 - 2047)"; + break; + case dshot_msg_code_t::DSHOT_ERROR_PERCENT_NOT_IN_RANGE: + msg_str = "Percent not in range! (0.0 - 100.0)"; + break; + case dshot_msg_code_t::DSHOT_ERROR_COMMAND_NOT_VALID: + msg_str = "Command not valid! (0 - 47)"; + break; + case dshot_msg_code_t::DSHOT_ERROR_BIDIR_NOT_ENABLED: + msg_str = "Bidirectional DShot not enabled!"; + break; + case dshot_msg_code_t::DSHOT_ERROR_TELEMETRY_FAILED: + msg_str = "No valid Telemetric Frame received!"; + break; + case dshot_msg_code_t::DSHOT_ERROR_INVALID_MAGNET_COUNT: + msg_str = "Invalid motor magnet count!"; + break; + case dshot_msg_code_t::DSHOT_ERROR_INVALID_COMMAND: + msg_str = "Invalid command!"; + break; + case dshot_msg_code_t::DSHOT_ERROR_TIMING_CORRECTION: + msg_str = "Timing correction!"; + break; + case dshot_msg_code_t::DSHOT_ERROR_INIT_FAILED: + msg_str = "SignalGeneratorRMT init failed!"; + break; + case dshot_msg_code_t::DSHOT_ERROR_INIT_SUCCESS: + msg_str = "SignalGeneratorRMT initialized successfully"; + break; + case dshot_msg_code_t::DSHOT_ERROR_TX_INIT_SUCCESS: + msg_str = "TX RMT channel initialized successfully"; + break; + case dshot_msg_code_t::DSHOT_ERROR_RX_INIT_SUCCESS: + msg_str = "RX RMT channel initialized successfully"; + break; + case dshot_msg_code_t::DSHOT_ERROR_ENCODING_SUCCESS: + msg_str = "Packet encoded successfully"; + break; + case dshot_msg_code_t::DSHOT_ERROR_TRANSMISSION_SUCCESS: + msg_str = "Transmission successfully"; + break; + case dshot_msg_code_t::DSHOT_ERROR_TELEMETRY_SUCCESS: + msg_str = "Valid Telemetric Frame received!"; + break; + case dshot_msg_code_t::DSHOT_ERROR_COMMAND_SUCCESS: + msg_str = "DShot command sent successfully"; + break; + default: + msg_str = "Unhandled Error Code!"; + break; + } + output.printf("Status: %s - %s", result.success ? "SUCCESS" : "FAILED", msg_str); + + // Print telemetry data if available + if (result.success && (result.erpm > 0 || result.motor_rpm > 0)) + { + output.printf(" | eRPM: %u, Motor RPM: %u", result.erpm, result.motor_rpm); + } + + output.println(); +} + // Public Static Utility Functions void DShotRMT::printDShotInfo(const DShotRMT &dshot_rmt, Stream &output) { output.println("\n === DShot Signal Info === "); - output.printf("Current Mode: DSHOT%d\n", dshot_rmt.getMode() == dshot_mode_t::DSHOT150 ? 150 : dshot_rmt.getMode() == dshot_mode_t::DSHOT300 ? 300 - : dshot_rmt.getMode() == dshot_mode_t::DSHOT600 ? 600 - : dshot_rmt.getMode() == dshot_mode_t::DSHOT1200 ? 1200 - : 0); + + int mode_val = 0; + switch (dshot_rmt.getMode()) + { + case dshot_mode_t::DSHOT150: + mode_val = 150; + break; + case dshot_mode_t::DSHOT300: + mode_val = 300; + break; + case dshot_mode_t::DSHOT600: + mode_val = 600; + break; + case dshot_mode_t::DSHOT1200: + mode_val = 1200; + break; + default: + break; + } + output.printf("Current Mode: DSHOT%d\n", mode_val); + output.printf("Bidirectional: %s\n", dshot_rmt.isBidirectional() ? "YES" : "NO"); output.printf("Current Packet: "); diff --git a/src/DShotRMT.h b/src/DShotRMT.h index a9cc23c..7a45321 100644 --- a/src/DShotRMT.h +++ b/src/DShotRMT.h @@ -113,6 +113,13 @@ public: dshot_result_t saveESCSettings(); // Public Utility & Info Functions + /** + * @brief Prints the result of a DShot operation to the specified output stream. + * @param result The dshot_result_t structure containing the operation's outcome. + * @param output The output stream (e.g., Serial) to print to. Defaults to Serial. + */ + static void printDShotResult(const dshot_result_t &result, Stream &output = Serial); + /** * @brief Prints detailed DShot signal information for a given DShotRMT instance. * @param dshot_rmt The DShotRMT instance to get information from. @@ -213,8 +220,8 @@ private: uint64_t _last_command_timestamp; uint16_t _encoded_frame_value; dshot_packet_t _packet; - uint16_t _level0; // DShot protocol: Signal is idle-low, so pulses start by going HIGH. - uint16_t _level1; // DShot protocol: Signal returns to LOW after the high pulse. + uint16_t _pulse_level; // DShot protocol: Signal is idle-low, so pulses start by going HIGH. + uint16_t _idle_level; // DShot protocol: Signal returns to LOW after the high pulse. // RMT Hardware Handles rmt_channel_handle_t _rmt_tx_channel; @@ -245,12 +252,12 @@ private: // Private Frame Processing Functions dshot_result_t _sendDShotFrame(const dshot_packet_t &packet); - dshot_result_t _encodeDShotFrame(const dshot_packet_t &packet, rmt_symbol_word_t *symbols); + uint16_t _decodeDShotFrame(const rmt_symbol_word_t *symbols); // Private Timing Control Functions - bool _timer_signal(); - bool _timer_reset(); + bool _isFrameIntervalElapsed(); + void _recordFrameTransmissionTime(); // Static Callback Functions static bool _on_rx_done(rmt_channel_handle_t rmt_rx_channel, const rmt_rx_done_event_data_t *edata, void *user_data); diff --git a/src/dshot_definitions.h b/src/dshot_definitions.h index 268f342..a3b6725 100644 --- a/src/dshot_definitions.h +++ b/src/dshot_definitions.h @@ -217,101 +217,3 @@ const char *const TIMING_CORRECTION = "Timing correction!"; const char *const CALLBACK_REGISTERING_FAILED = "RMT RX Callback registering failed!"; const char *const INVALID_COMMAND = "Invalid command!"; const char *const COMMAND_SUCCESS = "DShot command sent successfully"; - -// Helper Functions -/** - * @brief Prints the result of a DShot operation to the specified output stream. - * @param result The dshot_result_t structure containing the operation's outcome. - * @param output The output stream (e.g., Serial) to print to. Defaults to Serial. - */ -inline void printDShotResult(dshot_result_t &result, Stream &output = Serial) -{ - const char *msg_str; - switch (result.error_code) - { - case dshot_msg_code_t::DSHOT_ERROR_NONE: - msg_str = "None"; - break; - case dshot_msg_code_t::DSHOT_ERROR_UNKNOWN: - msg_str = "Unknown Error!"; - break; - case dshot_msg_code_t::DSHOT_ERROR_TX_INIT_FAILED: - msg_str = "TX RMT channel init failed!"; - break; - case dshot_msg_code_t::DSHOT_ERROR_RX_INIT_FAILED: - msg_str = "RX RMT channel init failed!"; - break; - case dshot_msg_code_t::DSHOT_ERROR_ENCODER_INIT_FAILED: - msg_str = "RMT encoder init failed!"; - break; - case dshot_msg_code_t::DSHOT_ERROR_CALLBACK_REGISTERING_FAILED: - msg_str = "RMT RX Callback registering failed!"; - break; - case dshot_msg_code_t::DSHOT_ERROR_RECEIVER_FAILED: - msg_str = "RMT receiver failed!"; - break; - case dshot_msg_code_t::DSHOT_ERROR_TRANSMISSION_FAILED: - msg_str = "Transmission failed!"; - break; - case dshot_msg_code_t::DSHOT_ERROR_THROTTLE_NOT_IN_RANGE: - msg_str = "Throttle not in range! (48 - 2047)"; - break; - case dshot_msg_code_t::DSHOT_ERROR_PERCENT_NOT_IN_RANGE: - msg_str = "Percent not in range! (0.0 - 100.0)"; - break; - case dshot_msg_code_t::DSHOT_ERROR_COMMAND_NOT_VALID: - msg_str = "Command not valid! (0 - 47)"; - break; - case dshot_msg_code_t::DSHOT_ERROR_BIDIR_NOT_ENABLED: - msg_str = "Bidirectional DShot not enabled!"; - break; - case dshot_msg_code_t::DSHOT_ERROR_TELEMETRY_FAILED: - msg_str = "No valid Telemetric Frame received!"; - break; - case dshot_msg_code_t::DSHOT_ERROR_INVALID_MAGNET_COUNT: - msg_str = "Invalid motor magnet count!"; - break; - case dshot_msg_code_t::DSHOT_ERROR_INVALID_COMMAND: - msg_str = "Invalid command!"; - break; - case dshot_msg_code_t::DSHOT_ERROR_TIMING_CORRECTION: - msg_str = "Timing correction!"; - break; - case dshot_msg_code_t::DSHOT_ERROR_INIT_FAILED: - msg_str = "SignalGeneratorRMT init failed!"; - break; - case dshot_msg_code_t::DSHOT_ERROR_INIT_SUCCESS: - msg_str = "SignalGeneratorRMT initialized successfully"; - break; - case dshot_msg_code_t::DSHOT_ERROR_TX_INIT_SUCCESS: - msg_str = "TX RMT channel initialized successfully"; - break; - case dshot_msg_code_t::DSHOT_ERROR_RX_INIT_SUCCESS: - msg_str = "RX RMT channel initialized successfully"; - break; - case dshot_msg_code_t::DSHOT_ERROR_ENCODING_SUCCESS: - msg_str = "Packet encoded successfully"; - break; - case dshot_msg_code_t::DSHOT_ERROR_TRANSMISSION_SUCCESS: - msg_str = "Transmission successfully"; - break; - case dshot_msg_code_t::DSHOT_ERROR_TELEMETRY_SUCCESS: - msg_str = "Valid Telemetric Frame received!"; - break; - case dshot_msg_code_t::DSHOT_ERROR_COMMAND_SUCCESS: - msg_str = "DShot command sent successfully"; - break; - default: - msg_str = "Unhandled Error Code!"; - break; - } - output.printf("Status: %s - %s", result.success ? "SUCCESS" : "FAILED", msg_str); - - // Print telemetry data if available - if (result.success && (result.erpm > 0 || result.motor_rpm > 0)) - { - output.printf(" | eRPM: %u, Motor RPM: %u", result.erpm, result.motor_rpm); - } - - output.println(); -} \ No newline at end of file